Merge pull request #315 from cdburkard/patches/fix_bindpy
Added support for mount namespaces in bind.py
This commit is contained in:
+42
-167
@@ -1,197 +1,72 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
|
||||||
"""
|
"""
|
||||||
bind.py: Bind mount prototype
|
bind.py: Bind mount example
|
||||||
|
|
||||||
This creates hosts with private directories as desired.
|
This creates hosts with private directories that the user specifies.
|
||||||
|
These hosts may have persistent directories that will be available
|
||||||
|
across multiple mininet session, or temporary directories that will
|
||||||
|
only last for one mininet session. To specify a persistent
|
||||||
|
directory, add a tuple to a list of private directories:
|
||||||
|
|
||||||
|
[ ( 'directory to be mounted on', 'directory to be mounted' ) ]
|
||||||
|
|
||||||
|
String expansion may be used to create a directory template for
|
||||||
|
each host. To do this, add a %(name)s in place of the host name
|
||||||
|
when creating your list of directories:
|
||||||
|
|
||||||
|
[ ( '/var/run', '/tmp/%(name)s/var/run' ) ]
|
||||||
|
|
||||||
|
If no persistent directory is specified, the directories will default
|
||||||
|
to temporary private directories. To do this, simply create a list of
|
||||||
|
directories to be made private. A tmpfs will then be mounted on them.
|
||||||
|
|
||||||
|
You may use both temporary and persistent directories at the same
|
||||||
|
time. In the following privateDirs string, each host will have a
|
||||||
|
persistent directory in the root filesystem at
|
||||||
|
"/tmp/(hostname)/var/run" mounted on "/var/run". Each host will also
|
||||||
|
have a temporary private directory mounted on "/var/log".
|
||||||
|
|
||||||
|
[ ( '/var/run', '/tmp/%(name)s/var/run' ), '/var/log' ]
|
||||||
|
|
||||||
|
This example has both persistent directories mounted on '/var/log'
|
||||||
|
and '/var/run'. It also has a temporary private directory mounted
|
||||||
|
on '/var/mn'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mininet.net import Mininet
|
from mininet.net import Mininet
|
||||||
from mininet.node import Host
|
from mininet.node import Host, HostWithPrivateDirs
|
||||||
from mininet.cli import CLI
|
from mininet.cli import CLI
|
||||||
from mininet.util import errFail, quietRun, errRun
|
|
||||||
from mininet.topo import SingleSwitchTopo
|
from mininet.topo import SingleSwitchTopo
|
||||||
from mininet.log import setLogLevel, info, debug
|
from mininet.log import setLogLevel, info, debug
|
||||||
|
|
||||||
from os.path import realpath
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
|
||||||
# Utility functions for unmounting a tree
|
|
||||||
|
|
||||||
MNRUNDIR = realpath( '/var/run/mn' )
|
|
||||||
|
|
||||||
def mountPoints():
|
|
||||||
"Return list of mounted file systems"
|
|
||||||
mtab, _err, _ret = errFail( 'cat /proc/mounts' )
|
|
||||||
lines = mtab.split( '\n' )
|
|
||||||
mounts = []
|
|
||||||
for line in lines:
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
fields = line.split( ' ')
|
|
||||||
mount = fields[ 1 ]
|
|
||||||
mounts.append( mount )
|
|
||||||
return mounts
|
|
||||||
|
|
||||||
def unmountAll( rootdir=MNRUNDIR ):
|
|
||||||
"Unmount all mounts under a directory tree"
|
|
||||||
rootdir = realpath( rootdir )
|
|
||||||
# Find all mounts below rootdir
|
|
||||||
# This is subtle because /foo is not
|
|
||||||
# a parent of /foot
|
|
||||||
dirslash = rootdir + '/'
|
|
||||||
mounts = [ m for m in mountPoints()
|
|
||||||
if m == dir or m.find( dirslash ) == 0 ]
|
|
||||||
# Unmount them from bottom to top
|
|
||||||
mounts.sort( reverse=True )
|
|
||||||
for mount in mounts:
|
|
||||||
debug( 'Unmounting', mount, '\n' )
|
|
||||||
_out, err, code = errRun( 'umount', mount )
|
|
||||||
if code != 0:
|
|
||||||
info( '*** Warning: failed to umount', mount, '\n' )
|
|
||||||
info( err )
|
|
||||||
|
|
||||||
|
|
||||||
class HostWithPrivateDirs( Host ):
|
|
||||||
"Host with private directories"
|
|
||||||
|
|
||||||
mnRunDir = MNRUNDIR
|
|
||||||
|
|
||||||
def __init__(self, name, *args, **kwargs ):
|
|
||||||
"""privateDirs: list of private directories
|
|
||||||
remounts: dirs to remount
|
|
||||||
unmount: unmount dirs in cleanup? (True)
|
|
||||||
Note: if unmount is False, you must call unmountAll()
|
|
||||||
manually."""
|
|
||||||
self.privateDirs = kwargs.pop( 'privateDirs', [] )
|
|
||||||
self.remounts = kwargs.pop( 'remounts', [] )
|
|
||||||
self.unmount = kwargs.pop( 'unmount', True )
|
|
||||||
Host.__init__( self, name, *args, **kwargs )
|
|
||||||
self.rundir = '%s/%s' % ( self.mnRunDir, name )
|
|
||||||
self.root, self.private = None, None # set in createBindMounts
|
|
||||||
if self.privateDirs:
|
|
||||||
self.privateDirs = [ realpath( d ) for d in self.privateDirs ]
|
|
||||||
self.createBindMounts()
|
|
||||||
# These should run in the namespace before we chroot,
|
|
||||||
# in order to put the right entries in /etc/mtab
|
|
||||||
# Eventually this will allow a local pid space
|
|
||||||
# Now we chroot and cd to wherever we were before.
|
|
||||||
pwd = self.cmd( 'pwd' ).strip()
|
|
||||||
self.sendCmd( 'exec chroot', self.root, 'bash -ms mininet:'
|
|
||||||
+ self.name )
|
|
||||||
self.waiting = False
|
|
||||||
self.cmd( 'cd', pwd )
|
|
||||||
# In order for many utilities to work,
|
|
||||||
# we need to remount /proc and /sys
|
|
||||||
self.cmd( 'mount /proc' )
|
|
||||||
self.cmd( 'mount /sys' )
|
|
||||||
|
|
||||||
def mountPrivateDirs( self ):
|
|
||||||
"Create and bind mount private dirs"
|
|
||||||
for dir_ in self.privateDirs:
|
|
||||||
privateDir = self.private + dir_
|
|
||||||
errFail( 'mkdir -p ' + privateDir )
|
|
||||||
mountPoint = self.root + dir_
|
|
||||||
errFail( 'mount -B %s %s' %
|
|
||||||
( privateDir, mountPoint) )
|
|
||||||
|
|
||||||
def mountDirs( self, dirs ):
|
|
||||||
"Mount a list of directories"
|
|
||||||
for dir_ in dirs:
|
|
||||||
mountpoint = self.root + dir_
|
|
||||||
errFail( 'mount -B %s %s' %
|
|
||||||
( dir_, mountpoint ) )
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def findRemounts( cls, fstypes=None ):
|
|
||||||
"""Identify mount points in /proc/mounts to remount
|
|
||||||
fstypes: file system types to match"""
|
|
||||||
if fstypes is None:
|
|
||||||
fstypes = [ 'nfs' ]
|
|
||||||
dirs = quietRun( 'cat /proc/mounts' ).strip().split( '\n' )
|
|
||||||
remounts = []
|
|
||||||
for dir_ in dirs:
|
|
||||||
line = dir_.split()
|
|
||||||
mountpoint, fstype = line[ 1 ], line[ 2 ]
|
|
||||||
# Don't re-remount directories!!!
|
|
||||||
if mountpoint.find( cls.mnRunDir ) == 0:
|
|
||||||
continue
|
|
||||||
if fstype in fstypes:
|
|
||||||
remounts.append( mountpoint )
|
|
||||||
return remounts
|
|
||||||
|
|
||||||
def createBindMounts( self ):
|
|
||||||
"""Create a chroot directory structure,
|
|
||||||
with self.privateDirs as private dirs"""
|
|
||||||
errFail( 'mkdir -p '+ self.rundir )
|
|
||||||
unmountAll( self.rundir )
|
|
||||||
# Create /root and /private directories
|
|
||||||
self.root = self.rundir + '/root'
|
|
||||||
self.private = self.rundir + '/private'
|
|
||||||
errFail( 'mkdir -p ' + self.root )
|
|
||||||
errFail( 'mkdir -p ' + self.private )
|
|
||||||
# Recursively mount / in private doort
|
|
||||||
# note we'll remount /sys and /proc later
|
|
||||||
errFail( 'mount -B / ' + self.root )
|
|
||||||
self.mountDirs( self.remounts )
|
|
||||||
self.mountPrivateDirs()
|
|
||||||
|
|
||||||
def unmountBindMounts( self ):
|
|
||||||
"Unmount all of our bind mounts"
|
|
||||||
unmountAll( self.rundir )
|
|
||||||
|
|
||||||
def popen( self, *args, **kwargs ):
|
|
||||||
"Popen with chroot support"
|
|
||||||
chroot = kwargs.pop( 'chroot', True )
|
|
||||||
mncmd = kwargs.get( 'mncmd',
|
|
||||||
[ 'mnexec', '-a', str( self.pid ) ] )
|
|
||||||
if chroot:
|
|
||||||
mncmd = [ 'chroot', self.root ] + mncmd
|
|
||||||
kwargs[ 'mncmd' ] = mncmd
|
|
||||||
return Host.popen( self, *args, **kwargs )
|
|
||||||
|
|
||||||
def cleanup( self ):
|
|
||||||
"""Clean up, then unmount bind mounts
|
|
||||||
unmount: actually unmount bind mounts?"""
|
|
||||||
# Wait for process to actually terminate
|
|
||||||
self.shell.wait()
|
|
||||||
Host.cleanup( self )
|
|
||||||
if self.unmount:
|
|
||||||
self.unmountBindMounts()
|
|
||||||
errFail( 'rmdir ' + self.root )
|
|
||||||
|
|
||||||
|
|
||||||
# Convenience aliases
|
|
||||||
|
|
||||||
findRemounts = HostWithPrivateDirs.findRemounts
|
|
||||||
|
|
||||||
|
|
||||||
# Sample usage
|
# Sample usage
|
||||||
|
|
||||||
def testHostWithPrivateDirs():
|
def testHostWithPrivateDirs():
|
||||||
"Test bind mounts"
|
"Test bind mounts"
|
||||||
topo = SingleSwitchTopo( 10 )
|
topo = SingleSwitchTopo( 10 )
|
||||||
remounts = findRemounts( fstypes=[ 'nfs' ] )
|
privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ),
|
||||||
privateDirs = [ '/var/log', '/var/run' ]
|
( '/var/run', '/tmp/%(name)s/var/run' ),
|
||||||
host = partial( HostWithPrivateDirs, remounts=remounts,
|
'/var/mn' ]
|
||||||
privateDirs=privateDirs, unmount=False )
|
host = partial( HostWithPrivateDirs,
|
||||||
|
privateDirs=privateDirs )
|
||||||
net = Mininet( topo=topo, host=host )
|
net = Mininet( topo=topo, host=host )
|
||||||
net.start()
|
net.start()
|
||||||
info( 'Private Directories:', privateDirs, '\n' )
|
directories = []
|
||||||
|
for directory in privateDirs:
|
||||||
|
directories.append( directory[ 0 ]
|
||||||
|
if isinstance( directory, tuple )
|
||||||
|
else directory )
|
||||||
|
info( 'Private Directories:', directories, '\n' )
|
||||||
CLI( net )
|
CLI( net )
|
||||||
net.stop()
|
net.stop()
|
||||||
# We do this all at once to save a bit of time
|
|
||||||
info( 'Unmounting host bind mounts...\n' )
|
|
||||||
unmountAll()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unmountAll()
|
|
||||||
setLogLevel( 'info' )
|
setLogLevel( 'info' )
|
||||||
testHostWithPrivateDirs()
|
testHostWithPrivateDirs()
|
||||||
info( 'Done.\n')
|
info( 'Done.\n')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ Host: a virtual host. By default, a host is simply a shell; commands
|
|||||||
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
|
CPULimitedHost: a virtual host whose CPU bandwidth is limited by
|
||||||
RT or CFS bandwidth limiting.
|
RT or CFS bandwidth limiting.
|
||||||
|
|
||||||
|
HostWithPrivateDirs: a virtual host that has user-specified private
|
||||||
|
directories. These may be temporary directories stored as a tmpfs,
|
||||||
|
or persistent directories that are mounted from another directory in
|
||||||
|
the root filesystem.
|
||||||
|
|
||||||
Switch: superclass for switch nodes.
|
Switch: superclass for switch nodes.
|
||||||
|
|
||||||
UserSwitch: a switch using the user-space switch from the OpenFlow
|
UserSwitch: a switch using the user-space switch from the OpenFlow
|
||||||
@@ -725,6 +730,33 @@ class CPULimitedHost( Host ):
|
|||||||
mountCgroups()
|
mountCgroups()
|
||||||
cls.inited = True
|
cls.inited = True
|
||||||
|
|
||||||
|
class HostWithPrivateDirs( Host ):
|
||||||
|
"Host with private directories"
|
||||||
|
|
||||||
|
def __init__( self, name, *args, **kwargs ):
|
||||||
|
"privateDirs: list of private directory strings or tuples"
|
||||||
|
self.name = name
|
||||||
|
self.privateDirs = kwargs.pop( 'privateDirs', [] )
|
||||||
|
Host.__init__( self, name, *args, **kwargs )
|
||||||
|
self.mountPrivateDirs()
|
||||||
|
|
||||||
|
def mountPrivateDirs( self ):
|
||||||
|
"mount private directories"
|
||||||
|
for directory in self.privateDirs:
|
||||||
|
if isinstance( directory, tuple ):
|
||||||
|
# mount given private directory
|
||||||
|
privateDir = directory[ 1 ] % self.__dict__
|
||||||
|
mountPoint = directory[ 0 ]
|
||||||
|
self.cmd( 'mkdir -p %s' % privateDir )
|
||||||
|
self.cmd( 'mkdir -p %s' % mountPoint )
|
||||||
|
self.cmd( 'mount --bind %s %s' %
|
||||||
|
( privateDir, mountPoint ) )
|
||||||
|
else:
|
||||||
|
# mount temporary filesystem on directory
|
||||||
|
self.cmd( 'mkdir -p %s' % directory )
|
||||||
|
self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Some important things to note:
|
# Some important things to note:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ int main(int argc, char *argv[])
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
/* Attach to pid's network namespace */
|
/* Attach to pid's network namespace and mount namespace*/
|
||||||
pid = atoi(optarg);
|
pid = atoi(optarg);
|
||||||
sprintf(path, "/proc/%d/ns/net", pid );
|
sprintf(path, "/proc/%d/ns/net", pid );
|
||||||
nsid = open(path, O_RDONLY);
|
nsid = open(path, O_RDONLY);
|
||||||
@@ -152,6 +152,16 @@ int main(int argc, char *argv[])
|
|||||||
perror("setns");
|
perror("setns");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
sprintf(path, "/proc/%d/ns/mnt", pid );
|
||||||
|
nsid = open(path, O_RDONLY);
|
||||||
|
if (nsid < 0) {
|
||||||
|
perror(path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (setns(nsid, 0) != 0) {
|
||||||
|
perror("setns");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'g':
|
case 'g':
|
||||||
/* Attach to cgroup */
|
/* Attach to cgroup */
|
||||||
|
|||||||
Reference in New Issue
Block a user