Many more improvements.
This commit is contained in:
+144
-66
@@ -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,17 +84,17 @@ def srun( cmd, **kwargs ):
|
|||||||
return run( 'sudo ' + cmd, **kwargs )
|
return run( 'sudo ' + cmd, **kwargs )
|
||||||
|
|
||||||
|
|
||||||
# Install necessary packages
|
def depend():
|
||||||
print '* Installing package dependencies'
|
"Install packagedependencies"
|
||||||
packages = ( )
|
print '* Installing package dependencies'
|
||||||
run( 'sudo apt-get -y update' )
|
packages = ( )
|
||||||
run( 'sudo apt-get install -y'
|
run( 'sudo apt-get -y update' )
|
||||||
|
run( 'sudo apt-get install -y'
|
||||||
' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
|
' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
|
||||||
' e2fsprogs '
|
' e2fsprogs '
|
||||||
' 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'
|
|
||||||
|
|
||||||
start = time()
|
def cleanup():
|
||||||
cow, kernel = makeCOWDisk( image )
|
"Clean up leftover qemu-nbd processes and other junk"
|
||||||
print '* VM image for', image, 'created as', cow
|
call( 'sudo pkill -9 qemu-nbd', shell=True )
|
||||||
vm, logfile = boot( cow, kernel )
|
|
||||||
interact( vm )
|
|
||||||
logfile.close()
|
|
||||||
end = time()
|
|
||||||
elapsed = end - start
|
|
||||||
print '* Results logged to', logfile.name
|
|
||||||
print '* Completed in %.2f seconds' % elapsed
|
|
||||||
print '* DONE!!!!! :D'
|
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
cow, kernel = makeCOWDisk( flavor )
|
||||||
|
print '* VM image for', flavor, 'created as', cow
|
||||||
|
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 )
|
||||||
|
# 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()
|
||||||
|
elapsed = end - start
|
||||||
|
print '* Results logged to', logfile.name
|
||||||
|
print '* Completed in %.2f seconds' % elapsed
|
||||||
|
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user