From 9109233886f88fdfd6b87dd8f368328c838a7098 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 10 Jun 2014 11:44:03 -0700 Subject: [PATCH 1/6] Added support for mount namespaces in bind.py. Also moved it to the node class as a host type. --- examples/bind.py | 164 +---------------------------------------------- mininet/node.py | 17 +++++ 2 files changed, 20 insertions(+), 161 deletions(-) diff --git a/examples/bind.py b/examples/bind.py index 2c317cf..af02254 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -7,191 +7,33 @@ This creates hosts with private directories as desired. """ from mininet.net import Mininet -from mininet.node import Host +from mininet.node import Host, HostWithPrivateDirs from mininet.cli import CLI -from mininet.util import errFail, quietRun, errRun from mininet.topo import SingleSwitchTopo from mininet.log import setLogLevel, info, debug -from os.path import realpath 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 def testHostWithPrivateDirs(): "Test bind mounts" topo = SingleSwitchTopo( 10 ) - remounts = findRemounts( fstypes=[ 'nfs' ] ) privateDirs = [ '/var/log', '/var/run' ] - host = partial( HostWithPrivateDirs, remounts=remounts, - privateDirs=privateDirs, unmount=False ) + host = partial( HostWithPrivateDirs, + privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) net.start() info( 'Private Directories:', privateDirs, '\n' ) CLI( net ) net.stop() - # We do this all at once to save a bit of time - info( 'Unmounting host bind mounts...\n' ) - unmountAll() if __name__ == '__main__': - unmountAll() setLogLevel( 'info' ) testHostWithPrivateDirs() info( 'Done.\n') - diff --git a/mininet/node.py b/mininet/node.py index e5e8cb1..a9852b2 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -725,6 +725,23 @@ class CPULimitedHost( Host ): mountCgroups() cls.inited = True +class HostWithPrivateDirs( Host ): + "Host with private directories" + + def __init__(self, *args, **kwargs ): + """privateDirs: list of private directories""" + + self.privateDirs = kwargs.pop( 'privateDirs', [] ) + Host.__init__( self, *args, **kwargs ) + self.mountPrivateDirs() + + def mountPrivateDirs( self ): + "Mount tmpfs for each private directory" + for dir_ in self.privateDirs: + self.cmd( 'mkdir -p ' + dir_ ) + self.cmd( 'mount -n -t tmpfs tmpfs %s' % dir_ ) + + # Some important things to note: # From 6a81b6dfb38297a50e20dfb2e29d57be4e572ee2 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Thu, 19 Jun 2014 15:08:26 -0700 Subject: [PATCH 2/6] added persistence option to HostWithPrivateDirs. also attached mount namespaces when mnexec -a is specified --- examples/bind.py | 4 +++- mininet/node.py | 20 +++++++++++++++++--- mnexec.c | 15 ++++++++++++++- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/examples/bind.py b/examples/bind.py index af02254..c2e7902 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -20,7 +20,9 @@ from functools import partial def testHostWithPrivateDirs(): "Test bind mounts" topo = SingleSwitchTopo( 10 ) - privateDirs = [ '/var/log', '/var/run' ] + privateDirs = [ ( '/var/log', '/onos/%(name)s/var/log' ), + ( '/var/run', '/ovx/%(name)s/var/run' ), + '/mn' ] host = partial( HostWithPrivateDirs, privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) diff --git a/mininet/node.py b/mininet/node.py index a9852b2..9d5516c 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -728,14 +728,28 @@ class CPULimitedHost( Host ): class HostWithPrivateDirs( Host ): "Host with private directories" - def __init__(self, *args, **kwargs ): + def __init__(self, name, *args, **kwargs ): """privateDirs: list of private directories""" - + self.name = name self.privateDirs = kwargs.pop( 'privateDirs', [] ) - Host.__init__( self, *args, **kwargs ) + Host.__init__( self, name, *args, **kwargs ) self.mountPrivateDirs() def mountPrivateDirs( self ): + "mount the directories that have specified mountpoints" + for directory in self.privateDirs: + if isinstance( directory, tuple ): + 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: + self.cmd( 'mkdir -p %s' %directory ) + self.cmd( 'mount -n -t tmpfs tmpfs %s' %directory ) + + + def mountTempDirs( self ): "Mount tmpfs for each private directory" for dir_ in self.privateDirs: self.cmd( 'mkdir -p ' + dir_ ) diff --git a/mnexec.c b/mnexec.c index fee3d25..8f6830e 100644 --- a/mnexec.c +++ b/mnexec.c @@ -133,6 +133,7 @@ int main(int argc, char *argv[]) perror("mount"); return 1; } + break; case 'p': /* print pid */ @@ -140,7 +141,7 @@ int main(int argc, char *argv[]) fflush(stdout); break; case 'a': - /* Attach to pid's network namespace */ + /* Attach to pid's network namespace and mount namespace*/ pid = atoi(optarg); sprintf(path, "/proc/%d/ns/net", pid ); nsid = open(path, O_RDONLY); @@ -152,6 +153,18 @@ int main(int argc, char *argv[]) perror("setns"); 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; case 'g': /* Attach to cgroup */ From 752c2d6e7cbe1b8bde132677cec317b169266dd6 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Fri, 20 Jun 2014 23:54:18 -0700 Subject: [PATCH 3/6] mountprivatedirs is no longer needed --- examples/bind.py | 8 +++++++- mininet/node.py | 7 ------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/bind.py b/examples/bind.py index c2e7902..22eb3a2 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -27,7 +27,13 @@ def testHostWithPrivateDirs(): privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) net.start() - info( 'Private Directories:', privateDirs, '\n' ) + info( 'private Directories: [ ' ) + for directory in privateDirs: + if isinstance( directory, tuple ): + info( '%s, ' %directory[0] ) + else: + info( '%s, ' %directory ) + info( ']\n' ) CLI( net ) net.stop() diff --git a/mininet/node.py b/mininet/node.py index 9d5516c..a2deb44 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -749,13 +749,6 @@ class HostWithPrivateDirs( Host ): self.cmd( 'mount -n -t tmpfs tmpfs %s' %directory ) - def mountTempDirs( self ): - "Mount tmpfs for each private directory" - for dir_ in self.privateDirs: - self.cmd( 'mkdir -p ' + dir_ ) - self.cmd( 'mount -n -t tmpfs tmpfs %s' % dir_ ) - - # Some important things to note: # From af2f67d98c337ff5b8055b52d55d2153b68ce3e0 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Thu, 26 Jun 2014 09:29:06 -0700 Subject: [PATCH 4/6] added documentation for HostWithPrivateDirs --- examples/bind.py | 65 +++++++++++++++++++++++++++++++++++++++--------- mininet/node.py | 13 +++++++--- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/examples/bind.py b/examples/bind.py index 22eb3a2..9eb027e 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -1,9 +1,38 @@ #!/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 runs TWO mininet instances, one after the other. +The first mounts persistent private directories on /var/log and +/var/run. The second mounts temporary private directories on the same +directories. """ from mininet.net import Mininet @@ -15,33 +44,45 @@ from mininet.log import setLogLevel, info, debug from functools import partial -# Sample usage +# Persistent directory sample usage -def testHostWithPrivateDirs(): +def testPersistentPrivateDirs(): "Test bind mounts" topo = SingleSwitchTopo( 10 ) - privateDirs = [ ( '/var/log', '/onos/%(name)s/var/log' ), - ( '/var/run', '/ovx/%(name)s/var/run' ), - '/mn' ] + privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ), + ( '/var/run', '/tmp/%(name)s/var/run' ) ] host = partial( HostWithPrivateDirs, privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) net.start() - info( 'private Directories: [ ' ) + info( 'Private Directories: [ ' ) for directory in privateDirs: if isinstance( directory, tuple ): - info( '%s, ' %directory[0] ) + info( '%s, ' %directory[ 0 ] ) else: info( '%s, ' %directory ) info( ']\n' ) CLI( net ) net.stop() +# Temporary directory sample usage + +def testTempPrivateDirs(): + "test bind mounts with temporary directories" + topo = SingleSwitchTopo( 10 ) + privateDirs = [ '/var/log', '/var/run' ] + host = partial( HostWithPrivateDirs, + privateDirs=privateDirs ) + net = Mininet( topo=topo, host=host ) + net.start() + info( 'Private Directories: ', privateDirs, '\n' ) + CLI( net ) + net.stop() if __name__ == '__main__': setLogLevel( 'info' ) - testHostWithPrivateDirs() - info( 'Done.\n') - + testPersistentPrivateDirs() + testTempPrivateDirs() + info( 'Done.\n' ) diff --git a/mininet/node.py b/mininet/node.py index a2deb44..bdf7642 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -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 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. UserSwitch: a switch using the user-space switch from the OpenFlow @@ -728,23 +733,25 @@ class CPULimitedHost( Host ): class HostWithPrivateDirs( Host ): "Host with private directories" - def __init__(self, name, *args, **kwargs ): - """privateDirs: list of 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 the directories that have specified mountpoints" + "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 ) From 40a4a25dd8585789ed7d75fcbb0ef3bd526e7928 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Tue, 1 Jul 2014 14:51:07 -0700 Subject: [PATCH 5/6] use a single mininet instance in bindpy --- examples/bind.py | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/examples/bind.py b/examples/bind.py index 9eb027e..287c97a 100755 --- a/examples/bind.py +++ b/examples/bind.py @@ -29,10 +29,9 @@ have a temporary private directory mounted on "/var/log". [ ( '/var/run', '/tmp/%(name)s/var/run' ), '/var/log' ] -This example runs TWO mininet instances, one after the other. -The first mounts persistent private directories on /var/log and -/var/run. The second mounts temporary private directories on the same -directories. +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 @@ -44,45 +43,30 @@ from mininet.log import setLogLevel, info, debug from functools import partial -# Persistent directory sample usage +# Sample usage -def testPersistentPrivateDirs(): +def testHostWithPrivateDirs(): "Test bind mounts" topo = SingleSwitchTopo( 10 ) privateDirs = [ ( '/var/log', '/tmp/%(name)s/var/log' ), - ( '/var/run', '/tmp/%(name)s/var/run' ) ] + ( '/var/run', '/tmp/%(name)s/var/run' ), + '/var/mn' ] host = partial( HostWithPrivateDirs, privateDirs=privateDirs ) net = Mininet( topo=topo, host=host ) net.start() - info( 'Private Directories: [ ' ) + directories = [] for directory in privateDirs: - if isinstance( directory, tuple ): - info( '%s, ' %directory[ 0 ] ) - else: - info( '%s, ' %directory ) - info( ']\n' ) - CLI( net ) - net.stop() - -# Temporary directory sample usage - -def testTempPrivateDirs(): - "test bind mounts with temporary directories" - topo = SingleSwitchTopo( 10 ) - privateDirs = [ '/var/log', '/var/run' ] - host = partial( HostWithPrivateDirs, - privateDirs=privateDirs ) - net = Mininet( topo=topo, host=host ) - net.start() - info( 'Private Directories: ', privateDirs, '\n' ) + directories.append( directory[ 0 ] + if isinstance( directory, tuple ) + else directory ) + info( 'Private Directories:', directories, '\n' ) CLI( net ) net.stop() if __name__ == '__main__': setLogLevel( 'info' ) - testPersistentPrivateDirs() - testTempPrivateDirs() - info( 'Done.\n' ) + testHostWithPrivateDirs() + info( 'Done.\n') From 9c3ecfe338e361cbeaec18689c5632c372873794 Mon Sep 17 00:00:00 2001 From: Cody Burkard Date: Wed, 2 Jul 2014 10:53:41 -0700 Subject: [PATCH 6/6] conforming to mininet style --- mininet/node.py | 13 +++++++------ mnexec.c | 3 --- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/mininet/node.py b/mininet/node.py index bdf7642..72c782a 100644 --- a/mininet/node.py +++ b/mininet/node.py @@ -745,15 +745,16 @@ class HostWithPrivateDirs( Host ): for directory in self.privateDirs: if isinstance( directory, tuple ): # mount given private directory - privateDir = directory[ 1 ] %self.__dict__ + 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 ) ) + 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 ) + self.cmd( 'mkdir -p %s' % directory ) + self.cmd( 'mount -n -t tmpfs tmpfs %s' % directory ) diff --git a/mnexec.c b/mnexec.c index 8f6830e..c7103d4 100644 --- a/mnexec.c +++ b/mnexec.c @@ -133,7 +133,6 @@ int main(int argc, char *argv[]) perror("mount"); return 1; } - break; case 'p': /* print pid */ @@ -163,8 +162,6 @@ int main(int argc, char *argv[]) perror("setns"); return 1; } - - break; case 'g': /* Attach to cgroup */