Move utility functions out of mininet.py

Having a file with the same name as its package creates hard-to-diagnose
import error, so rename mininet.py to net.py.  This commit moves utility
functions, generally those dealing with network namespaces and interface
management, into their own file.

Also fix a few minor formatting bugs and clean up logging code (which
had issues when multiple files would import mininet.logging_mod).
This commit is contained in:
Brandon Heller
2009-12-20 09:32:43 -08:00
parent bc547080b8
commit 220890a0c9
6 changed files with 261 additions and 188 deletions
+5 -36
View File
@@ -11,44 +11,12 @@ from subprocess import call
import sys
from time import sleep
from mininet.mininet import Switch, Controller, Host, lg
from mininet.mininet import init, quietRun, checkRun, retry, MOVEINTF_DELAY
from ripcord.topo import FatTreeTopo
def make_veth_pair(intf1, intf2):
'''Create a veth pair connecting intf1 and intf2.
@param intf1 string, interface name
@param intf2 string, interface name
'''
# Delete any old interfaces with the same names
quietRun('ip link del ' + intf1)
quietRun('ip link del ' + intf2)
# Create new pair
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
#lg.info('running command: %s\n' % cmd)
return checkRun(cmd)
def move_intf(intf, node):
'''Move interface to node.
@param intf string interface name
@param node Node object
@return success boolean, did operation complete?
'''
cmd = 'ip link set ' + intf + ' netns ' + repr(node.pid)
#lg.info('running command: %s\n' % cmd)
quietRun(cmd)
#lg.info(' output: %s\n' % output)
links = node.cmd('ip link show')
if not intf in links:
lg.error('*** Error: move_intf: % not successfully moved to %s:\n' %
(intf, node.name))
return False
return True
from mininet.net import Switch, Controller, Host, init
from mininet.logging_mod import lg, set_loglevel
from mininet.util import make_veth_pair, move_intf, retry, quietRun
from mininet.util import MOVEINTF_DELAY
class Mininet(object):
@@ -506,6 +474,7 @@ class ControllerParams(object):
if __name__ == '__main__':
set_loglevel('info')
init()
controller_params = ControllerParams(0x0a000000, 8) # 10.0.0.0/8
mn = Mininet(FatTreeTopo(4), Switch, Host, NOXController,
+1 -1
View File
@@ -12,7 +12,7 @@ irreplaceable!
from subprocess import Popen, PIPE
import re
from mininet import quietRun
from mininet.util import quietRun
def sh( cmd ):
"Print a command and send it to the shell"
+76 -12
View File
@@ -1,15 +1,33 @@
# Since StreamHandler automatically adds newlines, define a mod to more easily
# support interactive mode when we want it, or errors-only logging for running
# unit tests.
'''Logging functions for Mininet.'''
from logging import StreamHandler
import logging
import types
# Modified from python2.5/__init__.py to not require newlines
class StreamHandlerNoNewline(StreamHandler):
LEVELS = {'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL}
# change this to logging.INFO to get printouts when running unit tests
LOG_LEVEL_DEFAULT = logging.WARNING
#default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
LOG_MSG_FORMAT = '%(message)s'
# Modified from python2.5/__init__.py
class StreamHandlerNoNewline(logging.StreamHandler):
'''StreamHandler that doesn't print newlines by default.
Since StreamHandler automatically adds newlines, define a mod to more
easily support interactive mode when we want it, or errors-only logging for
running unit tests.
'''
def emit(self, record):
"""
'''
Emit a record.
If a formatter is specified, it is used to format the record.
@@ -17,19 +35,65 @@ class StreamHandlerNoNewline(StreamHandler):
[N.B. this may be removed depending on feedback]. If exception
information is present, it is formatted using
traceback.print_exception and appended to the stream.
"""
'''
try:
msg = self.format(record)
fs = "%s" # was "%s\n"
if not hasattr(types, "UnicodeType"): #if no unicode support...
fs = '%s' # was '%s\n'
if not hasattr(types, 'UnicodeType'): #if no unicode support...
self.stream.write(fs % msg)
else:
try:
self.stream.write(fs % msg)
except UnicodeError:
self.stream.write(fs % msg.encode("UTF-8"))
self.stream.write(fs % msg.encode('UTF-8'))
self.flush()
except (KeyboardInterrupt, SystemExit):
raise
except:
self.handleError(record)
self.handleError(record)
def set_loglevel(level_name = None):
'''Setup loglevel.
@param level_name level name from LEVELS
'''
level = LOG_LEVEL_DEFAULT
if level_name != None:
if level_name not in LEVELS:
raise Exception('unknown loglevel seen in set_loglevel')
else:
level = LEVELS.get(level_name, level)
lg.setLevel(level)
if len(lg.handlers) != 1:
raise Exception('lg.handlers length not zero in logging_mod')
lg.handlers[0].setLevel(level)
def _setup_logging():
'''Setup logging for Mininet.'''
global lg
# create logger if first time
if 'lg' not in globals():
lg = logging.getLogger('mininet')
# create console handler
ch = StreamHandlerNoNewline()
# create formatter
formatter = logging.Formatter(LOG_MSG_FORMAT)
# add formatter to ch
ch.setFormatter(formatter)
# add ch to lg
lg.addHandler(ch)
else:
raise Exception('setup_logging called twice')
set_loglevel()
# There has to be some better way to ensure we only ever have one logging
# variable. If this check isn't in, the order in which imports occur can
# affect whether a program runs, because the variable lg may get rebound.
if 'lg' not in globals():
_setup_logging()
+9 -138
View File
@@ -67,80 +67,19 @@ History:
12/13/09 Added support for custom controller and switch classes
"""
from subprocess import call, check_call, Popen, PIPE, STDOUT
from subprocess import call, Popen, PIPE, STDOUT
from time import sleep
import os, re, signal, sys, select
flush = sys.stdout.flush
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
import logging
import sys
from logging_mod import StreamHandlerNoNewline
from mininet.logging_mod import lg, set_loglevel
from mininet.util import run, checkRun, quietRun, makeIntfPair, moveIntf
from mininet.util import createLink
DATAPATHS = ['user', 'kernel']
LEVELS = {'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL}
# change this to get printouts when running unit tests
LOG_LEVEL_DEFAULT = logging.WARNING
#default: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
LOG_MSG_FORMAT = "%(message)s"
lg = None
def setup_logging(loglevel):
global lg
# create logger
lg = logging.getLogger("mininet")
lg.setLevel(loglevel)
# create console handler and set level to debug
ch = StreamHandlerNoNewline()
ch.setLevel(loglevel)
# create formatter
formatter = logging.Formatter(LOG_MSG_FORMAT)
# add formatter to ch
ch.setFormatter(formatter)
# add ch to lg
lg.addHandler(ch)
# Utility routines to make it easier to run commands
def run( cmd ):
"Simple interface to subprocess.call()"
return call( cmd.split( ' ' ) )
def checkRun( cmd ):
"Simple interface to subprocess.check_call()"
check_call( cmd.split( ' ' ) )
def quietRun( cmd ):
"Run a command, routing stderr to stdout, and return the output."
if isinstance( cmd, str ): cmd = cmd.split( ' ' )
popen = Popen( cmd, stdout=PIPE, stderr=STDOUT)
# We can't use Popen.communicate() because it uses
# select(), which can't handle
# high file descriptor numbers! poll() can, however.
output = ''
readable = select.poll()
readable.register( popen.stdout )
while True:
while readable.poll():
data = popen.stdout.read( 1024 )
if len( data ) == 0: break
output += data
popen.poll()
if popen.returncode != None: break
return output
class Node( object ):
"""A virtual network node is simply a shell in a network namespace.
We communicate with it using pipes."""
@@ -361,69 +300,7 @@ class Switch( Node ):
def monitor( self ):
if not self.execed: return Node.monitor( self )
else: return True, ''
# Interface management
#
# Interfaces are managed as strings which are simply the
# interface names, of the form "nodeN-ethM".
#
# To connect nodes, we create a pair of veth interfaces, and then place them
# in the pair of nodes that we want to communicate. We then update the node's
# list of interfaces and connectivity map.
#
# For the kernel datapath, switch interfaces
# live in the root namespace and thus do not have to be
# explicitly moved.
MOVEINTF_DELAY = 0.0001
def makeIntfPair( intf1, intf2 ):
"Make a veth pair of intf1 and intf2."
# Delete any old interfaces with the same names
quietRun( 'ip link del ' + intf1 )
quietRun( 'ip link del ' + intf2 )
# Create new pair
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
return checkRun( cmd )
def moveIntf( intf, node, print_error = False ):
"Move intf to node."
cmd = 'ip link set ' + intf + ' netns ' + `node.pid`
quietRun( cmd )
links = node.cmd( 'ip link show' )
if not intf in links:
if print_error:
lg.error("*** Error: moveIntf: % not successfully moved to %s:\n" %
(intf, node.name))
return False
return True
def retry( n, retry_delay, fn, *args):
'''Try something N times before giving up.
@param n number of times to retry
@param retry_delay seconds wait this long between tries
@param fn function to call
@param args args to apply to function call
'''
tries = 0
while not apply( fn, args ) and tries < n:
sleep( retry_delay )
tries += 1
if tries >= n:
lg.error("*** gave up after %i retries\n" % tries)
exit( 1 )
def createLink( node1, node2 ):
"Create a link node1-intf1 <---> node2-intf2."
intf1 = node1.newIntf()
intf2 = node2.newIntf()
makeIntfPair( intf1, intf2 )
if node1.inNamespace: retry( 3, MOVEINTF_DELAY, moveIntf, intf1, node1 )
if node2.inNamespace: retry( 3, MOVEINTF_DELAY, moveIntf, intf2, node2 )
node1.connection[ intf1 ] = ( node2, intf2 )
node2.connection[ intf2 ] = ( node1, intf1 )
return intf1, intf2
# Handy utilities
@@ -506,7 +383,6 @@ def configureRoutedControlNetwork( controller, switches, ips):
if pingTest( hosts=[ switch, controller ] ) != 0:
lg.error("*** Error: control network test failed\n")
exit( 1 )
lg.info("\n")
def configHosts( hosts, ips ):
"""Configure a set of hosts, starting at IP address a.b.c.d"""
@@ -578,6 +454,7 @@ class Network( object ):
lg.info("*** Starting %s switches" % len(self.switches))
for switch in self.switches:
switch.start( self.controllers[ 0 ] )
lg.info("\n")
def stop( self ):
"""Stop the controller(s), switches and hosts\n"""
lg.info("*** Stopping hosts\n")
@@ -649,7 +526,6 @@ class TreeNet( Network ):
def makeNet( self, controller ):
root, switches, hosts = self.treeNet( controller,
self.depth, self.fanout )
lg.info("\n")
return switches, hosts
# Grid network
@@ -903,12 +779,10 @@ def init():
fixLimits()
if __name__ == '__main__':
level = logging.INFO
if len(sys.argv) > 1:
level_name = sys.argv[1]
level = LEVELS.get(level_name, level)
setup_logging(level)
#lg.basicConfig(level = level, format = LOG_MSG_FORMAT)
set_loglevel(sys.argv[1])
else:
set_loglevel('info')
init()
results = {}
@@ -920,7 +794,4 @@ if __name__ == '__main__':
network = TreeNet( depth=2, fanout=4, kernel=k)
result = network.run( pingTestVerbose )
results[ datapath ] = result
lg.info("*** Test results: %s\n", results)
else:
setup_logging(LOG_LEVEL_DEFAULT)
#lg.basicConfig(level = LOG_LEVEL_DEFAULT, format = LOG_MSG_FORMAT)
lg.info("*** Test results: %s\n", results)
+1 -1
View File
@@ -7,7 +7,7 @@ Test creation and all-pairs ping for each included mininet topo type.
from time import sleep
import unittest
from mininet.mininet import init, TreeNet, LinearNet, pingTest, DATAPATHS
from mininet.net import init, TreeNet, LinearNet, pingTest, DATAPATHS
class testMinimal(unittest.TestCase):
'''For each datapath type, test ping with a minimal topology.
+169
View File
@@ -0,0 +1,169 @@
#!/usr/bin/env python
'''Utility functions for Mininet.'''
from time import sleep
import select
from subprocess import call, check_call, Popen, PIPE, STDOUT
from mininet.logging_mod import lg
def run(cmd):
'''Simple interface to subprocess.call()
@param cmd list of command params
'''
return call(cmd.split(' '))
def checkRun(cmd):
'''Simple interface to subprocess.check_call()
@param cmd list of command params
'''
check_call(cmd.split(' '))
def quietRun(cmd):
'''Run a command, routing stderr to stdout, and return the output.
@param cmd list of command params
'''
if isinstance(cmd, str):
cmd = cmd.split(' ')
popen = Popen(cmd, stdout=PIPE, stderr=STDOUT)
# We can't use Popen.communicate() because it uses
# select(), which can't handle
# high file descriptor numbers! poll() can, however.
output = ''
readable = select.poll()
readable.register(popen.stdout)
while True:
while readable.poll():
data = popen.stdout.read(1024)
if len(data) == 0:
break
output += data
popen.poll()
if popen.returncode != None:
break
return output
def make_veth_pair(intf1, intf2):
'''Create a veth pair connecting intf1 and intf2.
@param intf1 string, interface name
@param intf2 string, interface name
'''
# Delete any old interfaces with the same names
quietRun('ip link del ' + intf1)
quietRun('ip link del ' + intf2)
# Create new pair
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
#lg.info('running command: %s\n' % cmd)
return checkRun(cmd)
def move_intf(intf, node):
'''Move interface to node.
@param intf string interface name
@param node Node object
@return success boolean, did operation complete?
'''
cmd = 'ip link set ' + intf + ' netns ' + repr(node.pid)
#lg.info('running command: %s\n' % cmd)
quietRun(cmd)
#lg.info(' output: %s\n' % output)
links = node.cmd('ip link show')
if not intf in links:
lg.error('*** Error: move_intf: % not successfully moved to %s:\n' %
(intf, node.name))
return False
return True
# Interface management
#
# Interfaces are managed as strings which are simply the
# interface names, of the form 'nodeN-ethM'.
#
# To connect nodes, we create a pair of veth interfaces, and then place them
# in the pair of nodes that we want to communicate. We then update the node's
# list of interfaces and connectivity map.
#
# For the kernel datapath, switch interfaces
# live in the root namespace and thus do not have to be
# explicitly moved.
def makeIntfPair(intf1, intf2):
'''Make a veth pair.
@param intf1 string, interface
@param intf2 string, interface
@return success boolean
'''
# Delete any old interfaces with the same names
quietRun('ip link del ' + intf1)
quietRun('ip link del ' + intf2)
# Create new pair
cmd = 'ip link add name ' + intf1 + ' type veth peer name ' + intf2
return checkRun( cmd )
def moveIntf(intf, node, print_error = False):
'''Move interface to node.
@param intf string, interface
@param node Node object
@param print_error if true, print error
'''
cmd = 'ip link set ' + intf + ' netns ' + repr(node.pid)
quietRun(cmd)
links = node.cmd('ip link show')
if not intf in links:
if print_error:
lg.error('*** Error: moveIntf: % not successfully moved to %s:\n' %
(intf, node.name))
return False
return True
def retry(n, retry_delay, fn, *args):
'''Try something N times before giving up.
@param n number of times to retry
@param retry_delay seconds wait this long between tries
@param fn function to call
@param args args to apply to function call
'''
tries = 0
while not apply(fn, args) and tries < n:
sleep(retry_delay)
tries += 1
if tries >= n:
lg.error("*** gave up after %i retries\n" % tries)
exit( 1 )
# delay between interface move checks in seconds
MOVEINTF_DELAY = 0.0001
def createLink(node1, node2):
'''Create a link between nodes, making an interface for each.
@param node1 Node object
@param node2 Node object
'''
intf1 = node1.newIntf()
intf2 = node2.newIntf()
makeIntfPair(intf1, intf2)
if node1.inNamespace:
retry(3, MOVEINTF_DELAY, moveIntf, intf1, node1)
if node2.inNamespace:
retry(3, MOVEINTF_DELAY, moveIntf, intf2, node2)
node1.connection[intf1] = (node2, intf2)
node2.connection[intf2] = (node1, intf1)
return intf1, intf2