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:
+5
-36
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user