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: 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 + ' ' )
|
||||||
|
|
||||||
@@ -296,7 +311,11 @@ class Mininet( object ):
|
|||||||
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
@@ -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
|
||||||
|
|
||||||
|
|||||||
+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.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.'''
|
||||||
@@ -58,7 +65,8 @@ 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,28 +103,37 @@ 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):
|
||||||
@@ -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.'''
|
||||||
|
|||||||
Reference in New Issue
Block a user