Skip to content
Snippets Groups Projects
Commit a6bcad8f authored by Bob Lantz's avatar Bob Lantz
Browse files

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

parent 6f446f6e
No related branches found
No related tags found
No related merge requests found
"""
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 )
""" """
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 @@ ...@@ -96,6 +96,7 @@
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 ): ...@@ -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 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, ...@@ -126,6 +128,7 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
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 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host, ...@@ -150,30 +153,38 @@ def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
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)
def addSwitch( self, name, mac=None, ip=None ): self.hosts.append( h )
self.nameToNode[ name ] = h
return h
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 @@ def addSwitch( self, name, mac=None, ip=None ): ...@@ -181,12 +192,12 @@ def addSwitch( self, name, mac=None, ip=None ):
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 @@ def addController( self, name='c0', controller=None, **kwargs ): ...@@ -210,6 +221,9 @@ def addController( self, name='c0', controller=None, **kwargs ):
# 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 @@ def configureControlNetwork( self ): ...@@ -221,8 +235,7 @@ def configureControlNetwork( self ):
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 @@ def configHosts( self ): ...@@ -256,8 +269,8 @@ def configHosts( self ):
"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 @@ def buildFromTopo( self, topo ): ...@@ -272,9 +285,11 @@ def buildFromTopo( self, topo ):
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 @@ def addNode( prefix, addMethod, nodeId ): ...@@ -291,12 +306,16 @@ def addNode( prefix, addMethod, nodeId ):
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 @@ def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ): ...@@ -510,7 +529,7 @@ def iperf( self, hosts=None, l4Type='TCP', udpBw='10M' ):
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 )
......
...@@ -46,11 +46,11 @@ ...@@ -46,11 +46,11 @@
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 @@ def __init__( self, name, inNamespace=True, ...@@ -78,7 +78,7 @@ def __init__( self, name, inNamespace=True,
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 @@ def __init__( self, name, inNamespace=True, ...@@ -89,12 +89,10 @@ def __init__( self, name, inNamespace=True,
# 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 @@ def sendCmd( self, *args, **kwargs ): ...@@ -172,7 +170,7 @@ def sendCmd( self, *args, **kwargs ):
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 @@ def cmdPrint( self, *args): ...@@ -254,10 +252,6 @@ def cmdPrint( self, *args):
# 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 @@ def newPort( self ): ...@@ -266,56 +260,45 @@ def newPort( self ):
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 @@ def deleteIntfs( self ): ...@@ -325,18 +308,10 @@ def deleteIntfs( self ):
# 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 @@ def setARP( self, ip, mac ): ...@@ -345,16 +320,6 @@ def setARP( self, ip, mac ):
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 @@ def setDefaultRoute( self, intf ): ...@@ -365,62 +330,52 @@ def setDefaultRoute( self, intf ):
"""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 ):
"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 ): def intfIsUp( self, intf=None ):
"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 ): ...@@ -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 @@ def stop( self ): ...@@ -671,7 +625,6 @@ def stop( self ):
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 @@ def stop( self ): ...@@ -704,8 +657,9 @@ def stop( self ):
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
......
...@@ -16,7 +16,14 @@ ...@@ -16,7 +16,14 @@
# 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 @@ def ip_str(self): ...@@ -54,11 +61,12 @@ def ip_str(self):
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 @@ def __init__(self, connected = False, admin_on = True, ...@@ -66,18 +74,26 @@ def __init__(self, connected = False, admin_on = True,
@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 @@ def __init__(self, admin_on = True, power_on = True, fault = False): ...@@ -87,31 +103,40 @@ def __init__(self, admin_on = True, power_on = True, fault = False):
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 @@ def add_edge(self, src, dst, edge = None): ...@@ -121,7 +146,7 @@ def add_edge(self, src, dst, edge = None):
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 @@ def port(self, src, dst): ...@@ -276,6 +301,12 @@ def port(self, src, dst):
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,7 +352,12 @@ def ip(self, dpid): ...@@ -321,7 +352,12 @@ def ip(self, dpid):
''' '''
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.'''
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment