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
|
import sys
|
||||||
from time import sleep
|
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
|
from ripcord.topo import FatTreeTopo
|
||||||
|
|
||||||
def make_veth_pair(intf1, intf2):
|
from mininet.net import Switch, Controller, Host, init
|
||||||
'''Create a veth pair connecting intf1 and intf2.
|
from mininet.logging_mod import lg, set_loglevel
|
||||||
|
from mininet.util import make_veth_pair, move_intf, retry, quietRun
|
||||||
@param intf1 string, interface name
|
from mininet.util import MOVEINTF_DELAY
|
||||||
@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
|
|
||||||
|
|
||||||
|
|
||||||
class Mininet(object):
|
class Mininet(object):
|
||||||
@@ -506,6 +474,7 @@ class ControllerParams(object):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
set_loglevel('info')
|
||||||
init()
|
init()
|
||||||
controller_params = ControllerParams(0x0a000000, 8) # 10.0.0.0/8
|
controller_params = ControllerParams(0x0a000000, 8) # 10.0.0.0/8
|
||||||
mn = Mininet(FatTreeTopo(4), Switch, Host, NOXController,
|
mn = Mininet(FatTreeTopo(4), Switch, Host, NOXController,
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ irreplaceable!
|
|||||||
from subprocess import Popen, PIPE
|
from subprocess import Popen, PIPE
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from mininet import quietRun
|
from mininet.util import quietRun
|
||||||
|
|
||||||
def sh( cmd ):
|
def sh( cmd ):
|
||||||
"Print a command and send it to the shell"
|
"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
|
'''Logging functions for Mininet.'''
|
||||||
# support interactive mode when we want it, or errors-only logging for running
|
|
||||||
# unit tests.
|
|
||||||
|
|
||||||
from logging import StreamHandler
|
import logging
|
||||||
import types
|
import types
|
||||||
|
|
||||||
# Modified from python2.5/__init__.py to not require newlines
|
LEVELS = {'debug': logging.DEBUG,
|
||||||
class StreamHandlerNoNewline(StreamHandler):
|
'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):
|
def emit(self, record):
|
||||||
"""
|
'''
|
||||||
Emit a record.
|
Emit a record.
|
||||||
|
|
||||||
If a formatter is specified, it is used to format the 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
|
[N.B. this may be removed depending on feedback]. If exception
|
||||||
information is present, it is formatted using
|
information is present, it is formatted using
|
||||||
traceback.print_exception and appended to the stream.
|
traceback.print_exception and appended to the stream.
|
||||||
"""
|
'''
|
||||||
try:
|
try:
|
||||||
msg = self.format(record)
|
msg = self.format(record)
|
||||||
fs = "%s" # was "%s\n"
|
fs = '%s' # was '%s\n'
|
||||||
if not hasattr(types, "UnicodeType"): #if no unicode support...
|
if not hasattr(types, 'UnicodeType'): #if no unicode support...
|
||||||
self.stream.write(fs % msg)
|
self.stream.write(fs % msg)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
self.stream.write(fs % msg)
|
self.stream.write(fs % msg)
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
self.stream.write(fs % msg.encode("UTF-8"))
|
self.stream.write(fs % msg.encode('UTF-8'))
|
||||||
self.flush()
|
self.flush()
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except (KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except:
|
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
|
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
|
from time import sleep
|
||||||
import os, re, signal, sys, select
|
import os, re, signal, sys, select
|
||||||
flush = sys.stdout.flush
|
flush = sys.stdout.flush
|
||||||
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
|
from resource import setrlimit, RLIMIT_NPROC, RLIMIT_NOFILE
|
||||||
|
|
||||||
import logging
|
from mininet.logging_mod import lg, set_loglevel
|
||||||
import sys
|
from mininet.util import run, checkRun, quietRun, makeIntfPair, moveIntf
|
||||||
|
from mininet.util import createLink
|
||||||
from logging_mod import StreamHandlerNoNewline
|
|
||||||
|
|
||||||
DATAPATHS = ['user', 'kernel']
|
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 ):
|
class Node( object ):
|
||||||
"""A virtual network node is simply a shell in a network namespace.
|
"""A virtual network node is simply a shell in a network namespace.
|
||||||
We communicate with it using pipes."""
|
We communicate with it using pipes."""
|
||||||
@@ -361,69 +300,7 @@ class Switch( Node ):
|
|||||||
def monitor( self ):
|
def monitor( self ):
|
||||||
if not self.execed: return Node.monitor( self )
|
if not self.execed: return Node.monitor( self )
|
||||||
else: return True, ''
|
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
|
# Handy utilities
|
||||||
|
|
||||||
@@ -506,7 +383,6 @@ def configureRoutedControlNetwork( controller, switches, ips):
|
|||||||
if pingTest( hosts=[ switch, controller ] ) != 0:
|
if pingTest( hosts=[ switch, controller ] ) != 0:
|
||||||
lg.error("*** Error: control network test failed\n")
|
lg.error("*** Error: control network test failed\n")
|
||||||
exit( 1 )
|
exit( 1 )
|
||||||
lg.info("\n")
|
|
||||||
|
|
||||||
def configHosts( hosts, ips ):
|
def configHosts( hosts, ips ):
|
||||||
"""Configure a set of hosts, starting at IP address a.b.c.d"""
|
"""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))
|
lg.info("*** Starting %s switches" % len(self.switches))
|
||||||
for switch in self.switches:
|
for switch in self.switches:
|
||||||
switch.start( self.controllers[ 0 ] )
|
switch.start( self.controllers[ 0 ] )
|
||||||
|
lg.info("\n")
|
||||||
def stop( self ):
|
def stop( self ):
|
||||||
"""Stop the controller(s), switches and hosts\n"""
|
"""Stop the controller(s), switches and hosts\n"""
|
||||||
lg.info("*** Stopping hosts\n")
|
lg.info("*** Stopping hosts\n")
|
||||||
@@ -649,7 +526,6 @@ class TreeNet( Network ):
|
|||||||
def makeNet( self, controller ):
|
def makeNet( self, controller ):
|
||||||
root, switches, hosts = self.treeNet( controller,
|
root, switches, hosts = self.treeNet( controller,
|
||||||
self.depth, self.fanout )
|
self.depth, self.fanout )
|
||||||
lg.info("\n")
|
|
||||||
return switches, hosts
|
return switches, hosts
|
||||||
|
|
||||||
# Grid network
|
# Grid network
|
||||||
@@ -903,12 +779,10 @@ def init():
|
|||||||
fixLimits()
|
fixLimits()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
level = logging.INFO
|
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
level_name = sys.argv[1]
|
set_loglevel(sys.argv[1])
|
||||||
level = LEVELS.get(level_name, level)
|
else:
|
||||||
setup_logging(level)
|
set_loglevel('info')
|
||||||
#lg.basicConfig(level = level, format = LOG_MSG_FORMAT)
|
|
||||||
|
|
||||||
init()
|
init()
|
||||||
results = {}
|
results = {}
|
||||||
@@ -920,7 +794,4 @@ if __name__ == '__main__':
|
|||||||
network = TreeNet( depth=2, fanout=4, kernel=k)
|
network = TreeNet( depth=2, fanout=4, kernel=k)
|
||||||
result = network.run( pingTestVerbose )
|
result = network.run( pingTestVerbose )
|
||||||
results[ datapath ] = result
|
results[ datapath ] = result
|
||||||
lg.info("*** Test results: %s\n", results)
|
lg.info("*** Test results: %s\n", results)
|
||||||
else:
|
|
||||||
setup_logging(LOG_LEVEL_DEFAULT)
|
|
||||||
#lg.basicConfig(level = LOG_LEVEL_DEFAULT, format = LOG_MSG_FORMAT)
|
|
||||||
@@ -7,7 +7,7 @@ Test creation and all-pairs ping for each included mininet topo type.
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from mininet.mininet import init, TreeNet, LinearNet, pingTest, DATAPATHS
|
from mininet.net import init, TreeNet, LinearNet, pingTest, DATAPATHS
|
||||||
|
|
||||||
class testMinimal(unittest.TestCase):
|
class testMinimal(unittest.TestCase):
|
||||||
'''For each datapath type, test ping with a minimal topology.
|
'''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