Revision d4279559 util/vm/build.py

View differences:

util/vm/build.py
30 30
from os import stat, path
31 31
from stat import ST_MODE
32 32
from os.path import abspath
33
from sys import exit, argv
33
from sys import exit, stdout
34
import re
34 35
from glob import glob
35 36
from subprocess import check_output, call, Popen
36 37
from tempfile import mkdtemp
......
42 43
# boot can be slooooow!!!! need to debug/optimize somehow
43 44
TIMEOUT=600
44 45

  
46
# Some configuration
47
LogToConsole = False        # VM output to console rather than log file
48
SaveQCOW2 = False           # Save QCOW2 image rather than deleting it
49

  
45 50
VMImageDir = os.environ[ 'HOME' ] + '/vm-images'
46 51

  
47 52
isoURLs = {
......
307 312
    return floppy, kickstart, preseed
308 313

  
309 314

  
310
def kvmFor( filepath ):
311
    "Guess kvm version for file path"
315
def archFor( filepath ):
316
    "Guess architecture for file path"
312 317
    name = path.basename( filepath )
313 318
    if '64' in name:
314
        kvm = 'qemu-system-x86_64'
319
        arch = 'x86_64'
315 320
    elif 'i386' in name or '32' in name:
316
        kvm = 'qemu-system-i386'
321
        arch = 'i386'
317 322
    else:
318 323
        log( "Error: can't discern CPU for file name", name )
319 324
        exit( 1 )
320
    return kvm
325
    return arch
321 326

  
322 327

  
323 328
def installUbuntu( iso, image, logfilename='install.log' ):
324 329
    "Install Ubuntu from iso onto image"
325
    kvm = kvmFor( iso )
330
    kvm = 'qemu-system-' + archFor( iso )
326 331
    floppy, kickstart, preseed = makeKickstartFloppy()
327 332
    # Mount iso so we can use its kernel
328 333
    mnt = mkdtemp()
......
349 354
    log( '* INSTALLING UBUNTU FROM', iso, 'ONTO', image )
350 355
    log( ' '.join( cmd ) )
351 356
    log( '* logging to', abspath( logfilename ) )
352
    logfile = open( logfilename, 'w' )
353
    vm = Popen( cmd, stdout=logfile, stderr=logfile )
357
    params = {}
358
    if not LogToConsole:
359
        logfile = open( logfilename, 'w' )
360
        params = { 'stdout': logfile, 'stderr': logfile }
361
    vm = Popen( cmd, **params )
354 362
    log( '* Waiting for installation to complete')
355 363
    vm.wait()
356
    logfile.close()
364
    if not LogToConsole:
365
        logfile.close()
357 366
    elapsed = time() - ubuntuStart
358 367
    # Unmount iso and clean up
359 368
    srun( 'umount ' + mnt )
......
374 383
    # pexpect might not be installed until after depend() is called
375 384
    global pexpect
376 385
    import pexpect
377
    kvm = kvmFor( kernel )
378
    cmd = [ 'sudo', kvm,
386
    arch = archFor( kernel )
387
    cmd = [ 'sudo', 'qemu-system-' + arch,
379 388
            '-machine accel=kvm',
380 389
            '-nographic',
381 390
            '-netdev user,id=mnbuild',
......
478 487
    <domain>
479 488
        <boot type="hvm">
480 489
            <guest>
481
                <arch>%s/arch>
490
                <arch>%s<arch>
482 491
            </guest>
483 492
            <os>
484 493
                <loader dev="hd"/>
......
498 507
</image>
499 508
"""
500 509

  
510

  
501 511
def genVirtImage( name, mem, diskname, disksize ):
502 512
    "Generate and return virt-image file name.xml"
503 513
    # Our strategy is going to be: create a
504 514
    # virt-image file and then use virt-convert to convert
505 515
    # it to an .ovf file
506 516
    xmlfile = name + '.xml'
507
    xmltext = VirtImageXML % ( name, mem, diskname, disksize )
517
    arch = archFor( name )
518
    xmltext = VirtImageXML % ( name, arch, mem, diskname, disksize )
508 519
    with open( xmlfile, 'w+' ) as f:
509 520
        f.write( xmltext )
510 521
    return xmlfile
511 522

  
512 523

  
524
def qcow2size( qcow2 ):
525
    "Return virtual disk size (in bytes) of qcow2 image"
526
    output = check_output( [ 'file', qcow2 ] )
527
    assert 'QCOW' in output
528
    bytes = int( re.findall( '(\d+) bytes', output )[ 0 ] )
529
    return bytes
530

  
531

  
513 532
def build( flavor='raring32server' ):
514
    "Build a Mininet VM"
533
    "Build a Mininet VM; return vmdk and vdisk size"
515 534
    global LogFile
516 535
    start = time()
517 536
    date = strftime( '%y%m%d-%H-%M-%S', localtime())
......
528 547
    volume = flavor + '.qcow2'
529 548
    run( 'qemu-img create -f qcow2 -b %s %s' % ( image, volume ) )
530 549
    log( '* VM image for', flavor, 'created as', volume )
531
    logfile = open( flavor + '.log', 'w+' )
550
    if LogToConsole:
551
        logfile = stdout
552
    else:
553
        logfile = open( flavor + '.log', 'w+' )
532 554
    log( '* Logging results to', abspath( logfile.name ) )
533 555
    vm = boot( volume, kernel, initrd, logfile )
534 556
    interact( vm )
557
    size = qcow2size( volume )
535 558
    vmdk = convert( volume, basename=flavor )
536
    log( '* Removing qcow2 volume', volume )
537
    os.remove( volume )
559
    if not SaveQCOW2:
560
        log( '* Removing qcow2 volume', volume )
561
        os.remove( volume )
538 562
    log( '* Converted VM image stored as', abspath( vmdk ) )
563
    vimage = genVirtImage( flavor, mem=512, diskname=vmdk, disksize=size )
564
    log( '* Generated virtimage file as', vimage )
539 565
    end = time()
540 566
    elapsed = end - start
541 567
    log( '* Results logged to', abspath( logfile.name ) )
......
543 569
    log( '* %s VM build DONE!!!!! :D' % flavor )
544 570
    os.chdir( '..' )
545 571

  
546

  
547
def listFlavors():
548
    "List valid build flavors"
549
    print '\nvalid build flavors:', ' '.join( isoURLs ), '\n'
572
def buildFlavorString():
573
    "Return string listing valid build flavors"
574
    return 'valid build flavors: %s' % ' '.join( sorted( isoURLs ) )
550 575

  
551 576

  
552 577
def parseArgs():
553 578
    "Parse command line arguments and run"
554
    parser = argparse.ArgumentParser( description='Mininet VM build script' )
555
    parser.add_argument( '--depend', action='store_true',
579
    global LogToConsole
580
    parser = argparse.ArgumentParser( description='Mininet VM build script',
581
                                      epilog=buildFlavorString() )
582
    parser.add_argument( '-v', '--verbose', action='store_true',
583
                        help='send VM output to console rather than log file' )
584
    parser.add_argument( '-d', '--depend', action='store_true',
556 585
                         help='install dependencies for this script' )
557
    parser.add_argument( '--list', action='store_true',
586
    parser.add_argument( '-l', '--list', action='store_true',
558 587
                         help='list valid build flavors' )
559
    parser.add_argument( '--clean', action='store_true',
588
    parser.add_argument( '-c', '--clean', action='store_true',
560 589
                         help='clean up leftover build junk (e.g. qemu-nbd)' )
590
    parser.add_argument( '-q', '--qcow2', action='store_true',
591
                         help='save qcow2 image rather than deleting it' )
561 592
    parser.add_argument( 'flavor', nargs='*',
562
                         help='VM flavor to build (e.g. raring32server)' )
563
    args = parser.parse_args( argv )
593
                         help='VM flavor(s) to build (e.g. raring32server)' )
594
    args = parser.parse_args()
564 595
    if args.depend:
565 596
        depend()
566 597
    if args.list:
567
        listFlavors()
598
        print buildFlavorString()
568 599
    if args.clean:
569 600
        cleanup()
570
    flavors = args.flavor[ 1: ]
571
    for flavor in flavors:
601
    if args.verbose:
602
        LogToConsole = True
603
    for flavor in args.flavor:
572 604
        if flavor not in isoURLs:
573
            parser.print_help()
574
            listFlavors()
605
            print "Unknown build flavor:", flavor
606
            print buildFlavorString()
575 607
            break
576 608
        # try:
577 609
        build( flavor )
578 610
        # except Exception as e:
579 611
        # log( '* BUILD FAILED with exception: ', e )
580 612
        # exit( 1 )
581
    if not ( args.depend or args.list or args.clean or flavors ):
613
    if not ( args.depend or args.list or args.clean or args.flavor ):
582 614
        parser.print_help()
583
        listFlavors()
615

  
584 616

  
585 617
if __name__ == '__main__':
586 618
    parseArgs()

Also available in: Unified diff