Added support for creating a volume rather than a raw partition.
This commit is contained in:
+92
-32
@@ -41,22 +41,41 @@ Something to think about:
|
|||||||
Maybe download the cloud image and customize it so that
|
Maybe download the cloud image and customize it so that
|
||||||
it is an actual usable/bootable image???
|
it is an actual usable/bootable image???
|
||||||
|
|
||||||
|
More notes:
|
||||||
|
|
||||||
|
We really want a full, partitioned disk image!
|
||||||
|
|
||||||
|
This means we want to use the disk1.image file ???
|
||||||
|
|
||||||
|
However, this means that we will need to change the grub2
|
||||||
|
configuratin to use a serial console.
|
||||||
|
|
||||||
|
/etc/default/grub:
|
||||||
|
GRUB_TERMINAL=serial
|
||||||
|
GRUB_SERIAL_COMMAND="serial --unit=0 --speed=38400 --word=8 --parity=no --stop=1"
|
||||||
|
BOOT_IMAGE="console=ttyS0"
|
||||||
|
|
||||||
|
# grub2-mkconfig -o /boot/grub2/grub.cfg
|
||||||
|
|
||||||
|
by the way, we should use wget -c
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from os import stat
|
from os import stat
|
||||||
from stat import ST_MODE
|
from stat import ST_MODE
|
||||||
from os.path import exists, splitext
|
from os.path import exists, splitext, abspath, realpath
|
||||||
from sys import exit, argv
|
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
|
||||||
from time import time
|
from time import time
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
pexpect = None # For code check - imported dynamically
|
||||||
|
|
||||||
|
|
||||||
# boot can be slooooow!!!! need to debug/optimize somehow
|
# boot can be slooooow!!!! need to debug/optimize somehow
|
||||||
TIMEOUT=600
|
TIMEOUT=600
|
||||||
|
|
||||||
@@ -87,7 +106,6 @@ def srun( cmd, **kwargs ):
|
|||||||
def depend():
|
def depend():
|
||||||
"Install packagedependencies"
|
"Install packagedependencies"
|
||||||
print '* Installing package dependencies'
|
print '* Installing package dependencies'
|
||||||
packages = ( )
|
|
||||||
run( 'sudo apt-get -y update' )
|
run( 'sudo apt-get -y update' )
|
||||||
run( 'sudo apt-get install -y'
|
run( 'sudo apt-get install -y'
|
||||||
' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
|
' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
|
||||||
@@ -127,10 +145,9 @@ def fetchImage( image, path=None ):
|
|||||||
"Fetch base VM image if it's not there already"
|
"Fetch base VM image if it's not there already"
|
||||||
if not path:
|
if not path:
|
||||||
path = imagePath( image )
|
path = imagePath( image )
|
||||||
tgz = path + '.tar.gz'
|
tgz = path + '.disk1.img'
|
||||||
disk = path + '.img'
|
disk = path + '.img'
|
||||||
kernel = path + '-vmlinuz-generic'
|
kernel = path + '-vmlinuz-generic'
|
||||||
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
|
# Detect race condition with multiple builds
|
||||||
@@ -213,8 +230,11 @@ def addMininetUser( nbd ):
|
|||||||
srun( 'e2fsck -y ' + nbd )
|
srun( 'e2fsck -y ' + nbd )
|
||||||
|
|
||||||
|
|
||||||
def connectCOWdevice( cow ):
|
def attachNBD( cow, flags='' ):
|
||||||
"Attempt to connect a COW disk and return its nbd device"
|
"""Attempt to attach a COW disk image and return its nbd device
|
||||||
|
flags: additional flags for qemu-nbd (e.g. -r for readonly)"""
|
||||||
|
# qemu-nbd requires an absolute path
|
||||||
|
cow = abspath( cow )
|
||||||
print '* Checking for unused /dev/nbdX device ',
|
print '* Checking for unused /dev/nbdX device ',
|
||||||
for i in range ( 0, 63 ):
|
for i in range ( 0, 63 ):
|
||||||
nbd = '/dev/nbd%d' % i
|
nbd = '/dev/nbd%d' % i
|
||||||
@@ -224,33 +244,70 @@ def connectCOWdevice( cow ):
|
|||||||
continue
|
continue
|
||||||
# Fails without -v for some annoying reason...
|
# Fails without -v for some annoying reason...
|
||||||
print
|
print
|
||||||
srun( 'qemu-nbd -c %s %s' % ( nbd, cow ) )
|
srun( 'qemu-nbd %s -c %s %s' % ( flags, nbd, cow ) )
|
||||||
return nbd
|
return nbd
|
||||||
raise Exception( "Error: could not find unused /dev/nbdX device" )
|
raise Exception( "Error: could not find unused /dev/nbdX device" )
|
||||||
|
|
||||||
|
|
||||||
def disconnectCOWdevice( nbd ):
|
def detachNBD( nbd ):
|
||||||
|
"Detatch an nbd device"
|
||||||
srun( 'qemu-nbd -d ' + nbd )
|
srun( 'qemu-nbd -d ' + nbd )
|
||||||
|
|
||||||
|
|
||||||
def makeCOWDisk( image ):
|
def makeCOWDisk( image, dir='.' ):
|
||||||
"Create new COW disk for image"
|
"Create new COW disk for image"
|
||||||
disk, kernel = fetchImage( image )
|
disk, kernel = fetchImage( image )
|
||||||
cow = NamedTemporaryFile( prefix=image + '-', suffix='.qcow2',
|
cow = '%s/%s.qcow2' % ( dir, image )
|
||||||
dir='.' ).name
|
|
||||||
print '* Creating COW disk', cow
|
print '* Creating COW disk', 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 max-part=64')
|
srun( 'modprobe nbd max-part=64')
|
||||||
nbd = connectCOWdevice( cow )
|
nbd = attachNBD( cow )
|
||||||
srun( 'e2fsck -y ' + nbd )
|
srun( 'e2fsck -y ' + nbd )
|
||||||
srun( 'resize2fs ' + nbd )
|
srun( 'resize2fs ' + nbd )
|
||||||
addMininetUser( nbd )
|
addMininetUser( nbd )
|
||||||
disconnectCOWdevice( nbd )
|
detachNBD( nbd )
|
||||||
return cow, kernel
|
return cow, kernel
|
||||||
|
|
||||||
|
|
||||||
|
def makeVolume( volume, cylinders=1000 ):
|
||||||
|
"""Create volume as a qcow2 and add a single boot partition
|
||||||
|
cylinders: number of ~8MB (255*63*512) cylinders in volume"""
|
||||||
|
heads, sectors, bytes = 255, 63, 512
|
||||||
|
size = cylinders * heads * sectors * bytes
|
||||||
|
print '* Creating volume of size', size
|
||||||
|
run( 'qemu-img create -f qcow2 %s %s' % ( volume, size ) )
|
||||||
|
print '* Partitioning volume'
|
||||||
|
# We need to mount it using qemu-nbd!!
|
||||||
|
nbd = attachNBD( volume )
|
||||||
|
# A bit hacky - we may change this to use parted(8) later
|
||||||
|
fdisk = Popen( [ 'sudo', 'fdisk', nbd ], stdin=PIPE )
|
||||||
|
cmds = 'x\nc\n%d\nr\no\nn\np\n1\n\n\na\n1\nw\n' % cylinders
|
||||||
|
fdisk.stdin.write( cmds )
|
||||||
|
fdisk.wait()
|
||||||
|
print '* Volume partition table:'
|
||||||
|
print srun( 'fdisk -l ' + nbd )
|
||||||
|
detachNBD( nbd )
|
||||||
|
|
||||||
|
|
||||||
|
def initPartition( partition, volume ):
|
||||||
|
"""Copy partition to volume-p1 and call addMininetUser"""
|
||||||
|
srcdev = attachNBD( partition, flags='-r' )
|
||||||
|
voldev = attachNBD( volume )
|
||||||
|
print srun( 'fdisk -l ' + voldev )
|
||||||
|
print srun( 'partx ' + voldev )
|
||||||
|
dstdev = voldev + 'p1'
|
||||||
|
print "* Copying partition from", srcdev, "to", dstdev
|
||||||
|
print srun( 'time dd if=%s of=%s bs=1M' % ( srcdev, dstdev ) )
|
||||||
|
print '* Resizing and adding Mininet user'
|
||||||
|
srun( 'resize2fs ' + dstdev )
|
||||||
|
srun( 'e2fsck -y ' + dstdev )
|
||||||
|
addMininetUser( dstdev )
|
||||||
|
detachNBD( voldev )
|
||||||
|
detachNBD( srcdev )
|
||||||
|
|
||||||
|
|
||||||
def boot( cow, kernel, tap ):
|
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
|
||||||
cow: COW disk path
|
cow: COW disk path
|
||||||
@@ -276,7 +333,7 @@ def boot( cow, kernel, tap ):
|
|||||||
'-k en-us',
|
'-k en-us',
|
||||||
'-kernel', kernel,
|
'-kernel', kernel,
|
||||||
'-drive file=%s,if=virtio' % cow,
|
'-drive file=%s,if=virtio' % cow,
|
||||||
'-append "root=/dev/vda init=/sbin/init console=ttyS0" ' ]
|
'-append "root=/dev/vda1 init=/sbin/init console=ttyS0" ' ]
|
||||||
cmd = ' '.join( cmd )
|
cmd = ' '.join( cmd )
|
||||||
print '* STARTING VM'
|
print '* STARTING VM'
|
||||||
print cmd
|
print cmd
|
||||||
@@ -315,7 +372,7 @@ def interact( vm ):
|
|||||||
vm.expect( prompt )
|
vm.expect( prompt )
|
||||||
print '* Testing Mininet'
|
print '* Testing Mininet'
|
||||||
vm.sendline( 'sudo mn --test pingall' )
|
vm.sendline( 'sudo mn --test pingall' )
|
||||||
if vm.expect( [ ' 0% dropped', pexpect.TIMEOUT ], timeout=30 ):
|
if vm.expect( [ ' 0% dropped', pexpect.TIMEOUT ], timeout=45 ):
|
||||||
print '* Sanity check succeeded'
|
print '* Sanity check succeeded'
|
||||||
else:
|
else:
|
||||||
print '* Sanity check FAILED'
|
print '* Sanity check FAILED'
|
||||||
@@ -343,34 +400,37 @@ def cleanup():
|
|||||||
def convert( cow, basename ):
|
def convert( cow, basename ):
|
||||||
"""Convert a qcow2 disk to a vmdk and put it a new directory
|
"""Convert a qcow2 disk to a vmdk and put it a new directory
|
||||||
basename: base name for output vmdk file"""
|
basename: base name for output vmdk file"""
|
||||||
dir = mkdtemp( prefix=basename, dir='.' )
|
vmdk = basename + '.vmdk'
|
||||||
vmdk = '%s/%s.vmdk' % ( dir, basename )
|
|
||||||
print '* Converting qcow2 to vmdk'
|
print '* Converting qcow2 to vmdk'
|
||||||
run( 'qemu-img convert -f qcow2 -O vmdk %s %s' % ( cow, vmdk ) )
|
run( 'qemu-img convert -f qcow2 -O vmdk %s %s' % ( cow, vmdk ) )
|
||||||
return vmdk
|
return vmdk
|
||||||
|
|
||||||
|
|
||||||
def build( flavor='raring-server-amd64' ):
|
def build( flavor='raring32server' ):
|
||||||
"Build a Mininet VM"
|
"Build a Mininet VM"
|
||||||
start = time()
|
start = time()
|
||||||
cow, kernel = makeCOWDisk( flavor )
|
dir = mkdtemp( prefix=flavor + '-result-', dir='.' )
|
||||||
print '* VM image for', flavor, 'created as', cow
|
os.chdir( dir )
|
||||||
with NamedTemporaryFile(
|
print '* Created working directory', dir
|
||||||
prefix='mn-build-%s-' % flavor, suffix='.log', dir='.' ) as logfile:
|
image, kernel = fetchImage( flavor )
|
||||||
print '* Logging results to', logfile.name
|
volume = flavor + '.qcow2'
|
||||||
vm = boot( cow, kernel, logfile )
|
makeVolume( volume )
|
||||||
vm.logfile_read = logfile
|
initPartition( image, volume )
|
||||||
interact( vm )
|
print '* VM image for', flavor, 'created as', volume
|
||||||
# cow is a temporary file and will go away when we quit!
|
logfile = open( flavor + '.log', 'w+' )
|
||||||
# We convert it to a .vmdk which can be used in most VMMs
|
print '* Logging results to', abspath( logfile.name )
|
||||||
vmdk = convert( cow, basename=flavor )
|
vm = boot( volume, kernel, logfile )
|
||||||
|
vm.logfile_read = logfile
|
||||||
|
interact( vm )
|
||||||
|
vmdk = convert( volume, basename=flavor )
|
||||||
print '* Converted VM image stored as', vmdk
|
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', abspath( logfile.name )
|
||||||
print '* Completed in %.2f seconds' % elapsed
|
print '* Completed in %.2f seconds' % elapsed
|
||||||
print '* %s VM build DONE!!!!! :D' % flavor
|
print '* %s VM build DONE!!!!! :D' % flavor
|
||||||
print
|
print
|
||||||
|
os.chdir( '..' )
|
||||||
|
|
||||||
|
|
||||||
def parseArgs():
|
def parseArgs():
|
||||||
|
|||||||
Reference in New Issue
Block a user