Intf and Link classes. Latter support bandwidth limits using tc.

This commit is contained in:
Bob Lantz
2012-03-02 15:45:21 -08:00
parent 6f446f6e55
commit a6bcad8f48
4 changed files with 448 additions and 164 deletions
+275
View File
@@ -0,0 +1,275 @@
"""
link.py: interface and link abstractions for mininet
It seems useful to bundle functionality for interfaces into a single
class.
Also it seems useful to enable the possibility of multiple flavors of
links, including:
- simple veth pairs
- tunneled links
- patchable links (which can be disconnected and reconnected via a patchbay)
- link simulators (e.g. wireless)
Basic division of labor:
Nodes: know how to execute commands
Intfs: know how to configure themselves
Links: know how to connect nodes together
"""
from mininet.log import info, error, debug
from mininet.util import makeIntfPair
from time import sleep
import re
class BasicIntf( object ):
"Basic interface object that can configure itself."
def __init__( self, node, name=None, link=None, **kwargs ):
"""node: owning node (where this intf most likely lives)
name: interface name (e.g. h1-eth0)
link: parent link if any
other arguments are used to configure link parameters"""
self.node = node
self.name = name
self.link = link
self.mac, self.ip = None, None
self.config( **kwargs )
def cmd( self, *args, **kwargs ):
self.node.cmd( *args, **kwargs )
def ifconfig( self, *args ):
"Configure ourselves using ifconfig"
return self.cmd( 'ifconfig', self.name, *args )
def setIP( self, ipstr ):
"""Set our IP address"""
# This is a sign that we should perhaps rethink our prefix
# mechanism
self.ip, self.prefixLen = ipstr.split( '/' )
return self.ifconfig( ipstr, 'up' )
def setMAC( self, macstr ):
"""Set the MAC address for an interface.
macstr: MAC address as string"""
self.mac = macstr
return ( self.ifconfig( 'down' ) +
self.ifconfig( 'hw', 'ether', macstr ) +
self.ifconfig( 'up' ) )
_ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
_macMatchRegex = re.compile( r'..:..:..:..:..:..' )
def updateIP( self ):
"Return updated IP address based on ifconfig"
ifconfig = self.ifconfig()
ips = self._ipMatchRegex.findall( ifconfig )
self.ip = ips[ 0 ] if ips else None
return self.ip
def updateMAC( self, intf ):
"Return updated MAC address based on ifconfig"
ifconfig = self.ifconfig()
macs = self._macMatchRegex.findall( ifconfig )
self.mac = macs[ 0 ] if macs else None
return self.mac
def IP( self ):
"Return IP address"
return self.ip
def MAC( self ):
"Return MAC address"
return self.mac
def isUp( self, set=False ):
"Return whether interface is up"
return "UP" in self.ifconfig()
# Map of config params to config methods
# Perhaps this could be more graceful, but it
# is flexible
configMap = { 'mac': 'setMAC',
'ip': 'setIP',
'ifconfig': 'ifconfig' }
def config( self, **params ):
"Configure interface based on parameters"
self.__dict__.update(**params)
for name, value in params.iteritems():
method = self.configMap.get( name, None )
if method:
if type( value ) is str:
value = value.split( ',' )
method( value )
def delete( self ):
"Delete interface"
self.cmd( 'ip link del ' + self.name )
# Does it help to sleep to let things run?
sleep( 0.001 )
def __str__( self ):
return self.name
class TCIntf( BasicIntf ):
"Interface customized by tc (traffic control) utility"
def config( self, bw=None, delay=None, loss=0, disable_gro=True,
speedup=0, use_hfsc=False, use_tbf=False, enable_ecn=False,
enable_red=False, max_queue_size=1000, **kwargs ):
"Configure the port and set its properties."
BasicIntf.config( self, **kwargs)
# disable GRO
if disable_gro:
self.cmd( 'ethtool -K %s gro off' % self )
if bw is None and not delay and not loss:
return
if bw and ( bw < 0 or bw > 1000 ):
error( 'Bandwidth', bw, 'is outside range 0..1000 Mbps\n' )
return
if delay and delay < 0:
error( 'Negative delay', delay, '\n' )
return
if loss and ( loss < 0 or loss > 100 ):
error( 'Bad loss percentage', loss, '%%\n' )
return
if delay is None:
delay = '0ms'
if bw is not None and delay is not None:
info( self, '(bw %.2fMbit, delay %s, loss %d%%)\n' %
( bw, delay, loss ) )
# BL: hmm... what exactly is this???
# This seems kind of brittle
if speedup > 0 and self.node.name[0:2] == 'sw':
bw = speedup
tc = 'tc' # was getCmd( 'tc' )
# Bandwidth control algorithms
if use_hfsc:
cmds = [ '%s qdisc del dev %s root',
'%s qdisc add dev %s root handle 1:0 hfsc default 1' ]
if bw is not None:
cmds.append( '%s class add dev %s parent 1:0 classid 1:1 hfsc sc ' +
'rate %fMbit ul rate %fMbit' % ( bw, bw ) )
elif use_tbf:
latency_us = 10 * 1500 * 8 / bw
cmds = ['%s qdisc del dev %s root',
'%s qdisc add dev %s root handle 1: tbf ' +
'rate %fMbit burst 15000 latency %fus' % (bw, latency_us) ]
else:
cmds = [ '%s qdisc del dev %s root',
'%s qdisc add dev %s root handle 1:0 htb default 1',
'%s class add dev %s parent 1:0 classid 1:1 htb ' +
'rate %fMbit burst 15k' % bw ]
# ECN or RED
if enable_ecn:
info( 'Enabling ECN\n' )
cmds += [ '%s qdisc add dev %s parent 1:1 '+
'handle 10: red limit 1000000 '+
'min 20000 max 25000 avpkt 1000 '+
'burst 20 '+
'bandwidth %fmbit probability 1 ecn' % bw ]
elif enable_red:
info( 'Enabling RED\n' )
cmds += [ '%s qdisc add dev %s parent 1:1 '+
'handle 10: red limit 1000000 '+
'min 20000 max 25000 avpkt 1000 '+
'burst 20 '+
'bandwidth %fmbit probability 1' % bw ]
else:
cmds += [ '%s qdisc add dev %s parent 1:1 handle 10:0 netem ' +
'delay ' + '%s' % delay + ' loss ' + '%d' % loss +
' limit %d' % (max_queue_size) ]
# Execute all the commands in the container
debug("at map stage w/cmds: %s\n" % cmds)
def doConfigPort(s):
c = s % (tc, self)
debug(" *** executing command: %s\n" % c)
return self.cmd(c)
outputs = [ doConfigPort(cmd) for cmd in cmds ]
debug( "outputs: %s\n" % outputs )
Intf = TCIntf
class Link( object ):
"""A basic link is just a veth pair.
Other types of links could be tunnels, link emulators, etc.."""
def __init__( self, node1, node2, port1=None, port2=None, intfName1=None, intfName2=None,
intf=Intf, params1={}, params2={} ):
"""Create veth link to another node, making two new interfaces.
node1: first node
node2: second node
port1: node1 port number (optional)
port2: node2 port number (optional)
intfName1: node1 interface name (optional)
intfName2: node2 interface name (optional)"""
# This is a bit awkward; it seems that having everything in
# params would be more orthogonal, but being able to specify
# in-line arguments is more convenient!
if port1 is None:
port1 = node1.newPort()
if port2 is None:
port2 = node2.newPort()
if not intfName1:
intfName1 = self.intfName( node1, port1 )
if not intfName2:
intfName2 = self.intfName( node2, port2 )
self.makeIntfPair( intfName1, intfName2 )
intf1 = intf( name=intfName1, node=node1, link=self, **params1 )
intf2 = intf( name=intfName2, node=node2, link=self, **params2 )
# Add to nodes
node1.addIntf( intf1 )
node2.addIntf( intf2 )
self.intf1, self.intf2 = intf1, intf2
@classmethod
def intfName( cls, node, n ):
"Construct a canonical interface name node-ethN for interface n."
return node.name + '-eth' + repr( n )
@classmethod
def makeIntfPair( cls, intf1, intf2 ):
"""Create pair of interfaces
intf1: name of interface 1
intf2: name of interface 2
(override this class method [and possibly delete()] to change link type)"""
makeIntfPair( intf1, intf2 )
def delete( self ):
"Delete this link"
self.intf1.delete()
self.intf2.delete()
def __str__( self ):
return '%s<->%s' % ( self.intf1, self.intf2 )
+49 -30
View File
@@ -1,6 +1,6 @@
""" """
Mininet: A simple networking testbed for OpenFlow! Mininet: A simple networking testbed for OpenFlow/SDN!
author: Bob Lantz (rlantz@cs.stanford.edu) author: Bob Lantz (rlantz@cs.stanford.edu)
author: Brandon Heller (brandonh@stanford.edu) author: Brandon Heller (brandonh@stanford.edu)
@@ -96,6 +96,7 @@ from mininet.cli import CLI
from mininet.log import info, error, debug, output from mininet.log import info, error, debug, output
from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller
from mininet.node import ControllerParams from mininet.node import ControllerParams
from mininet.link import Link
from mininet.util import quietRun, fixLimits from mininet.util import quietRun, fixLimits
from mininet.util import createLink, macColonHex, ipStr, ipParse from mininet.util import createLink, macColonHex, ipStr, ipParse
from mininet.term import cleanUpScreens, makeTerms from mininet.term import cleanUpScreens, makeTerms
@@ -104,16 +105,17 @@ class Mininet( object ):
"Network emulation with hosts spawned in network namespaces." "Network emulation with hosts spawned in network namespaces."
def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
controller=Controller, controller=Controller, link=Link,
cparams=ControllerParams( '10.0.0.0', 8 ), cparams=ControllerParams( '10.0.0.0', 8 ),
build=True, xterms=False, cleanup=False, build=True, xterms=False, cleanup=False,
inNamespace=False, inNamespace=False,
autoSetMacs=False, autoStaticArp=False, listenPort=None ): autoSetMacs=False, autoStaticArp=False, listenPort=None ):
"""Create Mininet object. """Create Mininet object.
topo: Topo (topology) object or None topo: Topo (topology) object or None
switch: Switch class switch: default Switch class
host: Host class host: default Host class/constructor
controller: Controller class controller: default Controller class/constructor
link: default Link class/constructor
cparams: ControllerParams object cparams: ControllerParams object
build: build now from topo? build: build now from topo?
xterms: if build now, spawn xterms? xterms: if build now, spawn xterms?
@@ -126,6 +128,7 @@ class Mininet( object ):
self.switch = switch self.switch = switch
self.host = host self.host = host
self.controller = controller self.controller = controller
self.link = link
self.cparams = cparams self.cparams = cparams
self.topo = topo self.topo = topo
self.inNamespace = inNamespace self.inNamespace = inNamespace
@@ -150,30 +153,38 @@ class Mininet( object ):
if topo and build: if topo and build:
self.build() self.build()
def addHost( self, name, mac=None, ip=None ): # BL Note:
# The specific items for host/switch/etc. should probably be
# handled in the node classes rather than here!!
def addHost( self, name, mac=None, ip=None, host=None, **params ):
"""Add host. """Add host.
name: name of host to add name: name of host to add
mac: default MAC address for intf 0 mac: default MAC address for intf 0
ip: default IP address for intf 0 ip: default IP address for intf 0
returns: added host""" returns: added host"""
host = self.host( name, defaultMAC=mac, defaultIP=ip ) if not host:
self.hosts.append( host ) host = self.host
self.nameToNode[ name ] = host defaults = { 'defaultMAC': mac, 'defaultIP': ip }
return host defaults.update( params )
h = host( name, **defaults)
self.hosts.append( h )
self.nameToNode[ name ] = h
return h
def addSwitch( self, name, mac=None, ip=None ): def addSwitch( self, name, switch=None, **params ):
"""Add switch. """Add switch.
name: name of switch to add name: name of switch to add
mac: default MAC address for kernel/OVS switch intf 0
returns: added switch returns: added switch
side effect: increments the listenPort member variable.""" side effect: increments listenPort and dps ivars."""
if self.switch == UserSwitch: defaults = { 'listenPort': self.listenPort,
sw = self.switch( name, listenPort=self.listenPort, 'inNamespace': self.inNamespace }
defaultMAC=mac, defaultIP=ip, inNamespace=self.inNamespace ) if not switch:
else: switch = self.switch
sw = self.switch( name, listenPort=self.listenPort, if switch != UserSwitch:
defaultMAC=mac, defaultIP=ip, dp=self.dps, defaults[ 'dps' ] = self.dps
inNamespace=self.inNamespace ) defaults.update( params )
sw = self.switch( name, **defaults )
if not self.inNamespace and self.listenPort: if not self.inNamespace and self.listenPort:
self.listenPort += 1 self.listenPort += 1
self.dps += 1 self.dps += 1
@@ -181,12 +192,12 @@ class Mininet( object ):
self.nameToNode[ name ] = sw self.nameToNode[ name ] = sw
return sw return sw
def addController( self, name='c0', controller=None, **kwargs ): def addController( self, name='c0', controller=None, **params ):
"""Add controller. """Add controller.
controller: Controller class""" controller: Controller class"""
if not controller: if not controller:
controller = self.controller controller = self.controller
controller_new = controller( name, **kwargs ) controller_new = controller( name, **params )
if controller_new: # allow controller-less setups if controller_new: # allow controller-less setups
self.controllers.append( controller_new ) self.controllers.append( controller_new )
self.nameToNode[ name ] = controller_new self.nameToNode[ name ] = controller_new
@@ -210,6 +221,9 @@ class Mininet( object ):
# 4. Even if we dispense with this in general, it could still be # 4. Even if we dispense with this in general, it could still be
# useful for people who wish to simulate a separate control # useful for people who wish to simulate a separate control
# network (since real networks may need one!) # network (since real networks may need one!)
#
# 5. Basically nobody ever uses this method, so perhaps it should be moved
# out of this core class.
def configureControlNetwork( self ): def configureControlNetwork( self ):
"Configure control network." "Configure control network."
@@ -221,8 +235,7 @@ class Mininet( object ):
def configureRoutedControlNetwork( self, ip='192.168.123.1', def configureRoutedControlNetwork( self, ip='192.168.123.1',
prefixLen=16 ): prefixLen=16 ):
"""Configure a routed control network on controller and switches. """Configure a routed control network on controller and switches.
For use with the user datapath only right now. For use with the user datapath only right now."""
"""
controller = self.controllers[ 0 ] controller = self.controllers[ 0 ]
info( controller.name + ' <->' ) info( controller.name + ' <->' )
cip = ip cip = ip
@@ -256,8 +269,8 @@ class Mininet( object ):
"Configure a set of hosts." "Configure a set of hosts."
# params were: hosts, ips # params were: hosts, ips
for host in self.hosts: for host in self.hosts:
hintf = host.intfs[ 0 ] hintf = host.defaultIntf()
host.setIP( hintf, host.defaultIP, self.cparams.prefixLen ) host.setIP( host.defaultIP, self.cparams.prefixLen, hintf )
host.setDefaultRoute( hintf ) host.setDefaultRoute( hintf )
# You're low priority, dude! # You're low priority, dude!
quietRun( 'renice +18 -p ' + repr( host.pid ) ) quietRun( 'renice +18 -p ' + repr( host.pid ) )
@@ -272,9 +285,11 @@ class Mininet( object ):
def addNode( prefix, addMethod, nodeId ): def addNode( prefix, addMethod, nodeId ):
"Add a host or a switch." "Add a host or a switch."
name = prefix + topo.name( nodeId ) name = prefix + topo.name( nodeId )
# MAC and IP should probably be from nodeInfo...
mac = macColonHex( nodeId ) if self.setMacs else None mac = macColonHex( nodeId ) if self.setMacs else None
ip = topo.ip( nodeId ) ip = topo.ip( nodeId )
node = addMethod( name, mac=mac, ip=ip ) ni = topo.nodeInfo( nodeId )
node = addMethod( name, cls=ni.cls, mac=mac, ip=ip, **ni.params )
self.idToNode[ nodeId ] = node self.idToNode[ nodeId ] = node
info( name + ' ' ) info( name + ' ' )
@@ -291,12 +306,16 @@ class Mininet( object ):
addNode( 'h', self.addHost, hostId ) addNode( 'h', self.addHost, hostId )
info( '\n*** Adding switches:\n' ) info( '\n*** Adding switches:\n' )
for switchId in sorted( topo.switches() ): for switchId in sorted( topo.switches() ):
addNode( 's', self.addSwitch, switchId ) addNode( 's', self.addSwitch, switchId)
info( '\n*** Adding links:\n' ) info( '\n*** Adding links:\n' )
for srcId, dstId in sorted( topo.edges() ): for srcId, dstId in sorted( topo.edges() ):
src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ] src, dst = self.idToNode[ srcId ], self.idToNode[ dstId ]
srcPort, dstPort = topo.port( srcId, dstId ) srcPort, dstPort = topo.port( srcId, dstId )
createLink( src, dst, srcPort, dstPort ) ei = topo.edgeInfo( srcId, dstId )
link, params = ei.cls, ei.params
if not link:
link = self.link
link( src, dst, srcPort, dstPort, **params )
info( '(%s, %s) ' % ( src.name, dst.name ) ) info( '(%s, %s) ' % ( src.name, dst.name ) )
info( '\n' ) info( '\n' )
@@ -510,7 +529,7 @@ class Mininet( object ):
servout += server.monitor() servout += server.monitor()
while 'Connected' not in client.cmd( while 'Connected' not in client.cmd(
'sh -c "echo A | telnet -e A %s 5001"' % server.IP()): 'sh -c "echo A | telnet -e A %s 5001"' % server.IP()):
output('waiting for iperf to start up') output('waiting for iperf to start up...')
sleep(.5) sleep(.5)
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' + cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
bwArgs ) bwArgs )
+75 -121
View File
@@ -46,11 +46,11 @@ import re
import signal import signal
import select import select
from subprocess import Popen, PIPE, STDOUT from subprocess import Popen, PIPE, STDOUT
from time import sleep
from mininet.log import info, error, debug from mininet.log import info, error, debug
from mininet.util import quietRun, errRun, makeIntfPair, moveIntf, isShellBuiltin from mininet.util import quietRun, errRun, moveIntf, isShellBuiltin
from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN from mininet.moduledeps import moduleDeps, pathCheck, OVS_KMOD, OF_KMOD, TUN
from mininet.link import Link
SWITCH_PORT_BASE = 1 # For OF > 0.9, switch ports start at 1 rather than zero SWITCH_PORT_BASE = 1 # For OF > 0.9, switch ports start at 1 rather than zero
@@ -78,7 +78,7 @@ class Node( object ):
opts += 'n' opts += 'n'
cmd = [ 'mnexec', opts, 'bash', '-m' ] cmd = [ 'mnexec', opts, 'bash', '-m' ]
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT, self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
close_fds=False ) close_fds=True )
self.stdin = self.shell.stdin self.stdin = self.shell.stdin
self.stdout = self.shell.stdout self.stdout = self.shell.stdout
self.pid = self.shell.pid self.pid = self.shell.pid
@@ -89,12 +89,10 @@ class Node( object ):
# using select.poll() # using select.poll()
self.outToNode[ self.stdout.fileno() ] = self self.outToNode[ self.stdout.fileno() ] = self
self.inToNode[ self.stdin.fileno() ] = self self.inToNode[ self.stdin.fileno() ] = self
self.intfs = {} # dict of port numbers to interface names self.intfs = {} # dict of port numbers to interfaces
self.ports = {} # dict of interface names to port numbers self.ports = {} # dict of interfaces to port numbers
# replace with Port objects, eventually ? # replace with Port objects, eventually ?
self.ips = {} # dict of interfaces to ip addresses as strings self.nameToIntf = {} # dict of interface names to Intfs
self.macs = {} # dict of interfacesto mac addresses as strings
self.connection = {} # remote node connected to each interface
self.execed = False self.execed = False
self.lastCmd = None self.lastCmd = None
self.lastPid = None self.lastPid = None
@@ -172,7 +170,7 @@ class Node( object ):
if len( args ) > 0: if len( args ) > 0:
cmd = args cmd = args
if not isinstance( cmd, str ): if not isinstance( cmd, str ):
cmd = ' '.join( cmd ) cmd = ' '.join( [ str( c ) for c in cmd ] )
if not re.search( r'\w', cmd ): if not re.search( r'\w', cmd ):
# Replace empty commands with something harmless # Replace empty commands with something harmless
cmd = 'echo -n' cmd = 'echo -n'
@@ -254,10 +252,6 @@ class Node( object ):
# the real interfaces are created as veth pairs, so we can't # the real interfaces are created as veth pairs, so we can't
# make a single interface at a time. # make a single interface at a time.
def intfName( self, n ):
"Construct a canonical interface name node-ethN for interface n."
return self.name + '-eth' + repr( n )
def newPort( self ): def newPort( self ):
"Return the next port number to allocate." "Return the next port number to allocate."
if len( self.ports ) > 0: if len( self.ports ) > 0:
@@ -266,56 +260,45 @@ class Node( object ):
def addIntf( self, intf, port=None ): def addIntf( self, intf, port=None ):
"""Add an interface. """Add an interface.
intf: interface name (e.g. nodeN-ethM) intf: interface
port: port number (optional, typically OpenFlow port number)""" port: port number (optional, typically OpenFlow port number)"""
if port is None: if port is None:
port = self.newPort() port = self.newPort()
self.intfs[ port ] = intf self.intfs[ port ] = intf
self.ports[ intf ] = port self.ports[ intf ] = port
#info( '\n' ) self.nameToIntf[ intf.name ] = intf
#info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) ) info( '\n' )
info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
if self.inNamespace: if self.inNamespace:
#info( 'moving w/inNamespace set\n' ) info( 'moving', intf, 'into namespace for', self.name, '\n' )
moveIntf( intf, self ) moveIntf( intf.name, self )
def registerIntf( self, intf, dstNode, dstIntf ): def defaultIntf( self ):
"Register connection of intf to dstIntf on dstNode." "Return interface for lowest port"
self.connection[ intf ] = ( dstNode, dstIntf ) ports = self.intfs.keys()
if ports:
return self.intfs[ min( ports ) ]
def connectionsTo( self, node): def intf( self, intf='' ):
"Return [(srcIntf, dstIntf)..] for connections to dstNode." """Return our interface object with given name,x
or default intf if name is empty"""
if not intf:
return self.defaultIntf()
elif type( intf) is str:
return self.nameToIntf[ intf ]
else:
return intf
def linksTo( self, node):
"Return [ link1, link2...] for all links from self to node."
# We could optimize this if it is important # We could optimize this if it is important
connections = [] links = []
for intf in self.connection.keys(): for intf in self.intfs:
dstNode, dstIntf = self.connection[ intf ] link = intf.link
if dstNode == node: nodes = ( link.intf1.node, link.intf2.node )
connections.append( ( intf, dstIntf ) ) if self in nodes and node in nodes:
return connections links.append( link )
return links
# This is a symmetric operation, but it makes sense to put
# the code here since it is tightly coupled to routines in
# this class. For a more symmetric API, you can use
# mininet.util.createLink()
def linkTo( self, node2, port1=None, port2=None ):
"""Create link to another node, making two new interfaces.
node2: Node to link us to
port1: our port number (optional)
port2: node2 port number (optional)
returns: intf1 name, intf2 name"""
node1 = self
if port1 is None:
port1 = node1.newPort()
if port2 is None:
port2 = node2.newPort()
intf1 = node1.intfName( port1 )
intf2 = node2.intfName( port2 )
makeIntfPair( intf1, intf2 )
node1.addIntf( intf1, port1 )
node2.addIntf( intf2, port2 )
node1.registerIntf( intf1, node2, intf2 )
node2.registerIntf( intf2, node1, intf1 )
return intf1, intf2
def deleteIntfs( self ): def deleteIntfs( self ):
"Delete all of our interfaces." "Delete all of our interfaces."
@@ -325,18 +308,10 @@ class Node( object ):
# have been removed by the kernel. Unfortunately this is very slow, # have been removed by the kernel. Unfortunately this is very slow,
# at least with Linux kernels before 2.6.33 # at least with Linux kernels before 2.6.33
for intf in self.intfs.values(): for intf in self.intfs.values():
quietRun( 'ip link del ' + intf ) intf.delete()
info( '.' ) info( '.' )
# Does it help to sleep to let things run?
sleep( 0.001 )
def setMAC( self, intf, mac ): # Routing support
"""Set the MAC address for an interface.
mac: MAC address as string"""
result = self.cmd( 'ifconfig', intf, 'down' )
result += self.cmd( 'ifconfig', intf, 'hw', 'ether', mac )
result += self.cmd( 'ifconfig', intf, 'up' )
return result
def setARP( self, ip, mac ): def setARP( self, ip, mac ):
"""Add an ARP entry. """Add an ARP entry.
@@ -345,16 +320,6 @@ class Node( object ):
result = self.cmd( 'arp', '-s', ip, mac ) result = self.cmd( 'arp', '-s', ip, mac )
return result return result
def setIP( self, intf, ip, prefixLen=8 ):
"""Set the IP address for an interface.
intf: interface name
ip: IP address as a string
prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
ipSub = '%s/%d' % ( ip, prefixLen )
result = self.cmd( 'ifconfig', intf, ipSub, 'up' )
self.ips[ intf ] = ip
return result
def setHostRoute( self, ip, intf ): def setHostRoute( self, ip, intf ):
"""Add route to host. """Add route to host.
ip: IP address as dotted decimal ip: IP address as dotted decimal
@@ -365,62 +330,52 @@ class Node( object ):
"""Set the default route to go through intf. """Set the default route to go through intf.
intf: string, interface name""" intf: string, interface name"""
self.cmd( 'ip route flush root 0/0' ) self.cmd( 'ip route flush root 0/0' )
return self.cmd( 'route add default ' + intf ) return self.cmd( 'route add default %s' % intf )
def defaultIntf( self ): # Convenience methods
"Return interface for lowest port"
ports = self.intfs.keys()
if ports:
return self.intfs[ min( ports ) ]
_ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' ) def setMAC( self, mac, intf=''):
_macMatchRegex = re.compile( r'..:..:..:..:..:..' ) """Set the MAC address for an interface.
intf: intf or intf name
mac: MAC address as string"""
return self.intf( intf ).setMAC( mac )
def setIP( self, ip, prefixLen=8, intf='' ):
"""Set the IP address for an interface.
intf: interface name
ip: IP address as a string
prefixLen: prefix length, e.g. 8 for /8 or 16M addrs"""
# This should probably be rethought:
ipSub = '%s/%s' % ( ip, prefixLen )
return self.intf( intf ).setIP( ipSub )
def IP( self, intf=None ): def IP( self, intf=None ):
"Return IP address of a node or specific interface." "Return IP address of a node or specific interface."
if intf is None: return self.intf( intf ).IP()
intf = self.defaultIntf()
if intf and not self.waiting:
self.updateIP( intf )
return self.ips.get( intf, None )
def MAC( self, intf=None ): def MAC( self, intf=None ):
"Return MAC address of a node or specific interface." "Return MAC address of a node or specific interface."
if intf is None: return self.intf( intf ).MAC()
intf = self.defaultIntf()
if intf and not self.waiting:
self.updateMAC( intf )
return self.macs.get( intf, None )
def updateIP( self, intf ): def intfIsUp( self, intf=None ):
"Update IP address for an interface"
assert not self.waiting
ifconfig = self.cmd( 'ifconfig ' + intf )
ips = self._ipMatchRegex.findall( ifconfig )
if ips:
self.ips[ intf ] = ips[ 0 ]
else:
self.ips[ intf ] = None
def updateMAC( self, intf ):
"Update MAC address for an interface"
assert not self.waiting
ifconfig = self.cmd( 'ifconfig ' + intf )
macs = self._macMatchRegex.findall( ifconfig )
if macs:
self.macs[ intf ] = macs[ 0 ]
else:
self.macs[ intf ] = None
def intfIsUp( self, intf ):
"Check if an interface is up." "Check if an interface is up."
return 'UP' in self.cmd( 'ifconfig ' + intf ) return self.intf( intf ).isUp()
# 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 # Other methods
def intfNames( self ):
"The names of our interfaces"
return [ str( i ) for i in sorted( self.ports.values() ) ]
def __str__( self ): def __str__( self ):
intfs = sorted( self.intfs.values() )
return '%s: IP=%s intfs=%s pid=%s' % ( return '%s: IP=%s intfs=%s pid=%s' % (
self.name, self.IP(), ','.join( intfs ), self.pid ) self.name, self.IP(), ','.join( self.intfNames() ), self.pid )
class Host( Node ): class Host( Node ):
@@ -623,10 +578,9 @@ class OVSSwitch( Switch ):
def __init__( self, name, dp=None, **kwargs ): def __init__( self, name, dp=None, **kwargs ):
"""Init. """Init.
name: name for switch name: name for switch
dp: netlink id (0, 1, 2, ...)
defaultMAC: default MAC as unsigned int; random value if None""" defaultMAC: default MAC as unsigned int; random value if None"""
Switch.__init__( self, name, **kwargs ) Switch.__init__( self, name, **kwargs )
self.dp = 'dp%i' % dp self.dp = name
@staticmethod @staticmethod
def setup(): def setup():
@@ -671,7 +625,6 @@ class OVSSwitch( Switch ):
OVSKernelSwitch = OVSSwitch OVSKernelSwitch = OVSSwitch
class Controller( Node ): class Controller( Node ):
"""A Controller is a Node that is running (or has execed?) an """A Controller is a Node that is running (or has execed?) an
OpenFlow controller.""" OpenFlow controller."""
@@ -704,8 +657,9 @@ class Controller( Node ):
def IP( self, intf=None ): def IP( self, intf=None ):
"Return IP address of the Controller" "Return IP address of the Controller"
ip = Node.IP( self, intf=intf ) if self.intfs:
if ip is None: ip = Node.IP( self, intf )
else:
ip = self.defaultIP ip = self.defaultIP
return ip return ip
+48 -12
View File
@@ -16,7 +16,14 @@ setup for testing, and can even be emulated with the Mininet package.
# from networkx.classes.graph import Graph # from networkx.classes.graph import Graph
from networkx import Graph from networkx import Graph
from mininet.node import SWITCH_PORT_BASE from mininet.node import SWITCH_PORT_BASE, Host, OVSSwitch
from mininet.link import Link
# BL: it's hard to figure out how to do this right yet remain flexible
# These classes will be used as the defaults if no class is passed
# into either Topo() or Node()
TopoDefaultNode = Host
TopoDefaultSwitch = OVSSwitch
class NodeID(object): class NodeID(object):
'''Topo node identifier.''' '''Topo node identifier.'''
@@ -54,11 +61,12 @@ class NodeID(object):
return "10.%i.%i.%i" % (hi, mid, lo) return "10.%i.%i.%i" % (hi, mid, lo)
class Node(object): class Node( object ):
'''Node-specific vertex metadata for a Topo object.''' '''Node-specific vertex metadata for a Topo object.'''
def __init__(self, connected = False, admin_on = True, def __init__(self, connected=False, admin_on=True,
power_on = True, fault = False, is_switch = True): power_on=True, fault=False, is_switch=True,
cls=None, **params ):
'''Init. '''Init.
@param connected actively connected to controller @param connected actively connected to controller
@@ -66,18 +74,26 @@ class Node(object):
@param power_on powered on or off @param power_on powered on or off
@param fault fault seen on node @param fault fault seen on node
@param is_switch switch or host @param is_switch switch or host
@param cls node class (e.g. Host, Switch)
@param params node parameters
''' '''
self.connected = connected self.connected = connected
self.admin_on = admin_on self.admin_on = admin_on
self.power_on = power_on self.power_on = power_on
self.fault = fault self.fault = fault
self.is_switch = is_switch self.is_switch = is_switch
# Above should be deleted and replaced by the following
# BL: is_switch is a bit annoying if we can just specify
# the node class instead!!
self.cls = cls if cls else ( TopoDefaultSwitch if is_switch else TopoDefaultNode )
self.params = params if params else {}
class Edge(object): class Edge(object):
'''Edge-specific metadata for a StructuredTopo graph.''' '''Edge-specific metadata for a StructuredTopo graph.'''
def __init__(self, admin_on = True, power_on = True, fault = False): def __init__(self, admin_on=True, power_on=True, fault=False,
cls=Link, **params):
'''Init. '''Init.
@param admin_on administratively on or off; defaults to True @param admin_on administratively on or off; defaults to True
@@ -87,31 +103,40 @@ class Edge(object):
self.admin_on = admin_on self.admin_on = admin_on
self.power_on = power_on self.power_on = power_on
self.fault = fault self.fault = fault
# Above should be deleted and replaced by the following
self.cls = cls
self.params = params
class Topo(object): class Topo(object):
'''Data center network representation for structured multi-trees.''' '''Data center network representation for structured multi-trees.'''
def __init__(self): def __init__(self, node=Host, switch=None, link=Link):
'''Create Topo object. """Create Topo object.
node: default node/host class
''' switch: default switch class
Link: default link class"""
self.g = Graph() self.g = Graph()
self.node_info = {} # dpids hash to Node objects self.node_info = {} # dpids hash to Node objects
self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects self.edge_info = {} # (src_dpid, dst_dpid) tuples hash to Edge objects
self.ports = {} # ports[src][dst] is port on src that connects to dst self.ports = {} # ports[src][dst] is port on src that connects to dst
self.id_gen = NodeID # class used to generate dpid self.id_gen = NodeID # class used to generate dpid
self.node = node
self.switch = switch
self.link = link
def add_node(self, dpid, node): def add_node(self, dpid, node=None):
'''Add Node to graph. '''Add Node to graph.
@param dpid dpid @param dpid dpid
@param node Node object @param node Node object
''' '''
self.g.add_node(dpid) self.g.add_node(dpid)
if not node:
node = Node( link=self.link )
self.node_info[dpid] = node self.node_info[dpid] = node
def add_edge(self, src, dst, edge = None): def add_edge(self, src, dst, edge=None):
'''Add edge (Node, Node) to graph. '''Add edge (Node, Node) to graph.
@param src src dpid @param src src dpid
@@ -121,7 +146,7 @@ class Topo(object):
src, dst = tuple(sorted([src, dst])) src, dst = tuple(sorted([src, dst]))
self.g.add_edge(src, dst) self.g.add_edge(src, dst)
if not edge: if not edge:
edge = Edge() edge = Edge( link=self.link )
self.edge_info[(src, dst)] = edge self.edge_info[(src, dst)] = edge
self.add_port(src, dst) self.add_port(src, dst)
@@ -276,6 +301,12 @@ class Topo(object):
assert dst in self.ports and src in self.ports[dst] assert dst in self.ports and src in self.ports[dst]
return (self.ports[src][dst], self.ports[dst][src]) return (self.ports[src][dst], self.ports[dst][src])
def edgeInfo( self, src, dst ):
"Return edge metadata"
# BL: Perhaps this should be rethought or we should just use the
# dicts...
return self.edge_info[ ( src, dst ) ]
def enable_edges(self): def enable_edges(self):
'''Enable all edges in the network graph. '''Enable all edges in the network graph.
@@ -321,6 +352,11 @@ class Topo(object):
''' '''
return self.id_gen(dpid = dpid).ip_str() return self.id_gen(dpid = dpid).ip_str()
def nodeInfo( self, dpid ):
"Return metadata for node"
# BL: may wish to rethink this or just use dicts..
return self.node_info[ dpid ]
class SingleSwitchTopo(Topo): class SingleSwitchTopo(Topo):
'''Single switch connected to k hosts.''' '''Single switch connected to k hosts.'''