Statistics
| Branch: | Tag: | Revision:

mininet / util / vm / build.py @ 0038720c

History | View | Annotate | Download (22.7 KB)

1
#!/usr/bin/python
2

    
3
"""
4
build.py: build a Mininet VM
5

6
Basic idea:
7

8
    prepare
9
    -> create base install image if it's missing
10
        - download iso if it's missing
11
        - install from iso onto image
12

13
    build
14
    -> create cow disk for new VM, based on base image
15
    -> boot it in qemu/kvm with text /serial console
16
    -> install Mininet
17

18
    test
19
    -> sudo mn --test pingall
20
    -> make test
21

22
    release
23
    -> shut down VM
24
    -> shrink-wrap VM
25
    -> upload to storage
26

27
"""
28

    
29
import os
30
from os import stat, path
31
from stat import ST_MODE, ST_SIZE
32
from os.path import abspath
33
from sys import exit, stdout, argv
34
import re
35
from glob import glob
36
from subprocess import check_output, call, Popen
37
from tempfile import mkdtemp
38
from time import time, strftime, localtime
39
import argparse
40
from distutils.spawn import find_executable
41

    
42
pexpect = None  # For code check - imported dynamically
43

    
44
# boot can be slooooow!!!! need to debug/optimize somehow
45
TIMEOUT=600
46

    
47
# Some configuration
48
LogToConsole = False        # VM output to console rather than log file
49
SaveQCOW2 = False           # Save QCOW2 image rather than deleting it
50
NoKVM = False               # Don't use kvm and use emulation instead
51

    
52
VMImageDir = os.environ[ 'HOME' ] + '/vm-images'
53

    
54
isoURLs = {
55
    'precise32server':
56
    'http://mirrors.kernel.org/ubuntu-releases/12.04/'
57
    'ubuntu-12.04.3-server-i386.iso',
58
    'precise64server':
59
    'http://mirrors.kernel.org/ubuntu-releases/12.04/'
60
    'ubuntu-12.04.3-server-amd64.iso',
61
    'quantal32server':
62
    'http://mirrors.kernel.org/ubuntu-releases/12.10/'
63
    'ubuntu-12.10-server-i386.iso',
64
    'quantal64server':
65
    'http://mirrors.kernel.org/ubuntu-releases/12.10/'
66
    'ubuntu-12.10-server-amd64.iso',
67
    'raring32server':
68
    'http://mirrors.kernel.org/ubuntu-releases/13.04/'
69
    'ubuntu-13.04-server-i386.iso',
70
    'raring64server':
71
    'http://mirrors.kernel.org/ubuntu-releases/13.04/'
72
    'ubuntu-13.04-server-amd64.iso',
73
    'saucy32server':
74
    'http://mirrors.kernel.org/ubuntu-releases/13.10/'
75
    'ubuntu-13.10-server-i386.iso',
76
    'saucy64server':
77
    'http://mirrors.kernel.org/ubuntu-releases/13.10/'
78
    'ubuntu-13.10-server-amd64.iso',
79
}
80

    
81

    
82
LogStartTime = time()
83
LogFile = None
84

    
85
def log( *args, **kwargs ):
86
    """Simple log function: log( message along with local and elapsed time
87
       cr: False/0 for no CR"""
88
    cr = kwargs.get( 'cr', True )
89
    elapsed = time() - LogStartTime
90
    clocktime = strftime( '%H:%M:%S', localtime() )
91
    msg = ' '.join( str( arg ) for arg in args )
92
    output = '%s [ %.3f ] %s' % ( clocktime, elapsed, msg )
93
    if cr:
94
        print output
95
    else:
96
        print output,
97
    # Optionally mirror to LogFile
98
    if type( LogFile ) is file:
99
        if cr:
100
            output += '\n'
101
        LogFile.write( output )
102
        LogFile.flush()
103

    
104

    
105
def run( cmd, **kwargs ):
106
    "Convenient interface to check_output"
107
    log( '-', cmd )
108
    cmd = cmd.split()
109
    arg0 = cmd[ 0 ]
110
    if not find_executable( arg0 ):
111
        raise Exception( 'Cannot find executable "%s";' % arg0 +
112
                         'you might try %s --depend' % argv[ 0 ] )
113
    return check_output( cmd, **kwargs )
114

    
115

    
116
def srun( cmd, **kwargs ):
117
    "Run + sudo"
118
    return run( 'sudo ' + cmd, **kwargs )
119

    
120

    
121
# BL: we should probably have a "checkDepend()" which
122
# checks to make sure all dependencies are satisfied!
123

    
124
def depend():
125
    "Install package dependencies"
126
    log( '* Installing package dependencies' )
127
    run( 'sudo apt-get -y update' )
128
    run( 'sudo apt-get install -y'
129
         ' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
130
         ' e2fsprogs '
131
         ' landscape-client'
132
         ' python-setuptools mtools' )
133
    run( 'sudo easy_install pexpect' )
134

    
135

    
136
def popen( cmd ):
137
    "Convenient interface to popen"
138
    log( cmd )
139
    cmd = cmd.split()
140
    return Popen( cmd )
141

    
142

    
143
def remove( fname ):
144
    "rm -f fname"
145
    return run( 'rm -f %s' % fname )
146

    
147

    
148
def findiso( flavor ):
149
    "Find iso, fetching it if it's not there already"
150
    url = isoURLs[ flavor ]
151
    name = path.basename( url )
152
    iso = path.join( VMImageDir, name )
153
    if not path.exists( iso ) or ( stat( iso )[ ST_MODE ] & 0777 != 0444 ):
154
        log( '* Retrieving', url )
155
        run( 'curl -C - -o %s %s' % ( iso, url ) )
156
        if 'ISO' not in run( 'file ' + iso ):
157
            os.remove( iso )
158
            raise Exception( 'findiso: could not download iso from ' + url )
159
        # Write-protect iso, signaling it is complete
160
        log( '* Write-protecting iso', iso)
161
        os.chmod( iso, 0444 )
162
    log( '* Using iso', iso )
163
    return iso
164

    
165

    
166
def attachNBD( cow, flags='' ):
167
    """Attempt to attach a COW disk image and return its nbd device
168
        flags: additional flags for qemu-nbd (e.g. -r for readonly)"""
169
    # qemu-nbd requires an absolute path
170
    cow = abspath( cow )
171
    log( '* Checking for unused /dev/nbdX device ' )
172
    for i in range ( 0, 63 ):
173
        nbd = '/dev/nbd%d' % i
174
        # Check whether someone's already messing with that device
175
        if call( [ 'pgrep', '-f', nbd ] ) == 0:
176
            continue
177
        srun( 'modprobe nbd max-part=64' )
178
        srun( 'qemu-nbd %s -c %s %s' % ( flags, nbd, cow ) )
179
        print
180
        return nbd
181
    raise Exception( "Error: could not find unused /dev/nbdX device" )
182

    
183

    
184
def detachNBD( nbd ):
185
    "Detatch an nbd device"
186
    srun( 'qemu-nbd -d ' + nbd )
187

    
188

    
189
def extractKernel( image, flavor ):
190
    "Extract kernel and initrd from base image"
191
    kernel = path.join( VMImageDir, flavor + '-vmlinuz' )
192
    initrd = path.join( VMImageDir, flavor + '-initrd' )
193
    if path.exists( kernel ) and ( stat( image )[ ST_MODE ] & 0777 ) == 0444:
194
        # If kernel is there, then initrd should also be there
195
        return kernel, initrd
196
    log( '* Extracting kernel to', kernel )
197
    nbd = attachNBD( image, flags='-r' )
198
    print srun( 'partx ' + nbd )
199
    # Assume kernel is in partition 1/boot/vmlinuz*generic for now
200
    part = nbd + 'p1'
201
    mnt = mkdtemp()
202
    srun( 'mount -o ro %s %s' % ( part, mnt  ) )
203
    kernsrc = glob( '%s/boot/vmlinuz*generic' % mnt )[ 0 ]
204
    initrdsrc = glob( '%s/boot/initrd*generic' % mnt )[ 0 ]
205
    srun( 'cp %s %s' % ( initrdsrc, initrd ) )
206
    srun( 'chmod 0444 ' + initrd )
207
    srun( 'cp %s %s' % ( kernsrc, kernel ) )
208
    srun( 'chmod 0444 ' + kernel )
209
    srun( 'umount ' + mnt )
210
    run( 'rmdir ' + mnt )
211
    detachNBD( nbd )
212
    return kernel, initrd
213

    
214

    
215
def findBaseImage( flavor, size='8G' ):
216
    "Return base VM image and kernel, creating them if needed"
217
    image = path.join( VMImageDir, flavor + '-base.qcow2' )
218
    if path.exists( image ):
219
        # Detect race condition with multiple builds
220
        perms = stat( image )[ ST_MODE ] & 0777
221
        if perms != 0444:
222
            raise Exception( 'Error - %s is writable ' % image +
223
                            '; are multiple builds running?' )
224
    else:
225
        # We create VMImageDir here since we are called first
226
        run( 'mkdir -p %s' % VMImageDir )
227
        iso = findiso( flavor )
228
        log( '* Creating image file', image )
229
        run( 'qemu-img create -f qcow2 %s %s' % ( image, size ) )
230
        installUbuntu( iso, image )
231
        # Write-protect image, also signaling it is complete
232
        log( '* Write-protecting image', image)
233
        os.chmod( image, 0444 )
234
    kernel, initrd = extractKernel( image, flavor )
235
    log( '* Using base image', image, 'and kernel', kernel )
236
    return image, kernel, initrd
237

    
238

    
239
# Kickstart and Preseed files for Ubuntu/Debian installer
240
#
241
# Comments: this is really clunky and painful. If Ubuntu
242
# gets their act together and supports kickstart a bit better
243
# then we can get rid of preseed and even use this as a
244
# Fedora installer as well.
245
#
246
# Another annoying thing about Ubuntu is that it can't just
247
# install a normal system from the iso - it has to download
248
# junk from the internet, making this house of cards even
249
# more precarious.
250

    
251
KickstartText ="""
252
#Generated by Kickstart Configurator
253
#platform=x86
254

255
#System language
256
lang en_US
257
#Language modules to install
258
langsupport en_US
259
#System keyboard
260
keyboard us
261
#System mouse
262
mouse
263
#System timezone
264
timezone America/Los_Angeles
265
#Root password
266
rootpw --disabled
267
#Initial user
268
user mininet --fullname "mininet" --password "mininet"
269
#Use text mode install
270
text
271
#Install OS instead of upgrade
272
install
273
#Use CDROM installation media
274
cdrom
275
#System bootloader configuration
276
bootloader --location=mbr
277
#Clear the Master Boot Record
278
zerombr yes
279
#Partition clearing information
280
clearpart --all --initlabel
281
#Automatic partitioning
282
autopart
283
#System authorization infomation
284
auth  --useshadow  --enablemd5
285
#Firewall configuration
286
firewall --disabled
287
#Do not configure the X Window System
288
skipx
289
"""
290

    
291
# Tell the Ubuntu/Debian installer to stop asking stupid questions
292

    
293
PreseedText = """
294
d-i mirror/country string manual
295
d-i mirror/http/hostname string mirrors.kernel.org
296
d-i mirror/http/directory string /ubuntu
297
d-i mirror/http/proxy string
298
d-i partman/confirm_write_new_label boolean true
299
d-i partman/choose_partition select finish
300
d-i partman/confirm boolean true
301
d-i partman/confirm_nooverwrite boolean true
302
d-i user-setup/allow-password-weak boolean true
303
d-i finish-install/reboot_in_progress note
304
d-i debian-installer/exit/poweroff boolean true
305
"""
306

    
307
def makeKickstartFloppy():
308
    "Create and return kickstart floppy, kickstart, preseed"
309
    kickstart = 'ks.cfg'
310
    with open( kickstart, 'w' ) as f:
311
        f.write( KickstartText )
312
    preseed = 'ks.preseed'
313
    with open( preseed, 'w' ) as f:
314
        f.write( PreseedText )
315
    # Create floppy and copy files to it
316
    floppy = 'ksfloppy.img'
317
    run( 'qemu-img create %s 1440k' % floppy )
318
    run( 'mkfs -t msdos ' + floppy )
319
    run( 'mcopy -i %s %s ::/' % ( floppy, kickstart ) )
320
    run( 'mcopy -i %s %s ::/' % ( floppy, preseed ) )
321
    return floppy, kickstart, preseed
322

    
323

    
324
def archFor( filepath ):
325
    "Guess architecture for file path"
326
    name = path.basename( filepath )
327
    if '64' in name:
328
        arch = 'x86_64'
329
    elif 'i386' in name or '32' in name:
330
        arch = 'i386'
331
    else:
332
        log( "Error: can't discern CPU for file name", name )
333
        exit( 1 )
334
    return arch
335

    
336

    
337
def installUbuntu( iso, image, logfilename='install.log' ):
338
    "Install Ubuntu from iso onto image"
339
    kvm = 'qemu-system-' + archFor( iso )
340
    floppy, kickstart, preseed = makeKickstartFloppy()
341
    # Mount iso so we can use its kernel
342
    mnt = mkdtemp()
343
    srun( 'mount %s %s' % ( iso, mnt ) )
344
    kernel = path.join( mnt, 'install/vmlinuz' )
345
    initrd = path.join( mnt, 'install/initrd.gz' )
346
    if NoKVM:
347
        accel = 'tcg'
348
    else:
349
        accel = 'kvm'
350
    cmd = [ 'sudo', kvm,
351
           '-machine', 'accel=%s' % accel,
352
           '-nographic',
353
           '-netdev', 'user,id=mnbuild',
354
           '-device', 'virtio-net,netdev=mnbuild',
355
           '-m', '1024',
356
           '-k', 'en-us',
357
           '-fda', floppy,
358
           '-drive', 'file=%s,if=virtio' % image,
359
           '-cdrom', iso,
360
           '-kernel', kernel,
361
           '-initrd', initrd,
362
           '-append',
363
           ' ks=floppy:/' + kickstart +
364
           ' preseed/file=floppy://' + preseed +
365
           ' console=ttyS0' ]
366
    ubuntuStart = time()
367
    log( '* INSTALLING UBUNTU FROM', iso, 'ONTO', image )
368
    log( ' '.join( cmd ) )
369
    log( '* logging to', abspath( logfilename ) )
370
    params = {}
371
    if not LogToConsole:
372
        logfile = open( logfilename, 'w' )
373
        params = { 'stdout': logfile, 'stderr': logfile }
374
    vm = Popen( cmd, **params )
375
    log( '* Waiting for installation to complete')
376
    vm.wait()
377
    if not LogToConsole:
378
        logfile.close()
379
    elapsed = time() - ubuntuStart
380
    # Unmount iso and clean up
381
    srun( 'umount ' + mnt )
382
    run( 'rmdir ' + mnt )
383
    if vm.returncode != 0:
384
        raise Exception( 'Ubuntu installation returned error %d' %
385
                          vm.returncode )
386
    log( '* UBUNTU INSTALLATION COMPLETED FOR', image )
387
    log( '* Ubuntu installation completed in %.2f seconds ' % elapsed )
388

    
389

    
390
def boot( cow, kernel, initrd, logfile ):
391
    """Boot qemu/kvm with a COW disk and local/user data store
392
       cow: COW disk path
393
       kernel: kernel path
394
       logfile: log file for pexpect object
395
       returns: pexpect object to qemu process"""
396
    # pexpect might not be installed until after depend() is called
397
    global pexpect
398
    import pexpect
399
    arch = archFor( kernel )
400
    if NoKVM:
401
        accel = 'tcg'
402
    else:
403
        accel = 'kvm'
404
    cmd = [ 'sudo', 'qemu-system-' + arch,
405
            '-machine accel=%s' % accel,
406
            '-nographic',
407
            '-netdev user,id=mnbuild',
408
            '-device virtio-net,netdev=mnbuild',
409
            '-m 1024',
410
            '-k en-us',
411
            '-kernel', kernel,
412
            '-initrd', initrd,
413
            '-drive file=%s,if=virtio' % cow,
414
            '-append "root=/dev/vda1 init=/sbin/init console=ttyS0" ' ]
415
    cmd = ' '.join( cmd )
416
    log( '* BOOTING VM FROM', cow )
417
    log( cmd )
418
    vm = pexpect.spawn( cmd, timeout=TIMEOUT, logfile=logfile )
419
    return vm
420

    
421

    
422
def interact( vm ):
423
    "Interact with vm, which is a pexpect object"
424
    prompt = '\$ '
425
    log( '* Waiting for login prompt' )
426
    vm.expect( 'login: ' )
427
    log( '* Logging in' )
428
    vm.sendline( 'mininet' )
429
    log( '* Waiting for password prompt' )
430
    vm.expect( 'Password: ' )
431
    log( '* Sending password' )
432
    vm.sendline( 'mininet' )
433
    log( '* Waiting for login...' )
434
    vm.expect( prompt )
435
    log( '* Sending hostname command' )
436
    vm.sendline( 'hostname' )
437
    log( '* Waiting for output' )
438
    vm.expect( prompt )
439
    log( '* Fetching Mininet VM install script' )
440
    vm.sendline( 'wget '
441
                 'https://raw.github.com/mininet/mininet/master/util/vm/'
442
                 'install-mininet-vm.sh' )
443
    vm.expect( prompt )
444
    log( '* Running VM install script' )
445
    vm.sendline( 'bash install-mininet-vm.sh' )
446
    vm.expect ( 'password for mininet: ' )
447
    vm.sendline( 'mininet' )
448
    log( '* Waiting for script to complete... ' )
449
    # Gigantic timeout for now ;-(
450
    vm.expect( 'Done preparing Mininet', timeout=3600 )
451
    log( '* Completed successfully' )
452
    vm.expect( prompt )
453
    log( '* Testing Mininet' )
454
    vm.sendline( 'sudo mn --test pingall' )
455
    if vm.expect( [ ' 0% dropped', pexpect.TIMEOUT ], timeout=45 ) == 0:
456
        log( '* Sanity check OK' )
457
    else:
458
        log( '* Sanity check FAILED' )
459
    vm.expect( prompt )
460
    log( '* Making sure cgroups are mounted' )
461
    vm.sendline( 'sudo service cgroup-lite restart' )
462
    vm.expect( prompt )
463
    vm.sendline( 'sudo cgroups-mount' )
464
    vm.expect( prompt )
465
    log( '* Running make test' )
466
    vm.sendline( 'cd ~/mininet; sudo make test' )
467
    # We should change "make test" to report the number of
468
    # successful and failed tests. For now, we have to
469
    # know the time for each test, which means that this
470
    # script will have to change as we add more tests.
471
    for test in range( 0, 2 ):
472
        if vm.expect( [ 'OK', 'FAILED', pexpect.TIMEOUT ], timeout=180 ) == 0:
473
            log( '* Test', test, 'OK' )
474
        else:
475
            log( '* Test', test, 'FAILED' )
476
    vm.expect( prompt )
477
    log( '* Shutting down' )
478
    vm.sendline( 'sync; sudo shutdown -h now' )
479
    log( '* Waiting for EOF/shutdown' )
480
    vm.read()
481
    log( '* Interaction complete' )
482

    
483

    
484
def cleanup():
485
    "Clean up leftover qemu-nbd processes and other junk"
486
    call( [ 'sudo', 'pkill', '-9', 'qemu-nbd' ] )
487

    
488

    
489
def convert( cow, basename ):
490
    """Convert a qcow2 disk to a vmdk and put it a new directory
491
       basename: base name for output vmdk file"""
492
    vmdk = basename + '.vmdk'
493
    log( '* Converting qcow2 to vmdk' )
494
    run( 'qemu-img convert -f qcow2 -O vmdk %s %s' % ( cow, vmdk ) )
495
    return vmdk
496

    
497

    
498
# Template for OVF - a very verbose format!
499
# In the best of all possible worlds, we might use an XML
500
# library to generate this, but a template is easier and
501
# possibly more concise!
502

    
503
OVFTemplate = """
504
<?xml version="1.0"?>
505
<Envelope ovf:version="1.0" xml:lang="en-US" 
506
    xmlns="http://schemas.dmtf.org/ovf/envelope/1"
507
    xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"
508
    xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
509
    xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
510
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
511
<References>
512
<File ovf:href="%s" ovf:id="file1" ovf:size="%d"/>
513
</References>
514
<DiskSection>
515
<Info>Virtual disk information</Info>
516
<Disk ovf:capacity="%d" ovf:capacityAllocationUnits="byte" 
517
    ovf:diskId="vmdisk1" ovf:fileRef="file1" 
518
    ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html"/>
519
</DiskSection>
520
<NetworkSection>
521
<Info>The list of logical networks</Info>
522
<Network ovf:name="nat">
523
<Description>The nat  network</Description>
524
</Network>
525
</NetworkSection>
526
<VirtualSystem ovf:id="Mininet-VM">
527
<Info>A Mininet Virtual Machine (%s)</Info>
528
<Name>mininet-vm</Name>
529
<VirtualHardwareSection>
530
<Info>Virtual hardware requirements</Info>
531
<Item>
532
<rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
533
<rasd:Description>Number of Virtual CPUs</rasd:Description>
534
<rasd:ElementName>1 virtual CPU(s)</rasd:ElementName>
535
<rasd:InstanceID>1</rasd:InstanceID>
536
<rasd:ResourceType>3</rasd:ResourceType>
537
<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
538
</Item>
539
<Item>
540
<rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
541
<rasd:Description>Memory Size</rasd:Description>
542
<rasd:ElementName>%dMB of memory</rasd:ElementName>
543
<rasd:InstanceID>2</rasd:InstanceID>
544
<rasd:ResourceType>4</rasd:ResourceType>
545
<rasd:VirtualQuantity>%d</rasd:VirtualQuantity>
546
</Item>
547
<Item>
548
<rasd:Address>0</rasd:Address>
549
<rasd:Caption>scsiController0</rasd:Caption>
550
<rasd:Description>SCSI Controller</rasd:Description>
551
<rasd:ElementName>scsiController0</rasd:ElementName>
552
<rasd:InstanceID>4</rasd:InstanceID>
553
<rasd:ResourceSubType>lsilogic</rasd:ResourceSubType>
554
<rasd:ResourceType>6</rasd:ResourceType>
555
</Item>
556
<Item>
557
<rasd:AddressOnParent>0</rasd:AddressOnParent>
558
<rasd:ElementName>disk1</rasd:ElementName>
559
<rasd:HostResource>ovf:/disk/vmdisk1</rasd:HostResource>
560
<rasd:InstanceID>11</rasd:InstanceID>
561
<rasd:Parent>4</rasd:Parent>
562
<rasd:ResourceType>17</rasd:ResourceType>
563
</Item>
564
<Item>
565
<rasd:AddressOnParent>2</rasd:AddressOnParent>
566
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
567
<rasd:Connection>nat</rasd:Connection>
568
<rasd:Description>E1000 ethernet adapter on nat</rasd:Description>
569
<rasd:ElementName>ethernet0</rasd:ElementName>
570
<rasd:InstanceID>12</rasd:InstanceID>
571
<rasd:ResourceSubType>E1000</rasd:ResourceSubType>
572
<rasd:ResourceType>10</rasd:ResourceType>
573
</Item>
574
<Item>
575
<rasd:Address>0</rasd:Address>
576
<rasd:Caption>usb</rasd:Caption>
577
<rasd:Description>USB Controller</rasd:Description>
578
<rasd:ElementName>usb</rasd:ElementName>
579
<rasd:InstanceID>9</rasd:InstanceID>
580
<rasd:ResourceType>23</rasd:ResourceType>
581
</Item>
582
</VirtualHardwareSection>
583
</VirtualSystem>
584
</Envelope>
585
"""
586

    
587

    
588
def generateOVF( name, diskname, disksize, mem=1024 ):
589
    """Generate (and return) OVF file "name.ovf"
590
       name: root name of OVF file to generate
591
       diskname: name of disk file
592
       disksize: size of virtual disk in bytes
593
       mem: VM memory size in MB"""
594
    ovf = name + '.ovf'
595
    filesize = stat( diskname )[ ST_SIZE ]
596
    # OVFTemplate uses the memory size twice in a row
597
    xmltext = OVFTemplate % ( diskname, filesize, disksize, name, mem, mem )
598
    with open( ovf, 'w+' ) as f:
599
        f.write( xmltext )
600
    return ovf
601

    
602

    
603
def qcow2size( qcow2 ):
604
    "Return virtual disk size (in bytes) of qcow2 image"
605
    output = check_output( [ 'file', qcow2 ] )
606
    assert 'QCOW' in output
607
    bytes = int( re.findall( '(\d+) bytes', output )[ 0 ] )
608
    return bytes
609

    
610

    
611
def build( flavor='raring32server' ):
612
    "Build a Mininet VM; return vmdk and vdisk size"
613
    global LogFile
614
    start = time()
615
    date = strftime( '%y%m%d-%H-%M-%S', localtime())
616
    dir = 'mn-%s-%s' % ( flavor, date )
617
    try:
618
        os.mkdir( dir )
619
    except:
620
        raise Exception( "Failed to create build directory %s" % dir )
621
    os.chdir( dir )
622
    LogFile = open( 'build.log', 'w' )
623
    log( '* Logging to', abspath( LogFile.name ) )
624
    log( '* Created working directory', dir )
625
    image, kernel, initrd = findBaseImage( flavor )
626
    volume = flavor + '.qcow2'
627
    run( 'qemu-img create -f qcow2 -b %s %s' % ( image, volume ) )
628
    log( '* VM image for', flavor, 'created as', volume )
629
    if LogToConsole:
630
        logfile = stdout
631
    else:
632
        logfile = open( flavor + '.log', 'w+' )
633
    log( '* Logging results to', abspath( logfile.name ) )
634
    vm = boot( volume, kernel, initrd, logfile )
635
    interact( vm )
636
    size = qcow2size( volume )
637
    vmdk = convert( volume, basename=flavor )
638
    if not SaveQCOW2:
639
        log( '* Removing qcow2 volume', volume )
640
        os.remove( volume )
641
    log( '* Converted VM image stored as', abspath( vmdk ) )
642
    ovf = generateOVF( diskname=vmdk, disksize=size, name=flavor, mem=1024 )
643
    log( '* Generated OVF descriptor file', ovf )
644
    end = time()
645
    elapsed = end - start
646
    log( '* Results logged to', abspath( logfile.name ) )
647
    log( '* Completed in %.2f seconds' % elapsed )
648
    log( '* %s VM build DONE!!!!! :D' % flavor )
649
    os.chdir( '..' )
650

    
651
def buildFlavorString():
652
    "Return string listing valid build flavors"
653
    return 'valid build flavors: %s' % ' '.join( sorted( isoURLs ) )
654

    
655

    
656
def parseArgs():
657
    "Parse command line arguments and run"
658
    global LogToConsole, NoKVM
659
    parser = argparse.ArgumentParser( description='Mininet VM build script',
660
                                      epilog=buildFlavorString() )
661
    parser.add_argument( '-v', '--verbose', action='store_true',
662
                        help='send VM output to console rather than log file' )
663
    parser.add_argument( '-d', '--depend', action='store_true',
664
                         help='install dependencies for this script' )
665
    parser.add_argument( '-l', '--list', action='store_true',
666
                         help='list valid build flavors' )
667
    parser.add_argument( '-c', '--clean', action='store_true',
668
                         help='clean up leftover build junk (e.g. qemu-nbd)' )
669
    parser.add_argument( '-q', '--qcow2', action='store_true',
670
                         help='save qcow2 image rather than deleting it' )
671
    parser.add_argument( '-n', '--nokvm', action='store_true',
672
                         help="Don't use kvm - use tcg emulation instead" )
673
    parser.add_argument( 'flavor', nargs='*',
674
                         help='VM flavor(s) to build (e.g. raring32server)' )
675
    args = parser.parse_args()
676
    if args.depend:
677
        depend()
678
    if args.list:
679
        print buildFlavorString()
680
    if args.clean:
681
        cleanup()
682
    if args.verbose:
683
        LogToConsole = True
684
    if args.nokvm:
685
        NoKVM = True
686
    for flavor in args.flavor:
687
        if flavor not in isoURLs:
688
            print "Unknown build flavor:", flavor
689
            print buildFlavorString()
690
            break
691
        # try:
692
        build( flavor )
693
        # except Exception as e:
694
        # log( '* BUILD FAILED with exception: ', e )
695
        # exit( 1 )
696
    if not ( args.depend or args.list or args.clean or args.flavor ):
697
        parser.print_help()
698

    
699

    
700
if __name__ == '__main__':
701
    parseArgs()