From 94954177e5fc524aa35c7986acc402ae01087e09 Mon Sep 17 00:00:00 2001
From: Bob Lantz <rlantz@cs.stanford.edu>
Date: Wed, 21 Aug 2013 21:18:11 -0700
Subject: [PATCH] Added support for creating a volume rather than a raw
 partition.

---
 util/vm/build.py | 124 +++++++++++++++++++++++++++++++++++------------
 1 file changed, 92 insertions(+), 32 deletions(-)

diff --git a/util/vm/build.py b/util/vm/build.py
index eb54c947..9accde19 100755
--- a/util/vm/build.py
+++ b/util/vm/build.py
@@ -41,22 +41,41 @@
 Maybe download the cloud image and customize it so that
 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
 from os import stat
 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 glob import glob
 from urllib import urlretrieve
 from subprocess import check_output, call, Popen, PIPE
-from tempfile import mkdtemp, NamedTemporaryFile
+from tempfile import mkdtemp
 from time import time
 import argparse
 
+pexpect = None  # For code check - imported dynamically
+
+
 # boot can be slooooow!!!! need to debug/optimize somehow
 TIMEOUT=600
 
@@ -87,7 +106,6 @@ def srun( cmd, **kwargs ):
 def depend():
     "Install packagedependencies"
     print '* Installing package dependencies'
-    packages = ( )
     run( 'sudo apt-get -y update' )
     run( 'sudo apt-get install -y'
          ' 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"
     if not path:
         path = imagePath( image )
-    tgz = path + '.tar.gz'
+    tgz = path + '.disk1.img'
     disk = path + '.img'
     kernel = path + '-vmlinuz-generic'
-    floppy = path + '-floppy'
     if exists( disk ) and exists( kernel ):
         print '* Found', disk, 'and', kernel
         # Detect race condition with multiple builds
@@ -213,8 +230,11 @@ def chroot( cmd ):
     srun( 'e2fsck -y ' + nbd )
 
 
-def connectCOWdevice( cow ):
-    "Attempt to connect a COW disk and return its nbd device"
+def attachNBD( cow, flags='' ):
+    """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 ',
     for i in range ( 0, 63 ):
         nbd = '/dev/nbd%d' % i
@@ -224,33 +244,70 @@ def connectCOWdevice( cow ):
             continue
         # Fails without -v for some annoying reason...
         print
-        srun( 'qemu-nbd -c %s %s' % ( nbd, cow ) )
+        srun( 'qemu-nbd %s -c %s %s' % ( flags, nbd, cow ) )
         return nbd
     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 )
 
 
-def makeCOWDisk( image ):
+def makeCOWDisk( image, dir='.' ):
     "Create new COW disk for image"
     disk, kernel = fetchImage( image )
-    cow = NamedTemporaryFile( prefix=image + '-', suffix='.qcow2',
-                              dir='.' ).name
+    cow = '%s/%s.qcow2' % ( dir, image )
     print '* Creating COW disk', cow
     run( 'qemu-img create -f qcow2 -b %s %s' % ( disk, cow ) )
     print '* Resizing COW disk and file system'
     run( 'qemu-img resize %s +8G' % cow )
     srun( 'modprobe nbd max-part=64')
-    nbd = connectCOWdevice( cow )
+    nbd = attachNBD( cow )
     srun( 'e2fsck -y ' + nbd )
     srun( 'resize2fs ' + nbd )
     addMininetUser( nbd )
-    disconnectCOWdevice( nbd )
+    detachNBD( nbd )
     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 ):
     """Boot qemu/kvm with a COW disk and local/user data store
        cow: COW disk path
@@ -276,7 +333,7 @@ def boot( cow, kernel, tap ):
             '-k en-us',
             '-kernel', kernel,
             '-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 )
     print '* STARTING VM'
     print cmd
@@ -315,7 +372,7 @@ def interact( vm ):
     vm.expect( prompt )
     print '* Testing Mininet'
     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'
     else:
         print '* Sanity check FAILED'
@@ -343,34 +400,37 @@ def cleanup():
 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 )
+    vmdk = basename + '.vmdk'
     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' ):
+def build( flavor='raring32server' ):
     "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 )
+    dir = mkdtemp( prefix=flavor + '-result-', dir='.' )
+    os.chdir( dir )
+    print '* Created working directory', dir
+    image, kernel = fetchImage( flavor )
+    volume = flavor + '.qcow2'
+    makeVolume( volume )
+    initPartition( image, volume )
+    print '* VM image for', flavor, 'created as', volume
+    logfile = open( flavor + '.log', 'w+' )
+    print '* Logging results to', abspath( logfile.name )
+    vm = boot( volume, kernel, logfile )
+    vm.logfile_read = logfile
+    interact( vm )
+    vmdk = convert( volume, basename=flavor )
     print '* Converted VM image stored as', vmdk
     end = time()
     elapsed = end - start
-    print '* Results logged to', logfile.name
+    print '* Results logged to', abspath( logfile.name )
     print '* Completed in %.2f seconds' % elapsed
     print '* %s VM build DONE!!!!! :D' % flavor
     print
+    os.chdir( '..' )
 
 
 def parseArgs():
-- 
GitLab