Intf and Link classes. Latter support bandwidth limits using tc.
This commit is contained in:
+275
@@ -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 )
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+48
-29
@@ -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: Brandon Heller (brandonh@stanford.edu)
|
||||
@@ -96,6 +96,7 @@ from mininet.cli import CLI
|
||||
from mininet.log import info, error, debug, output
|
||||
from mininet.node import Host, UserSwitch, OVSKernelSwitch, Controller
|
||||
from mininet.node import ControllerParams
|
||||
from mininet.link import Link
|
||||
from mininet.util import quietRun, fixLimits
|
||||
from mininet.util import createLink, macColonHex, ipStr, ipParse
|
||||
from mininet.term import cleanUpScreens, makeTerms
|
||||
@@ -104,16 +105,17 @@ class Mininet( object ):
|
||||
"Network emulation with hosts spawned in network namespaces."
|
||||
|
||||
def __init__( self, topo=None, switch=OVSKernelSwitch, host=Host,
|
||||
controller=Controller,
|
||||
controller=Controller, link=Link,
|
||||
cparams=ControllerParams( '10.0.0.0', 8 ),
|
||||
build=True, xterms=False, cleanup=False,
|
||||
inNamespace=False,
|
||||
autoSetMacs=False, autoStaticArp=False, listenPort=None ):
|
||||
"""Create Mininet object.
|
||||
topo: Topo (topology) object or None
|
||||
switch: Switch class
|
||||
host: Host class
|
||||
controller: Controller class
|
||||
switch: default Switch class
|
||||
host: default Host class/constructor
|
||||
controller: default Controller class/constructor
|
||||
link: default Link class/constructor
|
||||
cparams: ControllerParams object
|
||||
build: build now from topo?
|
||||
xterms: if build now, spawn xterms?
|
||||
@@ -126,6 +128,7 @@ class Mininet( object ):
|
||||
self.switch = switch
|
||||
self.host = host
|
||||
self.controller = controller
|
||||
self.link = link
|
||||
self.cparams = cparams
|
||||
self.topo = topo
|
||||
self.inNamespace = inNamespace
|
||||
@@ -150,30 +153,38 @@ class Mininet( object ):
|
||||
if topo and 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.
|
||||
name: name of host to add
|
||||
mac: default MAC address for intf 0
|
||||
ip: default IP address for intf 0
|
||||
returns: added host"""
|
||||
host = self.host( name, defaultMAC=mac, defaultIP=ip )
|
||||
self.hosts.append( host )
|
||||
self.nameToNode[ name ] = host
|
||||
return host
|
||||
if not host:
|
||||
host = self.host
|
||||
defaults = { 'defaultMAC': mac, 'defaultIP': ip }
|
||||
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.
|
||||
name: name of switch to add
|
||||
mac: default MAC address for kernel/OVS switch intf 0
|
||||
returns: added switch
|
||||
side effect: increments the listenPort member variable."""
|
||||
if self.switch == UserSwitch:
|
||||
sw = self.switch( name, listenPort=self.listenPort,
|
||||
defaultMAC=mac, defaultIP=ip, inNamespace=self.inNamespace )
|
||||
else:
|
||||
sw = self.switch( name, listenPort=self.listenPort,
|
||||
defaultMAC=mac, defaultIP=ip, dp=self.dps,
|
||||
inNamespace=self.inNamespace )
|
||||
side effect: increments listenPort and dps ivars."""
|
||||
defaults = { 'listenPort': self.listenPort,
|
||||
'inNamespace': self.inNamespace }
|
||||
if not switch:
|
||||
switch = self.switch
|
||||
if switch != UserSwitch:
|
||||
defaults[ 'dps' ] = self.dps
|
||||
defaults.update( params )
|
||||
sw = self.switch( name, **defaults )
|
||||
if not self.inNamespace and self.listenPort:
|
||||
self.listenPort += 1
|
||||
self.dps += 1
|
||||
@@ -181,12 +192,12 @@ class Mininet( object ):
|
||||
self.nameToNode[ name ] = sw
|
||||
return sw
|
||||
|
||||
def addController( self, name='c0', controller=None, **kwargs ):
|
||||
def addController( self, name='c0', controller=None, **params ):
|
||||
"""Add controller.
|
||||
controller: Controller class"""
|
||||
if not controller:
|
||||
controller = self.controller
|
||||
controller_new = controller( name, **kwargs )
|
||||
controller_new = controller( name, **params )
|
||||
if controller_new: # allow controller-less setups
|
||||
self.controllers.append( 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
|
||||
# useful for people who wish to simulate a separate control
|
||||
# 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 ):
|
||||
"Configure control network."
|
||||
@@ -221,8 +235,7 @@ class Mininet( object ):
|
||||
def configureRoutedControlNetwork( self, ip='192.168.123.1',
|
||||
prefixLen=16 ):
|
||||
"""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 ]
|
||||
info( controller.name + ' <->' )
|
||||
cip = ip
|
||||
@@ -256,8 +269,8 @@ class Mininet( object ):
|
||||
"Configure a set of hosts."
|
||||
# params were: hosts, ips
|
||||
for host in self.hosts:
|
||||
hintf = host.intfs[ 0 ]
|
||||
host.setIP( hintf, host.defaultIP, self.cparams.prefixLen )
|
||||
hintf = host.defaultIntf()
|
||||
host.setIP( host.defaultIP, self.cparams.prefixLen, hintf )
|
||||
host.setDefaultRoute( hintf )
|
||||
# You're low priority, dude!
|
||||
quietRun( 'renice +18 -p ' + repr( host.pid ) )
|
||||
@@ -272,9 +285,11 @@ class Mininet( object ):
|
||||
def addNode( prefix, addMethod, nodeId ):
|
||||
"Add a host or a switch."
|
||||
name = prefix + topo.name( nodeId )
|
||||
# MAC and IP should probably be from nodeInfo...
|
||||
mac = macColonHex( nodeId ) if self.setMacs else None
|
||||
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
|
||||
info( name + ' ' )
|
||||
|
||||
@@ -296,7 +311,11 @@ class Mininet( object ):
|
||||
for srcId, dstId in sorted( topo.edges() ):
|
||||
src, dst = self.idToNode[ srcId ], self.idToNode[ 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( '\n' )
|
||||
|
||||
@@ -510,7 +529,7 @@ class Mininet( object ):
|
||||
servout += server.monitor()
|
||||
while 'Connected' not in client.cmd(
|
||||
'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)
|
||||
cliout = client.cmd( iperfArgs + '-t 5 -c ' + server.IP() + ' ' +
|
||||
bwArgs )
|
||||
|
||||
+75
-121
@@ -46,11 +46,11 @@ import re
|
||||
import signal
|
||||
import select
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
from time import sleep
|
||||
|
||||
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.link import Link
|
||||
|
||||
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'
|
||||
cmd = [ 'mnexec', opts, 'bash', '-m' ]
|
||||
self.shell = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT,
|
||||
close_fds=False )
|
||||
close_fds=True )
|
||||
self.stdin = self.shell.stdin
|
||||
self.stdout = self.shell.stdout
|
||||
self.pid = self.shell.pid
|
||||
@@ -89,12 +89,10 @@ class Node( object ):
|
||||
# using select.poll()
|
||||
self.outToNode[ self.stdout.fileno() ] = self
|
||||
self.inToNode[ self.stdin.fileno() ] = self
|
||||
self.intfs = {} # dict of port numbers to interface names
|
||||
self.ports = {} # dict of interface names to port numbers
|
||||
self.intfs = {} # dict of port numbers to interfaces
|
||||
self.ports = {} # dict of interfaces to port numbers
|
||||
# replace with Port objects, eventually ?
|
||||
self.ips = {} # dict of interfaces to ip addresses as strings
|
||||
self.macs = {} # dict of interfacesto mac addresses as strings
|
||||
self.connection = {} # remote node connected to each interface
|
||||
self.nameToIntf = {} # dict of interface names to Intfs
|
||||
self.execed = False
|
||||
self.lastCmd = None
|
||||
self.lastPid = None
|
||||
@@ -172,7 +170,7 @@ class Node( object ):
|
||||
if len( args ) > 0:
|
||||
cmd = args
|
||||
if not isinstance( cmd, str ):
|
||||
cmd = ' '.join( cmd )
|
||||
cmd = ' '.join( [ str( c ) for c in cmd ] )
|
||||
if not re.search( r'\w', cmd ):
|
||||
# Replace empty commands with something harmless
|
||||
cmd = 'echo -n'
|
||||
@@ -254,10 +252,6 @@ class Node( object ):
|
||||
# the real interfaces are created as veth pairs, so we can't
|
||||
# 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 ):
|
||||
"Return the next port number to allocate."
|
||||
if len( self.ports ) > 0:
|
||||
@@ -266,56 +260,45 @@ class Node( object ):
|
||||
|
||||
def addIntf( self, intf, port=None ):
|
||||
"""Add an interface.
|
||||
intf: interface name (e.g. nodeN-ethM)
|
||||
intf: interface
|
||||
port: port number (optional, typically OpenFlow port number)"""
|
||||
if port is None:
|
||||
port = self.newPort()
|
||||
self.intfs[ port ] = intf
|
||||
self.ports[ intf ] = port
|
||||
#info( '\n' )
|
||||
#info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
|
||||
self.nameToIntf[ intf.name ] = intf
|
||||
info( '\n' )
|
||||
info( 'added intf %s:%d to node %s\n' % ( intf,port, self.name ) )
|
||||
if self.inNamespace:
|
||||
#info( 'moving w/inNamespace set\n' )
|
||||
moveIntf( intf, self )
|
||||
info( 'moving', intf, 'into namespace for', self.name, '\n' )
|
||||
moveIntf( intf.name, self )
|
||||
|
||||
def registerIntf( self, intf, dstNode, dstIntf ):
|
||||
"Register connection of intf to dstIntf on dstNode."
|
||||
self.connection[ intf ] = ( dstNode, dstIntf )
|
||||
def defaultIntf( self ):
|
||||
"Return interface for lowest port"
|
||||
ports = self.intfs.keys()
|
||||
if ports:
|
||||
return self.intfs[ min( ports ) ]
|
||||
|
||||
def connectionsTo( self, node):
|
||||
"Return [(srcIntf, dstIntf)..] for connections to dstNode."
|
||||
def intf( self, intf='' ):
|
||||
"""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
|
||||
connections = []
|
||||
for intf in self.connection.keys():
|
||||
dstNode, dstIntf = self.connection[ intf ]
|
||||
if dstNode == node:
|
||||
connections.append( ( intf, dstIntf ) )
|
||||
return connections
|
||||
|
||||
# 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
|
||||
links = []
|
||||
for intf in self.intfs:
|
||||
link = intf.link
|
||||
nodes = ( link.intf1.node, link.intf2.node )
|
||||
if self in nodes and node in nodes:
|
||||
links.append( link )
|
||||
return links
|
||||
|
||||
def deleteIntfs( self ):
|
||||
"Delete all of our interfaces."
|
||||
@@ -325,18 +308,10 @@ class Node( object ):
|
||||
# have been removed by the kernel. Unfortunately this is very slow,
|
||||
# at least with Linux kernels before 2.6.33
|
||||
for intf in self.intfs.values():
|
||||
quietRun( 'ip link del ' + intf )
|
||||
intf.delete()
|
||||
info( '.' )
|
||||
# Does it help to sleep to let things run?
|
||||
sleep( 0.001 )
|
||||
|
||||
def setMAC( self, intf, mac ):
|
||||
"""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
|
||||
# Routing support
|
||||
|
||||
def setARP( self, ip, mac ):
|
||||
"""Add an ARP entry.
|
||||
@@ -345,16 +320,6 @@ class Node( object ):
|
||||
result = self.cmd( 'arp', '-s', ip, mac )
|
||||
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 ):
|
||||
"""Add route to host.
|
||||
ip: IP address as dotted decimal
|
||||
@@ -365,62 +330,52 @@ class Node( object ):
|
||||
"""Set the default route to go through intf.
|
||||
intf: string, interface name"""
|
||||
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 ):
|
||||
"Return interface for lowest port"
|
||||
ports = self.intfs.keys()
|
||||
if ports:
|
||||
return self.intfs[ min( ports ) ]
|
||||
# Convenience methods
|
||||
|
||||
_ipMatchRegex = re.compile( r'\d+\.\d+\.\d+\.\d+' )
|
||||
_macMatchRegex = re.compile( r'..:..:..:..:..:..' )
|
||||
def setMAC( self, mac, intf=''):
|
||||
"""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 ):
|
||||
"Return IP address of a node or specific interface."
|
||||
if intf is None:
|
||||
intf = self.defaultIntf()
|
||||
if intf and not self.waiting:
|
||||
self.updateIP( intf )
|
||||
return self.ips.get( intf, None )
|
||||
return self.intf( intf ).IP()
|
||||
|
||||
def MAC( self, intf=None ):
|
||||
"Return MAC address of a node or specific interface."
|
||||
if intf is None:
|
||||
intf = self.defaultIntf()
|
||||
if intf and not self.waiting:
|
||||
self.updateMAC( intf )
|
||||
return self.macs.get( intf, None )
|
||||
return self.intf( intf ).MAC()
|
||||
|
||||
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 ):
|
||||
"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 ):
|
||||
def intfIsUp( self, intf=None ):
|
||||
"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
|
||||
|
||||
def intfNames( self ):
|
||||
"The names of our interfaces"
|
||||
return [ str( i ) for i in sorted( self.ports.values() ) ]
|
||||
|
||||
def __str__( self ):
|
||||
intfs = sorted( self.intfs.values() )
|
||||
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 ):
|
||||
@@ -623,10 +578,9 @@ class OVSSwitch( Switch ):
|
||||
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"""
|
||||
Switch.__init__( self, name, **kwargs )
|
||||
self.dp = 'dp%i' % dp
|
||||
self.dp = name
|
||||
|
||||
@staticmethod
|
||||
def setup():
|
||||
@@ -671,7 +625,6 @@ class OVSSwitch( Switch ):
|
||||
|
||||
OVSKernelSwitch = OVSSwitch
|
||||
|
||||
|
||||
class Controller( Node ):
|
||||
"""A Controller is a Node that is running (or has execed?) an
|
||||
OpenFlow controller."""
|
||||
@@ -704,8 +657,9 @@ class Controller( Node ):
|
||||
|
||||
def IP( self, intf=None ):
|
||||
"Return IP address of the Controller"
|
||||
ip = Node.IP( self, intf=intf )
|
||||
if ip is None:
|
||||
if self.intfs:
|
||||
ip = Node.IP( self, intf )
|
||||
else:
|
||||
ip = self.defaultIP
|
||||
return ip
|
||||
|
||||
|
||||
+45
-9
@@ -16,7 +16,14 @@ setup for testing, and can even be emulated with the Mininet package.
|
||||
# from networkx.classes.graph 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):
|
||||
'''Topo node identifier.'''
|
||||
@@ -58,7 +65,8 @@ class Node(object):
|
||||
'''Node-specific vertex metadata for a Topo object.'''
|
||||
|
||||
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.
|
||||
|
||||
@param connected actively connected to controller
|
||||
@@ -66,18 +74,26 @@ class Node(object):
|
||||
@param power_on powered on or off
|
||||
@param fault fault seen on node
|
||||
@param is_switch switch or host
|
||||
@param cls node class (e.g. Host, Switch)
|
||||
@param params node parameters
|
||||
'''
|
||||
self.connected = connected
|
||||
self.admin_on = admin_on
|
||||
self.power_on = power_on
|
||||
self.fault = fault
|
||||
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):
|
||||
'''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.
|
||||
|
||||
@param admin_on administratively on or off; defaults to True
|
||||
@@ -87,28 +103,37 @@ class Edge(object):
|
||||
self.admin_on = admin_on
|
||||
self.power_on = power_on
|
||||
self.fault = fault
|
||||
# Above should be deleted and replaced by the following
|
||||
self.cls = cls
|
||||
self.params = params
|
||||
|
||||
|
||||
class Topo(object):
|
||||
'''Data center network representation for structured multi-trees.'''
|
||||
|
||||
def __init__(self):
|
||||
'''Create Topo object.
|
||||
|
||||
'''
|
||||
def __init__(self, node=Host, switch=None, link=Link):
|
||||
"""Create Topo object.
|
||||
node: default node/host class
|
||||
switch: default switch class
|
||||
Link: default link class"""
|
||||
self.g = Graph()
|
||||
self.node_info = {} # dpids hash to Node 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.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.
|
||||
|
||||
@param dpid dpid
|
||||
@param node Node object
|
||||
'''
|
||||
self.g.add_node(dpid)
|
||||
if not node:
|
||||
node = Node( link=self.link )
|
||||
self.node_info[dpid] = node
|
||||
|
||||
def add_edge(self, src, dst, edge=None):
|
||||
@@ -121,7 +146,7 @@ class Topo(object):
|
||||
src, dst = tuple(sorted([src, dst]))
|
||||
self.g.add_edge(src, dst)
|
||||
if not edge:
|
||||
edge = Edge()
|
||||
edge = Edge( link=self.link )
|
||||
self.edge_info[(src, dst)] = edge
|
||||
self.add_port(src, dst)
|
||||
|
||||
@@ -276,6 +301,12 @@ class Topo(object):
|
||||
assert dst in self.ports and src in self.ports[dst]
|
||||
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):
|
||||
'''Enable all edges in the network graph.
|
||||
|
||||
@@ -321,6 +352,11 @@ class Topo(object):
|
||||
'''
|
||||
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):
|
||||
'''Single switch connected to k hosts.'''
|
||||
|
||||
Reference in New Issue
Block a user