Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 1 | #!/usr/bin/python |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 2 | import itertools |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 3 | import os |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 4 | import signal |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 5 | import sys |
| 6 | from argparse import ArgumentParser |
| 7 | from subprocess import call |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 8 | from threading import Thread |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 9 | from time import sleep |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 10 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 11 | import gratuitousArp |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 12 | from mininet.cli import CLI |
| 13 | from mininet.examples.controlnet import MininetFacade |
| 14 | from mininet.link import TCLink |
| 15 | from mininet.log import info, output, error |
| 16 | from mininet.log import setLogLevel |
| 17 | from mininet.net import Mininet |
| 18 | from mininet.node import RemoteController, Node |
| 19 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 20 | ARP_PATH = gratuitousArp.__file__.replace('.pyc', '.py') |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 21 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 22 | class ONOSMininet( Mininet ): |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 23 | |
| 24 | def __init__( self, controllers=[], gratuitousArp=True, build=True, *args, **kwargs ): |
| 25 | """Create Mininet object for ONOS. |
| 26 | controllers: List of controller IP addresses |
| 27 | gratuitousArp: Send an ARP from each host to aid controller's host discovery""" |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 28 | |
| 29 | # delay building for a second |
| 30 | kwargs[ 'build' ] = False |
| 31 | |
| 32 | Mininet.__init__(self, *args, **kwargs ) |
| 33 | |
| 34 | self.gratArp = gratuitousArp |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 35 | |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 36 | # If a controller is not provided, use list of remote controller IPs instead. |
| 37 | if 'controller' not in kwargs or not kwargs['controller']: |
| 38 | info ( '*** Adding controllers\n' ) |
| 39 | ctrl_count = 0 |
| 40 | for controllerIP in controllers: |
| 41 | self.addController( 'c%d' % ctrl_count, RemoteController, ip=controllerIP ) |
| 42 | info( ' c%d (%s)\n' % ( ctrl_count, controllerIP ) ) |
| 43 | ctrl_count = ctrl_count + 1 |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 44 | |
| 45 | if self.topo and build: |
| 46 | self.build() |
| 47 | |
| 48 | def start( self ): |
| 49 | Mininet.start( self ) |
| 50 | if self.gratArp: |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 51 | self.waitConnected( timeout=5 ) |
Thomas Vachuska | 552c382 | 2018-02-07 14:08:48 -0800 | [diff] [blame] | 52 | sleep(2) |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 53 | info ( '*** Sending a gratuitious ARP from each host\n' ) |
| 54 | self.gratuitousArp() |
| 55 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 56 | def verifyHosts( self, hosts ): |
| 57 | for i in range( len( hosts ) ): |
| 58 | if isinstance( hosts[i], str): |
| 59 | if hosts[i] in self: |
| 60 | hosts[i] = self[ hosts[i] ] |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 61 | else: |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 62 | info( '*** ERROR: %s is not a host\n' % hosts[i] ) |
| 63 | del hosts[i] |
| 64 | elif not isinstance( hosts[i], Node): |
| 65 | del hosts[i] |
| 66 | |
| 67 | def gratuitousArp( self, hosts=[] ): |
| 68 | "Send an ARP from each host to aid controller's host discovery; fallback to ping if necessary" |
| 69 | if not hosts: |
| 70 | hosts = self.hosts |
| 71 | self.verifyHosts( hosts ) |
| 72 | |
| 73 | for host in hosts: |
| 74 | info( '%s ' % host.name ) |
| 75 | info( host.cmd( ARP_PATH ) ) |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 76 | info ( '\n' ) |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 77 | |
| 78 | def pingloop( self ): |
| 79 | "Loop forever pinging the full mesh of hosts" |
| 80 | setLogLevel( 'error' ) |
| 81 | try: |
| 82 | while True: |
| 83 | self.ping() |
| 84 | finally: |
| 85 | setLogLevel( 'info' ) |
| 86 | |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 87 | def bgIperf( self, hosts=[], seconds=10 ): |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 88 | self.verifyHosts( hosts ) |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 89 | servers = [ host.popen("iperf -s") for host in hosts ] |
| 90 | |
| 91 | clients = [] |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 92 | for s, d in itertools.combinations(hosts, 2): |
| 93 | info ( '%s <--> %s\n' % ( s.name, d.name )) |
| 94 | cmd = 'iperf -c %s -t %s -y csv' % (d.IP(), seconds) |
| 95 | p = s.popen(cmd) |
| 96 | p.s = s.name |
| 97 | p.d = d.name |
| 98 | clients.append(p) |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 99 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 100 | def handler (_signum, _frame): |
| 101 | raise BackgroundException() |
| 102 | oldSignal = signal.getsignal(signal.SIGTSTP) |
| 103 | signal.signal(signal.SIGTSTP, handler) |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 104 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 105 | def finish( verbose=True ): |
| 106 | for c in clients: |
| 107 | out, err = c.communicate() |
| 108 | if verbose: |
| 109 | if err: |
| 110 | info( err ) |
| 111 | else: |
| 112 | bw = out.split( ',' )[8] |
| 113 | info( '%s <--> %s: %s\n' % ( c.s, c.d, formatBw(bw) ) ) |
| 114 | for s in servers: |
| 115 | s.terminate() |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 116 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 117 | try: |
| 118 | info ( 'Press ^Z to continue in background or ^C to abort\n') |
| 119 | progress( seconds ) |
| 120 | finish() |
| 121 | except KeyboardInterrupt: |
| 122 | for c in clients: |
| 123 | c.terminate() |
| 124 | for s in servers: |
| 125 | s.terminate() |
| 126 | except BackgroundException: |
| 127 | info( '\n*** Continuing in background...\n' ) |
| 128 | t = Thread( target=finish, args=[ False ] ) |
| 129 | t.start() |
| 130 | finally: |
| 131 | #Disable custom background signal |
| 132 | signal.signal(signal.SIGTSTP, oldSignal) |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 133 | |
| 134 | def progress(t): |
| 135 | while t > 0: |
| 136 | sys.stdout.write( '.' ) |
| 137 | t -= 1 |
| 138 | sys.stdout.flush() |
| 139 | sleep(1) |
| 140 | print |
| 141 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 142 | def formatBw( bw ): |
| 143 | bw = float(bw) |
| 144 | if bw > 1000: |
| 145 | bw /= 1000 |
| 146 | if bw > 1000: |
| 147 | bw /= 1000 |
| 148 | if bw > 1000: |
| 149 | bw /= 1000 |
| 150 | return '%.2f Gbps' % bw |
| 151 | return '%.2f Mbps' % bw |
| 152 | return '%.2f Kbps' % bw |
| 153 | return '%.2f bps' % bw |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 154 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 155 | class BackgroundException( Exception ): |
| 156 | pass |
| 157 | |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 158 | |
| 159 | def get_mn(mn): |
| 160 | if isinstance(mn, ONOSMininet): |
| 161 | return mn |
| 162 | elif isinstance(mn, MininetFacade): |
| 163 | # There's more Mininet objects instantiated (e.g. one for the control network in onos.py). |
| 164 | for net in mn.nets: |
| 165 | if isinstance(net, ONOSMininet): |
| 166 | return net |
| 167 | return None |
| 168 | |
| 169 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 170 | def do_bgIperf( self, line ): |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 171 | args = line.split() |
| 172 | if not args: |
| 173 | output( 'Provide a list of hosts.\n' ) |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 174 | |
| 175 | #Try to parse the '-t' argument as the number of seconds |
| 176 | seconds = 10 |
| 177 | for i, arg in enumerate(args): |
| 178 | if arg == '-t': |
| 179 | if i + 1 < len(args): |
| 180 | try: |
| 181 | seconds = int(args[i + 1]) |
| 182 | except ValueError: |
| 183 | error( 'Could not parse number of seconds: %s', args[i+1] ) |
| 184 | del(args[i+1]) |
| 185 | del args[i] |
| 186 | |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 187 | hosts = [] |
| 188 | err = False |
| 189 | for arg in args: |
| 190 | if arg not in self.mn: |
| 191 | err = True |
| 192 | error( "node '%s' not in network\n" % arg ) |
| 193 | else: |
| 194 | hosts.append( self.mn[ arg ] ) |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 195 | mn = get_mn( self.mn ) |
| 196 | if "bgIperf" in dir( mn ) and not err: |
| 197 | mn.bgIperf( hosts, seconds=seconds ) |
| 198 | else: |
| 199 | output('Background Iperf is not supported.\n') |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 200 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 201 | def do_gratuitousArp( self, line ): |
| 202 | args = line.split() |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 203 | mn = get_mn(self.mn) |
| 204 | if "gratuitousArp" in dir( mn ): |
| 205 | mn.gratuitousArp( args ) |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 206 | else: |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 207 | output( 'Gratuitous ARP is not supported.\n' ) |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 208 | |
Brian O'Connor | e9b4dd7 | 2016-03-05 01:07:18 -0800 | [diff] [blame] | 209 | CLI.do_bgIperf = do_bgIperf |
Brian O'Connor | d6c73fb | 2016-03-04 15:24:52 -0800 | [diff] [blame] | 210 | CLI.do_gratuitousArp = do_gratuitousArp |
| 211 | |
Luca Prete | 3d402dc | 2016-12-13 16:25:03 -0800 | [diff] [blame] | 212 | def parse_args(): |
| 213 | parser = ArgumentParser(description='ONOS Mininet') |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 214 | parser.add_argument('--cluster-size', help='Starts an ONOS cluster with the given number of instances', |
| 215 | type=int, action='store', dest='clusterSize', required=False, default=0) |
| 216 | parser.add_argument('--netcfg', help='Relative path of the JSON file to be used with netcfg', |
| 217 | type=str, action='store', dest='netcfgJson', required=False, default='') |
| 218 | parser.add_argument('ipAddrs', metavar='IP', type=str, nargs='*', |
| 219 | help='List of controller IP addresses', default=[]) |
Luca Prete | 3d402dc | 2016-12-13 16:25:03 -0800 | [diff] [blame] | 220 | return parser.parse_args() |
| 221 | |
| 222 | def run( topo, controllers=None, link=TCLink, autoSetMacs=True): |
| 223 | if not topo: |
| 224 | print 'Need to provide a topology' |
| 225 | exit(1) |
| 226 | |
| 227 | args = parse_args() |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 228 | |
| 229 | if not controllers and len(args.ipAddrs) > 0: |
| 230 | controllers = args.ipAddrs |
| 231 | |
| 232 | if not controllers and args.clusterSize < 1: |
| 233 | print 'Need to provide a list of controller IPs, or define a cluster size.' |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 234 | exit( 1 ) |
| 235 | |
| 236 | setLogLevel( 'info' ) |
| 237 | |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 238 | if args.clusterSize > 0: |
| 239 | if 'ONOS_ROOT' not in os.environ: |
| 240 | print "Environment var $ONOS_ROOT not set (needed to import onos.py)" |
| 241 | exit( 1 ) |
| 242 | sys.path.append(os.environ["ONOS_ROOT"] + "/tools/dev/mininet") |
| 243 | from onos import ONOSCluster, ONOSOVSSwitch, ONOSCLI |
| 244 | controller = ONOSCluster('c0', args.clusterSize) |
| 245 | onosAddr = controller.nodes()[0].IP() |
| 246 | net = ONOSMininet( topo=topo, controller=controller, switch=ONOSOVSSwitch, link=link, |
| 247 | autoSetMacs=autoSetMacs ) |
| 248 | cli = ONOSCLI |
| 249 | else: |
| 250 | onosAddr = controllers[0] |
| 251 | net = ONOSMininet(topo=topo, controllers=controllers, link=link, autoSetMacs=autoSetMacs) |
| 252 | cli = CLI |
| 253 | |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 254 | net.start() |
Carmelo Cascone | c52a4b1 | 2016-10-24 16:47:17 +0200 | [diff] [blame] | 255 | |
| 256 | if len(args.netcfgJson) > 0: |
| 257 | if not os.path.isfile(args.netcfgJson): |
| 258 | error('*** WARNING no such netcfg file: %s\n' % args.netcfgJson) |
| 259 | else: |
| 260 | info('*** Setting netcfg: %s\n' % args.netcfgJson) |
| 261 | call(("onos-netcfg", onosAddr, args.netcfgJson)) |
| 262 | |
| 263 | cli( net ) |
Thomas Vachuska | 9ee4979 | 2016-03-01 16:51:14 -0800 | [diff] [blame] | 264 | net.stop() |