8000 mininet/mininet/node.py at nat-cmd · gityzhu/mininet · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
1449 lines (1262 loc) · 54.9 KB

node.py

File metadata and controls

1449 lines (1262 loc) · 54.9 KB
result = f( *value )
elif isinstance( value, dict ):
result = f( **value )
else:
result = f( value )
results[ name ] = result
return result
def config( self, mac=None, ip=None,
defaultRoute=None, lo='up', **_params ):
"""Configure Node according to (optional) parameters:
mac: MAC address for default interface
ip: IP address for default interface
ifconfig: arbitrary interface configuration
Subclasses should override this method and call
the parent class's config(**params)"""
# If we were overriding this method, we would call
# the superclass config method here as follows:
# r = Parent.config( **_params )
r = {}
self.setParam( r, 'setMAC', mac=mac )
self.setParam( r, 'setIP', ip=ip )
self.setParam( r, 'setDefaultRoute', defaultRoute=defaultRoute )
# This should be examined
self.cmd( 'ifconfig lo ' + lo )
return r
def configDefault( self, **moreParams ):
"Configure with default parameters"
self.params.update( moreParams )
self.config( **self.params )
# This is here for backward compatibility
def linkTo( self, node, link=Link ):
"""(Deprecated) Link to another node
replace with Link( node1, node2)"""
return link( self, node )
# Other methods
def intfList( self ):
"List of our interfaces sorted by port number"
return [ self.intfs[ p ] for p in sorted( self.intfs.iterkeys() ) ]
def intfNames( self ):
"The names of our interfaces sorted by port number"
return [ str( i ) for i in self.intfList() ]
def __repr__( self ):
"More informative string representation"
intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
for i in self.intfList() ] ) )
return '<%s %s: %s pid=%s> ' % (
self.__class__.__name__, self.name, intfs, self.pid )
def __str__( self ):
"Abbreviated string representation"
return self.name
# Automatic class setup support
isSetup = False
@classmethod
def checkSetup( cls ):
"Make sure our class and superclasses are set up"
while cls and not getattr( cls, 'isSetup', True ):
cls.setup()
cls.isSetup = True
# Make pylint happy
cls = getattr( type( cls ), '__base__', None )
@classmethod
def setup( cls ):
"Make sure our class dependencies are available"
pathCheck( 'mnexec', 'ifconfig', moduleName='Mininet')
class Host( Node ):
"A host is simply a Node"
pass
class CPULimitedHost( Host ):
"CPU limited host"
def __init__( self, name, sched='cfs', **kwargs ):
Host.__init__( self, name, **kwargs )
# Initialize class if necessary
if not CPULimitedHost.inited:
CPULimitedHost.init()
# Create a cgroup and move shell into it
self.cgroup = 'cpu,cpuacct,cpuset:/' + self.name
errFail( 'cgcreate -g ' + self.cgroup )
# We don't add ourselves to a cpuset because you must
# specify the cpu and memory placement first
errFail( 'cgclassify -g cpu,cpuacct:/%s %s' % ( self.name, self.pid ) )
# BL: Setting the correct period/quota is tricky, particularly
# for RT. RT allows very small quotas, but the overhead
# seems to be high. CFS has a mininimum quota of 1 ms, but
# still does better with larger period values.
self.period_us = kwargs.get( 'period_us', 100000 )
self.sched = sched
if sched == 'rt':
self.checkRtGroupSched()
self.rtprio = 20
def cgroupSet( self, param, value, resource='cpu' ):
"Set a cgroup parameter and return its value"
cmd = 'cgset -r %s.%s=%s /%s' % (
resource, param, value, self.name )
quietRun( cmd )
nvalue = int( self.cgroupGet( param, resource ) )
if nvalue != value:
error( '*** error: cgroupSet: %s set to %s instead of %s\n'
% ( param, nvalue, value ) )
return nvalue
def cgroupGet( self, param, resource='cpu' ):
"Return value of cgroup parameter"
cmd = 'cgget -r %s.%s /%s' % (
resource, param, self.name )
return int( quietRun( cmd ).split()[ -1 ] )
def cgroupDel( self ):
"Clean up our cgroup"
# info( '*** deleting cgroup', self.cgroup, '\n' )
_out, _err, exitcode = errRun( 'cgdelete -r ' + self.cgroup )
return exitcode != 0
def popen( self, *args, **kwargs ):
"""Return a Popen() object in node's namespace
args: Popen() args, single list, or string
kwargs: Popen() keyword args"""
# Tell mnexec to execute command in our cgroup
mncmd = [ 'mnexec', '-g', self.name,
'-da', str( self.pid ) ]
# if our cgroup is not given any cpu time,
# we cannot assign the RR Scheduler.
if self.sched == 'rt':
if int( self.cgroupGet( 'rt_runtime_us', 'cpu' ) ) <= 0:
mncmd += [ '-r', str( self.rtprio ) ]
else:
debug( '*** error: not enough cpu time available for %s.' % self.name,
'Using cfs scheduler for subprocess\n' )
return Host.popen( self, *args, mncmd=mncmd, **kwargs )
def cleanup( self ):
"Clean up Node, then clean up our cgroup"
super( CPULimitedHost, self ).cleanup()
retry( retries=3, delaySecs=1, fn=self.cgroupDel )
_rtGroupSched = False # internal class var: Is CONFIG_RT_GROUP_SCHED set?
@classmethod
def checkRtGroupSched( cls ):
"Check (Ubuntu,Debian) kernel config for CONFIG_RT_GROUP_SCHED for RT"
if not cls._rtGroupSched:
release = quietRun( 'uname -r' ).strip('\r\n')
output = quietRun( 'grep CONFIG_RT_GROUP_SCHED /boot/config-%s' % release )
if output == '# CONFIG_RT_GROUP_SCHED is not set\n':
error( '\n*** error: please enable RT_GROUP_SCHED in your kernel\n' )
exit( 1 )
cls._rtGroupSched = True
def chrt( self ):
"Set RT scheduling priority"
quietRun( 'chrt -p %s %s' % ( self.rtprio, self.pid ) )
result = quietRun( 'chrt -p %s' % self.pid )
firstline = result.split( '\n' )[ 0 ]
lastword = firstline.split( ' ' )[ -1 ]
if lastword != 'SCHED_RR':
error( '*** error: could not assign SCHED_RR to %s\n' % self.name )
return lastword
def rtInfo( self, f ):
"Internal method: return parameters for RT bandwidth"
pstr, qstr = 'rt_period_us', 'rt_runtime_us'
# RT uses wall clock time for period and quota
quota = int( self.period_us * f )
return pstr, qstr, self.period_us, quota
def cfsInfo( self, f ):
"Internal method: return parameters for CFS bandwidth"
pstr, qstr = 'cfs_period_us', 'cfs_quota_us'
# CFS uses wall clock time for period and CPU time for quota.
quota = int( self.period_us * f * numCores() )
period = self.period_us
if f > 0 and quota < 1000:
debug( '(cfsInfo: increasing default period) ' )
quota = 1000
period = int( quota / f / numCores() )
# Reset to unlimited on negative quota
if quota < 0:
quota = -1
return pstr, qstr, period, quota
# BL comment:
# This may not be the right API,
# since it doesn't specify CPU bandwidth in "absolute"
# units the way link bandwidth is specified.
# We should use MIPS or SPECINT or something instead.
# Alternatively, we should change from system fraction
# to CPU seconds per second, essentially assuming that
# all CPUs are the same.
def setCPUFrac( self, f, sched=None ):
"""Set overall CPU fraction for this host
f: CPU bandwidth limit (positive fraction, or -1 for cfs unlimited)
sched: 'rt' or 'cfs'
Note 'cfs' requires CONFIG_CFS_BANDWIDTH,
and 'rt' requires CONFIG_RT_GROUP_SCHED"""
if not sched:
sched = self.sched
if sched == 'rt':
if not f or f < 0:
raise Exception( 'Please set a positive CPU fraction for sched=rt\n' )
return
pstr, qstr, period, quota = self.rtInfo( f )
elif sched == 'cfs':
pstr, qstr, period, quota = self.cfsInfo( f )
else:
return
# Set cgroup's period and quota
setPeriod = self.cgroupSet( pstr, period )
setQuota = self.cgroupSet( qstr, quota )
if sched == 'rt':
# Set RT priority if necessary
sched = self.chrt()
info( '(%s %d/%dus) ' % ( sched, setQuota, setPeriod ) )
def setCPUs( self, cores, mems=0 ):
"Specify (real) cores that our cgroup can run on"
if not cores:
return
if isinstance( cores, list ):
cores = ','.join( [ str( c ) for c in cores ] )
self.cgroupSet( resource='cpuset', param='cpus',
value=cores )
# Memory placement is probably not relevant, but we
# must specify it anyway
self.cgroupSet( resource='cpuset', param='mems',
value=mems)
# We have to do this here after we've specified
# cpus and mems
errFail( 'cgclassify -g cpuset:/%s %s' % (
self.name, self.pid ) )
def config( self, cpu=-1, cores=None, **params ):
"""cpu: desired overall system CPU fraction
cores: (real) core(s) this host can run on
params: parameters for Node.config()"""
r = Node.config( self, **params )
# Was considering cpu={'cpu': cpu , 'sched': sched}, but
# that seems redundant
self.setParam( r, 'setCPUFrac', cpu=cpu )
self.setParam( r, 'setCPUs', cores=cores )
return r
inited = False
@classmethod
def init( cls ):
"Initialization for CPULimitedHost class"
mountCgroups()
cls.inited = True
# Some important things to note:
#
# The "IP" address which setIP() assigns to the switch is not
# an "IP address for the switch" in the sense of IP routing.
# Rather, it is the IP address for the control interface,
# on the control network, and it is only relevant to the
# controller. If you are running in the root namespace
# (which is the only way to run OVS at the moment), the
# control interface is the loopback interface, and you
# normally never want to change its IP address!
#
# In general, you NEVER want to attempt to use Linux's
# network stack (i.e. ifconfig) to "assign" an IP address or
# MAC address to a switch data port. Instead, you "assign"
# the IP and MAC addresses in the controller by specifying
# packets that you want to receive or send. The "MAC" address
# reported by ifconfig for a switch data port is essentially
# meaningless. It is important to understand this if you
# want to create a functional router using OpenFlow.
class Switch( Node ):
"""A Switch is a Node that is running (or has execed?)
an OpenFlow switch."""
portBase = 1 # Switches start with port 1 in OpenFlow
dpidLen = 16 # digits in dpid passed to switch
def __init__( self, name, dpid=None, opts='', listenPort=None, **params):
"""dpid: dpid hex string (or None to derive from name, e.g. s1 -> 1)
opts: additional switch options
listenPort: port to listen on for dpctl connections"""
Node.__init__( self, name, **params )
self.dpid = self.defaultDpid( dpid )
self.opts = opts
self.listenPort = listenPort
if not self.inNamespace:
self.controlIntf = Intf( 'lo', self, port=0 )
def defaultDpid( self, dpid=None ):
"Return correctly formatted dpid from dpid or switch name (s1 -> 1)"
if dpid:
# Remove any colons and make sure it's a good hex number
dpid = dpid.translate( None, ':' )
assert len( dpid ) <= self.dpidLen and int( dpid, 16 ) >= 0
else:
# Use hex of the first number in the switch name
nums = re.findall( r'\d+', self.name )
if nums:
dpid = hex( int( nums[ 0 ] ) )[ 2: ]
else:
raise Exception( 'Unable to derive default datapath ID - '
'please either specify a dpid or use a '
'canonical switch name such as s23.' )
return '0' * ( self.dpidLen - len( dpid ) ) + dpid
def defaultIntf( self ):
"Return control interface"
if self.controlIntf:
return self.controlIntf
else:
return Node.defaultIntf( self )
def sendCmd( self, *cmd, **kwargs ):
"""Send command to Node.
cmd: string"""
kwargs.setdefault( 'printPid', False )
if not self.execed:
return Node.sendCmd( self, *cmd, **kwargs )
else:
error( '*** Error: %s has execed and cannot accept commands' %
self.name )
def connected( self ):
"Is the switch connected to a controller? (override this method)"
return False and self # satisfy pylint
def __repr__( self ):
"More informative string representation"
intfs = ( ','.join( [ '%s:%s' % ( i.name, i.IP() )
for i in self.intfList() ] ) )
return '<%s %s: %s pid=%s> ' % (
self.__class__.__name__, self.name, intfs, self.pid )
class UserSwitch( Switch ):
"User-space switch."
dpidLen = 12
def __init__( self, name, dpopts='--no-slicing', **kwargs ):
"""Init.
name: name for the switch
dpopts: additional arguments to ofdatapath (--no-slicing)"""
Switch.__init__( self, name, **kwargs )
pathCheck( 'ofdatapath', 'ofprotocol',
moduleName='the OpenFlow reference user switch' +
'(openflow.org)' )
if self.listenPort:
self.opts += ' --listen=ptcp:%i ' % self.listenPort
else:
self.opts += ' --listen=punix:/tmp/%s.listen' % self.name
self.dpopts = dpopts
@classmethod
def setup( cls ):
"Ensure any dependencies are loaded; if not, try to load them."
if not os.path.exists( '/dev/net/tun' ):
moduleDeps( add=TUN )
def dpctl( self, *args ):
"Run dpctl command"
listenAddr = None
if not self.listenPort:
listenAddr = 'unix:/tmp/%s.listen' % self.name
else:
listenAddr = 'tcp:127.0.0.1:%i' % self.listenPort
return self.cmd( 'dpctl ' + ' '.join( args ) +
' ' + listenAddr )
def connected( self ):
"Is the switch connected to a controller?"
status = self.dpctl( 'status' )
return ( 'remote.is-connected=true' in status and
'local.is-connected=true' in status )
@staticmethod
def TCReapply( intf ):
"""Unfortunately user switch and Mininet are fighting
over tc queuing disciplines. To resolve the conflict,
we re-create the user switch's configuration, but as a
leaf of the TCIntf-created configuration."""
if isinstance( intf, TCIntf ):
ifspeed = 10000000000 # 10 Gbps
minspeed = ifspeed * 0.001
res = intf.config( **intf.params )
if res is None: # link may not have TC parameters
return
# Re-add qdisc, root, and default classes user switch created, but
# with new parent, as setup by Mininet's TCIntf
parent = res['parent']
intf.tc( "%s qdisc add dev %s " + parent +
" handle 1: htb default 0xfffe" )
intf.tc( "%s class add dev %s classid 1:0xffff parent 1: htb rate "
+ str(ifspeed) )
intf.tc( "%s class add dev %s classid 1:0xfffe parent 1:0xffff " +
"htb rate " + str(minspeed) + " ceil " + str(ifspeed) )
def start( self, controllers ):
"""Start OpenFlow reference user datapath.
Log to /tmp/sN-{ofd,ofp}.log.
controllers: list of controller objects"""
# Add controllers
clist = ','.join( [ 'tcp:%s:%d' % ( c.IP(), c.port )
for c in controllers ] )
ofdlog = '/tmp/' + self.name + '-ofd.log'
ofplog = '/tmp/' + self.name + '-ofp.log'
intfs = [ str( i ) for i in self.intfList() if not i.IP() ]
self.cmd( 'ofdatapath -i ' + ','.join( intfs ) +
' punix:/tmp/' + self.name + ' -d %s ' % self.dpid +
self.dpopts +
' 1> ' + ofdlog + ' 2> ' + ofdlog + ' &' )
self.cmd( 'ofprotocol unix:/tmp/' + self.name +
' ' + clist +
' --fail=closed ' + self.opts +
' 1> ' + ofplog + ' 2>' + ofplog + ' &' )
if "no-slicing" not in self.dpopts:
# Only TCReapply if slicing is enable
sleep(1) # Allow ofdatapath to start before re-arranging qdisc's
for intf in self.intfList():
if not intf.IP():
self.TCReapply( intf )
def stop( self, deleteIntfs=True ):
"Stop OpenFlow reference user datapath."
self.cmd( 'kill %ofdatapath' )
self.cmd( 'kill %ofprotocol' )
if deleteIntfs:
self.deleteIntfs()
class OVSLegacyKernelSwitch( Switch ):
"""Open VSwitch legacy kernel-space switch using ovs-openflowd.
Currently only works in the root namespace."""
def __init__( self, name, dp=None, **kwargs ):
"""Init.
name: name for switch
dp: netlink id (0, 1, 2, ...)
defaultMAC: default MAC as unsigned int; random value if None"""
0