Revision 216a4b7c

View differences:

bin/mn
20 20
from mininet.cli import CLI
21 21
from mininet.log import lg, LEVELS, info
22 22
from mininet.net import Mininet, init
23
from mininet.node import Host, Controller, ControllerParams, NOX
23
from mininet.node import Host, CPULimitedHost, Controller, NOX
24 24
from mininet.node import RemoteController, UserSwitch, OVSKernelSwitch
25
from mininet.link import Intf, TCIntf
25 26
from mininet.topo import SingleSwitchTopo, LinearTopo, SingleSwitchReversedTopo
26 27
from mininet.topolib import TreeTopo
27
from mininet.util import makeNumeric
28
from mininet.util import makeNumeric, custom
29

  
30
def customNode( constructors, argStr ):
31
    "Return custom Node constructor based on argStr"
32
    cname, noargs, kwargs = splitArgs( argStr )
33
    constructor = constructors.get( cname, None )
34
    if noargs:
35
        raise Exception( "please specify keyword  arguments for " + cname )
36
    if not constructor:
37
        raise Exception( "error: %s is unknown - please specify one of %s" %
38
               ( cname, constructors.keys() ) )
39
    def custom( *args, **params ):
40
        params.update( kwargs )
41
        print 'CONSTRUCTOR', constructor, 'ARGS', args, 'PARAMS', params
42
        return constructor( *args, **params )
43
    return custom
28 44

  
29 45
# built in topologies, created only when run
30 46
TOPODEF = 'minimal'
......
38 54
SWITCHES = { 'user': UserSwitch,
39 55
            'ovsk': OVSKernelSwitch }
40 56

  
41
HOSTDEF = 'process'
42
HOSTS = { 'process': Host }
57
HOSTDEF = 'proc'
58
HOSTS = { 'proc': Host,
59
          'rt': custom( CPULimitedHost, sched='rt' ),
60
          'cfs': custom( CPULimitedHost, sched='cfs' ) }
43 61

  
44 62
CONTROLLERDEF = 'ref'
45
# a and b are the name and inNamespace params.
46 63
CONTROLLERS = { 'ref': Controller,
47
               'nox_dump': lambda name: NOX( name, 'packetdump' ),
48
               'nox_pysw': lambda name: NOX( name, 'pyswitch' ),
49
               'remote': lambda name: None,
64
               'nox': NOX, 
65
               'remote': RemoteController,
50 66
               'none': lambda name: None }
51 67

  
68
INTFDEF = 'default'
69
INTFS = { 'default': Intf,
70
          'tc': TCIntf }
71

  
72

  
52 73
# optional tests to run
53 74
TESTS = [ 'cli', 'build', 'pingall', 'pingpair', 'iperf', 'all', 'iperfudp',
54 75
         'none' ]
......
56 77
ALTSPELLING = { 'pingall': 'pingAll', 'pingpair': 'pingPair',
57 78
    'iperfudp': 'iperfUdp', 'iperfUDP': 'iperfUdp', 'prefixlen': 'prefixLen' }
58 79

  
59
def buildTopo( topo ):
60
    "Create topology from string with format (object, arg1, arg2,...)."
61
    topo_split = topo.split( ',' )
62
    topo_name = topo_split[ 0 ]
63
    topo_params = topo_split[ 1: ]
64

  
65
    # Convert int and float args; removes the need for every topology to
66
    # be flexible with input arg formats.
67
    topo_seq_params = [ s for s in topo_params if '=' not in s ]
68
    topo_seq_params = [ makeNumeric( s ) for s in topo_seq_params ]
69
    topo_kw_params = {}
70
    for s in [ p for p in topo_params if '=' in p ]:
80

  
81
def splitArgs( argstr ):
82
    """Split argument string into usable python arguments
83
       argstr: argument string with format fn,arg2,kw1=arg3...
84
       returns: fn, args, kwargs"""
85
    split = argstr.split( ',' )
86
    fn = split[ 0 ]
87
    params = split[ 1: ]
88
    # Convert int and float args; removes the need for function
89
    # to be flexible with input arg formats.
90
    args = [ s for s in params if '=' not in s ]
91
    args = map( makeNumeric, args )
92
    kwargs = {}
93
    for s in [ p for p in params if '=' in p ]:
71 94
        key, val = s.split( '=' )
72
        topo_kw_params[ key ] = makeNumeric( val )
95
        kwargs[ key ] = makeNumeric( val )
96
    return fn, args, kwargs
73 97

  
74
    if topo_name not in TOPOS.keys():
75
        raise Exception( 'Invalid topo_name %s' % topo_name )
76
    return TOPOS[ topo_name ]( *topo_seq_params, **topo_kw_params )
98

  
99
def buildTopo( topoStr ):
100
    "Create topology from string with format (object, arg1, arg2,...)."
101
    topo, args, kwargs = splitArgs( topoStr )
102
    if topo not in TOPOS:
103
        raise Exception( 'Invalid topo name %s' % topo )
104
    return TOPOS[ topo ]( *args, **kwargs )
77 105

  
78 106

  
79 107
def addDictOption( opts, choicesDict, default, name, helpStr=None ):
......
87 115
        raise Exception( 'Invalid  default %s for choices dict: %s' %
88 116
                        ( default, name ) )
89 117
    if not helpStr:
90
        helpStr = '[' + ' '.join( choicesDict.keys() ) + ']'
118
        helpStr = '|'.join( sorted( choicesDict.keys() ) ) + '[,param=value...]'
91 119
    opts.add_option( '--' + name,
92
                    type='choice',
93
                    choices=choicesDict.keys(),
120
                    type='string',
94 121
                    default = default,
95 122
                    help = helpStr )
96 123

  
......
135 162
        """Parse command-line args and return options object.
136 163
           returns: opts parse options dict"""
137 164
        if '--custom' in sys.argv:
138
            print "custom in sys.argv"
139 165
            index = sys.argv.index( '--custom' )
140 166
            if len( sys.argv ) > index + 1:
141 167
                custom = sys.argv[ index + 1 ]
......
147 173
        addDictOption( opts, SWITCHES, SWITCHDEF, 'switch' )
148 174
        addDictOption( opts, HOSTS, HOSTDEF, 'host' )
149 175
        addDictOption( opts, CONTROLLERS, CONTROLLERDEF, 'controller' )
176
        addDictOption( opts, INTFS, INTFDEF, 'intf' )
177
        addDictOption( opts, TOPOS, TOPODEF, 'topo' )
150 178

  
151
        opts.add_option( '--topo', type='string', default=TOPODEF,
152
                        help='[' + ' '.join( TOPOS.keys() ) + '],arg1,arg2,'
153
                        '...argN')
154 179
        opts.add_option( '--clean', '-c', action='store_true',
155 180
                        default=False, help='clean and exit' )
156 181
        opts.add_option( '--custom', type='string', default=None,
157 182
                        help='read custom topo and node params from .py file' )
158 183
        opts.add_option( '--test', type='choice', choices=TESTS,
159 184
                        default=TESTS[ 0 ],
160
                        help='[' + ' '.join( TESTS ) + ']' )
185
                        help='|'.join( TESTS ) )
161 186
        opts.add_option( '--xterms', '-x', action='store_true',
162 187
                        default=False, help='spawn xterms for each node' )
163 188
        opts.add_option( '--mac', action='store_true',
164
                        default=False, help='set MACs equal to DPIDs' )
189
                        default=False, help='automatically set host MACs' )
165 190
        opts.add_option( '--arp', action='store_true',
166 191
                        default=False, help='set all-pairs ARP entries' )
167 192
        opts.add_option( '--verbosity', '-v', type='choice',
168 193
                        choices=LEVELS.keys(), default = 'info',
169
                        help = '[' + ' '.join( LEVELS.keys() ) + ']' )
194
                        help = '|'.join( LEVELS.keys() )  )
170 195
        opts.add_option( '--ip', type='string', default='127.0.0.1',
171
                        help='[ip address as a dotted decimal string for a'
172
                        'remote controller]' )
173
        opts.add_option( '--port', type='int', default=6633,
174
                        help='[port integer for a listening remote'
175
                        ' controller]' )
196
                        help='ip address as a dotted decimal string for a'
197
                        'remote controller' )
176 198
        opts.add_option( '--innamespace', action='store_true',
177 199
                        default=False, help='sw and ctrl in namespace?' )
178 200
        opts.add_option( '--listenport', type='int', default=6634,
179
                        help='[base port for passive switch listening'
180
                        ' controller]' )
201
                         help='base port for passive switch listening' )
181 202
        opts.add_option( '--nolistenport', action='store_true',
182 203
                        default=False, help="don't use passive listening port")
183 204
        opts.add_option( '--pre', type='string', default=None,
184
                        help='[CLI script to run before tests]' )
205
                        help='CLI script to run before tests' )
185 206
        opts.add_option( '--post', type='string', default=None,
186
                        help='[CLI script to run after tests]' )
207
                        help='CLI script to run after tests' )
187 208
        opts.add_option( '--prefixlen', type='int', default=8,
188
                        help='[prefix length (e.g. /8) for automatic '
189
                        'network configuration]' )
209
                        help='prefix length (e.g. /8) for automatic '
210
                        'network configuration' )
190 211

  
191 212
        self.options, self.args = opts.parse_args()
192 213

  
......
214 235
        start = time.time()
215 236

  
216 237
        topo = buildTopo( self.options.topo )
217
        switch = SWITCHES[ self.options.switch ]
218
        host = HOSTS[ self.options.host ]
219
        controller = CONTROLLERS[ self.options.controller ]
220
        if self.options.controller == 'remote':
221
            controller = lambda a: RemoteController( a,
222
                             defaultIP=self.options.ip,
223
                             port=self.options.port )
238
        switch = customNode( SWITCHES,  self.options.switch )
239
        host = customNode( HOSTS, self.options.host )
240
        controller = customNode( CONTROLLERS, self.options.controller )
241
        intf = customNode( INTFS, self.options.intf )
224 242

  
225 243
        if self.validate:
226 244
            self.validate( self.options )
227 245

  
228
        # We should clarify what this is actually for...
229
        # It seems like it should be default values for the
230
        # *data* network, so it may be misnamed.
231
        controllerParams = ControllerParams( '10.0.0.0',
232
            self.options.prefixlen)
233

  
234 246
        inNamespace = self.options.innamespace
235 247
        xterms = self.options.xterms
236 248
        mac = self.options.mac
......
238 250
        listenPort = None
239 251
        if not self.options.nolistenport:
240 252
            listenPort = self.options.listenport
241
        mn = Mininet( topo, switch, host, controller, controllerParams,
242
                     inNamespace=inNamespace,
243
                     xterms=xterms, autoSetMacs=mac,
244
                     autoStaticArp=arp, listenPort=listenPort )
253
        mn = Mininet( topo=topo, 
254
                      switch=switch, host=host, controller=controller, 
255
                      intf=intf,
256
                      inNamespace=inNamespace,
257
                      xterms=xterms, autoSetMacs=mac,
258
                      autoStaticArp=arp, listenPort=listenPort )
245 259

  
246 260
        if self.options.pre:
247 261
            CLI( mn, script=self.options.pre )
examples/limit.py
5 5
"""
6 6

  
7 7
from mininet.net import Mininet
8
from mininet.link import TCIntf, Link
8
from mininet.link import TCIntf
9 9
from mininet.node import CPULimitedHost
10 10
from mininet.topolib import TreeTopo
11 11
from mininet.util import custom, quietRun
12 12
from mininet.log import setLogLevel
13 13
from time import sleep
14 14

  
15
def testLinkLimit( net ):
16
    print '*** Testing network bandwidth limit'
17
    net.iperf()
15
def testLinkLimit( net, bw ):
16
    print '*** Testing network %.2f Mbps bandwidth limit' % bw
17
    net.iperf( )
18 18

  
19
def testCpuLimit( net ):
20
    print '*** Testing CPU bandwidth limit'
19
def testCpuLimit( net, cpu ):
20
    pct = cpu * 100
21
    print '*** Testing CPU %.0f%% bandwidth limit' % pct
21 22
    h1, h2 = net.hosts
22 23
    h1.cmd( 'while true; do a=1; done &' )
23 24
    h2.cmd( 'while true; do a=1; done &' )
......
26 27
    cmd = 'ps -p %s,%s -o pid,%%cpu,args' % ( pid1, pid2 )
27 28
    for i in range( 0, 5):
28 29
        sleep( 1 ) 
29
        print quietRun( cmd )
30
        print quietRun( cmd ).strip()
30 31
    h1.cmd( 'kill %1')
31 32
    h2.cmd( 'kill %1')
32 33

  
33
def limit():
34
    "Example/test of link and CPU bandwidth limits"
35
    # 1 Mbps interfaces limited using tc
36
    intf1Mbps = custom( TCIntf, bw=1 )
37
    # Links consisting of two 10 Mbps interfaces
38
    link1Mbps = custom( Link, intf=intf1Mbps, cls2=TCIntf )
39
    # Hosts with 30% of system bandwidth
40
    host30pct = custom( CPULimitedHost, cpu=.3 )
34
def limit( bw=1, cpu=.3 ):
35
    """Example/test of link and CPU bandwidth limits
36
       bw: interface bandwidth limit in Mbps
37
       cpu: cpu limit as fraction of overall CPU time"""
38
    intf = custom( TCIntf, bw=1 )
41 39
    myTopo = TreeTopo( depth=1, fanout=2 )
42
    net = Mininet( topo=myTopo,
43
                   link=link1Mbps, 
44
                   host=host30pct )
45
    net.start()
46
    testLinkLimit( net )
47
    testCpuLimit( net )
48
    net.stop()
40
    for sched in 'rt', 'cfs':
41
        print '*** Testing with', sched, 'bandwidth limiting'
42
        host = custom( CPULimitedHost, sched=sched, cpu=cpu )
43
        net = Mininet( topo=myTopo, intf=intf, host=host )
44
        net.start()
45
        testLinkLimit( net, bw=bw )
46
        testCpuLimit( net, cpu=cpu )
47
        net.stop()
49 48

  
50 49
if __name__ == '__main__':
51 50
    setLogLevel( 'info' )
mininet/net.py
104 104
    "Network emulation with hosts spawned in network namespaces."
105 105

  
106 106
    def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
107
                 controller=Controller, link=Link,
107
                 controller=Controller, link=Link, intf=None, 
108 108
                 build=True, xterms=False, cleanup=False,
109 109
                 inNamespace=False,
110 110
                 autoSetMacs=False, autoStaticArp=False, listenPort=None ):
......
114 114
           host: default Host class/constructor
115 115
           controller: default Controller class/constructor
116 116
           link: default Link class/constructor
117
           intf: default Intf class/constructor
117 118
           ipBase: base IP address for hosts,
118 119
           build: build now from topo?
119 120
           xterms: if build now, spawn xterms?
......
123 124
           autoStaticArp: set all-pairs static MAC addrs?
124 125
           listenPort: base listening port to open; will be incremented for
125 126
               each additional switch in the net if inNamespace=False"""
127
        self.topo = topo
126 128
        self.switch = switch
127 129
        self.host = host
128 130
        self.controller = controller
129 131
        self.link = link
130
        self.topo = topo
132
        self.intf = intf
131 133
        self.inNamespace = inNamespace
132 134
        self.xterms = xterms
133 135
        self.cleanup = cleanup
......
199 201
    def configHosts( self ):
200 202
        "Configure a set of hosts."
201 203
        for host in self.hosts:
204
            info( host.name + ' ' )
202 205
            host.configDefault( defaultRoute=host.defaultIntf )
203 206
            # You're low priority, dude!
204 207
            # BL: do we want to do this here or not?
205 208
            # May not make sense if we have CPU lmiting...
206 209
            # quietRun( 'renice +18 -p ' + repr( host.pid ) )
207
            info( host.name + ' ' )
208 210
        info( '\n' )
209 211

  
210 212
    def buildFromTopo( self, topo=None ):
......
235 237
            ei = topo.edgeInfo( srcId, dstId )
236 238
            link = getattr( ei, 'cls', link )
237 239
            params = ei.params
240
            if self.intf and not 'intf' in params:
241
                params[ 'intf' ] = self.intf
238 242
            if not link:
239 243
                link = self.link
240 244
            info( '(%s, %s) ' % ( src.name, dst.name ) )
......
447 451
            error( 'could not parse iperf output: ' + iperfOutput )
448 452
            return ''
449 453

  
454
    # XXX This should be cleaned up
455

  
450 456
    def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
451 457
        """Run iperf between two hosts.
452 458
           hosts: list of hosts; if None, uses opposite hosts
mininet/node.py
127 127
        if self.shell:
128 128
            error( "%s: shell is already running" )
129 129
            return
130
        # mnexec: (c)lose descriptors, (d)etach from tty,
131
        # (p)rint pid, and run in (n)amespace 
130 132
        opts = '-cdp'
131 133
        if self.inNamespace:
132 134
            opts += 'n'
135
        # bash -m: enable job control
133 136
        cmd = [ 'mnexec', opts, 'bash', '-m' ]
134 137
        self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
135 138
            close_fds=True )
......
405 408
    # annoying, but at least the information is there!
406 409

  
407 410
    def setParam( self, results, method, **param ):
408
        """Internal method: configure single parameter"""
411
        """Internal method: configure a *single* parameter
412
           results: dict of results to update
413
           method: config method name
414
           param: arg=value (ignore if value=None)
415
           value may also be list or dict"""
409 416
        name, value = param.items()[ 0 ]
410 417
        f = getattr( self, method, None )
411
        if not value or not f:
418
        if not f or value is None:
412 419
            return
413 420
        if type( value ) is list:
414 421
            result = f( *value )
......
417 424
        else:
418 425
            result = f( value )
419 426
        results[ name ] = result
427
        return result
420 428

  
421 429
    def config( self, mac=None, ip=None, ifconfig=None, 
422 430
                defaultRoute=None, **params):
......
462 470
            self.name, self.IP(), ','.join( self.intfNames() ), self.pid )
463 471

  
464 472

  
465
class CPULimitedHost( Node ):
473
class Host( Node ):
474
    "A host is simply a Node"
475
    pass
476

  
477

  
478
class CPULimitedHost( Host ):
466 479

  
467 480
    "CPU limited host"
468 481

  
......
472 485
        cgroup = 'cpu,cpuacct:/' + self.name
473 486
        errFail( 'cgcreate -g ' + cgroup )
474 487
        errFail( 'cgclassify -g %s %s' % ( cgroup, self.pid ) )
475
        self.sched = 'rt'
476
        self.period_us = 10000
477
        self.rtset = False
488
        self.period_us = kwargs.get( 'period_us', 10000 )
489
        self.sched = kwargs.get( 'sched', 'rt' )
478 490

  
479 491
    def cgroupSet( self, param, value, resource='cpu' ):
480 492
        "Set a cgroup parameter and return its value"
......
495 507
        lastword = firstline.split( ' ' )[ -1 ]
496 508
        return lastword
497 509

  
498
    def setCPUFrac( self, f=-1 ):
499
        "Set overall CPU fraction for this host"
500
        if ( f < 0 or f is None):
510
    # BL comment:
511
    # This may not be the right API, 
512
    # since it doesn't specify CPU bandwidth in "absolute"
513
    # units the way link bandwidth is specified.
514
    # We should use MIPS or SPECINT or something instead.
515
    # Alternatively, we should change from system fraction
516
    # to CPU seconds per second, essentially assuming that
517
    # all CPUs are the same.
518
    
519
    def setCPUFrac( self, f=-1, sched=None):
520
        """Set overall CPU fraction for this host
521
           f: CPU bandwidth limit (fraction)
522
           sched: 'rt' or 'cfs'
523
           Note 'cfs' requires CONFIG_CFS_BANDWIDTH"""
524
        if not f:
525
            return
526
        if not sched:
527
            sched = self.sched
528
        period = self.period_us
529
        if sched == 'rt':
530
            pstr, qstr = 'rt_period_us', 'rt_runtime_us'
531
            # RT uses system time for period and quota
532
            quota = int( period * f * numCores() )
533
        elif sched == 'cfs':
534
            pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
535
            # CFS uses wall clock time for period and CPU time for quota.
536
            quota = int( self.period_us * f * numCores() )
537
            if f > 0 and quota < 1000:
538
                info( '*** setCPUFrac: quota too small - adjusting period\n' )
539
                quota = 1000
540
                period = int( quota / f / numCores() )
541
        else:
542
            return
543
        if quota < 0:
501 544
            # Reset to unlimited
502
            f = -1
503
        # Set new period and quota
504
        pstr, qstr = 'rt_period_us', 'rt_runtime_us'
505
        quota = int( self.period_us * f * numCores() )
506
        self.cgroupSet( pstr, self.period_us )
545
            quota = -1
546
        # Set cgroup's period and quota
547
        self.cgroupSet( pstr, period )
507 548
        nquota = int ( self.cgroupGet( qstr ) )
508 549
        self.cgroupSet( qstr, quota )
509 550
        nperiod = int( self.cgroupGet( pstr ) )
510
        # Set RT priority
511
        nchrt = self.chrt( prio=20 )
512
        # Check to make sure it worked
513
        if 'SCHED_RR' not in nchrt:
514
            error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
551
        # Make sure it worked
515 552
        if nperiod != self.period_us:
516 553
            error( '*** error: period is %s rather than %s\n' % (
517 554
                    nperiod, self.period_us ) )
518 555
        if nquota != quota:
519 556
            error( '*** error: quota is %s rather than %s\n' % (
520 557
                    nquota, quota ) )
558
        if sched == 'rt':
559
            # Set RT priority if necessary
560
            nchrt = self.chrt( prio=20 )
561
            # Nake sure it worked
562
            if sched == 'SCHED_RR' not in nchrt:
563
                error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
564
            info( '( period', nperiod, 'quota', nquota, nchrt, ') ' )
565
        else:
566
            info( '( period', nperiod, 'quota', nquota, ') ' )
521 567

  
522
    def config( self, cpu=None, **params ):
568
    def config( self, cpu=None, sched=None, **params ):
523 569
        """cpu: desired overall system CPU fraction
524 570
           params: parameters for Node.config()"""
525 571
        r = Node.config( self, **params )
572
        # Was considering cpu={'cpu': cpu , 'sched': sched}, but
573
        # that seems redundant
526 574
        self.setParam( r, 'setCPUFrac', cpu=cpu )
527 575
        return r
528 576

  
529
Host = CPULimitedHost
530

  
531

  
532 577
# Some important things to note:
533 578
#
534 579
# The "IP" address which we assign to the switch is not
......
805 850
class NOX( Controller ):
806 851
    "Controller to run a NOX application."
807 852

  
808
    def __init__( self, name, noxArgs=None, **kwargs ):
853
    def __init__( self, name, noxArgs=[], **kwargs ):
809 854
        """Init.
810 855
           name: name to give controller
811 856
           noxArgs: list of args, or single arg, to pass to NOX"""

Also available in: Unified diff