]>
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
36 from lib
. topolog
import logger
38 from mininet
. topo
import Topo
39 from mininet
. net
import Mininet
40 from mininet
. node
import Node
, OVSSwitch
, Host
41 from mininet
. log
import setLogLevel
, info
42 from mininet
. cli
import CLI
43 from mininet
. link
import Intf
45 from time
import sleep
47 class json_cmp_result ( object ):
48 "json_cmp result class for better assertion messages"
53 def add_error ( self
, error
):
54 "Append error message to the result"
55 self
. errors
. append ( error
)
58 "Returns True if there were errors, otherwise False."
59 return len ( self
. errors
) > 0
62 def json_cmp ( d1
, d2
, reason
= False ):
64 JSON compare function. Receives two parameters:
66 * `d2`: json subset which we expect
68 Returns `None` when all keys that `d1` has matches `d2`,
69 otherwise a string containing what failed.
71 Note: key absence can be tested by adding a key with value `None`.
73 squeue
= [( d1
, d2
, 'json' )]
74 result
= json_cmp_result ()
77 s1
, s2
= set ( nd1
), set ( nd2
)
79 # Expect all required fields to exist.
80 s2_req
= set ([ key
for key
in nd2
if nd2
[ key
] is not None ])
83 result
. add_error ( 'expected key(s) {} in {} (have {})' . format (
84 str ( list ( diff
)), parent
, str ( list ( s1
))))
86 for key
in s2
. intersection ( s1
):
87 # Test for non existence of key in d2
89 result
. add_error ( '"{}" should not exist in {} (have {})' . format (
90 key
, parent
, str ( s1
)))
92 # If nd1 key is a dict, we have to recurse in it later.
93 if isinstance ( nd2
[ key
], type ({})):
94 if not isinstance ( nd1
[ key
], type ({})):
96 '{}["{}"] has different type than expected ' . format ( parent
, key
) +
97 '(have {}, expected {})' . format ( type ( nd1
[ key
]), type ( nd2
[ key
])))
99 nparent
= '{}["{}"]' . format ( parent
, key
)
100 squeue
. append (( nd1
[ key
], nd2
[ key
], nparent
))
103 if isinstance ( nd2
[ key
], type ([])):
104 if not isinstance ( nd1
[ key
], type ([])):
106 '{}["{}"] has different type than expected ' . format ( parent
, key
) +
107 '(have {}, expected {})' . format ( type ( nd1
[ key
]), type ( nd2
[ key
])))
110 if len ( nd2
[ key
]) > len ( nd1
[ key
]):
112 '{}["{}"] too few items ' . format ( parent
, key
) +
113 '(have ({}) "{}", expected ({}) "{}")' . format (
114 len ( nd1
[ key
]), str ( nd1
[ key
]), len ( nd2
[ key
]), str ( nd2
[ key
])))
117 # List all unmatched items errors
119 for expected
in nd2
[ key
]:
121 for value
in nd1
[ key
]:
122 if json_cmp ({ 'json' : value
}, { 'json' : expected
}) is None :
129 unmatched
. append ( expected
)
131 # If there are unmatched items, error out.
134 '{}["{}"] value is different (have "{}", expected "{}")' . format (
135 parent
, key
, str ( nd1
[ key
]), str ( nd2
[ key
])))
138 # Compare JSON values
139 if nd1
[ key
] != nd2
[ key
]:
141 '{}["{}"] value is different (have "{}", expected "{}")' . format (
142 parent
, key
, str ( nd1
[ key
]), str ( nd2
[ key
])))
145 if result
. has_errors ():
150 def run_and_expect ( func
, what
, count
= 20 , wait
= 3 ):
152 Run `func` and compare the result with `what`. Do it for `count` times
153 waiting `wait` seconds between tries. By default it tries 20 times with
154 3 seconds delay between tries.
156 Returns (True, func-return) on success or
157 (False, func-return) on failure.
165 return ( True , result
)
166 return ( False , result
)
170 "Converting Integer to DPID"
174 dpid
= '0' *( 16 - len ( dpid
))+ dpid
177 raise Exception ( 'Unable to derive default datapath ID - '
178 'please either specify a dpid or use a '
179 'canonical switch name such as s23.' )
182 "Check whether pid exists in the current process table."
188 except OSError as err
:
189 if err
. errno
== errno
. ESRCH
:
190 # ESRCH == No such process
192 elif err
. errno
== errno
. EPERM
:
193 # EPERM clearly means there's a process to deny access to
196 # According to "man 2 kill" possible error values are
197 # (EINVAL, EPERM, ESRCH)
202 def get_textdiff ( text1
, text2
, title1
= "" , title2
= "" ):
203 "Returns empty string if same or formatted diff"
205 diff
= ' \n ' . join ( difflib
. context_diff ( text1
, text2
,
206 fromfile
= title1
, tofile
= title2
))
207 # Clean up line endings
208 diff
= os
. linesep
. join ([ s
for s
in diff
. splitlines () if s
])
211 def difflines ( text1
, text2
, title1
= '' , title2
= '' ):
212 "Wrapper for get_textdiff to avoid string transformations."
213 text1
= ( ' \n ' . join ( text1
. rstrip (). splitlines ()) + ' \n ' ). splitlines ( 1 )
214 text2
= ( ' \n ' . join ( text2
. rstrip (). splitlines ()) + ' \n ' ). splitlines ( 1 )
215 return get_textdiff ( text1
, text2
, title1
, title2
)
217 def get_file ( content
):
219 Generates a temporary file in '/tmp' with `content` and returns the file name.
221 fde
= tempfile
. NamedTemporaryFile ( mode
= 'w' , delete
= False )
227 def normalize_text ( text
):
229 Strips formating spaces/tabs and carriage returns.
231 text
= re
. sub ( r
'[ \t]+' , ' ' , text
)
232 text
= re
. sub ( r
'\r' , '' , text
)
235 def checkAddressSanitizerError ( output
, router
, component
):
236 "Checks for AddressSanitizer in output. If found, then logs it and returns true, false otherwise"
238 addressSantizerError
= re
. search ( '(==[0-9]+==)ERROR: AddressSanitizer: ([^\s]*) ' , output
)
239 if addressSantizerError
:
240 sys
. stderr
. write ( " %s : %s triggered an exception by AddressSanitizer \n " % ( router
, component
))
241 # Sanitizer Error found in log
242 pidMark
= addressSantizerError
. group ( 1 )
243 addressSantizerLog
= re
. search ( ' %s (.*) %s ' % ( pidMark
, pidMark
), output
, re
. DOTALL
)
244 if addressSantizerLog
:
245 callingTest
= os
. path
. basename ( sys
._ current
_ frames
(). values ()[ 0 ]. f_back
. f_back
. f_globals
[ '__file__' ])
246 callingProc
= sys
._ getframe
( 2 ). f_code
. co_name
247 with
open ( "/tmp/AddressSanitzer.txt" , "a" ) as addrSanFile
:
248 sys
. stderr
. write ( ' \n ' . join ( addressSantizerLog
. group ( 1 ). splitlines ()) + ' \n ' )
249 addrSanFile
. write ( "## Error: %s \n\n " % addressSantizerError
. group ( 2 ))
250 addrSanFile
. write ( "### AddressSanitizer error in topotest ` %s `, test ` %s `, router ` %s ` \n\n " % ( callingTest
, callingProc
, router
))
251 addrSanFile
. write ( ' ' + ' \n ' . join ( addressSantizerLog
. group ( 1 ). splitlines ()) + ' \n ' )
252 addrSanFile
. write ( " \n --------------- \n " )
256 def addRouter ( topo
, name
):
257 "Adding a FRRouter (or Quagga) to Topology"
259 MyPrivateDirs
= [ '/etc/frr' ,
264 return topo
. addNode ( name
, cls
= Router
, privateDirs
= MyPrivateDirs
)
266 def set_sysctl ( node
, sysctl
, value
):
267 "Set a sysctl value and return None on success or an error string"
268 valuestr
= '{}' . format ( value
)
269 command
= "sysctl {0} = {1} " . format ( sysctl
, valuestr
)
270 cmdret
= node
. cmd ( command
)
272 matches
= re
. search ( r
'([^ ]+) = ([^\s]+)' , cmdret
)
275 if matches
. group ( 1 ) != sysctl
:
277 if matches
. group ( 2 ) != valuestr
:
282 def assert_sysctl ( node
, sysctl
, value
):
283 "Set and assert that the sysctl is set with the specified value."
284 assert set_sysctl ( node
, sysctl
, value
) is None
286 class LinuxRouter ( Node
):
287 "A Node with IPv4/IPv6 forwarding enabled."
289 def config ( self
, ** params
):
290 super ( LinuxRouter
, self
). config (** params
)
291 # Enable forwarding on the router
292 assert_sysctl ( self
, 'net.ipv4.ip_forward' , 1 )
293 assert_sysctl ( self
, 'net.ipv6.conf.all.forwarding' , 1 )
296 Terminate generic LinuxRouter Mininet instance
298 set_sysctl ( self
, 'net.ipv4.ip_forward' , 0 )
299 set_sysctl ( self
, 'net.ipv6.conf.all.forwarding' , 0 )
300 super ( LinuxRouter
, self
). terminate ()
303 "A Node with IPv4/IPv6 forwarding enabled and Quagga as Routing Engine"
305 def __init__ ( self
, name
, ** params
):
306 super ( Router
, self
) .__ init
__ ( name
, ** params
)
307 self
. logdir
= params
. get ( 'logdir' , '/tmp' )
308 self
. daemondir
= None
309 self
. routertype
= 'frr'
310 self
. daemons
= { 'zebra' : 0 , 'ripd' : 0 , 'ripngd' : 0 , 'ospfd' : 0 ,
311 'ospf6d' : 0 , 'isisd' : 0 , 'bgpd' : 0 , 'pimd' : 0 ,
314 def _config_frr ( self
, ** params
):
315 "Configure FRR binaries"
316 self
. daemondir
= params
. get ( 'frrdir' )
317 if self
. daemondir
is None :
318 self
. daemondir
= '/usr/lib/frr'
320 zebra_path
= os
. path
. join ( self
. daemondir
, 'zebra' )
321 if not os
. path
. isfile ( zebra_path
):
322 raise Exception ( "FRR zebra binary doesn't exist at {}" . format ( zebra_path
))
324 def _config_quagga ( self
, ** params
):
325 "Configure Quagga binaries"
326 self
. daemondir
= params
. get ( 'quaggadir' )
327 if self
. daemondir
is None :
328 self
. daemondir
= '/usr/lib/quagga'
330 zebra_path
= os
. path
. join ( self
. daemondir
, 'zebra' )
331 if not os
. path
. isfile ( zebra_path
):
332 raise Exception ( "Quagga zebra binary doesn't exist at {}" . format ( zebra_path
))
334 # pylint: disable=W0221
335 # Some params are only meaningful for the parent class.
336 def config ( self
, ** params
):
337 super ( Router
, self
). config (** params
)
339 # User did not specify the daemons directory, try to autodetect it.
340 self
. daemondir
= params
. get ( 'daemondir' )
341 if self
. daemondir
is None :
342 self
. routertype
= params
. get ( 'routertype' , 'frr' )
343 if self
. routertype
== 'quagga' :
344 self
._ config
_ quagga
(** params
)
346 self
._ config
_ frr
(** params
)
348 # Test the provided path
349 zpath
= os
. path
. join ( self
. daemondir
, 'zebra' )
350 if not os
. path
. isfile ( zpath
):
351 raise Exception ( 'No zebra binary found in {}' . format ( zpath
))
352 # Allow user to specify routertype when the path was specified.
353 if params
. get ( 'routertype' ) is not None :
354 self
. routertype
= self
. params
. get ( 'routertype' )
356 # Enable forwarding on the router
357 assert_sysctl ( self
, 'net.ipv4.ip_forward' , 1 )
358 assert_sysctl ( self
, 'net.ipv6.conf.all.forwarding' , 1 )
360 assert_sysctl ( self
, 'kernel.core_uses_pid' , 1 )
361 assert_sysctl ( self
, 'fs.suid_dumpable' , 2 )
362 corefile
= '{}/{}_ %e _core-sig_ %s- pid_%p.dmp' . format ( self
. logdir
, self
. name
)
363 assert_sysctl ( self
, 'kernel.core_pattern' , corefile
)
364 self
. cmd ( 'ulimit -c unlimited' )
365 # Set ownership of config files
366 self
. cmd ( 'chown {0} : {0} vty /etc/ {0} ' . format ( self
. routertype
))
369 # Delete Running Quagga or FRR Daemons
371 # rundaemons = self.cmd('ls -1 /var/run/%s/*.pid' % self.routertype)
372 # for d in StringIO.StringIO(rundaemons):
373 # self.cmd('kill -7 `cat %s`' % d.rstrip())
376 set_sysctl ( self
, 'net.ipv4.ip_forward' , 0 )
377 set_sysctl ( self
, 'net.ipv6.conf.all.forwarding' , 0 )
378 super ( Router
, self
). terminate ()
379 def stopRouter ( self
):
380 # Stop Running Quagga or FRR Daemons
381 rundaemons
= self
. cmd ( 'ls -1 /var/run/ %s /*.pid' % self
. routertype
)
382 if rundaemons
is not None :
383 for d
in StringIO
. StringIO ( rundaemons
):
384 daemonpid
= self
. cmd ( 'cat %s ' % d
. rstrip ()). rstrip ()
385 if ( daemonpid
. isdigit () and pid_exists ( int ( daemonpid
))):
386 self
. cmd ( 'kill -7 %s ' % daemonpid
)
389 for interface
in self
. intfNames ():
390 self
. cmd ( 'ip address flush' , interface
)
391 def loadConf ( self
, daemon
, source
= None ):
392 # print "Daemons before:", self.daemons
393 if daemon
in self
. daemons
. keys ():
394 self
. daemons
[ daemon
] = 1
396 self
. cmd ( 'touch /etc/ %s / %s .conf' % ( self
. routertype
, daemon
))
399 self
. cmd ( 'cp %s /etc/ %s / %s .conf' % ( source
, self
. routertype
, daemon
))
401 self
. cmd ( 'chmod 640 /etc/ %s / %s .conf' % ( self
. routertype
, daemon
))
403 self
. cmd ( 'chown %s : %s /etc/ %s / %s .conf' % ( self
. routertype
, self
. routertype
, self
. routertype
, daemon
))
406 logger
. warning ( 'No daemon {} known' . format ( daemon
))
407 # print "Daemons after:", self.daemons
408 def startRouter ( self
):
409 # Disable integrated-vtysh-config
410 self
. cmd ( 'echo "no service integrated-vtysh-config" >> /etc/ %s /vtysh.conf' % self
. routertype
)
411 self
. cmd ( 'chown %s : %s vty /etc/ %s /vtysh.conf' % ( self
. routertype
, self
. routertype
, self
. routertype
))
412 # TODO remove the following lines after all tests are migrated to Topogen.
413 # Try to find relevant old logfiles in /tmp and delete them
414 map ( os
. remove
, glob
. glob ( "/tmp/* %s *.log" % self
. name
))
415 # Remove old core files
416 map ( os
. remove
, glob
. glob ( "/tmp/ %s *.dmp" % self
. name
))
417 # Remove IP addresses from OS first - we have them in zebra.conf
419 # If ldp is used, check for LDP to be compiled and Linux Kernel to be 4.5 or higher
420 # No error - but return message and skip all the tests
421 if self
. daemons
[ 'ldpd' ] == 1 :
422 ldpd_path
= os
. path
. join ( self
. daemondir
, 'ldpd' )
423 if not os
. path
. isfile ( ldpd_path
):
424 logger
. warning ( "LDP Test, but no ldpd compiled or installed" )
425 return "LDP Test, but no ldpd compiled or installed"
426 kernel_version
= re
. search ( r
'([0-9]+)\.([0-9]+).*' , platform
. release ())
429 if ( float ( kernel_version
. group ( 1 )) < 4 or
430 ( float ( kernel_version
. group ( 1 )) == 4 and float ( kernel_version
. group ( 2 )) < 5 )):
431 logger
. warning ( "LDP Test need Linux Kernel 4.5 minimum" )
432 return "LDP Test need Linux Kernel 4.5 minimum"
434 self
. cmd ( '/sbin/modprobe mpls-router' )
435 self
. cmd ( '/sbin/modprobe mpls-iptunnel' )
436 self
. cmd ( 'echo 100000 > /proc/sys/net/mpls/platform_labels' )
437 # Init done - now restarting daemons
440 def restartRouter ( self
):
441 # Starts actuall daemons without init (ie restart)
443 if self
. daemons
[ 'zebra' ] == 1 :
444 zebra_path
= os
. path
. join ( self
. daemondir
, 'zebra' )
445 self
. cmd ( ' {0} > {1} / {2} -zebra.out 2> {1} / {2} -zebra.err &' . format (
446 zebra_path
, self
. logdir
, self
. name
449 logger
. debug ( '{}: {} zebra started' . format ( self
, self
. routertype
))
451 # Fix Link-Local Addresses
452 # Somehow (on Mininet only), Zebra removes the IPv6 Link-Local addresses on start. Fix this
453 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' )
454 # Now start all the other daemons
455 for daemon
in self
. daemons
:
456 # Skip disabled daemons and zebra
457 if self
. daemons
[ daemon
] == 0 or daemon
== 'zebra' :
460 daemon_path
= os
. path
. join ( self
. daemondir
, daemon
)
461 self
. cmd ( ' {0} > {1} / {2} - {3} .out 2> {1} / {2} - {3} .err &' . format (
462 daemon_path
, self
. logdir
, self
. name
, daemon
465 logger
. debug ( '{}: {} {} started' . format ( self
, self
. routertype
, daemon
))
466 def getStdErr ( self
, daemon
):
467 return self
. getLog ( 'err' , daemon
)
468 def getStdOut ( self
, daemon
):
469 return self
. getLog ( 'out' , daemon
)
470 def getLog ( self
, log
, daemon
):
471 return self
. cmd ( 'cat {}/{}-{}.{}' . format ( self
. logdir
, self
. name
, daemon
, log
))
472 def checkRouterRunning ( self
):
473 "Check if router daemons are running and collect crashinfo they don't run"
477 daemonsRunning
= self
. cmd ( 'vtysh -c "show log" | grep "Logging configuration for"' )
478 # Look for AddressSanitizer Errors in vtysh output and append to /tmp/AddressSanitzer.txt if found
479 if checkAddressSanitizerError ( daemonsRunning
, self
. name
, "vtysh" ):
480 return " %s : vtysh killed by AddressSanitizer" % ( self
. name
)
482 for daemon
in self
. daemons
:
483 if ( self
. daemons
[ daemon
] == 1 ) and not ( daemon
in daemonsRunning
):
484 sys
. stderr
. write ( " %s : Daemon %s not running \n " % ( self
. name
, daemon
))
486 corefiles
= glob
. glob ( '{}/{}_{}_core*.dmp' . format (
487 self
. logdir
, self
. name
, daemon
))
488 if ( len ( corefiles
) > 0 ):
489 daemon_path
= os
. path
. join ( self
. daemondir
, daemon
)
490 backtrace
= subprocess
. check_output ([
491 "gdb {} {} --batch -ex bt 2> /dev/null" . format ( daemon_path
, corefiles
[ 0 ])
493 sys
. stderr
. write ( " \n %s : %s crashed. Core file found - Backtrace follows: \n " % ( self
. name
, daemon
))
494 sys
. stderr
. write ( " %s \n " % backtrace
)
496 # No core found - If we find matching logfile in /tmp, then print last 20 lines from it.
497 if os
. path
. isfile ( '{}/{}-{}.log' . format ( self
. logdir
, self
. name
, daemon
)):
498 log_tail
= subprocess
. check_output ([
499 "tail -n20 {}/{}-{}.log 2> /dev/null" . format (
500 self
. logdir
, self
. name
, daemon
)
502 sys
. stderr
. write ( " \n From %s %s %s log file: \n " % ( self
. routertype
, self
. name
, daemon
))
503 sys
. stderr
. write ( " %s \n " % log_tail
)
505 # Look for AddressSanitizer Errors and append to /tmp/AddressSanitzer.txt if found
506 if checkAddressSanitizerError ( self
. getStdErr ( daemon
), self
. name
, daemon
):
507 return " %s : Daemon %s not running - killed by AddressSanitizer" % ( self
. name
, daemon
)
509 return " %s : Daemon %s not running" % ( self
. name
, daemon
)
511 def get_ipv6_linklocal ( self
):
512 "Get LinkLocal Addresses from interfaces"
516 ifaces
= self
. cmd ( 'ip -6 address' )
517 # Fix newlines (make them all the same)
518 ifaces
= ( ' \n ' . join ( ifaces
. splitlines ()) + ' \n ' ). splitlines ()
522 m
= re
. search ( '[0-9]+: ([^:@]+)[@if0-9:]+ <' , line
)
524 interface
= m
. group ( 1 )
526 m
= re
. search ( 'inet6 (fe80::[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+)[/0-9]* scope link' , line
)
530 if ( ll_per_if_count
> 1 ):
531 linklocal
+= [[ " %s-%s " % ( interface
, ll_per_if_count
), local
]]
533 linklocal
+= [[ interface
, local
]]
535 def daemon_available ( self
, daemon
):
536 "Check if specified daemon is installed (and for ldp if kernel supports MPLS)"
538 daemon_path
= os
. path
. join ( self
. daemondir
, daemon
)
539 if not os
. path
. isfile ( daemon_path
):
541 if ( daemon
== 'ldpd' ):
542 kernel_version
= re
. search ( r
'([0-9]+)\.([0-9]+).*' , platform
. release ())
544 if ( float ( kernel_version
. group ( 1 )) < 4 or
545 ( float ( kernel_version
. group ( 1 )) == 4 and float ( kernel_version
. group ( 2 )) < 5 )):
550 def get_routertype ( self
):
551 "Return the type of Router (frr or quagga)"
553 return self
. routertype
554 def report_memory_leaks ( self
, filename_prefix
, testscript
):
555 "Report Memory Leaks to file prefixed with given string"
558 filename
= filename_prefix
+ re
. sub ( r
"\.py" , "" , testscript
) + ".txt"
559 for daemon
in self
. daemons
:
560 if ( self
. daemons
[ daemon
] == 1 ):
561 log
= self
. getStdErr ( daemon
)
562 if "memstats" in log
:
564 logger
. info ( ' \n Router {} {} StdErr Log: \n {}' . format (
565 self
. name
, daemon
, log
))
568 # Check if file already exists
569 fileexists
= os
. path
. isfile ( filename
)
570 leakfile
= open ( filename
, "a" )
572 # New file - add header
573 leakfile
. write ( "# Memory Leak Detection for topotest %s \n\n " % testscript
)
574 leakfile
. write ( "## Router %s \n " % self
. name
)
575 leakfile
. write ( "### Process %s \n " % daemon
)
576 log
= re
. sub ( "core_handler: " , "" , log
)
577 log
= re
. sub ( r
"(showing active allocations in memory group [a-zA-Z0-9]+)" , r
"\n#### \1\n" , log
)
578 log
= re
. sub ( "memstats: " , " " , log
)
585 class LegacySwitch ( OVSSwitch
):
586 "A Legacy Switch without OpenFlow"
588 def __init__ ( self
, name
, ** params
):
589 OVSSwitch
.__ init
__ ( self
, name
, failMode
= 'standalone' , ** params
)