diff --git a/examples/ripcordtest.py b/examples/ripcordtest.py index b27b08672161150eb59cb2c5cabefbd7eab2f652..41853b9a152a32da48e2dc7698aa3a788b41c105 100755 --- a/examples/ripcordtest.py +++ b/examples/ripcordtest.py @@ -11,44 +11,12 @@ 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 @@ def __init__(self, ip, subnet_size): if __name__ == '__main__': + set_loglevel('info') init() controller_params = ControllerParams(0x0a000000, 8) # 10.0.0.0/8 mn = Mininet(FatTreeTopo(4), Switch, Host, NOXController, diff --git a/mininet/cleanup b/mininet/cleanup index 92a5cd1ff17821adaf1f1e94247c4590bf5aa6c7..8897a5668c156da1773176ac110f09cbb6690339 100755 --- a/mininet/cleanup +++ b/mininet/cleanup @@ -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" diff --git a/mininet/logging_mod.py b/mininet/logging_mod.py index 38035b7a08265babfce00a29fe7b958c84daf638..6c6e024fc3960ed5fec406b59b5eb3102cb0b348 100644 --- a/mininet/logging_mod.py +++ b/mininet/logging_mod.py @@ -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 @@ def emit(self, record): [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) \ No newline at end of file + 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() diff --git a/mininet/mininet.py b/mininet/net.py similarity index 87% rename from mininet/mininet.py rename to mininet/net.py index eca9d9b1bc0a29ee4bc4a99fc846335748c0fcb0..674b9e093280f53c59c7a5bc9d59419a8c7edaf7 100755 --- a/mininet/mininet.py +++ b/mininet/net.py @@ -67,80 +67,19 @@ 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 @@ def sendCmd( self, cmd ): 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 @@ def start( self ): 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 @@ def treeNet( self, controller, depth, fanout, snames=None, 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 @@ def init(): 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) \ No newline at end of file + lg.info("*** Test results: %s\n", results) \ No newline at end of file diff --git a/mininet/test/test_nets.py b/mininet/test/test_nets.py index 215b1867c5f4cca418dd566aae97d922497a01e7..1696106128de3fc994bea6327059433e0ec1dec7 100755 --- a/mininet/test/test_nets.py +++ b/mininet/test/test_nets.py @@ -7,7 +7,7 @@ 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. diff --git a/mininet/util.py b/mininet/util.py new file mode 100644 index 0000000000000000000000000000000000000000..1b41165e5f0afba6fb541675ddf27f31cd79e047 --- /dev/null +++ b/mininet/util.py @@ -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 \ No newline at end of file