Statistics
| Branch: | Tag: | Revision:

mininet / util / vm / build.py @ 86af067e

History | View | Annotate | Download (35.7 KB)

1 4daeeff0 Bob Lantz
#!/usr/bin/python
2
3
"""
4
build.py: build a Mininet VM
5

6
Basic idea:
7

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

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

18
    test
19 b79ce2a5 Bob Lantz
    -> sudo mn --test pingall
20 4daeeff0 Bob Lantz
    -> make test
21

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

27 94954177 Bob Lantz
"""
28 4daeeff0 Bob Lantz
29
import os
30 fa1758b9 Bob Lantz
from os import stat, path
31 0038720c Bob Lantz
from stat import ST_MODE, ST_SIZE
32 fa1758b9 Bob Lantz
from os.path import abspath
33 7bd9a79b Bob Lantz
from sys import exit, stdout, argv, modules
34 d4279559 Bob Lantz
import re
35 4daeeff0 Bob Lantz
from glob import glob
36 fa1758b9 Bob Lantz
from subprocess import check_output, call, Popen
37 501a164e Bob Lantz
from tempfile import mkdtemp, NamedTemporaryFile
38 14903d6a Bob Lantz
from time import time, strftime, localtime
39 85dfac5c Bob Lantz
import argparse
40 0038720c Bob Lantz
from distutils.spawn import find_executable
41 7bd9a79b Bob Lantz
import inspect
42 4daeeff0 Bob Lantz
43 94954177 Bob Lantz
pexpect = None  # For code check - imported dynamically
44
45 4daeeff0 Bob Lantz
# boot can be slooooow!!!! need to debug/optimize somehow
46 860bcc02 Bob Lantz
TIMEOUT=600
47 4daeeff0 Bob Lantz
48 7bd9a79b Bob Lantz
# Some configuration options
49
# Possibly change this to use the parsed arguments instead!
50
51 d4279559 Bob Lantz
LogToConsole = False        # VM output to console rather than log file
52
SaveQCOW2 = False           # Save QCOW2 image rather than deleting it
53 0038720c Bob Lantz
NoKVM = False               # Don't use kvm and use emulation instead
54 7bd9a79b Bob Lantz
Branch = None               # Branch to update and check out before testing
55 6159e923 Bob Lantz
Zip = False                 # Archive .ovf and .vmdk into a .zip file
56
Forward = []                # VM port forwarding options (-redir)
57 d4279559 Bob Lantz
58 4daeeff0 Bob Lantz
VMImageDir = os.environ[ 'HOME' ] + '/vm-images'
59
60 501a164e Bob Lantz
Prompt = '\$ '              # Shell prompt that pexpect will wait for
61
62 fa1758b9 Bob Lantz
isoURLs = {
63 dbcfda77 Bob Lantz
    'precise32server':
64
    'http://mirrors.kernel.org/ubuntu-releases/12.04/'
65 1285fb22 Brian O'Connor
    'ubuntu-12.04.5-server-i386.iso',
66 dbcfda77 Bob Lantz
    'precise64server':
67
    'http://mirrors.kernel.org/ubuntu-releases/12.04/'
68 1285fb22 Brian O'Connor
    'ubuntu-12.04.5-server-amd64.iso',
69 a1557958 Bob Lantz
    'quantal32server':
70 fa1758b9 Bob Lantz
    'http://mirrors.kernel.org/ubuntu-releases/12.10/'
71
    'ubuntu-12.10-server-i386.iso',
72 a1557958 Bob Lantz
    'quantal64server':
73 dbcfda77 Bob Lantz
    'http://mirrors.kernel.org/ubuntu-releases/12.10/'
74
    'ubuntu-12.10-server-amd64.iso',
75 85dfac5c Bob Lantz
    'raring32server':
76 fa1758b9 Bob Lantz
    'http://mirrors.kernel.org/ubuntu-releases/13.04/'
77
    'ubuntu-13.04-server-i386.iso',
78 85dfac5c Bob Lantz
    'raring64server':
79 fa1758b9 Bob Lantz
    'http://mirrors.kernel.org/ubuntu-releases/13.04/'
80
    'ubuntu-13.04-server-amd64.iso',
81 20ba2959 Bob Lantz
    'saucy32server':
82
    'http://mirrors.kernel.org/ubuntu-releases/13.10/'
83
    'ubuntu-13.10-server-i386.iso',
84
    'saucy64server':
85
    'http://mirrors.kernel.org/ubuntu-releases/13.10/'
86
    'ubuntu-13.10-server-amd64.iso',
87 c3bf407a Bob Lantz
    'trusty32server':
88
    'http://mirrors.kernel.org/ubuntu-releases/14.04/'
89
    'ubuntu-14.04-server-i386.iso',
90
    'trusty64server':
91
    'http://mirrors.kernel.org/ubuntu-releases/14.04/'
92
    'ubuntu-14.04-server-amd64.iso',
93 29350004 Bob Lantz
    'utopic32server':
94
    'http://mirrors.kernel.org/ubuntu-releases/14.10/'
95
    'ubuntu-14.10-server-i386.iso',
96
    'utopic64server':
97
    'http://mirrors.kernel.org/ubuntu-releases/14.10/'
98
    'ubuntu-14.10-server-amd64.iso',
99 4daeeff0 Bob Lantz
}
100
101 662f2447 Bob Lantz
102 fce7f5c5 Bob Lantz
def OSVersion( flavor ):
103
    "Return full OS version string for build flavor"
104
    urlbase = path.basename( isoURLs.get( flavor, 'unknown' ) )
105
    return path.splitext( urlbase )[ 0 ]
106
107 dfd79bde Bob Lantz
def OVFOSNameID( flavor ):
108
    "Return OVF-specified ( OS Name, ID ) for flavor"
109
    version = OSVersion( flavor )
110
    arch = archFor( flavor )
111
    if 'ubuntu' in version:
112
        map = { 'i386': ( 'Ubuntu', 93 ),
113
                'x86_64': ( 'Ubuntu 64-bit', 94 ) }
114
    else:
115
        map = { 'i386': ( 'Linux', 36 ),
116
                'x86_64': ( 'Linux 64-bit', 101 ) }
117
    osname, osid = map[ arch ]
118
    return osname, osid
119 fce7f5c5 Bob Lantz
120 803a1a54 Bob Lantz
LogStartTime = time()
121
LogFile = None
122 14903d6a Bob Lantz
123
def log( *args, **kwargs ):
124
    """Simple log function: log( message along with local and elapsed time
125
       cr: False/0 for no CR"""
126
    cr = kwargs.get( 'cr', True )
127 803a1a54 Bob Lantz
    elapsed = time() - LogStartTime
128 14903d6a Bob Lantz
    clocktime = strftime( '%H:%M:%S', localtime() )
129
    msg = ' '.join( str( arg ) for arg in args )
130
    output = '%s [ %.3f ] %s' % ( clocktime, elapsed, msg )
131
    if cr:
132
        print output
133
    else:
134
        print output,
135 803a1a54 Bob Lantz
    # Optionally mirror to LogFile
136
    if type( LogFile ) is file:
137
        if cr:
138
            output += '\n'
139
        LogFile.write( output )
140 662f2447 Bob Lantz
        LogFile.flush()
141 14903d6a Bob Lantz
142 4daeeff0 Bob Lantz
143
def run( cmd, **kwargs ):
144
    "Convenient interface to check_output"
145 14903d6a Bob Lantz
    log( '-', cmd )
146 4daeeff0 Bob Lantz
    cmd = cmd.split()
147 0038720c Bob Lantz
    arg0 = cmd[ 0 ]
148
    if not find_executable( arg0 ):
149
        raise Exception( 'Cannot find executable "%s";' % arg0 +
150
                         'you might try %s --depend' % argv[ 0 ] )
151 4daeeff0 Bob Lantz
    return check_output( cmd, **kwargs )
152
153
154
def srun( cmd, **kwargs ):
155
    "Run + sudo"
156
    return run( 'sudo ' + cmd, **kwargs )
157
158
159 0038720c Bob Lantz
# BL: we should probably have a "checkDepend()" which
160
# checks to make sure all dependencies are satisfied!
161
162 85dfac5c Bob Lantz
def depend():
163 fa1758b9 Bob Lantz
    "Install package dependencies"
164 14903d6a Bob Lantz
    log( '* Installing package dependencies' )
165 635e8f11 Bob Lantz
    run( 'sudo apt-get -qy update' )
166 55b455e9 Bob Lantz
    run( 'sudo apt-get -qy install'
167 85dfac5c Bob Lantz
         ' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
168 e49c9d26 Brian O'Connor
         ' e2fsprogs dnsmasq curl'
169 fce7f5c5 Bob Lantz
         ' python-setuptools mtools zip' )
170 85dfac5c Bob Lantz
    run( 'sudo easy_install pexpect' )
171 4daeeff0 Bob Lantz
172
173
def popen( cmd ):
174
    "Convenient interface to popen"
175 14903d6a Bob Lantz
    log( cmd )
176 4daeeff0 Bob Lantz
    cmd = cmd.split()
177
    return Popen( cmd )
178
179
180
def remove( fname ):
181 501a164e Bob Lantz
    "Remove a file, ignoring errors"
182
    try:
183
        os.remove( fname )
184
    except OSError:
185
        pass
186 4daeeff0 Bob Lantz
187
188 fa1758b9 Bob Lantz
def findiso( flavor ):
189
    "Find iso, fetching it if it's not there already"
190
    url = isoURLs[ flavor ]
191
    name = path.basename( url )
192
    iso = path.join( VMImageDir, name )
193 f605a4e4 Bob Lantz
    if not path.exists( iso ) or ( stat( iso )[ ST_MODE ] & 0777 != 0444 ):
194 fa1758b9 Bob Lantz
        log( '* Retrieving', url )
195 f605a4e4 Bob Lantz
        run( 'curl -C - -o %s %s' % ( iso, url ) )
196 f2458d1d Bob Lantz
        # Make sure the file header/type is something reasonable like
197
        # 'ISO' or 'x86 boot sector', and not random html or text
198
        result = run( 'file ' + iso )
199
        if 'ISO' not in result and 'boot' not in result:
200 662fb712 Bob Lantz
            os.remove( iso )
201
            raise Exception( 'findiso: could not download iso from ' + url )
202 fa1758b9 Bob Lantz
        # Write-protect iso, signaling it is complete
203
        log( '* Write-protecting iso', iso)
204
        os.chmod( iso, 0444 )
205
    log( '* Using iso', iso )
206
    return iso
207 85dfac5c Bob Lantz
208
209 94954177 Bob Lantz
def attachNBD( cow, flags='' ):
210
    """Attempt to attach a COW disk image and return its nbd device
211 fa1758b9 Bob Lantz
        flags: additional flags for qemu-nbd (e.g. -r for readonly)"""
212 94954177 Bob Lantz
    # qemu-nbd requires an absolute path
213
    cow = abspath( cow )
214 14903d6a Bob Lantz
    log( '* Checking for unused /dev/nbdX device ' )
215 85dfac5c Bob Lantz
    for i in range ( 0, 63 ):
216
        nbd = '/dev/nbd%d' % i
217
        # Check whether someone's already messing with that device
218
        if call( [ 'pgrep', '-f', nbd ] ) == 0:
219
            continue
220 14903d6a Bob Lantz
        srun( 'modprobe nbd max-part=64' )
221 94954177 Bob Lantz
        srun( 'qemu-nbd %s -c %s %s' % ( flags, nbd, cow ) )
222 14903d6a Bob Lantz
        print
223 85dfac5c Bob Lantz
        return nbd
224
    raise Exception( "Error: could not find unused /dev/nbdX device" )
225
226
227 94954177 Bob Lantz
def detachNBD( nbd ):
228
    "Detatch an nbd device"
229 85dfac5c Bob Lantz
    srun( 'qemu-nbd -d ' + nbd )
230 4daeeff0 Bob Lantz
231
232 501a164e Bob Lantz
def extractKernel( image, flavor, imageDir=VMImageDir ):
233 1dfa7776 Bob Lantz
    "Extract kernel and initrd from base image"
234 501a164e Bob Lantz
    kernel = path.join( imageDir, flavor + '-vmlinuz' )
235
    initrd = path.join( imageDir, flavor + '-initrd' )
236 f605a4e4 Bob Lantz
    if path.exists( kernel ) and ( stat( image )[ ST_MODE ] & 0777 ) == 0444:
237 1dfa7776 Bob Lantz
        # If kernel is there, then initrd should also be there
238
        return kernel, initrd
239 f605a4e4 Bob Lantz
    log( '* Extracting kernel to', kernel )
240
    nbd = attachNBD( image, flags='-r' )
241 fa1758b9 Bob Lantz
    print srun( 'partx ' + nbd )
242
    # Assume kernel is in partition 1/boot/vmlinuz*generic for now
243
    part = nbd + 'p1'
244 14903d6a Bob Lantz
    mnt = mkdtemp()
245 1285fb22 Brian O'Connor
    srun( 'mount -o ro,noload %s %s' % ( part, mnt  ) )
246 fa1758b9 Bob Lantz
    kernsrc = glob( '%s/boot/vmlinuz*generic' % mnt )[ 0 ]
247 1dfa7776 Bob Lantz
    initrdsrc = glob( '%s/boot/initrd*generic' % mnt )[ 0 ]
248
    srun( 'cp %s %s' % ( initrdsrc, initrd ) )
249
    srun( 'chmod 0444 ' + initrd )
250
    srun( 'cp %s %s' % ( kernsrc, kernel ) )
251
    srun( 'chmod 0444 ' + kernel )
252 14903d6a Bob Lantz
    srun( 'umount ' + mnt )
253
    run( 'rmdir ' + mnt )
254 f605a4e4 Bob Lantz
    detachNBD( nbd )
255 1dfa7776 Bob Lantz
    return kernel, initrd
256 fa1758b9 Bob Lantz
257
258
def findBaseImage( flavor, size='8G' ):
259
    "Return base VM image and kernel, creating them if needed"
260 ad5a0e42 Bob Lantz
    image = path.join( VMImageDir, flavor + '-base.qcow2' )
261 fa1758b9 Bob Lantz
    if path.exists( image ):
262
        # Detect race condition with multiple builds
263
        perms = stat( image )[ ST_MODE ] & 0777
264
        if perms != 0444:
265
            raise Exception( 'Error - %s is writable ' % image +
266
                            '; are multiple builds running?' )
267
    else:
268
        # We create VMImageDir here since we are called first
269
        run( 'mkdir -p %s' % VMImageDir )
270
        iso = findiso( flavor )
271
        log( '* Creating image file', image )
272 ad5a0e42 Bob Lantz
        run( 'qemu-img create -f qcow2 %s %s' % ( image, size ) )
273 fa1758b9 Bob Lantz
        installUbuntu( iso, image )
274
        # Write-protect image, also signaling it is complete
275
        log( '* Write-protecting image', image)
276
        os.chmod( image, 0444 )
277 1dfa7776 Bob Lantz
    kernel, initrd = extractKernel( image, flavor )
278 f605a4e4 Bob Lantz
    log( '* Using base image', image, 'and kernel', kernel )
279 1dfa7776 Bob Lantz
    return image, kernel, initrd
280 fa1758b9 Bob Lantz
281
282 f605a4e4 Bob Lantz
# Kickstart and Preseed files for Ubuntu/Debian installer
283
#
284
# Comments: this is really clunky and painful. If Ubuntu
285
# gets their act together and supports kickstart a bit better
286
# then we can get rid of preseed and even use this as a
287
# Fedora installer as well.
288
#
289
# Another annoying thing about Ubuntu is that it can't just
290
# install a normal system from the iso - it has to download
291
# junk from the internet, making this house of cards even
292
# more precarious.
293
294
KickstartText ="""
295
#Generated by Kickstart Configurator
296
#platform=x86
297

298
#System language
299
lang en_US
300
#Language modules to install
301
langsupport en_US
302
#System keyboard
303
keyboard us
304
#System mouse
305
mouse
306
#System timezone
307
timezone America/Los_Angeles
308
#Root password
309
rootpw --disabled
310
#Initial user
311
user mininet --fullname "mininet" --password "mininet"
312
#Use text mode install
313
text
314
#Install OS instead of upgrade
315
install
316
#Use CDROM installation media
317
cdrom
318
#System bootloader configuration
319
bootloader --location=mbr
320
#Clear the Master Boot Record
321
zerombr yes
322
#Partition clearing information
323
clearpart --all --initlabel
324
#Automatic partitioning
325
autopart
326 8daa4193 Bob Lantz
#System authorization information
327 f605a4e4 Bob Lantz
auth  --useshadow  --enablemd5
328
#Firewall configuration
329
firewall --disabled
330
#Do not configure the X Window System
331
skipx
332
"""
333
334
# Tell the Ubuntu/Debian installer to stop asking stupid questions
335
336 6be4bfd0 Bob Lantz
PreseedText = ( """
337
"""
338
#d-i mirror/country string manual
339
#d-i mirror/http/hostname string mirrors.kernel.org
340
"""
341 f605a4e4 Bob Lantz
d-i mirror/http/directory string /ubuntu
342
d-i mirror/http/proxy string
343
d-i partman/confirm_write_new_label boolean true
344
d-i partman/choose_partition select finish
345
d-i partman/confirm boolean true
346
d-i partman/confirm_nooverwrite boolean true
347
d-i user-setup/allow-password-weak boolean true
348
d-i finish-install/reboot_in_progress note
349
d-i debian-installer/exit/poweroff boolean true
350 6be4bfd0 Bob Lantz
""" )
351 f605a4e4 Bob Lantz
352 fa1758b9 Bob Lantz
def makeKickstartFloppy():
353
    "Create and return kickstart floppy, kickstart, preseed"
354
    kickstart = 'ks.cfg'
355
    with open( kickstart, 'w' ) as f:
356 f605a4e4 Bob Lantz
        f.write( KickstartText )
357 fa1758b9 Bob Lantz
    preseed = 'ks.preseed'
358
    with open( preseed, 'w' ) as f:
359 f605a4e4 Bob Lantz
        f.write( PreseedText )
360 fa1758b9 Bob Lantz
    # Create floppy and copy files to it
361
    floppy = 'ksfloppy.img'
362 f605a4e4 Bob Lantz
    run( 'qemu-img create %s 1440k' % floppy )
363
    run( 'mkfs -t msdos ' + floppy )
364 fa1758b9 Bob Lantz
    run( 'mcopy -i %s %s ::/' % ( floppy, kickstart ) )
365
    run( 'mcopy -i %s %s ::/' % ( floppy, preseed ) )
366
    return floppy, kickstart, preseed
367
368
369 d4279559 Bob Lantz
def archFor( filepath ):
370
    "Guess architecture for file path"
371 1dfa7776 Bob Lantz
    name = path.basename( filepath )
372 6d3cb5bc Bob Lantz
    if 'amd64' in name or 'x86_64' in name:
373 d4279559 Bob Lantz
        arch = 'x86_64'
374 6d3cb5bc Bob Lantz
    # Beware of version 64 of a 32-bit OS
375
    elif 'i386' in name or '32' in name or 'x86' in name:
376 d4279559 Bob Lantz
        arch = 'i386'
377 6d3cb5bc Bob Lantz
    elif '64' in name:
378
        arch = 'x86_64'
379 fa1758b9 Bob Lantz
    else:
380 6d3cb5bc Bob Lantz
        log( "Error: can't discern CPU for name", name )
381 fa1758b9 Bob Lantz
        exit( 1 )
382 d4279559 Bob Lantz
    return arch
383 14903d6a Bob Lantz
384
385 568f6424 Bob Lantz
def installUbuntu( iso, image, logfilename='install.log', memory=1024 ):
386 fa1758b9 Bob Lantz
    "Install Ubuntu from iso onto image"
387 d4279559 Bob Lantz
    kvm = 'qemu-system-' + archFor( iso )
388 fa1758b9 Bob Lantz
    floppy, kickstart, preseed = makeKickstartFloppy()
389
    # Mount iso so we can use its kernel
390
    mnt = mkdtemp()
391
    srun( 'mount %s %s' % ( iso, mnt ) )
392 f605a4e4 Bob Lantz
    kernel = path.join( mnt, 'install/vmlinuz' )
393
    initrd = path.join( mnt, 'install/initrd.gz' )
394 0038720c Bob Lantz
    if NoKVM:
395
        accel = 'tcg'
396
    else:
397
        accel = 'kvm'
398 fa1758b9 Bob Lantz
    cmd = [ 'sudo', kvm,
399 0038720c Bob Lantz
           '-machine', 'accel=%s' % accel,
400 fa1758b9 Bob Lantz
           '-nographic',
401 f605a4e4 Bob Lantz
           '-netdev', 'user,id=mnbuild',
402
           '-device', 'virtio-net,netdev=mnbuild',
403 568f6424 Bob Lantz
           '-m', str( memory ),
404 f605a4e4 Bob Lantz
           '-k', 'en-us',
405 fa1758b9 Bob Lantz
           '-fda', floppy,
406 f605a4e4 Bob Lantz
           '-drive', 'file=%s,if=virtio' % image,
407
           '-cdrom', iso,
408 fa1758b9 Bob Lantz
           '-kernel', kernel,
409 f605a4e4 Bob Lantz
           '-initrd', initrd,
410
           '-append',
411
           ' ks=floppy:/' + kickstart +
412
           ' preseed/file=floppy://' + preseed +
413
           ' console=ttyS0' ]
414
    ubuntuStart = time()
415 fa1758b9 Bob Lantz
    log( '* INSTALLING UBUNTU FROM', iso, 'ONTO', image )
416 f605a4e4 Bob Lantz
    log( ' '.join( cmd ) )
417
    log( '* logging to', abspath( logfilename ) )
418 d4279559 Bob Lantz
    params = {}
419
    if not LogToConsole:
420
        logfile = open( logfilename, 'w' )
421
        params = { 'stdout': logfile, 'stderr': logfile }
422
    vm = Popen( cmd, **params )
423 f605a4e4 Bob Lantz
    log( '* Waiting for installation to complete')
424
    vm.wait()
425 d4279559 Bob Lantz
    if not LogToConsole:
426
        logfile.close()
427 f605a4e4 Bob Lantz
    elapsed = time() - ubuntuStart
428 fa1758b9 Bob Lantz
    # Unmount iso and clean up
429
    srun( 'umount ' + mnt )
430
    run( 'rmdir ' + mnt )
431 803a1a54 Bob Lantz
    if vm.returncode != 0:
432
        raise Exception( 'Ubuntu installation returned error %d' %
433
                          vm.returncode )
434 fa1758b9 Bob Lantz
    log( '* UBUNTU INSTALLATION COMPLETED FOR', image )
435 501a164e Bob Lantz
    log( '* Ubuntu installation completed in %.2f seconds' % elapsed )
436 fa1758b9 Bob Lantz
437
438 86af067e Jonathan Hart
def boot( cow, kernel, initrd, logfile, memory=1024, cpuCores=1 ):
439 4daeeff0 Bob Lantz
    """Boot qemu/kvm with a COW disk and local/user data store
440 85dfac5c Bob Lantz
       cow: COW disk path
441
       kernel: kernel path
442 fa1758b9 Bob Lantz
       logfile: log file for pexpect object
443 568f6424 Bob Lantz
       memory: memory size in MB
444 86af067e Jonathan Hart
       cpuCores: number of CPU cores to use
445 85dfac5c Bob Lantz
       returns: pexpect object to qemu process"""
446
    # pexpect might not be installed until after depend() is called
447
    global pexpect
448 61760eab Bob Lantz
    if not pexpect:
449
        import pexpect
450
    class Spawn( pexpect.spawn ):
451
        "Subprocess is sudo, so we have to sudo kill it"
452
        def close( self, force=False ):
453
            srun( 'kill %d' % self.pid )
454 8f546d89 Bob Lantz
    arch = archFor( kernel )
455
    log( '* Detected kernel architecture', arch )
456 0038720c Bob Lantz
    if NoKVM:
457
        accel = 'tcg'
458
    else:
459
        accel = 'kvm'
460 d4279559 Bob Lantz
    cmd = [ 'sudo', 'qemu-system-' + arch,
461 0038720c Bob Lantz
            '-machine accel=%s' % accel,
462 4daeeff0 Bob Lantz
            '-nographic',
463 85dfac5c Bob Lantz
            '-netdev user,id=mnbuild',
464
            '-device virtio-net,netdev=mnbuild',
465 568f6424 Bob Lantz
            '-m %s' % memory,
466 85dfac5c Bob Lantz
            '-k en-us',
467 4daeeff0 Bob Lantz
            '-kernel', kernel,
468 1dfa7776 Bob Lantz
            '-initrd', initrd,
469 85dfac5c Bob Lantz
            '-drive file=%s,if=virtio' % cow,
470 94954177 Bob Lantz
            '-append "root=/dev/vda1 init=/sbin/init console=ttyS0" ' ]
471 6159e923 Bob Lantz
    if Forward:
472
        cmd += sum( [ [ '-redir', f ] for f in Forward ], [] )
473 86af067e Jonathan Hart
    if cpuCores > 1:
474
        cmd += [ '-smp cores=%s' % cpuCores ]
475 4daeeff0 Bob Lantz
    cmd = ' '.join( cmd )
476 fa1758b9 Bob Lantz
    log( '* BOOTING VM FROM', cow )
477 14903d6a Bob Lantz
    log( cmd )
478 61760eab Bob Lantz
    vm = Spawn( cmd, timeout=TIMEOUT, logfile=logfile )
479 85dfac5c Bob Lantz
    return vm
480
481 4daeeff0 Bob Lantz
482 e49c9d26 Brian O'Connor
def login( vm, user='mininet', password='mininet' ):
483 501a164e Bob Lantz
    "Log in to vm (pexpect object)"
484 14903d6a Bob Lantz
    log( '* Waiting for login prompt' )
485 4daeeff0 Bob Lantz
    vm.expect( 'login: ' )
486 14903d6a Bob Lantz
    log( '* Logging in' )
487 e49c9d26 Brian O'Connor
    vm.sendline( user )
488 14903d6a Bob Lantz
    log( '* Waiting for password prompt' )
489 4daeeff0 Bob Lantz
    vm.expect( 'Password: ' )
490 14903d6a Bob Lantz
    log( '* Sending password' )
491 e49c9d26 Brian O'Connor
    vm.sendline( password )
492 14903d6a Bob Lantz
    log( '* Waiting for login...' )
493 501a164e Bob Lantz
494
495 abcdf185 Bob Lantz
def removeNtpd( vm, prompt=Prompt, ntpPackage='ntp' ):
496
    "Remove ntpd and set clock immediately"
497
    log( '* Removing ntpd' )
498 2059786f Bob Lantz
    vm.sendline( 'sudo -n apt-get -qy remove ' + ntpPackage )
499 abcdf185 Bob Lantz
    vm.expect( prompt )
500
    # Try to make sure that it isn't still running
501 2059786f Bob Lantz
    vm.sendline( 'sudo -n pkill ntpd' )
502 1bae1aab Bob Lantz
    vm.expect( prompt )
503 060d46a2 Bob Lantz
    log( '* Getting seconds since epoch from this server' )
504
    # Note r'date +%s' specifies a format for 'date', not python!
505
    seconds = int( run( r'date +%s' ) )
506
    log( '* Setting VM clock' )
507 2059786f Bob Lantz
    vm.sendline( 'sudo -n date -s @%d' % seconds )
508 1bae1aab Bob Lantz
509
510 501a164e Bob Lantz
def sanityTest( vm ):
511
    "Run Mininet sanity test (pingall) in vm"
512 2059786f Bob Lantz
    vm.sendline( 'sudo -n mn --test pingall' )
513 14903d6a Bob Lantz
    if vm.expect( [ ' 0% dropped', pexpect.TIMEOUT ], timeout=45 ) == 0:
514 28165f7b Bob Lantz
        log( '* Sanity check OK' )
515 860bcc02 Bob Lantz
    else:
516 14903d6a Bob Lantz
        log( '* Sanity check FAILED' )
517 2f3e8c2b Bob Lantz
        log( '* Sanity check output:' )
518
        log( vm.before )
519 501a164e Bob Lantz
520
521
def coreTest( vm, prompt=Prompt ):
522
    "Run core tests (make test) in VM"
523 14903d6a Bob Lantz
    log( '* Making sure cgroups are mounted' )
524 2059786f Bob Lantz
    vm.sendline( 'sudo -n service cgroup-lite restart' )
525 860bcc02 Bob Lantz
    vm.expect( prompt )
526 2059786f Bob Lantz
    vm.sendline( 'sudo -n cgroups-mount' )
527 4daeeff0 Bob Lantz
    vm.expect( prompt )
528 14903d6a Bob Lantz
    log( '* Running make test' )
529 4daeeff0 Bob Lantz
    vm.sendline( 'cd ~/mininet; sudo make test' )
530 28165f7b Bob Lantz
    # We should change "make test" to report the number of
531
    # successful and failed tests. For now, we have to
532
    # know the time for each test, which means that this
533
    # script will have to change as we add more tests.
534
    for test in range( 0, 2 ):
535 dedb06b2 Bob Lantz
        if vm.expect( [ 'OK.*\r\n', 'FAILED.*\r\n', pexpect.TIMEOUT ], timeout=180 ) == 0:
536 28165f7b Bob Lantz
            log( '* Test', test, 'OK' )
537
        else:
538
            log( '* Test', test, 'FAILED' )
539 2f3e8c2b Bob Lantz
            log( '* Test', test, 'output:' )
540
            log( vm.before )
541 501a164e Bob Lantz
542 635e8f11 Bob Lantz
543
def installPexpect( vm, prompt=Prompt ):
544
    "install pexpect"
545 2059786f Bob Lantz
    vm.sendline( 'sudo -n apt-get -qy install python-pexpect' )
546 635e8f11 Bob Lantz
    vm.expect( prompt )
547
548
549
def noneTest( vm, prompt=Prompt ):
550 49994c89 Brian O'Connor
    "This test does nothing"
551 635e8f11 Bob Lantz
    installPexpect( vm, prompt )
552 49994c89 Brian O'Connor
    vm.sendline( 'echo' )
553
554 635e8f11 Bob Lantz
555 7bd9a79b Bob Lantz
def examplesquickTest( vm, prompt=Prompt ):
556
    "Quick test of mininet examples"
557 635e8f11 Bob Lantz
    installPexpect( vm, prompt )
558 2059786f Bob Lantz
    vm.sendline( 'sudo -n python ~/mininet/examples/test/runner.py -v -quick' )
559 7bd9a79b Bob Lantz
560
561
def examplesfullTest( vm, prompt=Prompt ):
562
    "Full (slow) test of mininet examples"
563 635e8f11 Bob Lantz
    installPexpect( vm, prompt )
564 2059786f Bob Lantz
    vm.sendline( 'sudo -n python ~/mininet/examples/test/runner.py -v' )
565 7bd9a79b Bob Lantz
566
567 b7548e68 Bob Lantz
def walkthroughTest( vm, prompt=Prompt ):
568
    "Test mininet walkthrough"
569 635e8f11 Bob Lantz
    installPexpect( vm, prompt )
570 2059786f Bob Lantz
    vm.sendline( 'sudo -n python ~/mininet/mininet/test/test_walkthrough.py -v' )
571 b7548e68 Bob Lantz
572
573 629e58ca Bob Lantz
def useTest( vm, prompt=Prompt ):
574
    "Use VM interactively - exit by pressing control-]"
575
    old = vm.logfile
576
    if old == stdout:
577
        # Avoid doubling every output character!
578
        log( '* Temporarily disabling logging to stdout' )
579
        vm.logfile = None
580
    log( '* Switching to interactive use - press control-] to exit' )
581
    vm.interact()
582
    if old == stdout:
583
        log( '* Restoring logging to stdout' )
584
        vm.logfile = stdout
585
586
587 7bd9a79b Bob Lantz
def checkOutBranch( vm, branch, prompt=Prompt ):
588 5f51abd1 Bob Lantz
    # This is a bit subtle; it will check out an existing branch (e.g. master)
589
    # if it exists; otherwise it will create a detached branch.
590
    # The branch will be rebased to its parent on origin.
591
    # This probably doesn't matter since we're running on a COW disk
592
    # anyway.
593
    vm.sendline( 'cd ~/mininet; git fetch --all; git checkout '
594
                 + branch + '; git pull --rebase origin ' + branch )
595 7bd9a79b Bob Lantz
    vm.expect( prompt )
596 2059786f Bob Lantz
    vm.sendline( 'sudo -n make install' )
597 7bd9a79b Bob Lantz
598
599 0294f5ec Bob Lantz
def interact( vm, tests, pre='', post='', prompt=Prompt ):
600 501a164e Bob Lantz
    "Interact with vm, which is a pexpect object"
601
    login( vm )
602
    log( '* Waiting for login...' )
603
    vm.expect( prompt )
604
    log( '* Sending hostname command' )
605
    vm.sendline( 'hostname' )
606
    log( '* Waiting for output' )
607
    vm.expect( prompt )
608
    log( '* Fetching Mininet VM install script' )
609 2286ef4b Bob Lantz
    branch = Branch if Branch else 'master'
610 501a164e Bob Lantz
    vm.sendline( 'wget '
611 2286ef4b Bob Lantz
                 'https://raw.github.com/mininet/mininet/%s/util/vm/'
612
                 'install-mininet-vm.sh' % branch )
613 501a164e Bob Lantz
    vm.expect( prompt )
614
    log( '* Running VM install script' )
615 2059786f Bob Lantz
    installcmd = 'bash -v install-mininet-vm.sh'
616 5f51abd1 Bob Lantz
    if Branch:
617 317d6482 Bob Lantz
        installcmd += ' ' + Branch
618 5f51abd1 Bob Lantz
    vm.sendline( installcmd )
619 501a164e Bob Lantz
    vm.expect ( 'password for mininet: ' )
620
    vm.sendline( 'mininet' )
621
    log( '* Waiting for script to complete... ' )
622
    # Gigantic timeout for now ;-(
623
    vm.expect( 'Done preparing Mininet', timeout=3600 )
624
    log( '* Completed successfully' )
625
    vm.expect( prompt )
626 fce7f5c5 Bob Lantz
    version = getMininetVersion( vm )
627
    vm.expect( prompt )
628
    log( '* Mininet version: ', version )
629 501a164e Bob Lantz
    log( '* Testing Mininet' )
630 0294f5ec Bob Lantz
    runTests( vm, tests=tests, pre=pre, post=post )
631 2286ef4b Bob Lantz
    # Ubuntu adds this because we install via a serial console,
632
    # but we want the VM to boot via the VM console. Otherwise
633
    # we get the message 'error: terminal "serial" not found'
634
    log( '* Disabling serial console' )
635
    vm.sendline( "sudo sed -i -e 's/^GRUB_TERMINAL=serial/#GRUB_TERMINAL=serial/' "
636
                "/etc/default/grub; sudo update-grub" )
637
    vm.expect( prompt )
638 14903d6a Bob Lantz
    log( '* Shutting down' )
639 4daeeff0 Bob Lantz
    vm.sendline( 'sync; sudo shutdown -h now' )
640 14903d6a Bob Lantz
    log( '* Waiting for EOF/shutdown' )
641 4daeeff0 Bob Lantz
    vm.read()
642 4556e06f Bob Lantz
    log( '* Interaction complete' )
643 fce7f5c5 Bob Lantz
    return version
644 4daeeff0 Bob Lantz
645
646 85dfac5c Bob Lantz
def cleanup():
647
    "Clean up leftover qemu-nbd processes and other junk"
648 14903d6a Bob Lantz
    call( [ 'sudo', 'pkill', '-9', 'qemu-nbd' ] )
649 85dfac5c Bob Lantz
650
651
def convert( cow, basename ):
652
    """Convert a qcow2 disk to a vmdk and put it a new directory
653
       basename: base name for output vmdk file"""
654 94954177 Bob Lantz
    vmdk = basename + '.vmdk'
655 14903d6a Bob Lantz
    log( '* Converting qcow2 to vmdk' )
656 85dfac5c Bob Lantz
    run( 'qemu-img convert -f qcow2 -O vmdk %s %s' % ( cow, vmdk ) )
657
    return vmdk
658
659
660 0038720c Bob Lantz
# Template for OVF - a very verbose format!
661
# In the best of all possible worlds, we might use an XML
662
# library to generate this, but a template is easier and
663
# possibly more concise!
664 e02cdc0c Bob Lantz
# Warning: XML file cannot begin with a newline!
665 0038720c Bob Lantz
666 e02cdc0c Bob Lantz
OVFTemplate = """<?xml version="1.0"?>
667 9e725cb2 Bob Lantz
<Envelope ovf:version="1.0" xml:lang="en-US"
668 0038720c Bob Lantz
    xmlns="http://schemas.dmtf.org/ovf/envelope/1"
669
    xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"
670
    xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
671
    xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
672
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
673
<References>
674 dfd79bde Bob Lantz
<File ovf:href="%(diskname)s" ovf:id="file1" ovf:size="%(filesize)d"/>
675 0038720c Bob Lantz
</References>
676
<DiskSection>
677
<Info>Virtual disk information</Info>
678 dfd79bde Bob Lantz
<Disk ovf:capacity="%(disksize)d" ovf:capacityAllocationUnits="byte"
679 5da93762 Bob Lantz
    ovf:diskId="vmdisk1" ovf:fileRef="file1"
680 75abd94b Bob Lantz
    ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"/>
681 0038720c Bob Lantz
</DiskSection>
682
<NetworkSection>
683
<Info>The list of logical networks</Info>
684
<Network ovf:name="nat">
685
<Description>The nat  network</Description>
686
</Network>
687
</NetworkSection>
688 e49c9d26 Brian O'Connor
<VirtualSystem ovf:id="%(vmname)s">
689
<Info>%(vminfo)s (%(name)s)</Info>
690
<Name>%(vmname)s</Name>
691 dfd79bde Bob Lantz
<OperatingSystemSection ovf:id="%(osid)d">
692
<Info>The kind of installed guest operating system</Info>
693
<Description>%(osname)s</Description>
694
</OperatingSystemSection>
695 0038720c Bob Lantz
<VirtualHardwareSection>
696
<Info>Virtual hardware requirements</Info>
697
<Item>
698
<rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
699
<rasd:Description>Number of Virtual CPUs</rasd:Description>
700 e49c9d26 Brian O'Connor
<rasd:ElementName>%(cpus)s virtual CPU(s)</rasd:ElementName>
701 0038720c Bob Lantz
<rasd:InstanceID>1</rasd:InstanceID>
702
<rasd:ResourceType>3</rasd:ResourceType>
703 e49c9d26 Brian O'Connor
<rasd:VirtualQuantity>%(cpus)s</rasd:VirtualQuantity>
704 0038720c Bob Lantz
</Item>
705
<Item>
706
<rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
707
<rasd:Description>Memory Size</rasd:Description>
708 dfd79bde Bob Lantz
<rasd:ElementName>%(mem)dMB of memory</rasd:ElementName>
709 0038720c Bob Lantz
<rasd:InstanceID>2</rasd:InstanceID>
710
<rasd:ResourceType>4</rasd:ResourceType>
711 dfd79bde Bob Lantz
<rasd:VirtualQuantity>%(mem)d</rasd:VirtualQuantity>
712 0038720c Bob Lantz
</Item>
713
<Item>
714
<rasd:Address>0</rasd:Address>
715
<rasd:Caption>scsiController0</rasd:Caption>
716
<rasd:Description>SCSI Controller</rasd:Description>
717
<rasd:ElementName>scsiController0</rasd:ElementName>
718
<rasd:InstanceID>4</rasd:InstanceID>
719
<rasd:ResourceSubType>lsilogic</rasd:ResourceSubType>
720
<rasd:ResourceType>6</rasd:ResourceType>
721
</Item>
722
<Item>
723
<rasd:AddressOnParent>0</rasd:AddressOnParent>
724
<rasd:ElementName>disk1</rasd:ElementName>
725
<rasd:HostResource>ovf:/disk/vmdisk1</rasd:HostResource>
726
<rasd:InstanceID>11</rasd:InstanceID>
727
<rasd:Parent>4</rasd:Parent>
728
<rasd:ResourceType>17</rasd:ResourceType>
729
</Item>
730
<Item>
731
<rasd:AddressOnParent>2</rasd:AddressOnParent>
732
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
733
<rasd:Connection>nat</rasd:Connection>
734
<rasd:Description>E1000 ethernet adapter on nat</rasd:Description>
735
<rasd:ElementName>ethernet0</rasd:ElementName>
736
<rasd:InstanceID>12</rasd:InstanceID>
737
<rasd:ResourceSubType>E1000</rasd:ResourceSubType>
738
<rasd:ResourceType>10</rasd:ResourceType>
739
</Item>
740
<Item>
741
<rasd:Address>0</rasd:Address>
742
<rasd:Caption>usb</rasd:Caption>
743
<rasd:Description>USB Controller</rasd:Description>
744
<rasd:ElementName>usb</rasd:ElementName>
745
<rasd:InstanceID>9</rasd:InstanceID>
746
<rasd:ResourceType>23</rasd:ResourceType>
747
</Item>
748
</VirtualHardwareSection>
749
</VirtualSystem>
750
</Envelope>
751 662fb712 Bob Lantz
"""
752
753 d4279559 Bob Lantz
754 e49c9d26 Brian O'Connor
def generateOVF( name, osname, osid, diskname, disksize, mem=1024, cpus=1,
755
                 vmname='Mininet-VM', vminfo='A Mininet Virtual Machine' ):
756 0038720c Bob Lantz
    """Generate (and return) OVF file "name.ovf"
757
       name: root name of OVF file to generate
758 dfd79bde Bob Lantz
       osname: OS name for OVF (Ubuntu | Ubuntu 64-bit)
759
       osid: OS ID for OVF (93 | 94 )
760 0038720c Bob Lantz
       diskname: name of disk file
761
       disksize: size of virtual disk in bytes
762 e49c9d26 Brian O'Connor
       mem: VM memory size in MB
763
       cpus: # of virtual CPUs
764
       vmname: Name for VM (default name when importing)
765
       vmimfo: Brief description of VM for OVF"""
766 0038720c Bob Lantz
    ovf = name + '.ovf'
767
    filesize = stat( diskname )[ ST_SIZE ]
768 dfd79bde Bob Lantz
    params = dict( osname=osname, osid=osid, diskname=diskname,
769
                   filesize=filesize, disksize=disksize, name=name,
770 e49c9d26 Brian O'Connor
                   mem=mem, cpus=cpus, vmname=vmname, vminfo=vminfo )
771 dfd79bde Bob Lantz
    xmltext = OVFTemplate % params
772 0038720c Bob Lantz
    with open( ovf, 'w+' ) as f:
773 662fb712 Bob Lantz
        f.write( xmltext )
774 0038720c Bob Lantz
    return ovf
775 662fb712 Bob Lantz
776
777 d4279559 Bob Lantz
def qcow2size( qcow2 ):
778
    "Return virtual disk size (in bytes) of qcow2 image"
779 0333d3db Bob Lantz
    output = check_output( [ 'qemu-img', 'info', qcow2 ] )
780
    try:
781
        assert 'format: qcow' in output
782
        bytes = int( re.findall( '(\d+) bytes', output )[ 0 ] )
783
    except:
784
        raise Exception( 'Could not determine size of %s' % qcow2 )
785 d4279559 Bob Lantz
    return bytes
786
787
788 568f6424 Bob Lantz
def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ):
789 0294f5ec Bob Lantz
    """Build a Mininet VM; return vmdk and vdisk size
790
       tests: tests to run
791
       pre: command line to run in VM before tests
792
       post: command line to run in VM after tests
793 568f6424 Bob Lantz
       prompt: shell prompt (default '$ ')
794
       memory: memory size in MB"""
795 fce7f5c5 Bob Lantz
    global LogFile, Zip
796 85dfac5c Bob Lantz
    start = time()
797 700c5bf5 Bob Lantz
    lstart = localtime()
798
    date = strftime( '%y%m%d-%H-%M-%S', lstart)
799
    ovfdate = strftime( '%y%m%d', lstart )
800 1dfa7776 Bob Lantz
    dir = 'mn-%s-%s' % ( flavor, date )
801 c90fb34d Bob Lantz
    if Branch:
802
        dir = 'mn-%s-%s-%s' % ( Branch, flavor, date )
803 1dfa7776 Bob Lantz
    try:
804
        os.mkdir( dir )
805
    except:
806
        raise Exception( "Failed to create build directory %s" % dir )
807 94954177 Bob Lantz
    os.chdir( dir )
808 803a1a54 Bob Lantz
    LogFile = open( 'build.log', 'w' )
809 20005f5b Bob Lantz
    log( '* Logging to', abspath( LogFile.name ) )
810 14903d6a Bob Lantz
    log( '* Created working directory', dir )
811 1dfa7776 Bob Lantz
    image, kernel, initrd = findBaseImage( flavor )
812 9e725cb2 Bob Lantz
    basename = 'mininet-' + flavor
813
    volume = basename + '.qcow2'
814 fa1758b9 Bob Lantz
    run( 'qemu-img create -f qcow2 -b %s %s' % ( image, volume ) )
815 14903d6a Bob Lantz
    log( '* VM image for', flavor, 'created as', volume )
816 d4279559 Bob Lantz
    if LogToConsole:
817
        logfile = stdout
818
    else:
819
        logfile = open( flavor + '.log', 'w+' )
820 14903d6a Bob Lantz
    log( '* Logging results to', abspath( logfile.name ) )
821 568f6424 Bob Lantz
    vm = boot( volume, kernel, initrd, logfile, memory=memory )
822 0294f5ec Bob Lantz
    version = interact( vm, tests=tests, pre=pre, post=post )
823 d4279559 Bob Lantz
    size = qcow2size( volume )
824 dfd79bde Bob Lantz
    arch = archFor( flavor )
825
    vmdk = convert( volume, basename='mininet-vm-' + arch )
826 d4279559 Bob Lantz
    if not SaveQCOW2:
827
        log( '* Removing qcow2 volume', volume )
828
        os.remove( volume )
829 f605a4e4 Bob Lantz
    log( '* Converted VM image stored as', abspath( vmdk ) )
830 700c5bf5 Bob Lantz
    ovfname = 'mininet-%s-%s-%s' % ( version, ovfdate, OSVersion( flavor ) )
831 dfd79bde Bob Lantz
    osname, osid = OVFOSNameID( flavor )
832
    ovf = generateOVF( name=ovfname, osname=osname, osid=osid,
833
                       diskname=vmdk, disksize=size )
834 0038720c Bob Lantz
    log( '* Generated OVF descriptor file', ovf )
835 fce7f5c5 Bob Lantz
    if Zip:
836
        log( '* Generating .zip file' )
837
        run( 'zip %s-ovf.zip %s %s' % ( ovfname, ovf, vmdk ) )
838 85dfac5c Bob Lantz
    end = time()
839
    elapsed = end - start
840 14903d6a Bob Lantz
    log( '* Results logged to', abspath( logfile.name ) )
841
    log( '* Completed in %.2f seconds' % elapsed )
842
    log( '* %s VM build DONE!!!!! :D' % flavor )
843 bbf808c3 Bob Lantz
    os.chdir( '..' )
844 85dfac5c Bob Lantz
845 501a164e Bob Lantz
846 9e0c1549 Bob Lantz
def runTests( vm, tests=None, pre='', post='', prompt=Prompt, uninstallNtpd=False ):
847 7bd9a79b Bob Lantz
    "Run tests (list) in vm (pexpect object)"
848 060d46a2 Bob Lantz
    # We disable ntpd and set the time so that ntpd won't be
849 9e0c1549 Bob Lantz
    # messing with the time during tests. Set to true for a COW
850
    # disk and False for a non-COW disk.
851
    if uninstallNtpd:
852
        removeNtpd( vm )
853 e4db6981 Bob Lantz
        vm.expect( prompt )
854 8b215af8 Brian O'Connor
    if Branch:
855
        checkOutBranch( vm, branch=Branch )
856
        vm.expect( prompt )
857 7bd9a79b Bob Lantz
    if not tests:
858 0294f5ec Bob Lantz
        tests = []
859
    if pre:
860
        log( '* Running command', pre )
861
        vm.sendline( pre )
862
        vm.expect( prompt )
863 7bd9a79b Bob Lantz
    testfns = testDict()
864 0294f5ec Bob Lantz
    if tests:
865
        log( '* Running tests' )
866 7bd9a79b Bob Lantz
    for test in tests:
867
        if test not in testfns:
868
            raise Exception( 'Unknown test: ' + test )
869
        log( '* Running test', test )
870
        fn = testfns[ test ]
871
        fn( vm )
872
        vm.expect( prompt )
873 0294f5ec Bob Lantz
    if post:
874
        log( '* Running post-test command', post )
875
        vm.sendline( post )
876
        vm.expect( prompt )
877 7bd9a79b Bob Lantz
878 fce7f5c5 Bob Lantz
def getMininetVersion( vm ):
879
    "Run mn to find Mininet version in VM"
880
    vm.sendline( '~/mininet/bin/mn --version' )
881
    # Eat command line echo, then read output line
882
    vm.readline()
883
    version = vm.readline().strip()
884
    return version
885
886
887 86af067e Jonathan Hart
def bootAndRun( image, prompt=Prompt, memory=1024, cpuCores=1, outputFile=None,
888 8b215af8 Brian O'Connor
                runFunction=None, **runArgs ):
889 501a164e Bob Lantz
    """Boot and test VM
890 0294f5ec Bob Lantz
       tests: list of tests to run
891
       pre: command line to run in VM before tests
892
       post: command line to run in VM after tests
893 568f6424 Bob Lantz
       prompt: shell prompt (default '$ ')
894 86af067e Jonathan Hart
       memory: VM memory size in MB
895
       cpuCores: number of CPU cores to use"""
896 501a164e Bob Lantz
    bootTestStart = time()
897
    basename = path.basename( image )
898
    image = abspath( image )
899
    tmpdir = mkdtemp( prefix='test-' + basename )
900 7bd9a79b Bob Lantz
    log( '* Using tmpdir', tmpdir )
901
    cow = path.join( tmpdir, basename + '.qcow2' )
902
    log( '* Creating COW disk', cow )
903 501a164e Bob Lantz
    run( 'qemu-img create -f qcow2 -b %s %s' % ( image, cow ) )
904
    log( '* Extracting kernel and initrd' )
905
    kernel, initrd = extractKernel( image, flavor=basename, imageDir=tmpdir )
906
    if LogToConsole:
907
        logfile = stdout
908
    else:
909 7bd9a79b Bob Lantz
        logfile = NamedTemporaryFile( prefix=basename,
910
                                      suffix='.testlog', delete=False )
911 501a164e Bob Lantz
    log( '* Logging VM output to', logfile.name )
912 568f6424 Bob Lantz
    vm = boot( cow=cow, kernel=kernel, initrd=initrd, logfile=logfile,
913 86af067e Jonathan Hart
               memory=memory, cpuCores=cpuCores )
914 501a164e Bob Lantz
    login( vm )
915 2ca1ea92 Bob Lantz
    log( '* Waiting for prompt after login' )
916 501a164e Bob Lantz
    vm.expect( prompt )
917 8b215af8 Brian O'Connor
    # runFunction should begin with sendline and should eat its last prompt
918
    if runFunction:
919
        runFunction( vm, **runArgs )
920 501a164e Bob Lantz
    log( '* Shutting down' )
921 2059786f Bob Lantz
    vm.sendline( 'sudo -n shutdown -h now ' )
922 501a164e Bob Lantz
    log( '* Waiting for shutdown' )
923
    vm.wait()
924 fb51cdac Brian O'Connor
    if outputFile:
925
        log( '* Saving temporary image to %s' % outputFile )
926
        convert( cow, outputFile )
927 501a164e Bob Lantz
    log( '* Removing temporary dir', tmpdir )
928
    srun( 'rm -rf ' + tmpdir )
929
    elapsed = time() - bootTestStart
930
    log( '* Boot and test completed in %.2f seconds' % elapsed )
931
932
933 d4279559 Bob Lantz
def buildFlavorString():
934
    "Return string listing valid build flavors"
935 7bd9a79b Bob Lantz
    return 'valid build flavors: ( %s )' % ' '.join( sorted( isoURLs ) )
936
937
938
def testDict():
939
    "Return dict of tests in this module"
940
    suffix = 'Test'
941
    trim = len( suffix )
942
    fdict = dict( [ ( fname[ : -trim ], f ) for fname, f in
943
                    inspect.getmembers( modules[ __name__ ],
944
                                    inspect.isfunction )
945
                  if fname.endswith( suffix ) ] )
946
    return fdict
947
948
949
def testString():
950
    "Return string listing valid tests"
951
    return 'valid tests: ( %s )' % ' '.join( testDict().keys() )
952 fa1758b9 Bob Lantz
953 14903d6a Bob Lantz
954 85dfac5c Bob Lantz
def parseArgs():
955
    "Parse command line arguments and run"
956 6159e923 Bob Lantz
    global LogToConsole, NoKVM, Branch, Zip, TIMEOUT, Forward
957 d4279559 Bob Lantz
    parser = argparse.ArgumentParser( description='Mininet VM build script',
958 7bd9a79b Bob Lantz
                                      epilog=buildFlavorString() + ' ' +
959
                                      testString() )
960 d4279559 Bob Lantz
    parser.add_argument( '-v', '--verbose', action='store_true',
961
                        help='send VM output to console rather than log file' )
962
    parser.add_argument( '-d', '--depend', action='store_true',
963 1dfa7776 Bob Lantz
                         help='install dependencies for this script' )
964 d4279559 Bob Lantz
    parser.add_argument( '-l', '--list', action='store_true',
965 7bd9a79b Bob Lantz
                         help='list valid build flavors and tests' )
966 d4279559 Bob Lantz
    parser.add_argument( '-c', '--clean', action='store_true',
967 85dfac5c Bob Lantz
                         help='clean up leftover build junk (e.g. qemu-nbd)' )
968 d4279559 Bob Lantz
    parser.add_argument( '-q', '--qcow2', action='store_true',
969
                         help='save qcow2 image rather than deleting it' )
970 0038720c Bob Lantz
    parser.add_argument( '-n', '--nokvm', action='store_true',
971
                         help="Don't use kvm - use tcg emulation instead" )
972 568f6424 Bob Lantz
    parser.add_argument( '-m', '--memory', metavar='MB', type=int,
973 3151804c Bob Lantz
                        default=1024, help='VM memory size in MB' )
974 7bd9a79b Bob Lantz
    parser.add_argument( '-i', '--image', metavar='image', default=[],
975
                         action='append',
976
                         help='Boot and test an existing VM image' )
977
    parser.add_argument( '-t', '--test', metavar='test', default=[],
978 0294f5ec Bob Lantz
                         action='append',
979
                         help='specify a test to run' )
980 3151804c Bob Lantz
    parser.add_argument( '-w', '--timeout', metavar='timeout', type=int,
981
                            default=0, help='set expect timeout' )
982 0294f5ec Bob Lantz
    parser.add_argument( '-r', '--run', metavar='cmd', default='',
983
                         help='specify a command line to run before tests' )
984
    parser.add_argument( '-p', '--post', metavar='cmd', default='',
985
                         help='specify a command line to run after tests' )
986 7bd9a79b Bob Lantz
    parser.add_argument( '-b', '--branch', metavar='branch',
987 5f51abd1 Bob Lantz
                         help='branch to install and/or check out and test' )
988 85dfac5c Bob Lantz
    parser.add_argument( 'flavor', nargs='*',
989 d4279559 Bob Lantz
                         help='VM flavor(s) to build (e.g. raring32server)' )
990 fce7f5c5 Bob Lantz
    parser.add_argument( '-z', '--zip', action='store_true',
991 0294f5ec Bob Lantz
                         help='archive .ovf and .vmdk into .zip file' )
992 55e48112 Bob Lantz
    parser.add_argument( '-o', '--out',
993 fb51cdac Brian O'Connor
                         help='output file for test image (vmdk)' )
994 6159e923 Bob Lantz
    parser.add_argument( '-f', '--forward', default=[], action='append',
995
                         help='forward VM ports to local server, e.g. tcp:5555::22' )
996 d4279559 Bob Lantz
    args = parser.parse_args()
997 85dfac5c Bob Lantz
    if args.depend:
998
        depend()
999
    if args.list:
1000 d4279559 Bob Lantz
        print buildFlavorString()
1001 85dfac5c Bob Lantz
    if args.clean:
1002
        cleanup()
1003 d4279559 Bob Lantz
    if args.verbose:
1004
        LogToConsole = True
1005 0038720c Bob Lantz
    if args.nokvm:
1006
        NoKVM = True
1007 7bd9a79b Bob Lantz
    if args.branch:
1008
        Branch = args.branch
1009 fce7f5c5 Bob Lantz
    if args.zip:
1010
        Zip = True
1011 3151804c Bob Lantz
    if args.timeout:
1012
        TIMEOUT = args.timeout
1013 6159e923 Bob Lantz
    if args.forward:
1014
        Forward = args.forward
1015 e0b50c8a Bob Lantz
    if not args.test and not args.run and not args.post:
1016 0294f5ec Bob Lantz
        args.test = [ 'sanity', 'core' ]
1017 d4279559 Bob Lantz
    for flavor in args.flavor:
1018 fa1758b9 Bob Lantz
        if flavor not in isoURLs:
1019 d4279559 Bob Lantz
            print "Unknown build flavor:", flavor
1020
            print buildFlavorString()
1021 14903d6a Bob Lantz
            break
1022 b7548e68 Bob Lantz
        try:
1023 568f6424 Bob Lantz
            build( flavor, tests=args.test, pre=args.run, post=args.post,
1024
                   memory=args.memory )
1025 b7548e68 Bob Lantz
        except Exception as e:
1026
            log( '* BUILD FAILED with exception: ', e )
1027
            exit( 1 )
1028 7bd9a79b Bob Lantz
    for image in args.image:
1029 8b215af8 Brian O'Connor
        bootAndRun( image, runFunction=runTests, tests=args.test, pre=args.run,
1030 9e0c1549 Bob Lantz
                    post=args.post, memory=args.memory, outputFile=args.out,
1031
                    uninstallNtpd=True  )
1032 501a164e Bob Lantz
    if not ( args.depend or args.list or args.clean or args.flavor
1033 7bd9a79b Bob Lantz
             or args.image ):
1034 85dfac5c Bob Lantz
        parser.print_help()
1035 d4279559 Bob Lantz
1036 85dfac5c Bob Lantz
1037
if __name__ == '__main__':
1038
    parseArgs()