From a6bcad8f48aabae68287178c8dd54cff634a4982 Mon Sep 17 00:00:00 2001 From: Bob Lantz Date: Fri, 2 Mar 2012 15:45:21 -0800 Subject: [PATCH] Intf and Link classes. Latter support bandwidth limits using tc. --- mininet/link.py | 275 ++++++++++++++++++++++++++++++++++++++++++++++++ mininet/net.py | 79 ++++++++------ mininet/node.py | 196 +++++++++++++--------------------- mininet/topo.py | 62 ++++++++--- 4 files changed, 448 insertions(+), 164 deletions(-) create mode 100644 mininet/link.py diff --git a/mininet/link.py b/mininet/link.py new file mode 100644 index 0000000..c7c7aa2 --- /dev/null +++ b/mininet/link.py @@ -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 ) + + + + + + diff --git a/mininet/net.py b/mininet/net.py index 973a694..c5c2931 100755 --- a/mininet/net.py +++ b/mininet/net.py @@ -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 + ' ' ) @@ -291,12 +306,16 @@ class Mininet( object ): addNode( 'h', self.addHost, hostId ) info( '\n*** Adding switches:\n' ) for switchId in sorted( topo.switches() ): - addNode( 's', self.addSwitch, switchId ) + addNode( 's', self.addSwitch, switchId) info( '\n*** Adding links:\n' ) 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 ) diff --git a/mininet/node.py b/mininet/node.py index 70381ec..b9262e3 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -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 diff --git a/mininet/topo.py b/mininet/topo.py index f7de5fc..6330f56 100644 --- a/mininet/topo.py +++ b/mininet/topo.py @@ -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.''' @@ -54,11 +61,12 @@ class NodeID(object): return "10.%i.%i.%i" % (hi, mid, lo) -class Node(object): +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): + def __init__(self, connected=False, admin_on=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,31 +103,40 @@ 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): + def add_edge(self, src, dst, edge=None): '''Add edge (Node, Node) to graph. @param src src dpid @@ -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,7 +352,12 @@ 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.'''