mininet / mininet / util.py @ c273f490
History | View | Annotate | Download (19 KB)
1 |
"Utility functions for Mininet."
|
---|---|
2 |
|
3 |
from mininet.log import output, info, error, warn, debug |
4 |
|
5 |
from time import sleep |
6 |
from resource import getrlimit, setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE |
7 |
from select import poll, POLLIN, POLLHUP |
8 |
from subprocess import call, check_call, Popen, PIPE, STDOUT |
9 |
import re |
10 |
from fcntl import fcntl, F_GETFL, F_SETFL |
11 |
from os import O_NONBLOCK |
12 |
import os |
13 |
from functools import partial |
14 |
|
15 |
# Command execution support
|
16 |
|
17 |
def run( cmd ): |
18 |
"""Simple interface to subprocess.call()
|
19 |
cmd: list of command params"""
|
20 |
return call( cmd.split( ' ' ) ) |
21 |
|
22 |
def checkRun( cmd ): |
23 |
"""Simple interface to subprocess.check_call()
|
24 |
cmd: list of command params"""
|
25 |
return check_call( cmd.split( ' ' ) ) |
26 |
|
27 |
# pylint doesn't understand explicit type checking
|
28 |
# pylint: disable-msg=E1103
|
29 |
|
30 |
def oldQuietRun( *cmd ): |
31 |
"""Run a command, routing stderr to stdout, and return the output.
|
32 |
cmd: list of command params"""
|
33 |
if len( cmd ) == 1: |
34 |
cmd = cmd[ 0 ]
|
35 |
if isinstance( cmd, str ): |
36 |
cmd = cmd.split( ' ' )
|
37 |
popen = Popen( cmd, stdout=PIPE, stderr=STDOUT ) |
38 |
# We can't use Popen.communicate() because it uses
|
39 |
# select(), which can't handle
|
40 |
# high file descriptor numbers! poll() can, however.
|
41 |
out = ''
|
42 |
readable = poll() |
43 |
readable.register( popen.stdout ) |
44 |
while True: |
45 |
while readable.poll():
|
46 |
data = popen.stdout.read( 1024 )
|
47 |
if len( data ) == 0: |
48 |
break
|
49 |
out += data |
50 |
popen.poll() |
51 |
if popen.returncode is not None: |
52 |
break
|
53 |
return out
|
54 |
|
55 |
|
56 |
# This is a bit complicated, but it enables us to
|
57 |
# monitor command output as it is happening
|
58 |
|
59 |
def errRun( *cmd, **kwargs ): |
60 |
"""Run a command and return stdout, stderr and return code
|
61 |
cmd: string or list of command and args
|
62 |
stderr: STDOUT to merge stderr with stdout
|
63 |
shell: run command using shell
|
64 |
echo: monitor output to console"""
|
65 |
# By default we separate stderr, don't run in a shell, and don't echo
|
66 |
stderr = kwargs.get( 'stderr', PIPE )
|
67 |
shell = kwargs.get( 'shell', False ) |
68 |
echo = kwargs.get( 'echo', False ) |
69 |
if echo:
|
70 |
# cmd goes to stderr, output goes to stdout
|
71 |
info( cmd, '\n' )
|
72 |
if len( cmd ) == 1: |
73 |
cmd = cmd[ 0 ]
|
74 |
# Allow passing in a list or a string
|
75 |
if isinstance( cmd, str ) and not shell: |
76 |
cmd = cmd.split( ' ' )
|
77 |
cmd = [ str( arg ) for arg in cmd ] |
78 |
elif isinstance( cmd, list ) and shell: |
79 |
cmd = " ".join( arg for arg in cmd ) |
80 |
popen = Popen( cmd, stdout=PIPE, stderr=stderr, shell=shell ) |
81 |
# We use poll() because select() doesn't work with large fd numbers,
|
82 |
# and thus communicate() doesn't work either
|
83 |
out, err = '', '' |
84 |
poller = poll() |
85 |
poller.register( popen.stdout, POLLIN ) |
86 |
fdtofile = { popen.stdout.fileno(): popen.stdout } |
87 |
outDone, errDone = False, True |
88 |
if popen.stderr:
|
89 |
fdtofile[ popen.stderr.fileno() ] = popen.stderr |
90 |
poller.register( popen.stderr, POLLIN ) |
91 |
errDone = False
|
92 |
while not outDone or not errDone: |
93 |
readable = poller.poll() |
94 |
for fd, _event in readable: |
95 |
f = fdtofile[ fd ] |
96 |
data = f.read( 1024 )
|
97 |
if echo:
|
98 |
output( data ) |
99 |
if f == popen.stdout:
|
100 |
out += data |
101 |
if data == '': |
102 |
outDone = True
|
103 |
elif f == popen.stderr:
|
104 |
err += data |
105 |
if data == '': |
106 |
errDone = True
|
107 |
returncode = popen.wait() |
108 |
return out, err, returncode
|
109 |
|
110 |
def errFail( *cmd, **kwargs ): |
111 |
"Run a command using errRun and raise exception on nonzero exit"
|
112 |
out, err, ret = errRun( *cmd, **kwargs ) |
113 |
if ret:
|
114 |
raise Exception( "errFail: %s failed with return code %s: %s" |
115 |
% ( cmd, ret, err ) ) |
116 |
return out, err, ret
|
117 |
|
118 |
def quietRun( cmd, **kwargs ): |
119 |
"Run a command and return merged stdout and stderr"
|
120 |
return errRun( cmd, stderr=STDOUT, **kwargs )[ 0 ] |
121 |
|
122 |
# pylint: enable-msg=E1103
|
123 |
# pylint: disable-msg=E1101
|
124 |
|
125 |
def isShellBuiltin( cmd ): |
126 |
"Return True if cmd is a bash builtin."
|
127 |
if isShellBuiltin.builtIns is None: |
128 |
isShellBuiltin.builtIns = quietRun( 'bash -c enable' )
|
129 |
space = cmd.find( ' ' )
|
130 |
if space > 0: |
131 |
cmd = cmd[ :space] |
132 |
return cmd in isShellBuiltin.builtIns |
133 |
|
134 |
isShellBuiltin.builtIns = None
|
135 |
|
136 |
# pylint: enable-msg=E1101
|
137 |
|
138 |
# Interface management
|
139 |
#
|
140 |
# Interfaces are managed as strings which are simply the
|
141 |
# interface names, of the form 'nodeN-ethM'.
|
142 |
#
|
143 |
# To connect nodes, we create a pair of veth interfaces, and then place them
|
144 |
# in the pair of nodes that we want to communicate. We then update the node's
|
145 |
# list of interfaces and connectivity map.
|
146 |
#
|
147 |
# For the kernel datapath, switch interfaces
|
148 |
# live in the root namespace and thus do not have to be
|
149 |
# explicitly moved.
|
150 |
|
151 |
def makeIntfPair( intf1, intf2, addr1=None, addr2=None, run=quietRun ): |
152 |
"""Make a veth pair connecting intf1 and intf2.
|
153 |
intf1: string, interface
|
154 |
intf2: string, interface
|
155 |
node: node to run on or None (default)
|
156 |
returns: ip link add result"""
|
157 |
# Delete any old interfaces with the same names
|
158 |
run( 'ip link del ' + intf1 )
|
159 |
run( 'ip link del ' + intf2 )
|
160 |
# Create new pair
|
161 |
if addr1 is None and addr2 is None: |
162 |
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2 |
163 |
else:
|
164 |
cmd = ( 'ip link add name ' + intf1 + ' address ' + addr1 + |
165 |
' type veth peer name ' + intf2 + ' address ' + addr2 ) |
166 |
cmdOutput = run( cmd ) |
167 |
if cmdOutput == '': |
168 |
return True |
169 |
else:
|
170 |
error( "Error creating interface pair: %s " % cmdOutput )
|
171 |
return False |
172 |
|
173 |
def retry( retries, delaySecs, fn, *args, **keywords ): |
174 |
"""Try something several times before giving up.
|
175 |
n: number of times to retry
|
176 |
delaySecs: wait this long between tries
|
177 |
fn: function to call
|
178 |
args: args to apply to function call"""
|
179 |
tries = 0
|
180 |
while not fn( *args, **keywords ) and tries < retries: |
181 |
sleep( delaySecs ) |
182 |
tries += 1
|
183 |
if tries >= retries:
|
184 |
error( "*** gave up after %i retries\n" % tries )
|
185 |
exit( 1 ) |
186 |
|
187 |
def moveIntfNoRetry( intf, dstNode, printError=False ): |
188 |
"""Move interface to node, without retrying.
|
189 |
intf: string, interface
|
190 |
dstNode: destination Node
|
191 |
printError: if true, print error"""
|
192 |
intf = str( intf )
|
193 |
cmd = 'ip link set %s netns %s' % ( intf, dstNode.pid )
|
194 |
cmdOutput = quietRun( cmd ) |
195 |
# If ip link set does not produce any output, then we can assume
|
196 |
# that the link has been moved successfully.
|
197 |
if cmdOutput:
|
198 |
if printError:
|
199 |
error( '*** Error: moveIntf: ' + intf +
|
200 |
' not successfully moved to ' + dstNode.name + ':\n', |
201 |
cmdOutput ) |
202 |
return False |
203 |
return True |
204 |
|
205 |
def moveIntf( intf, dstNode, srcNode=None, printError=True, |
206 |
retries=3, delaySecs=0.001 ): |
207 |
"""Move interface to node, retrying on failure.
|
208 |
intf: string, interface
|
209 |
dstNode: destination Node
|
210 |
printError: if true, print error"""
|
211 |
retry( retries, delaySecs, moveIntfNoRetry, intf, dstNode, |
212 |
printError=printError ) |
213 |
|
214 |
# Support for dumping network
|
215 |
|
216 |
def dumpNodeConnections( nodes ): |
217 |
"Dump connections to/from nodes."
|
218 |
|
219 |
def dumpConnections( node ): |
220 |
"Helper function: dump connections to node"
|
221 |
for intf in node.intfList(): |
222 |
output( ' %s:' % intf )
|
223 |
if intf.link:
|
224 |
intfs = [ intf.link.intf1, intf.link.intf2 ] |
225 |
intfs.remove( intf ) |
226 |
output( intfs[ 0 ] )
|
227 |
else:
|
228 |
output( ' ' )
|
229 |
|
230 |
for node in nodes: |
231 |
output( node.name ) |
232 |
dumpConnections( node ) |
233 |
output( '\n' )
|
234 |
|
235 |
def dumpNetConnections( net ): |
236 |
"Dump connections in network"
|
237 |
nodes = net.controllers + net.switches + net.hosts |
238 |
dumpNodeConnections( nodes ) |
239 |
|
240 |
def dumpPorts( switches ): |
241 |
"dump interface to openflow port mappings for each switch"
|
242 |
for switch in switches: |
243 |
output( '%s ' % switch.name )
|
244 |
for intf in switch.intfList(): |
245 |
port = switch.ports[ intf ] |
246 |
output( '%s:%d ' % ( intf, port ) )
|
247 |
output( '\n' )
|
248 |
|
249 |
# IP and Mac address formatting and parsing
|
250 |
|
251 |
def _colonHex( val, bytecount ): |
252 |
"""Generate colon-hex string.
|
253 |
val: input as unsigned int
|
254 |
bytecount: number of bytes to convert
|
255 |
returns: chStr colon-hex string"""
|
256 |
pieces = [] |
257 |
for i in range( bytecount - 1, -1, -1 ): |
258 |
piece = ( ( 0xff << ( i * 8 ) ) & val ) >> ( i * 8 ) |
259 |
pieces.append( '%02x' % piece )
|
260 |
chStr = ':'.join( pieces )
|
261 |
return chStr
|
262 |
|
263 |
def macColonHex( mac ): |
264 |
"""Generate MAC colon-hex string from unsigned int.
|
265 |
mac: MAC address as unsigned int
|
266 |
returns: macStr MAC colon-hex string"""
|
267 |
return _colonHex( mac, 6 ) |
268 |
|
269 |
def ipStr( ip ): |
270 |
"""Generate IP address string from an unsigned int.
|
271 |
ip: unsigned int of form w << 24 | x << 16 | y << 8 | z
|
272 |
returns: ip address string w.x.y.z"""
|
273 |
w = ( ip >> 24 ) & 0xff |
274 |
x = ( ip >> 16 ) & 0xff |
275 |
y = ( ip >> 8 ) & 0xff |
276 |
z = ip & 0xff
|
277 |
return "%i.%i.%i.%i" % ( w, x, y, z ) |
278 |
|
279 |
def ipNum( w, x, y, z ): |
280 |
"""Generate unsigned int from components of IP address
|
281 |
returns: w << 24 | x << 16 | y << 8 | z"""
|
282 |
return ( w << 24 ) | ( x << 16 ) | ( y << 8 ) | z |
283 |
|
284 |
def ipAdd( i, prefixLen=8, ipBaseNum=0x0a000000 ): |
285 |
"""Return IP address string from ints
|
286 |
i: int to be added to ipbase
|
287 |
prefixLen: optional IP prefix length
|
288 |
ipBaseNum: option base IP address as int
|
289 |
returns IP address as string"""
|
290 |
imax = 0xffffffff >> prefixLen
|
291 |
assert i <= imax, 'Not enough IP addresses in the subnet' |
292 |
mask = 0xffffffff ^ imax
|
293 |
ipnum = ( ipBaseNum & mask ) + i |
294 |
return ipStr( ipnum )
|
295 |
|
296 |
def ipParse( ip ): |
297 |
"Parse an IP address and return an unsigned int."
|
298 |
args = [ int( arg ) for arg in ip.split( '.' ) ] |
299 |
while ( len(args) < 4 ): |
300 |
args.append( 0 )
|
301 |
return ipNum( *args )
|
302 |
|
303 |
def netParse( ipstr ): |
304 |
"""Parse an IP network specification, returning
|
305 |
address and prefix len as unsigned ints"""
|
306 |
prefixLen = 0
|
307 |
if '/' in ipstr: |
308 |
ip, pf = ipstr.split( '/' )
|
309 |
prefixLen = int( pf )
|
310 |
#if no prefix is specified, set the prefix to 24
|
311 |
else:
|
312 |
ip = ipstr |
313 |
prefixLen = 24
|
314 |
return ipParse( ip ), prefixLen
|
315 |
|
316 |
def checkInt( s ): |
317 |
"Check if input string is an int"
|
318 |
try:
|
319 |
int( s )
|
320 |
return True |
321 |
except ValueError: |
322 |
return False |
323 |
|
324 |
def checkFloat( s ): |
325 |
"Check if input string is a float"
|
326 |
try:
|
327 |
float( s )
|
328 |
return True |
329 |
except ValueError: |
330 |
return False |
331 |
|
332 |
def makeNumeric( s ): |
333 |
"Convert string to int or float if numeric."
|
334 |
if checkInt( s ):
|
335 |
return int( s ) |
336 |
elif checkFloat( s ):
|
337 |
return float( s ) |
338 |
else:
|
339 |
return s
|
340 |
|
341 |
# Popen support
|
342 |
|
343 |
def pmonitor(popens, timeoutms=500, readline=True, |
344 |
readmax=1024 ):
|
345 |
"""Monitor dict of hosts to popen objects
|
346 |
a line at a time
|
347 |
timeoutms: timeout for poll()
|
348 |
readline: return single line of output
|
349 |
yields: host, line/output (if any)
|
350 |
terminates: when all EOFs received"""
|
351 |
poller = poll() |
352 |
fdToHost = {} |
353 |
for host, popen in popens.iteritems(): |
354 |
fd = popen.stdout.fileno() |
355 |
fdToHost[ fd ] = host |
356 |
poller.register( fd, POLLIN ) |
357 |
if not readline: |
358 |
# Use non-blocking reads
|
359 |
flags = fcntl( fd, F_GETFL ) |
360 |
fcntl( fd, F_SETFL, flags | O_NONBLOCK ) |
361 |
while popens:
|
362 |
fds = poller.poll( timeoutms ) |
363 |
if fds:
|
364 |
for fd, event in fds: |
365 |
host = fdToHost[ fd ] |
366 |
popen = popens[ host ] |
367 |
if event & POLLIN:
|
368 |
if readline:
|
369 |
# Attempt to read a line of output
|
370 |
# This blocks until we receive a newline!
|
371 |
line = popen.stdout.readline() |
372 |
else:
|
373 |
line = popen.stdout.read( readmax ) |
374 |
yield host, line
|
375 |
# Check for EOF
|
376 |
elif event & POLLHUP:
|
377 |
poller.unregister( fd ) |
378 |
del popens[ host ]
|
379 |
else:
|
380 |
yield None, '' |
381 |
|
382 |
# Other stuff we use
|
383 |
def sysctlTestAndSet( name, limit ): |
384 |
"Helper function to set sysctl limits"
|
385 |
#convert non-directory names into directory names
|
386 |
if '/' not in name: |
387 |
name = '/proc/sys/' + name.replace( '.', '/' ) |
388 |
#read limit
|
389 |
with open( name, 'r' ) as readFile: |
390 |
oldLimit = readFile.readline() |
391 |
if isinstance( limit, int ): |
392 |
#compare integer limits before overriding
|
393 |
if int( oldLimit ) < limit: |
394 |
with open( name, 'w' ) as writeFile: |
395 |
writeFile.write( "%d" % limit )
|
396 |
else:
|
397 |
#overwrite non-integer limits
|
398 |
with open( name, 'w' ) as writeFile: |
399 |
writeFile.write( limit ) |
400 |
|
401 |
def rlimitTestAndSet( name, limit ): |
402 |
"Helper function to set rlimits"
|
403 |
soft, hard = getrlimit( name ) |
404 |
if soft < limit:
|
405 |
hardLimit = hard if limit < hard else limit |
406 |
setrlimit( name, ( limit, hardLimit ) ) |
407 |
|
408 |
def fixLimits(): |
409 |
"Fix ridiculously small resource limits."
|
410 |
debug( "*** Setting resource limits\n" )
|
411 |
try:
|
412 |
rlimitTestAndSet( RLIMIT_NPROC, 8192 )
|
413 |
rlimitTestAndSet( RLIMIT_NOFILE, 16384 )
|
414 |
#Increase open file limit
|
415 |
sysctlTestAndSet( 'fs.file-max', 10000 ) |
416 |
#Increase network buffer space
|
417 |
sysctlTestAndSet( 'net.core.wmem_max', 16777216 ) |
418 |
sysctlTestAndSet( 'net.core.rmem_max', 16777216 ) |
419 |
sysctlTestAndSet( 'net.ipv4.tcp_rmem', '10240 87380 16777216' ) |
420 |
sysctlTestAndSet( 'net.ipv4.tcp_wmem', '10240 87380 16777216' ) |
421 |
sysctlTestAndSet( 'net.core.netdev_max_backlog', 5000 ) |
422 |
#Increase arp cache size
|
423 |
sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh1', 4096 ) |
424 |
sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh2', 8192 ) |
425 |
sysctlTestAndSet( 'net.ipv4.neigh.default.gc_thresh3', 16384 ) |
426 |
#Increase routing table size
|
427 |
sysctlTestAndSet( 'net.ipv4.route.max_size', 32768 ) |
428 |
#Increase number of PTYs for nodes
|
429 |
sysctlTestAndSet( 'kernel.pty.max', 20000 ) |
430 |
except:
|
431 |
warn( "*** Error setting resource limits. "
|
432 |
"Mininet's performance may be affected.\n" )
|
433 |
|
434 |
def mountCgroups(): |
435 |
"Make sure cgroups file system is mounted"
|
436 |
mounts = quietRun( 'cat /proc/mounts' )
|
437 |
cgdir = '/sys/fs/cgroup'
|
438 |
csdir = cgdir + '/cpuset'
|
439 |
if ('cgroup %s' % cgdir not in mounts and |
440 |
'cgroups %s' % cgdir not in mounts): |
441 |
raise Exception( "cgroups not mounted on " + cgdir ) |
442 |
if 'cpuset %s' % csdir not in mounts: |
443 |
errRun( 'mkdir -p ' + csdir )
|
444 |
errRun( 'mount -t cgroup -ocpuset cpuset ' + csdir )
|
445 |
|
446 |
def natural( text ): |
447 |
"To sort sanely/alphabetically: sorted( l, key=natural )"
|
448 |
def num( s ): |
449 |
"Convert text segment to int if necessary"
|
450 |
return int( s ) if s.isdigit() else s |
451 |
return [ num( s ) for s in re.split( r'(\d+)', str( text ) ) ] |
452 |
|
453 |
def naturalSeq( t ): |
454 |
"Natural sort key function for sequences"
|
455 |
return [ natural( x ) for x in t ] |
456 |
|
457 |
def numCores(): |
458 |
"Returns number of CPU cores based on /proc/cpuinfo"
|
459 |
if hasattr( numCores, 'ncores' ): |
460 |
return numCores.ncores
|
461 |
try:
|
462 |
numCores.ncores = int( quietRun('grep -c processor /proc/cpuinfo') ) |
463 |
except ValueError: |
464 |
return 0 |
465 |
return numCores.ncores
|
466 |
|
467 |
def irange(start, end): |
468 |
"""Inclusive range from start to end (vs. Python insanity.)
|
469 |
irange(1,5) -> 1, 2, 3, 4, 5"""
|
470 |
return range( start, end + 1 ) |
471 |
|
472 |
def custom( cls, **params ): |
473 |
"Returns customized constructor for class cls."
|
474 |
# Note: we may wish to see if we can use functools.partial() here
|
475 |
# and in customConstructor
|
476 |
def customized( *args, **kwargs): |
477 |
"Customized constructor"
|
478 |
kwargs = kwargs.copy() |
479 |
kwargs.update( params ) |
480 |
return cls( *args, **kwargs )
|
481 |
customized.__name__ = 'custom(%s,%s)' % ( cls, params )
|
482 |
return customized
|
483 |
|
484 |
def splitArgs( argstr ): |
485 |
"""Split argument string into usable python arguments
|
486 |
argstr: argument string with format fn,arg2,kw1=arg3...
|
487 |
returns: fn, args, kwargs"""
|
488 |
split = argstr.split( ',' )
|
489 |
fn = split[ 0 ]
|
490 |
params = split[ 1: ]
|
491 |
# Convert int and float args; removes the need for function
|
492 |
# to be flexible with input arg formats.
|
493 |
args = [ makeNumeric( s ) for s in params if '=' not in s ] |
494 |
kwargs = {} |
495 |
for s in [ p for p in params if '=' in p ]: |
496 |
key, val = s.split( '=', 1 ) |
497 |
kwargs[ key ] = makeNumeric( val ) |
498 |
return fn, args, kwargs
|
499 |
|
500 |
def customConstructor( constructors, argStr ): |
501 |
"""Return custom constructor based on argStr
|
502 |
The args and key/val pairs in argsStr will be automatically applied
|
503 |
when the generated constructor is later used.
|
504 |
"""
|
505 |
cname, newargs, kwargs = splitArgs( argStr ) |
506 |
constructor = constructors.get( cname, None )
|
507 |
|
508 |
if not constructor: |
509 |
raise Exception( "error: %s is unknown - please specify one of %s" % |
510 |
( cname, constructors.keys() ) ) |
511 |
|
512 |
def customized( name, *args, **params ): |
513 |
"Customized constructor, useful for Node, Link, and other classes"
|
514 |
params = params.copy() |
515 |
params.update( kwargs ) |
516 |
if not newargs: |
517 |
return constructor( name, *args, **params )
|
518 |
if args:
|
519 |
warn( 'warning: %s replacing %s with %s\n' % (
|
520 |
constructor, args, newargs ) ) |
521 |
return constructor( name, *newargs, **params )
|
522 |
|
523 |
customized.__name__ = 'customConstructor(%s)' % argStr
|
524 |
return customized
|
525 |
|
526 |
def buildTopo( topos, topoStr ): |
527 |
"""Create topology from string with format (object, arg1, arg2,...).
|
528 |
input topos is a dict of topo names to constructors, possibly w/args.
|
529 |
"""
|
530 |
topo, args, kwargs = splitArgs( topoStr ) |
531 |
if topo not in topos: |
532 |
raise Exception( 'Invalid topo name %s' % topo ) |
533 |
return topos[ topo ]( *args, **kwargs )
|
534 |
|
535 |
def ensureRoot(): |
536 |
"""Ensure that we are running as root.
|
537 |
|
538 |
Probably we should only sudo when needed as per Big Switch's patch.
|
539 |
"""
|
540 |
if os.getuid() != 0: |
541 |
print "*** Mininet must run as root." |
542 |
exit( 1 ) |
543 |
return
|
544 |
|
545 |
def waitListening( client=None, server='127.0.0.1', port=80, timeout=None ): |
546 |
"""Wait until server is listening on port.
|
547 |
returns True if server is listening"""
|
548 |
run = ( client.cmd if client else |
549 |
partial( quietRun, shell=True ) )
|
550 |
if not run( 'which telnet' ): |
551 |
raise Exception('Could not find telnet' ) |
552 |
serverIP = server if isinstance( server, basestring ) else server.IP() |
553 |
cmd = ( 'sh -c "echo A | telnet -e A %s %s"' %
|
554 |
( serverIP, port ) ) |
555 |
time = 0
|
556 |
while 'Connected' not in run( cmd ): |
557 |
if timeout:
|
558 |
print time
|
559 |
if time >= timeout:
|
560 |
error( 'could not connect to %s on port %d\n'
|
561 |
% ( server, port ) ) |
562 |
return False |
563 |
output('waiting for', server,
|
564 |
'to listen on port', port, '\n') |
565 |
sleep( .5 )
|
566 |
time += .5
|
567 |
return True |