Many more improvements.

This commit is contained in:
Bob Lantz
2013-08-20 13:31:15 -07:00
parent 860bcc02c6
commit 85dfac5c06
+133 -55
View File
@@ -46,13 +46,16 @@ it is an actual usable/bootable image???
import os import os
from os import stat
from stat import ST_MODE
from os.path import exists, splitext from os.path import exists, splitext
from sys import exit, argv
from glob import glob from glob import glob
from urllib import urlretrieve from urllib import urlretrieve
from subprocess import check_output, call, Popen, PIPE from subprocess import check_output, call, Popen, PIPE
from tempfile import mkdtemp, NamedTemporaryFile from tempfile import mkdtemp, NamedTemporaryFile
from sys import exit
from time import time from time import time
import argparse
# boot can be slooooow!!!! need to debug/optimize somehow # boot can be slooooow!!!! need to debug/optimize somehow
TIMEOUT=600 TIMEOUT=600
@@ -60,7 +63,10 @@ TIMEOUT=600
VMImageDir = os.environ[ 'HOME' ] + '/vm-images' VMImageDir = os.environ[ 'HOME' ] + '/vm-images'
ImageURLBase = { ImageURLBase = {
'raring-server-amd64': 'raring32server':
'http://cloud-images.ubuntu.com/raring/current/'
'raring-server-cloudimg-i386',
'raring64server':
'http://cloud-images.ubuntu.com/raring/current/' 'http://cloud-images.ubuntu.com/raring/current/'
'raring-server-cloudimg-amd64' 'raring-server-cloudimg-amd64'
} }
@@ -78,7 +84,8 @@ def srun( cmd, **kwargs ):
return run( 'sudo ' + cmd, **kwargs ) return run( 'sudo ' + cmd, **kwargs )
# Install necessary packages def depend():
"Install packagedependencies"
print '* Installing package dependencies' print '* Installing package dependencies'
packages = ( ) packages = ( )
run( 'sudo apt-get -y update' ) run( 'sudo apt-get -y update' )
@@ -88,7 +95,6 @@ run( 'sudo apt-get install -y'
' landscape-client' ' landscape-client'
' python-setuptools' ) ' python-setuptools' )
run( 'sudo easy_install pexpect' ) run( 'sudo easy_install pexpect' )
import pexpect
def popen( cmd ): def popen( cmd ):
@@ -127,6 +133,11 @@ def fetchImage( image, path=None ):
floppy = path + '-floppy' floppy = path + '-floppy'
if exists( disk ) and exists( kernel ): if exists( disk ) and exists( kernel ):
print '* Found', disk, 'and', kernel print '* Found', disk, 'and', kernel
# Detect race condition with multiple builds
perms = stat( disk )[ ST_MODE ] & 0777
if perms != 0444:
raise Exception( 'Error - %s is writable ' % disk +
'; are multiple builds running?' )
else: else:
dir = os.path.dirname( path ) dir = os.path.dirname( path )
run( 'mkdir -p %s' % dir ) run( 'mkdir -p %s' % dir )
@@ -136,10 +147,7 @@ def fetchImage( image, path=None ):
urlretrieve( url, tgz ) urlretrieve( url, tgz )
print '* Extracting', tgz print '* Extracting', tgz
run( 'tar -C %s -xzf %s' % ( dir, tgz ) ) run( 'tar -C %s -xzf %s' % ( dir, tgz ) )
# Make sure Mininet user is there # Write-protect disk image so it remains pristine;
os.chmod( disk, 0664 )
addMininetUser( disk )
# Write-protect disk image so it remains somewhat pristine;
# We will not use it directly but will use a COW disk # We will not use it directly but will use a COW disk
print '* Write-protecting disk image', disk print '* Write-protecting disk image', disk
os.chmod( disk, 0444 ) os.chmod( disk, 0444 )
@@ -162,14 +170,14 @@ def disableCloud( bind ):
call( 'echo manual | sudo tee ' + override, shell=True ) call( 'echo manual | sudo tee ' + override, shell=True )
def addMininetUser( img ): def addMininetUser( nbd ):
"Add mininet user/group to root filesystem image" "Add mininet user/group to filesystem"
print '* Adding mininet user to', img print '* Adding mininet user to filesystem on device', nbd
# 1. We bind-mount / into a temporary directory, and # 1. We bind-mount / into a temporary directory, and
# then mount the volume's /etc and /home on top of it! # then mount the volume's /etc and /home on top of it!
mnt = mkdtemp() mnt = mkdtemp()
bind = mkdtemp() bind = mkdtemp()
srun( 'mount %s %s' % ( img, mnt ) ) srun( 'mount %s %s' % ( nbd, mnt ) )
srun( 'mount -B / ' + bind ) srun( 'mount -B / ' + bind )
srun( 'mount -B %s/etc %s/etc' % ( mnt, bind ) ) srun( 'mount -B %s/etc %s/etc' % ( mnt, bind ) )
srun( 'mount -B %s/home %s/home' % ( mnt, bind ) ) srun( 'mount -B %s/home %s/home' % ( mnt, bind ) )
@@ -202,66 +210,79 @@ def addMininetUser( img ):
run( 'rmdir ' + bind ) run( 'rmdir ' + bind )
run( 'rmdir ' + mnt ) run( 'rmdir ' + mnt )
# 4. Just to make sure, we check the filesystem # 4. Just to make sure, we check the filesystem
run( 'e2fsck -y ' + img ) srun( 'e2fsck -y ' + nbd )
def connectCOWdevice( cow ):
"Attempt to connect a COW disk and return its nbd device"
print '* Checking for unused /dev/nbdX device ',
for i in range ( 0, 63 ):
nbd = '/dev/nbd%d' % i
print i,
# Check whether someone's already messing with that device
if call( [ 'pgrep', '-f', nbd ] ) == 0:
continue
# Fails without -v for some annoying reason...
print
srun( 'qemu-nbd -c %s %s' % ( nbd, cow ) )
return nbd
raise Exception( "Error: could not find unused /dev/nbdX device" )
def disconnectCOWdevice( nbd ):
srun( 'qemu-nbd -d ' + nbd )
def makeCOWDisk( image ): def makeCOWDisk( image ):
"Create new COW disk for image" "Create new COW disk for image"
disk, kernel = fetchImage( image ) disk, kernel = fetchImage( image )
cow = disk + '.qcow2' cow = NamedTemporaryFile( prefix=image + '-', suffix='.qcow2',
dir='.' ).name
print '* Creating COW disk', cow print '* Creating COW disk', cow
remove( cow )
run( 'qemu-img create -f qcow2 -b %s %s' % ( disk, cow ) ) run( 'qemu-img create -f qcow2 -b %s %s' % ( disk, cow ) )
print '* Resizing COW disk and file system' print '* Resizing COW disk and file system'
run( 'qemu-img resize %s +8G' % cow ) run( 'qemu-img resize %s +8G' % cow )
srun( 'modprobe nbd') srun( 'modprobe nbd max-part=64')
# Ideally we should check if it's being used... nbd = connectCOWdevice( cow )
srun( 'qemu-nbd -d /dev/nbd0' ) srun( 'e2fsck -y ' + nbd )
srun( 'qemu-nbd -c /dev/nbd0 ' + cow ) srun( 'resize2fs ' + nbd )
srun( 'e2fsck -fy /dev/nbd0' ) addMininetUser( nbd )
srun( 'resize2fs /dev/nbd0' ) disconnectCOWdevice( nbd )
srun( 'qemu-nbd -d /dev/nbd0' )
return cow, kernel return cow, kernel
def boot( cow, kernel ): def boot( cow, kernel, tap ):
"""Boot qemu/kvm with a COW disk and local/user data store """Boot qemu/kvm with a COW disk and local/user data store
returns: popen object to qemu process""" cow: COW disk path
if 'amd64' in cow: kernel: kernel path
tap: tap device to connect to VM
returns: pexpect object to qemu process"""
# pexpect might not be installed until after depend() is called
global pexpect
import pexpect
if 'amd64' in kernel:
kvm = 'qemu-system-x86_64' kvm = 'qemu-system-x86_64'
elif 'i386' in cow: elif 'i386' in kernel:
kvm = 'qemu-system-i386' kvm = 'qemu-system-i386'
else: else:
print "Error: can't discern CPU for image", cow print "Error: can't discern CPU for image", cow
exit exit( 1 )
# was -nographic
# was -net=%net
# ' -net nic,model=virtio'
# ' -netdev tap,id=mininet0,ifname=%s,script=no ' % tap +
cmd = [ 'sudo', kvm, cmd = [ 'sudo', kvm,
'-machine', 'accel=kvm', '-machine accel=kvm',
'-nographic', '-nographic',
'-m', '512', '-netdev user,id=mnbuild',
'-k', 'en-us', '-device virtio-net,netdev=mnbuild',
'-m 1024',
'-k en-us',
'-kernel', kernel, '-kernel', kernel,
'-drive', 'file=%s,if=virtio' % cow, '-drive file=%s,if=virtio' % cow,
'-append', '-append "root=/dev/vda init=/sbin/init console=ttyS0" ' ]
' "root=/dev/vda'
' init=/sbin/init'
# ' init=/usr/lib/cloud-init/uncloud-init'
# ' ds=nocloud'
# ' --verbose'
' console=ttyS0"'
]
cmd = ' '.join( cmd ) cmd = ' '.join( cmd )
print '* STARTING VM' print '* STARTING VM'
print cmd print cmd
vm = pexpect.spawn( cmd, timeout=TIMEOUT ) vm = pexpect.spawn( cmd, timeout=TIMEOUT )
logfile = NamedTemporaryFile( prefix='mn-build-expect' ) return vm
print '* Logging results to', logfile.name
vm.logfile_read = logfile
return vm, logfile
def interact( vm ): def interact( vm ):
"Interact with vm, which is a pexpect object" "Interact with vm, which is a pexpect object"
@@ -288,7 +309,8 @@ def interact( vm ):
print '* Running VM install script' print '* Running VM install script'
vm.sendline( 'bash install-mininet-vm.sh' ) vm.sendline( 'bash install-mininet-vm.sh' )
print '* Waiting for script to complete... ' print '* Waiting for script to complete... '
vm.expect( 'Done preparing Mininet' ) # Gigantic timeout for now ;-(
vm.expect( 'Done preparing Mininet', timeout=3600 )
print '* Completed successfully' print '* Completed successfully'
vm.expect( prompt ) vm.expect( prompt )
print '* Testing Mininet' print '* Testing Mininet'
@@ -312,18 +334,74 @@ def interact( vm ):
vm.read() vm.read()
print '* Interaction complete' print '* Interaction complete'
image = 'raring-server-amd64'
def cleanup():
"Clean up leftover qemu-nbd processes and other junk"
call( 'sudo pkill -9 qemu-nbd', shell=True )
def convert( cow, basename ):
"""Convert a qcow2 disk to a vmdk and put it a new directory
basename: base name for output vmdk file"""
dir = mkdtemp( prefix=basename, dir='.' )
vmdk = '%s/%s.vmdk' % ( dir, basename )
print '* Converting qcow2 to vmdk'
run( 'qemu-img convert -f qcow2 -O vmdk %s %s' % ( cow, vmdk ) )
return vmdk
def build( flavor='raring-server-amd64' ):
"Build a Mininet VM"
start = time() start = time()
cow, kernel = makeCOWDisk( image ) cow, kernel = makeCOWDisk( flavor )
print '* VM image for', image, 'created as', cow print '* VM image for', flavor, 'created as', cow
vm, logfile = boot( cow, kernel ) with NamedTemporaryFile(
prefix='mn-build-%s-' % flavor, suffix='.log', dir='.' ) as logfile:
print '* Logging results to', logfile.name
vm = boot( cow, kernel, logfile )
vm.logfile_read = logfile
interact( vm ) interact( vm )
logfile.close() # cow is a temporary file and will go away when we quit!
# We convert it to a .vmdk which can be used in most VMMs
vmdk = convert( cow, basename=flavor )
print '* Converted VM image stored as', vmdk
end = time() end = time()
elapsed = end - start elapsed = end - start
print '* Results logged to', logfile.name print '* Results logged to', logfile.name
print '* Completed in %.2f seconds' % elapsed print '* Completed in %.2f seconds' % elapsed
print '* DONE!!!!! :D' print '* %s VM build DONE!!!!! :D' % flavor
print
def parseArgs():
"Parse command line arguments and run"
parser = argparse.ArgumentParser( description='Mininet VM build script' )
parser.add_argument( '--depend', action='store_true',
help='Install dependencies for this script' )
parser.add_argument( '--list', action='store_true',
help='list valid build flavors' )
parser.add_argument( '--clean', action='store_true',
help='clean up leftover build junk (e.g. qemu-nbd)' )
parser.add_argument( 'flavor', nargs='*',
help='VM flavor to build (e.g. raring32server)' )
args = parser.parse_args( argv )
if args.depend:
depend()
if args.list:
print 'valid build flavors:', ' '.join( ImageURLBase )
if args.clean:
cleanup()
flavors = args.flavor[ 1: ]
for flavor in flavors:
if flavor not in ImageURLBase:
parser.print_help()
# try:
build( flavor )
# except Exception as e:
# print '* BUILD FAILED with exception: ', e
# exit( 1 )
if not ( args.depend or args.list or args.clean or flavors ):
parser.print_help()
if __name__ == '__main__':
parseArgs()