Several improvements to onosnet.py

Replace arping and ping with custom gratuitous arp code
Don't block forever on switch.waitConnected()
Allow backgrounding bgIperf

Change-Id: I6cb10c3c8971e29336a6c6e2deffb5231900d463
diff --git a/tools/test/topos/onosnet.py b/tools/test/topos/onosnet.py
index d90505d..1738cf7 100644
--- a/tools/test/topos/onosnet.py
+++ b/tools/test/topos/onosnet.py
@@ -2,21 +2,22 @@
 
 import sys
 import itertools
+import signal
 from time import sleep
+from threading import Thread
 
 from mininet.net import Mininet
 from mininet.log import setLogLevel
-from mininet.node import RemoteController
-from mininet.log import info, debug, output
-from mininet.util import quietRun
+from mininet.node import RemoteController, Node
+from mininet.log import info, debug, output, error
 from mininet.link import TCLink
 from mininet.cli import CLI
 
-class ONOSMininet( Mininet ):
+# This is the program that each host will call
+import gratuitousArp
+ARP_PATH = gratuitousArp.__file__.replace('.pyc', '.py')
 
-    @classmethod
-    def setup( cls ):
-        cls.useArping = True if quietRun( 'which arping' ) else False
+class ONOSMininet( Mininet ):
 
     def __init__( self, controllers=[], gratuitousArp=True, build=True, *args, **kwargs ):
         """Create Mininet object for ONOS.
@@ -32,7 +33,6 @@
         Mininet.__init__(self, *args, **kwargs )
 
         self.gratArp = gratuitousArp
-        self.useArping = ONOSMininet.useArping
 
         info ( '*** Adding controllers\n' )
         ctrl_count = 0
@@ -47,31 +47,30 @@
     def start( self ):
         Mininet.start( self )
         if self.gratArp:
-            self.waitConnected()
+            self.waitConnected( timeout=5 )
             info ( '*** Sending a gratuitious ARP from each host\n' )
             self.gratuitousArp()
 
-
-    def gratuitousArp( self ):
-        "Send an ARP from each host to aid controller's host discovery; fallback to ping if necessary"
-        if self.useArping:
-            for host in self.hosts:
-                info( '%s ' % host.name )
-                debug( host.cmd( 'arping -U -c 1 ' + host.IP() ) )
-            info ( '\n' )
-        else:
-            info( '\nWARNING: arping is not found, using ping instead.\n'
-                  'For higher performance, install arping: sudo apt-get install iputils-arping\n\n' )
-
-            procs = [ s.popen( 'ping -w 0.1 -W 0.1 -c1 %s > /dev/null; printf "%s "'
-                                % ( d.IP(), s.name ), shell=True )
-                      for (s, d) in zip( self.hosts, self.hosts[1:] + self.hosts[0:1] ) ]
-            for t in procs:
-                out, err = t.communicate()
-                if err:
-                    info ( err )
+    def verifyHosts( self, hosts ):
+        for i in range( len( hosts ) ):
+            if isinstance( hosts[i], str):
+                if hosts[i] in self:
+                    hosts[i] = self[ hosts[i] ]
                 else:
-                    info ( out )
+                    info( '*** ERROR: %s is not a host\n' % hosts[i] )
+                    del hosts[i]
+            elif not isinstance( hosts[i], Node):
+                del hosts[i]
+
+    def gratuitousArp( self, hosts=[] ):
+        "Send an ARP from each host to aid controller's host discovery; fallback to ping if necessary"
+        if not hosts:
+            hosts = self.hosts
+        self.verifyHosts( hosts )
+
+        for host in hosts:
+            info( '%s ' % host.name )
+            info( host.cmd( ARP_PATH ) )
         info ( '\n' )
 
     def pingloop( self ):
@@ -84,28 +83,51 @@
             setLogLevel( 'info' )
 
     def bgIperf( self, hosts=[], seconds=10 ):
-        #TODO check if the hosts are strings or objects
-        #    h1 = net.getNodeByName('h1')
+        self.verifyHosts( hosts )
         servers = [ host.popen("iperf -s") for host in hosts ]
 
         clients = []
-        for pair in itertools.combinations(hosts, 2):
-            info ( '%s <--> %s\n' % ( pair[0].name, pair[1].name ))
-            cmd = "iperf -c %s -t %s" % (pair[1].IP(), seconds)
-            clients.append(pair[0].popen(cmd))
+        for s, d in itertools.combinations(hosts, 2):
+            info ( '%s <--> %s\n' % ( s.name, d.name ))
+            cmd = 'iperf -c %s -t %s -y csv' % (d.IP(), seconds)
+            p = s.popen(cmd)
+            p.s = s.name
+            p.d = d.name
+            clients.append(p)
 
-        progress( seconds )
+        def handler (_signum, _frame):
+            raise BackgroundException()
+        oldSignal = signal.getsignal(signal.SIGTSTP)
+        signal.signal(signal.SIGTSTP, handler)
 
-        for c in clients:
-            out, err = c.communicate()
-            if err:
-                info( err )
-            else:
-                debug( out )
-                #TODO parse output and print summary
+        def finish( verbose=True ):
+            for c in clients:
+                out, err = c.communicate()
+                if verbose:
+                    if err:
+                        info( err )
+                    else:
+                        bw = out.split( ',' )[8]
+                        info( '%s <--> %s: %s\n' % ( c.s, c.d, formatBw(bw) ) )
+            for s in servers:
+                s.terminate()
 
-        for s in servers:
-            s.terminate()
+        try:
+            info ( 'Press ^Z to continue in background or ^C to abort\n')
+            progress( seconds )
+            finish()
+        except KeyboardInterrupt:
+            for c in clients:
+                c.terminate()
+            for s in servers:
+                s.terminate()
+        except BackgroundException:
+            info( '\n*** Continuing in background...\n' )
+            t = Thread( target=finish, args=[ False ] )
+            t.start()
+        finally:
+            #Disable custom background signal
+            signal.signal(signal.SIGTSTP, oldSignal)
 
 def progress(t):
     while t > 0:
@@ -115,13 +137,39 @@
         sleep(1)
     print
 
-# Initialize ONOSMininet the first time that the class is loaded
-ONOSMininet.setup()
+def formatBw( bw ):
+    bw = float(bw)
+    if bw > 1000:
+        bw /= 1000
+        if bw > 1000:
+            bw /= 1000
+            if bw > 1000:
+                bw /= 1000
+                return '%.2f Gbps' % bw
+            return '%.2f Mbps' % bw
+        return '%.2f Kbps' % bw
+    return '%.2f bps' % bw
 
-def do_iperf( self, line ):
+class BackgroundException( Exception ):
+    pass
+
+def do_bgIperf( self, line ):
     args = line.split()
     if not args:
         output( 'Provide a list of hosts.\n' )
+
+    #Try to parse the '-t' argument as the number of seconds
+    seconds = 10
+    for i, arg in enumerate(args):
+        if arg == '-t':
+            if i + 1 < len(args):
+                try:
+                    seconds = int(args[i + 1])
+                except ValueError:
+                    error( 'Could not parse number of seconds: %s', args[i+1] )
+                del(args[i+1])
+            del args[i]
+
     hosts = []
     err = False
     for arg in args:
@@ -131,15 +179,16 @@
         else:
             hosts.append( self.mn[ arg ] )
     if "bgIperf" in dir(self.mn) and not err:
-        self.mn.bgIperf( hosts )
+        self.mn.bgIperf( hosts, seconds=seconds )
 
-def do_gratuitousArp( self, _line ):
-    if "gratuitousArp" in dir(self.mn):
-        self.mn.gratuitousArp()
+def do_gratuitousArp( self, line ):
+    args = line.split()
+    if "gratuitousArp" in dir( self.mn ):
+        self.mn.gratuitousArp( args )
     else:
-        output( 'Gratuitous ARP is not support.\n' )
+        output( 'Gratuitous ARP is not supported.\n' )
 
-CLI.do_bgIperf = do_iperf
+CLI.do_bgIperf = do_bgIperf
 CLI.do_gratuitousArp = do_gratuitousArp
 
 def run( topo, controllers=None, link=TCLink, autoSetMacs=True ):