mininet / util / vm / build.py @ 700c5bf5
History | View | Annotate | Download (30.3 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 | fce7f5c5 | Bob Lantz | Zip = False # Archive .ovf and .vmdk into a .zip file |
56 | d4279559 | Bob Lantz | |
57 | 4daeeff0 | Bob Lantz | VMImageDir = os.environ[ 'HOME' ] + '/vm-images' |
58 | |||
59 | 501a164e | Bob Lantz | Prompt = '\$ ' # Shell prompt that pexpect will wait for |
60 | |||
61 | fa1758b9 | Bob Lantz | isoURLs = { |
62 | dbcfda77 | Bob Lantz | 'precise32server':
|
63 | 'http://mirrors.kernel.org/ubuntu-releases/12.04/'
|
||
64 | 662fb712 | Bob Lantz | 'ubuntu-12.04.3-server-i386.iso',
|
65 | dbcfda77 | Bob Lantz | 'precise64server':
|
66 | 'http://mirrors.kernel.org/ubuntu-releases/12.04/'
|
||
67 | 662fb712 | Bob Lantz | 'ubuntu-12.04.3-server-amd64.iso',
|
68 | a1557958 | Bob Lantz | 'quantal32server':
|
69 | fa1758b9 | Bob Lantz | 'http://mirrors.kernel.org/ubuntu-releases/12.10/'
|
70 | 'ubuntu-12.10-server-i386.iso',
|
||
71 | a1557958 | Bob Lantz | 'quantal64server':
|
72 | dbcfda77 | Bob Lantz | 'http://mirrors.kernel.org/ubuntu-releases/12.10/'
|
73 | 'ubuntu-12.10-server-amd64.iso',
|
||
74 | 85dfac5c | Bob Lantz | 'raring32server':
|
75 | fa1758b9 | Bob Lantz | 'http://mirrors.kernel.org/ubuntu-releases/13.04/'
|
76 | 'ubuntu-13.04-server-i386.iso',
|
||
77 | 85dfac5c | Bob Lantz | 'raring64server':
|
78 | fa1758b9 | Bob Lantz | 'http://mirrors.kernel.org/ubuntu-releases/13.04/'
|
79 | 'ubuntu-13.04-server-amd64.iso',
|
||
80 | 20ba2959 | Bob Lantz | 'saucy32server':
|
81 | 'http://mirrors.kernel.org/ubuntu-releases/13.10/'
|
||
82 | 'ubuntu-13.10-server-i386.iso',
|
||
83 | 'saucy64server':
|
||
84 | 'http://mirrors.kernel.org/ubuntu-releases/13.10/'
|
||
85 | 'ubuntu-13.10-server-amd64.iso',
|
||
86 | 4daeeff0 | Bob Lantz | } |
87 | |||
88 | 662f2447 | Bob Lantz | |
89 | fce7f5c5 | Bob Lantz | def OSVersion( flavor ): |
90 | "Return full OS version string for build flavor"
|
||
91 | urlbase = path.basename( isoURLs.get( flavor, 'unknown' ) )
|
||
92 | return path.splitext( urlbase )[ 0 ] |
||
93 | |||
94 | |||
95 | 803a1a54 | Bob Lantz | LogStartTime = time() |
96 | LogFile = None
|
||
97 | 14903d6a | Bob Lantz | |
98 | def log( *args, **kwargs ): |
||
99 | """Simple log function: log( message along with local and elapsed time
|
||
100 | cr: False/0 for no CR"""
|
||
101 | cr = kwargs.get( 'cr', True ) |
||
102 | 803a1a54 | Bob Lantz | elapsed = time() - LogStartTime |
103 | 14903d6a | Bob Lantz | clocktime = strftime( '%H:%M:%S', localtime() )
|
104 | msg = ' '.join( str( arg ) for arg in args ) |
||
105 | output = '%s [ %.3f ] %s' % ( clocktime, elapsed, msg )
|
||
106 | if cr:
|
||
107 | print output
|
||
108 | else:
|
||
109 | print output,
|
||
110 | 803a1a54 | Bob Lantz | # Optionally mirror to LogFile
|
111 | if type( LogFile ) is file: |
||
112 | if cr:
|
||
113 | output += '\n'
|
||
114 | LogFile.write( output ) |
||
115 | 662f2447 | Bob Lantz | LogFile.flush() |
116 | 14903d6a | Bob Lantz | |
117 | 4daeeff0 | Bob Lantz | |
118 | def run( cmd, **kwargs ): |
||
119 | "Convenient interface to check_output"
|
||
120 | 14903d6a | Bob Lantz | log( '-', cmd )
|
121 | 4daeeff0 | Bob Lantz | cmd = cmd.split() |
122 | 0038720c | Bob Lantz | arg0 = cmd[ 0 ]
|
123 | if not find_executable( arg0 ): |
||
124 | raise Exception( 'Cannot find executable "%s";' % arg0 + |
||
125 | 'you might try %s --depend' % argv[ 0 ] ) |
||
126 | 4daeeff0 | Bob Lantz | return check_output( cmd, **kwargs )
|
127 | |||
128 | |||
129 | def srun( cmd, **kwargs ): |
||
130 | "Run + sudo"
|
||
131 | return run( 'sudo ' + cmd, **kwargs ) |
||
132 | |||
133 | |||
134 | 0038720c | Bob Lantz | # BL: we should probably have a "checkDepend()" which
|
135 | # checks to make sure all dependencies are satisfied!
|
||
136 | |||
137 | 85dfac5c | Bob Lantz | def depend(): |
138 | fa1758b9 | Bob Lantz | "Install package dependencies"
|
139 | 14903d6a | Bob Lantz | log( '* Installing package dependencies' )
|
140 | 85dfac5c | Bob Lantz | run( 'sudo apt-get -y update' )
|
141 | run( 'sudo apt-get install -y'
|
||
142 | ' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
|
||
143 | ' e2fsprogs '
|
||
144 | ' landscape-client'
|
||
145 | fce7f5c5 | Bob Lantz | ' python-setuptools mtools zip' )
|
146 | 85dfac5c | Bob Lantz | run( 'sudo easy_install pexpect' )
|
147 | 4daeeff0 | Bob Lantz | |
148 | |||
149 | def popen( cmd ): |
||
150 | "Convenient interface to popen"
|
||
151 | 14903d6a | Bob Lantz | log( cmd ) |
152 | 4daeeff0 | Bob Lantz | cmd = cmd.split() |
153 | return Popen( cmd )
|
||
154 | |||
155 | |||
156 | def remove( fname ): |
||
157 | 501a164e | Bob Lantz | "Remove a file, ignoring errors"
|
158 | try:
|
||
159 | os.remove( fname ) |
||
160 | except OSError: |
||
161 | pass
|
||
162 | 4daeeff0 | Bob Lantz | |
163 | |||
164 | fa1758b9 | Bob Lantz | def findiso( flavor ): |
165 | "Find iso, fetching it if it's not there already"
|
||
166 | url = isoURLs[ flavor ] |
||
167 | name = path.basename( url ) |
||
168 | iso = path.join( VMImageDir, name ) |
||
169 | f605a4e4 | Bob Lantz | if not path.exists( iso ) or ( stat( iso )[ ST_MODE ] & 0777 != 0444 ): |
170 | fa1758b9 | Bob Lantz | log( '* Retrieving', url )
|
171 | f605a4e4 | Bob Lantz | run( 'curl -C - -o %s %s' % ( iso, url ) )
|
172 | 662fb712 | Bob Lantz | if 'ISO' not in run( 'file ' + iso ): |
173 | os.remove( iso ) |
||
174 | raise Exception( 'findiso: could not download iso from ' + url ) |
||
175 | fa1758b9 | Bob Lantz | # Write-protect iso, signaling it is complete
|
176 | log( '* Write-protecting iso', iso)
|
||
177 | os.chmod( iso, 0444 )
|
||
178 | log( '* Using iso', iso )
|
||
179 | return iso
|
||
180 | 85dfac5c | Bob Lantz | |
181 | |||
182 | 94954177 | Bob Lantz | def attachNBD( cow, flags='' ): |
183 | """Attempt to attach a COW disk image and return its nbd device
|
||
184 | fa1758b9 | Bob Lantz | flags: additional flags for qemu-nbd (e.g. -r for readonly)"""
|
185 | 94954177 | Bob Lantz | # qemu-nbd requires an absolute path
|
186 | cow = abspath( cow ) |
||
187 | 14903d6a | Bob Lantz | log( '* Checking for unused /dev/nbdX device ' )
|
188 | 85dfac5c | Bob Lantz | for i in range ( 0, 63 ): |
189 | nbd = '/dev/nbd%d' % i
|
||
190 | # Check whether someone's already messing with that device
|
||
191 | if call( [ 'pgrep', '-f', nbd ] ) == 0: |
||
192 | continue
|
||
193 | 14903d6a | Bob Lantz | srun( 'modprobe nbd max-part=64' )
|
194 | 94954177 | Bob Lantz | srun( 'qemu-nbd %s -c %s %s' % ( flags, nbd, cow ) )
|
195 | 14903d6a | Bob Lantz | print
|
196 | 85dfac5c | Bob Lantz | return nbd
|
197 | raise Exception( "Error: could not find unused /dev/nbdX device" ) |
||
198 | |||
199 | |||
200 | 94954177 | Bob Lantz | def detachNBD( nbd ): |
201 | "Detatch an nbd device"
|
||
202 | 85dfac5c | Bob Lantz | srun( 'qemu-nbd -d ' + nbd )
|
203 | 4daeeff0 | Bob Lantz | |
204 | |||
205 | 501a164e | Bob Lantz | def extractKernel( image, flavor, imageDir=VMImageDir ): |
206 | 1dfa7776 | Bob Lantz | "Extract kernel and initrd from base image"
|
207 | 501a164e | Bob Lantz | kernel = path.join( imageDir, flavor + '-vmlinuz' )
|
208 | initrd = path.join( imageDir, flavor + '-initrd' )
|
||
209 | f605a4e4 | Bob Lantz | if path.exists( kernel ) and ( stat( image )[ ST_MODE ] & 0777 ) == 0444: |
210 | 1dfa7776 | Bob Lantz | # If kernel is there, then initrd should also be there
|
211 | return kernel, initrd
|
||
212 | f605a4e4 | Bob Lantz | log( '* Extracting kernel to', kernel )
|
213 | nbd = attachNBD( image, flags='-r' )
|
||
214 | fa1758b9 | Bob Lantz | print srun( 'partx ' + nbd ) |
215 | # Assume kernel is in partition 1/boot/vmlinuz*generic for now
|
||
216 | part = nbd + 'p1'
|
||
217 | 14903d6a | Bob Lantz | mnt = mkdtemp() |
218 | 1dfa7776 | Bob Lantz | srun( 'mount -o ro %s %s' % ( part, mnt ) )
|
219 | fa1758b9 | Bob Lantz | kernsrc = glob( '%s/boot/vmlinuz*generic' % mnt )[ 0 ] |
220 | 1dfa7776 | Bob Lantz | initrdsrc = glob( '%s/boot/initrd*generic' % mnt )[ 0 ] |
221 | srun( 'cp %s %s' % ( initrdsrc, initrd ) )
|
||
222 | srun( 'chmod 0444 ' + initrd )
|
||
223 | srun( 'cp %s %s' % ( kernsrc, kernel ) )
|
||
224 | srun( 'chmod 0444 ' + kernel )
|
||
225 | 14903d6a | Bob Lantz | srun( 'umount ' + mnt )
|
226 | run( 'rmdir ' + mnt )
|
||
227 | f605a4e4 | Bob Lantz | detachNBD( nbd ) |
228 | 1dfa7776 | Bob Lantz | return kernel, initrd
|
229 | fa1758b9 | Bob Lantz | |
230 | |||
231 | def findBaseImage( flavor, size='8G' ): |
||
232 | "Return base VM image and kernel, creating them if needed"
|
||
233 | ad5a0e42 | Bob Lantz | image = path.join( VMImageDir, flavor + '-base.qcow2' )
|
234 | fa1758b9 | Bob Lantz | if path.exists( image ):
|
235 | # Detect race condition with multiple builds
|
||
236 | perms = stat( image )[ ST_MODE ] & 0777
|
||
237 | if perms != 0444: |
||
238 | raise Exception( 'Error - %s is writable ' % image + |
||
239 | '; are multiple builds running?' )
|
||
240 | else:
|
||
241 | # We create VMImageDir here since we are called first
|
||
242 | run( 'mkdir -p %s' % VMImageDir )
|
||
243 | iso = findiso( flavor ) |
||
244 | log( '* Creating image file', image )
|
||
245 | ad5a0e42 | Bob Lantz | run( 'qemu-img create -f qcow2 %s %s' % ( image, size ) )
|
246 | fa1758b9 | Bob Lantz | installUbuntu( iso, image ) |
247 | # Write-protect image, also signaling it is complete
|
||
248 | log( '* Write-protecting image', image)
|
||
249 | os.chmod( image, 0444 )
|
||
250 | 1dfa7776 | Bob Lantz | kernel, initrd = extractKernel( image, flavor ) |
251 | f605a4e4 | Bob Lantz | log( '* Using base image', image, 'and kernel', kernel ) |
252 | 1dfa7776 | Bob Lantz | return image, kernel, initrd
|
253 | fa1758b9 | Bob Lantz | |
254 | |||
255 | f605a4e4 | Bob Lantz | # Kickstart and Preseed files for Ubuntu/Debian installer
|
256 | #
|
||
257 | # Comments: this is really clunky and painful. If Ubuntu
|
||
258 | # gets their act together and supports kickstart a bit better
|
||
259 | # then we can get rid of preseed and even use this as a
|
||
260 | # Fedora installer as well.
|
||
261 | #
|
||
262 | # Another annoying thing about Ubuntu is that it can't just
|
||
263 | # install a normal system from the iso - it has to download
|
||
264 | # junk from the internet, making this house of cards even
|
||
265 | # more precarious.
|
||
266 | |||
267 | KickstartText ="""
|
||
268 | #Generated by Kickstart Configurator
|
||
269 | #platform=x86
|
||
270 |
|
||
271 | #System language
|
||
272 | lang en_US
|
||
273 | #Language modules to install
|
||
274 | langsupport en_US
|
||
275 | #System keyboard
|
||
276 | keyboard us
|
||
277 | #System mouse
|
||
278 | mouse
|
||
279 | #System timezone
|
||
280 | timezone America/Los_Angeles
|
||
281 | #Root password
|
||
282 | rootpw --disabled
|
||
283 | #Initial user
|
||
284 | user mininet --fullname "mininet" --password "mininet"
|
||
285 | #Use text mode install
|
||
286 | text
|
||
287 | #Install OS instead of upgrade
|
||
288 | install
|
||
289 | #Use CDROM installation media
|
||
290 | cdrom
|
||
291 | #System bootloader configuration
|
||
292 | bootloader --location=mbr
|
||
293 | #Clear the Master Boot Record
|
||
294 | zerombr yes
|
||
295 | #Partition clearing information
|
||
296 | clearpart --all --initlabel
|
||
297 | #Automatic partitioning
|
||
298 | autopart
|
||
299 | #System authorization infomation
|
||
300 | auth --useshadow --enablemd5
|
||
301 | #Firewall configuration
|
||
302 | firewall --disabled
|
||
303 | #Do not configure the X Window System
|
||
304 | skipx
|
||
305 | """
|
||
306 | |||
307 | # Tell the Ubuntu/Debian installer to stop asking stupid questions
|
||
308 | |||
309 | PreseedText = """
|
||
310 | d-i mirror/country string manual
|
||
311 | d-i mirror/http/hostname string mirrors.kernel.org
|
||
312 | d-i mirror/http/directory string /ubuntu
|
||
313 | d-i mirror/http/proxy string
|
||
314 | d-i partman/confirm_write_new_label boolean true
|
||
315 | d-i partman/choose_partition select finish
|
||
316 | d-i partman/confirm boolean true
|
||
317 | d-i partman/confirm_nooverwrite boolean true
|
||
318 | d-i user-setup/allow-password-weak boolean true
|
||
319 | d-i finish-install/reboot_in_progress note
|
||
320 | d-i debian-installer/exit/poweroff boolean true
|
||
321 | """
|
||
322 | |||
323 | fa1758b9 | Bob Lantz | def makeKickstartFloppy(): |
324 | "Create and return kickstart floppy, kickstart, preseed"
|
||
325 | kickstart = 'ks.cfg'
|
||
326 | with open( kickstart, 'w' ) as f: |
||
327 | f605a4e4 | Bob Lantz | f.write( KickstartText ) |
328 | fa1758b9 | Bob Lantz | preseed = 'ks.preseed'
|
329 | with open( preseed, 'w' ) as f: |
||
330 | f605a4e4 | Bob Lantz | f.write( PreseedText ) |
331 | fa1758b9 | Bob Lantz | # Create floppy and copy files to it
|
332 | floppy = 'ksfloppy.img'
|
||
333 | f605a4e4 | Bob Lantz | run( 'qemu-img create %s 1440k' % floppy )
|
334 | run( 'mkfs -t msdos ' + floppy )
|
||
335 | fa1758b9 | Bob Lantz | run( 'mcopy -i %s %s ::/' % ( floppy, kickstart ) )
|
336 | run( 'mcopy -i %s %s ::/' % ( floppy, preseed ) )
|
||
337 | return floppy, kickstart, preseed
|
||
338 | |||
339 | |||
340 | d4279559 | Bob Lantz | def archFor( filepath ): |
341 | "Guess architecture for file path"
|
||
342 | 1dfa7776 | Bob Lantz | name = path.basename( filepath ) |
343 | 6d3cb5bc | Bob Lantz | if 'amd64' in name or 'x86_64' in name: |
344 | d4279559 | Bob Lantz | arch = 'x86_64'
|
345 | 6d3cb5bc | Bob Lantz | # Beware of version 64 of a 32-bit OS
|
346 | elif 'i386' in name or '32' in name or 'x86' in name: |
||
347 | d4279559 | Bob Lantz | arch = 'i386'
|
348 | 6d3cb5bc | Bob Lantz | elif '64' in name: |
349 | arch = 'x86_64'
|
||
350 | fa1758b9 | Bob Lantz | else:
|
351 | 6d3cb5bc | Bob Lantz | log( "Error: can't discern CPU for name", name )
|
352 | fa1758b9 | Bob Lantz | exit( 1 ) |
353 | d4279559 | Bob Lantz | return arch
|
354 | 14903d6a | Bob Lantz | |
355 | |||
356 | 568f6424 | Bob Lantz | def installUbuntu( iso, image, logfilename='install.log', memory=1024 ): |
357 | fa1758b9 | Bob Lantz | "Install Ubuntu from iso onto image"
|
358 | d4279559 | Bob Lantz | kvm = 'qemu-system-' + archFor( iso )
|
359 | fa1758b9 | Bob Lantz | floppy, kickstart, preseed = makeKickstartFloppy() |
360 | # Mount iso so we can use its kernel
|
||
361 | mnt = mkdtemp() |
||
362 | srun( 'mount %s %s' % ( iso, mnt ) )
|
||
363 | f605a4e4 | Bob Lantz | kernel = path.join( mnt, 'install/vmlinuz' )
|
364 | initrd = path.join( mnt, 'install/initrd.gz' )
|
||
365 | 0038720c | Bob Lantz | if NoKVM:
|
366 | accel = 'tcg'
|
||
367 | else:
|
||
368 | accel = 'kvm'
|
||
369 | fa1758b9 | Bob Lantz | cmd = [ 'sudo', kvm,
|
370 | 0038720c | Bob Lantz | '-machine', 'accel=%s' % accel, |
371 | fa1758b9 | Bob Lantz | '-nographic',
|
372 | f605a4e4 | Bob Lantz | '-netdev', 'user,id=mnbuild', |
373 | '-device', 'virtio-net,netdev=mnbuild', |
||
374 | 568f6424 | Bob Lantz | '-m', str( memory ), |
375 | f605a4e4 | Bob Lantz | '-k', 'en-us', |
376 | fa1758b9 | Bob Lantz | '-fda', floppy,
|
377 | f605a4e4 | Bob Lantz | '-drive', 'file=%s,if=virtio' % image, |
378 | '-cdrom', iso,
|
||
379 | fa1758b9 | Bob Lantz | '-kernel', kernel,
|
380 | f605a4e4 | Bob Lantz | '-initrd', initrd,
|
381 | '-append',
|
||
382 | ' ks=floppy:/' + kickstart +
|
||
383 | ' preseed/file=floppy://' + preseed +
|
||
384 | ' console=ttyS0' ]
|
||
385 | ubuntuStart = time() |
||
386 | fa1758b9 | Bob Lantz | log( '* INSTALLING UBUNTU FROM', iso, 'ONTO', image ) |
387 | f605a4e4 | Bob Lantz | log( ' '.join( cmd ) )
|
388 | log( '* logging to', abspath( logfilename ) )
|
||
389 | d4279559 | Bob Lantz | params = {} |
390 | if not LogToConsole: |
||
391 | logfile = open( logfilename, 'w' ) |
||
392 | params = { 'stdout': logfile, 'stderr': logfile } |
||
393 | vm = Popen( cmd, **params ) |
||
394 | f605a4e4 | Bob Lantz | log( '* Waiting for installation to complete')
|
395 | vm.wait() |
||
396 | d4279559 | Bob Lantz | if not LogToConsole: |
397 | logfile.close() |
||
398 | f605a4e4 | Bob Lantz | elapsed = time() - ubuntuStart |
399 | fa1758b9 | Bob Lantz | # Unmount iso and clean up
|
400 | srun( 'umount ' + mnt )
|
||
401 | run( 'rmdir ' + mnt )
|
||
402 | 803a1a54 | Bob Lantz | if vm.returncode != 0: |
403 | raise Exception( 'Ubuntu installation returned error %d' % |
||
404 | vm.returncode ) |
||
405 | fa1758b9 | Bob Lantz | log( '* UBUNTU INSTALLATION COMPLETED FOR', image )
|
406 | 501a164e | Bob Lantz | log( '* Ubuntu installation completed in %.2f seconds' % elapsed )
|
407 | fa1758b9 | Bob Lantz | |
408 | |||
409 | 568f6424 | Bob Lantz | def boot( cow, kernel, initrd, logfile, memory=1024 ): |
410 | 4daeeff0 | Bob Lantz | """Boot qemu/kvm with a COW disk and local/user data store
|
411 | 85dfac5c | Bob Lantz | cow: COW disk path
|
412 | kernel: kernel path
|
||
413 | fa1758b9 | Bob Lantz | logfile: log file for pexpect object
|
414 | 568f6424 | Bob Lantz | memory: memory size in MB
|
415 | 85dfac5c | Bob Lantz | returns: pexpect object to qemu process"""
|
416 | # pexpect might not be installed until after depend() is called
|
||
417 | global pexpect
|
||
418 | import pexpect |
||
419 | 8f546d89 | Bob Lantz | arch = archFor( kernel ) |
420 | log( '* Detected kernel architecture', arch )
|
||
421 | 0038720c | Bob Lantz | if NoKVM:
|
422 | accel = 'tcg'
|
||
423 | else:
|
||
424 | accel = 'kvm'
|
||
425 | d4279559 | Bob Lantz | cmd = [ 'sudo', 'qemu-system-' + arch, |
426 | 0038720c | Bob Lantz | '-machine accel=%s' % accel,
|
427 | 4daeeff0 | Bob Lantz | '-nographic',
|
428 | 85dfac5c | Bob Lantz | '-netdev user,id=mnbuild',
|
429 | '-device virtio-net,netdev=mnbuild',
|
||
430 | 568f6424 | Bob Lantz | '-m %s' % memory,
|
431 | 85dfac5c | Bob Lantz | '-k en-us',
|
432 | 4daeeff0 | Bob Lantz | '-kernel', kernel,
|
433 | 1dfa7776 | Bob Lantz | '-initrd', initrd,
|
434 | 85dfac5c | Bob Lantz | '-drive file=%s,if=virtio' % cow,
|
435 | 94954177 | Bob Lantz | '-append "root=/dev/vda1 init=/sbin/init console=ttyS0" ' ]
|
436 | 4daeeff0 | Bob Lantz | cmd = ' '.join( cmd )
|
437 | fa1758b9 | Bob Lantz | log( '* BOOTING VM FROM', cow )
|
438 | 14903d6a | Bob Lantz | log( cmd ) |
439 | fa1758b9 | Bob Lantz | vm = pexpect.spawn( cmd, timeout=TIMEOUT, logfile=logfile ) |
440 | 85dfac5c | Bob Lantz | return vm
|
441 | |||
442 | 4daeeff0 | Bob Lantz | |
443 | 501a164e | Bob Lantz | def login( vm ): |
444 | "Log in to vm (pexpect object)"
|
||
445 | 14903d6a | Bob Lantz | log( '* Waiting for login prompt' )
|
446 | 4daeeff0 | Bob Lantz | vm.expect( 'login: ' )
|
447 | 14903d6a | Bob Lantz | log( '* Logging in' )
|
448 | 4daeeff0 | Bob Lantz | vm.sendline( 'mininet' )
|
449 | 14903d6a | Bob Lantz | log( '* Waiting for password prompt' )
|
450 | 4daeeff0 | Bob Lantz | vm.expect( 'Password: ' )
|
451 | 14903d6a | Bob Lantz | log( '* Sending password' )
|
452 | 4daeeff0 | Bob Lantz | vm.sendline( 'mininet' )
|
453 | 14903d6a | Bob Lantz | log( '* Waiting for login...' )
|
454 | 501a164e | Bob Lantz | |
455 | |||
456 | def sanityTest( vm ): |
||
457 | "Run Mininet sanity test (pingall) in vm"
|
||
458 | 4daeeff0 | Bob Lantz | vm.sendline( 'sudo mn --test pingall' )
|
459 | 14903d6a | Bob Lantz | if vm.expect( [ ' 0% dropped', pexpect.TIMEOUT ], timeout=45 ) == 0: |
460 | 28165f7b | Bob Lantz | log( '* Sanity check OK' )
|
461 | 860bcc02 | Bob Lantz | else:
|
462 | 14903d6a | Bob Lantz | log( '* Sanity check FAILED' )
|
463 | 2f3e8c2b | Bob Lantz | log( '* Sanity check output:' )
|
464 | log( vm.before ) |
||
465 | 501a164e | Bob Lantz | |
466 | |||
467 | def coreTest( vm, prompt=Prompt ): |
||
468 | "Run core tests (make test) in VM"
|
||
469 | 14903d6a | Bob Lantz | log( '* Making sure cgroups are mounted' )
|
470 | 860bcc02 | Bob Lantz | vm.sendline( 'sudo service cgroup-lite restart' )
|
471 | vm.expect( prompt ) |
||
472 | vm.sendline( 'sudo cgroups-mount' )
|
||
473 | 4daeeff0 | Bob Lantz | vm.expect( prompt ) |
474 | 14903d6a | Bob Lantz | log( '* Running make test' )
|
475 | 4daeeff0 | Bob Lantz | vm.sendline( 'cd ~/mininet; sudo make test' )
|
476 | 28165f7b | Bob Lantz | # We should change "make test" to report the number of
|
477 | # successful and failed tests. For now, we have to
|
||
478 | # know the time for each test, which means that this
|
||
479 | # script will have to change as we add more tests.
|
||
480 | for test in range( 0, 2 ): |
||
481 | d2762938 | Bob Lantz | if vm.expect( [ 'OK', 'FAILED', pexpect.TIMEOUT ], timeout=180 ) == 0: |
482 | 28165f7b | Bob Lantz | log( '* Test', test, 'OK' ) |
483 | else:
|
||
484 | log( '* Test', test, 'FAILED' ) |
||
485 | 2f3e8c2b | Bob Lantz | log( '* Test', test, 'output:' ) |
486 | log( vm.before ) |
||
487 | 501a164e | Bob Lantz | |
488 | 7bd9a79b | Bob Lantz | def examplesquickTest( vm, prompt=Prompt ): |
489 | "Quick test of mininet examples"
|
||
490 | vm.sendline( 'sudo apt-get install python-pexpect' )
|
||
491 | vm.expect( prompt ) |
||
492 | 4e242e92 | Bob Lantz | vm.sendline( 'sudo python ~/mininet/examples/test/runner.py -v -quick' )
|
493 | 7bd9a79b | Bob Lantz | |
494 | |||
495 | def examplesfullTest( vm, prompt=Prompt ): |
||
496 | "Full (slow) test of mininet examples"
|
||
497 | vm.sendline( 'sudo apt-get install python-pexpect' )
|
||
498 | vm.expect( prompt ) |
||
499 | 4e242e92 | Bob Lantz | vm.sendline( 'sudo python ~/mininet/examples/test/runner.py -v' )
|
500 | 7bd9a79b | Bob Lantz | |
501 | |||
502 | b7548e68 | Bob Lantz | def walkthroughTest( vm, prompt=Prompt ): |
503 | "Test mininet walkthrough"
|
||
504 | vm.sendline( 'sudo apt-get install python-pexpect' )
|
||
505 | vm.expect( prompt ) |
||
506 | f5985854 | Bob Lantz | vm.sendline( 'sudo python ~/mininet/mininet/test/test_walkthrough.py -v' )
|
507 | b7548e68 | Bob Lantz | |
508 | |||
509 | 7bd9a79b | Bob Lantz | def checkOutBranch( vm, branch, prompt=Prompt ): |
510 | vm.sendline( 'cd ~/mininet; git fetch; git pull --rebase; git checkout '
|
||
511 | + branch ) |
||
512 | vm.expect( prompt ) |
||
513 | vm.sendline( 'sudo make install' )
|
||
514 | |||
515 | |||
516 | 0294f5ec | Bob Lantz | def interact( vm, tests, pre='', post='', prompt=Prompt ): |
517 | 501a164e | Bob Lantz | "Interact with vm, which is a pexpect object"
|
518 | login( vm ) |
||
519 | log( '* Waiting for login...' )
|
||
520 | vm.expect( prompt ) |
||
521 | log( '* Sending hostname command' )
|
||
522 | vm.sendline( 'hostname' )
|
||
523 | log( '* Waiting for output' )
|
||
524 | vm.expect( prompt ) |
||
525 | log( '* Fetching Mininet VM install script' )
|
||
526 | vm.sendline( 'wget '
|
||
527 | 'https://raw.github.com/mininet/mininet/master/util/vm/'
|
||
528 | 'install-mininet-vm.sh' )
|
||
529 | vm.expect( prompt ) |
||
530 | log( '* Running VM install script' )
|
||
531 | vm.sendline( 'bash install-mininet-vm.sh' )
|
||
532 | vm.expect ( 'password for mininet: ' )
|
||
533 | vm.sendline( 'mininet' )
|
||
534 | log( '* Waiting for script to complete... ' )
|
||
535 | # Gigantic timeout for now ;-(
|
||
536 | vm.expect( 'Done preparing Mininet', timeout=3600 ) |
||
537 | log( '* Completed successfully' )
|
||
538 | vm.expect( prompt ) |
||
539 | fce7f5c5 | Bob Lantz | version = getMininetVersion( vm ) |
540 | vm.expect( prompt ) |
||
541 | log( '* Mininet version: ', version )
|
||
542 | 501a164e | Bob Lantz | log( '* Testing Mininet' )
|
543 | 0294f5ec | Bob Lantz | runTests( vm, tests=tests, pre=pre, post=post ) |
544 | 14903d6a | Bob Lantz | log( '* Shutting down' )
|
545 | 4daeeff0 | Bob Lantz | vm.sendline( 'sync; sudo shutdown -h now' )
|
546 | 14903d6a | Bob Lantz | log( '* Waiting for EOF/shutdown' )
|
547 | 4daeeff0 | Bob Lantz | vm.read() |
548 | 4556e06f | Bob Lantz | log( '* Interaction complete' )
|
549 | fce7f5c5 | Bob Lantz | return version
|
550 | 4daeeff0 | Bob Lantz | |
551 | |||
552 | 85dfac5c | Bob Lantz | def cleanup(): |
553 | "Clean up leftover qemu-nbd processes and other junk"
|
||
554 | 14903d6a | Bob Lantz | call( [ 'sudo', 'pkill', '-9', 'qemu-nbd' ] ) |
555 | 85dfac5c | Bob Lantz | |
556 | |||
557 | def convert( cow, basename ): |
||
558 | """Convert a qcow2 disk to a vmdk and put it a new directory
|
||
559 | basename: base name for output vmdk file"""
|
||
560 | 94954177 | Bob Lantz | vmdk = basename + '.vmdk'
|
561 | 14903d6a | Bob Lantz | log( '* Converting qcow2 to vmdk' )
|
562 | 85dfac5c | Bob Lantz | run( 'qemu-img convert -f qcow2 -O vmdk %s %s' % ( cow, vmdk ) )
|
563 | return vmdk
|
||
564 | |||
565 | |||
566 | 0038720c | Bob Lantz | # Template for OVF - a very verbose format!
|
567 | # In the best of all possible worlds, we might use an XML
|
||
568 | # library to generate this, but a template is easier and
|
||
569 | # possibly more concise!
|
||
570 | e02cdc0c | Bob Lantz | # Warning: XML file cannot begin with a newline!
|
571 | 0038720c | Bob Lantz | |
572 | e02cdc0c | Bob Lantz | OVFTemplate = """<?xml version="1.0"?>
|
573 | 9e725cb2 | Bob Lantz | <Envelope ovf:version="1.0" xml:lang="en-US"
|
574 | 0038720c | Bob Lantz | xmlns="http://schemas.dmtf.org/ovf/envelope/1"
|
575 | xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1"
|
||
576 | xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
|
||
577 | xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
|
||
578 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||
579 | <References>
|
||
580 | <File ovf:href="%s" ovf:id="file1" ovf:size="%d"/>
|
||
581 | </References>
|
||
582 | <DiskSection>
|
||
583 | <Info>Virtual disk information</Info>
|
||
584 | <Disk ovf:capacity="%d" ovf:capacityAllocationUnits="byte"
|
||
585 | ovf:diskId="vmdisk1" ovf:fileRef="file1"
|
||
586 | ovf:format="http://www.vmware.com/interfaces/specifications/vmdk.html"/>
|
||
587 | </DiskSection>
|
||
588 | <NetworkSection>
|
||
589 | <Info>The list of logical networks</Info>
|
||
590 | <Network ovf:name="nat">
|
||
591 | <Description>The nat network</Description>
|
||
592 | </Network>
|
||
593 | </NetworkSection>
|
||
594 | <VirtualSystem ovf:id="Mininet-VM">
|
||
595 | <Info>A Mininet Virtual Machine (%s)</Info>
|
||
596 | <Name>mininet-vm</Name>
|
||
597 | <VirtualHardwareSection>
|
||
598 | <Info>Virtual hardware requirements</Info>
|
||
599 | <Item>
|
||
600 | <rasd:AllocationUnits>hertz * 10^6</rasd:AllocationUnits>
|
||
601 | <rasd:Description>Number of Virtual CPUs</rasd:Description>
|
||
602 | <rasd:ElementName>1 virtual CPU(s)</rasd:ElementName>
|
||
603 | <rasd:InstanceID>1</rasd:InstanceID>
|
||
604 | <rasd:ResourceType>3</rasd:ResourceType>
|
||
605 | <rasd:VirtualQuantity>1</rasd:VirtualQuantity>
|
||
606 | </Item>
|
||
607 | <Item>
|
||
608 | <rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
|
||
609 | <rasd:Description>Memory Size</rasd:Description>
|
||
610 | <rasd:ElementName>%dMB of memory</rasd:ElementName>
|
||
611 | <rasd:InstanceID>2</rasd:InstanceID>
|
||
612 | <rasd:ResourceType>4</rasd:ResourceType>
|
||
613 | <rasd:VirtualQuantity>%d</rasd:VirtualQuantity>
|
||
614 | </Item>
|
||
615 | <Item>
|
||
616 | <rasd:Address>0</rasd:Address>
|
||
617 | <rasd:Caption>scsiController0</rasd:Caption>
|
||
618 | <rasd:Description>SCSI Controller</rasd:Description>
|
||
619 | <rasd:ElementName>scsiController0</rasd:ElementName>
|
||
620 | <rasd:InstanceID>4</rasd:InstanceID>
|
||
621 | <rasd:ResourceSubType>lsilogic</rasd:ResourceSubType>
|
||
622 | <rasd:ResourceType>6</rasd:ResourceType>
|
||
623 | </Item>
|
||
624 | <Item>
|
||
625 | <rasd:AddressOnParent>0</rasd:AddressOnParent>
|
||
626 | <rasd:ElementName>disk1</rasd:ElementName>
|
||
627 | <rasd:HostResource>ovf:/disk/vmdisk1</rasd:HostResource>
|
||
628 | <rasd:InstanceID>11</rasd:InstanceID>
|
||
629 | <rasd:Parent>4</rasd:Parent>
|
||
630 | <rasd:ResourceType>17</rasd:ResourceType>
|
||
631 | </Item>
|
||
632 | <Item>
|
||
633 | <rasd:AddressOnParent>2</rasd:AddressOnParent>
|
||
634 | <rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
|
||
635 | <rasd:Connection>nat</rasd:Connection>
|
||
636 | <rasd:Description>E1000 ethernet adapter on nat</rasd:Description>
|
||
637 | <rasd:ElementName>ethernet0</rasd:ElementName>
|
||
638 | <rasd:InstanceID>12</rasd:InstanceID>
|
||
639 | <rasd:ResourceSubType>E1000</rasd:ResourceSubType>
|
||
640 | <rasd:ResourceType>10</rasd:ResourceType>
|
||
641 | </Item>
|
||
642 | <Item>
|
||
643 | <rasd:Address>0</rasd:Address>
|
||
644 | <rasd:Caption>usb</rasd:Caption>
|
||
645 | <rasd:Description>USB Controller</rasd:Description>
|
||
646 | <rasd:ElementName>usb</rasd:ElementName>
|
||
647 | <rasd:InstanceID>9</rasd:InstanceID>
|
||
648 | <rasd:ResourceType>23</rasd:ResourceType>
|
||
649 | </Item>
|
||
650 | </VirtualHardwareSection>
|
||
651 | </VirtualSystem>
|
||
652 | </Envelope>
|
||
653 | 662fb712 | Bob Lantz | """
|
654 | |||
655 | d4279559 | Bob Lantz | |
656 | 0038720c | Bob Lantz | def generateOVF( name, diskname, disksize, mem=1024 ): |
657 | """Generate (and return) OVF file "name.ovf"
|
||
658 | name: root name of OVF file to generate
|
||
659 | diskname: name of disk file
|
||
660 | disksize: size of virtual disk in bytes
|
||
661 | mem: VM memory size in MB"""
|
||
662 | ovf = name + '.ovf'
|
||
663 | filesize = stat( diskname )[ ST_SIZE ] |
||
664 | # OVFTemplate uses the memory size twice in a row
|
||
665 | xmltext = OVFTemplate % ( diskname, filesize, disksize, name, mem, mem ) |
||
666 | with open( ovf, 'w+' ) as f: |
||
667 | 662fb712 | Bob Lantz | f.write( xmltext ) |
668 | 0038720c | Bob Lantz | return ovf
|
669 | 662fb712 | Bob Lantz | |
670 | |||
671 | d4279559 | Bob Lantz | def qcow2size( qcow2 ): |
672 | "Return virtual disk size (in bytes) of qcow2 image"
|
||
673 | output = check_output( [ 'file', qcow2 ] )
|
||
674 | assert 'QCOW' in output |
||
675 | bytes = int( re.findall( '(\d+) bytes', output )[ 0 ] ) |
||
676 | return bytes |
||
677 | |||
678 | |||
679 | 568f6424 | Bob Lantz | def build( flavor='raring32server', tests=None, pre='', post='', memory=1024 ): |
680 | 0294f5ec | Bob Lantz | """Build a Mininet VM; return vmdk and vdisk size
|
681 | tests: tests to run
|
||
682 | pre: command line to run in VM before tests
|
||
683 | post: command line to run in VM after tests
|
||
684 | 568f6424 | Bob Lantz | prompt: shell prompt (default '$ ')
|
685 | memory: memory size in MB"""
|
||
686 | fce7f5c5 | Bob Lantz | global LogFile, Zip
|
687 | 85dfac5c | Bob Lantz | start = time() |
688 | 700c5bf5 | Bob Lantz | lstart = localtime() |
689 | date = strftime( '%y%m%d-%H-%M-%S', lstart)
|
||
690 | ovfdate = strftime( '%y%m%d', lstart )
|
||
691 | 1dfa7776 | Bob Lantz | dir = 'mn-%s-%s' % ( flavor, date )
|
692 | try:
|
||
693 | os.mkdir( dir )
|
||
694 | except:
|
||
695 | raise Exception( "Failed to create build directory %s" % dir ) |
||
696 | 94954177 | Bob Lantz | os.chdir( dir )
|
697 | 803a1a54 | Bob Lantz | LogFile = open( 'build.log', 'w' ) |
698 | 20005f5b | Bob Lantz | log( '* Logging to', abspath( LogFile.name ) )
|
699 | 14903d6a | Bob Lantz | log( '* Created working directory', dir ) |
700 | 1dfa7776 | Bob Lantz | image, kernel, initrd = findBaseImage( flavor ) |
701 | 9e725cb2 | Bob Lantz | basename = 'mininet-' + flavor
|
702 | volume = basename + '.qcow2'
|
||
703 | fa1758b9 | Bob Lantz | run( 'qemu-img create -f qcow2 -b %s %s' % ( image, volume ) )
|
704 | 14903d6a | Bob Lantz | log( '* VM image for', flavor, 'created as', volume ) |
705 | d4279559 | Bob Lantz | if LogToConsole:
|
706 | logfile = stdout |
||
707 | else:
|
||
708 | logfile = open( flavor + '.log', 'w+' ) |
||
709 | 14903d6a | Bob Lantz | log( '* Logging results to', abspath( logfile.name ) )
|
710 | 568f6424 | Bob Lantz | vm = boot( volume, kernel, initrd, logfile, memory=memory ) |
711 | 0294f5ec | Bob Lantz | version = interact( vm, tests=tests, pre=pre, post=post ) |
712 | d4279559 | Bob Lantz | size = qcow2size( volume ) |
713 | 6d3cb5bc | Bob Lantz | vmdk = convert( volume, basename='mininet-vm-' + archFor( flavor ) )
|
714 | d4279559 | Bob Lantz | if not SaveQCOW2: |
715 | log( '* Removing qcow2 volume', volume )
|
||
716 | os.remove( volume ) |
||
717 | f605a4e4 | Bob Lantz | log( '* Converted VM image stored as', abspath( vmdk ) )
|
718 | 700c5bf5 | Bob Lantz | ovfname = 'mininet-%s-%s-%s' % ( version, ovfdate, OSVersion( flavor ) )
|
719 | fce7f5c5 | Bob Lantz | ovf = generateOVF( diskname=vmdk, disksize=size, name=ovfname ) |
720 | 0038720c | Bob Lantz | log( '* Generated OVF descriptor file', ovf )
|
721 | fce7f5c5 | Bob Lantz | if Zip:
|
722 | log( '* Generating .zip file' )
|
||
723 | run( 'zip %s-ovf.zip %s %s' % ( ovfname, ovf, vmdk ) )
|
||
724 | 85dfac5c | Bob Lantz | end = time() |
725 | elapsed = end - start |
||
726 | 14903d6a | Bob Lantz | log( '* Results logged to', abspath( logfile.name ) )
|
727 | log( '* Completed in %.2f seconds' % elapsed )
|
||
728 | log( '* %s VM build DONE!!!!! :D' % flavor )
|
||
729 | bbf808c3 | Bob Lantz | os.chdir( '..' )
|
730 | 85dfac5c | Bob Lantz | |
731 | 501a164e | Bob Lantz | |
732 | 0294f5ec | Bob Lantz | def runTests( vm, tests=None, pre='', post='', prompt=Prompt ): |
733 | 7bd9a79b | Bob Lantz | "Run tests (list) in vm (pexpect object)"
|
734 | if not tests: |
||
735 | 0294f5ec | Bob Lantz | tests = [] |
736 | if pre:
|
||
737 | log( '* Running command', pre )
|
||
738 | vm.sendline( pre ) |
||
739 | vm.expect( prompt ) |
||
740 | 7bd9a79b | Bob Lantz | testfns = testDict() |
741 | 0294f5ec | Bob Lantz | if tests:
|
742 | log( '* Running tests' )
|
||
743 | 7bd9a79b | Bob Lantz | for test in tests: |
744 | if test not in testfns: |
||
745 | raise Exception( 'Unknown test: ' + test ) |
||
746 | log( '* Running test', test )
|
||
747 | fn = testfns[ test ] |
||
748 | fn( vm ) |
||
749 | vm.expect( prompt ) |
||
750 | 0294f5ec | Bob Lantz | if post:
|
751 | log( '* Running post-test command', post )
|
||
752 | vm.sendline( post ) |
||
753 | vm.expect( prompt ) |
||
754 | 7bd9a79b | Bob Lantz | |
755 | fce7f5c5 | Bob Lantz | def getMininetVersion( vm ): |
756 | "Run mn to find Mininet version in VM"
|
||
757 | vm.sendline( '~/mininet/bin/mn --version' )
|
||
758 | # Eat command line echo, then read output line
|
||
759 | vm.readline() |
||
760 | version = vm.readline().strip() |
||
761 | return version
|
||
762 | |||
763 | |||
764 | 568f6424 | Bob Lantz | def bootAndRunTests( image, tests=None, pre='', post='', prompt=Prompt, |
765 | memory=1024 ):
|
||
766 | 501a164e | Bob Lantz | """Boot and test VM
|
767 | 0294f5ec | Bob Lantz | tests: list of tests to run
|
768 | pre: command line to run in VM before tests
|
||
769 | post: command line to run in VM after tests
|
||
770 | 568f6424 | Bob Lantz | prompt: shell prompt (default '$ ')
|
771 | memory: VM memory size in MB"""
|
||
772 | 501a164e | Bob Lantz | bootTestStart = time() |
773 | basename = path.basename( image ) |
||
774 | image = abspath( image ) |
||
775 | tmpdir = mkdtemp( prefix='test-' + basename )
|
||
776 | 7bd9a79b | Bob Lantz | log( '* Using tmpdir', tmpdir )
|
777 | cow = path.join( tmpdir, basename + '.qcow2' )
|
||
778 | log( '* Creating COW disk', cow )
|
||
779 | 501a164e | Bob Lantz | run( 'qemu-img create -f qcow2 -b %s %s' % ( image, cow ) )
|
780 | log( '* Extracting kernel and initrd' )
|
||
781 | kernel, initrd = extractKernel( image, flavor=basename, imageDir=tmpdir ) |
||
782 | if LogToConsole:
|
||
783 | logfile = stdout |
||
784 | else:
|
||
785 | 7bd9a79b | Bob Lantz | logfile = NamedTemporaryFile( prefix=basename, |
786 | suffix='.testlog', delete=False ) |
||
787 | 501a164e | Bob Lantz | log( '* Logging VM output to', logfile.name )
|
788 | 568f6424 | Bob Lantz | vm = boot( cow=cow, kernel=kernel, initrd=initrd, logfile=logfile, |
789 | memory=memory ) |
||
790 | 501a164e | Bob Lantz | login( vm ) |
791 | 2ca1ea92 | Bob Lantz | log( '* Waiting for prompt after login' )
|
792 | 501a164e | Bob Lantz | vm.expect( prompt ) |
793 | 7bd9a79b | Bob Lantz | if Branch:
|
794 | checkOutBranch( vm, branch=Branch ) |
||
795 | 501a164e | Bob Lantz | vm.expect( prompt ) |
796 | 0294f5ec | Bob Lantz | runTests( vm, tests=tests, pre=pre, post=post ) |
797 | 7bd9a79b | Bob Lantz | # runTests eats its last prompt, but maybe it shouldn't...
|
798 | 501a164e | Bob Lantz | log( '* Shutting down' )
|
799 | vm.sendline( 'sudo shutdown -h now ' )
|
||
800 | log( '* Waiting for shutdown' )
|
||
801 | vm.wait() |
||
802 | log( '* Removing temporary dir', tmpdir )
|
||
803 | srun( 'rm -rf ' + tmpdir )
|
||
804 | elapsed = time() - bootTestStart |
||
805 | log( '* Boot and test completed in %.2f seconds' % elapsed )
|
||
806 | |||
807 | |||
808 | d4279559 | Bob Lantz | def buildFlavorString(): |
809 | "Return string listing valid build flavors"
|
||
810 | 7bd9a79b | Bob Lantz | return 'valid build flavors: ( %s )' % ' '.join( sorted( isoURLs ) ) |
811 | |||
812 | |||
813 | def testDict(): |
||
814 | "Return dict of tests in this module"
|
||
815 | suffix = 'Test'
|
||
816 | trim = len( suffix )
|
||
817 | fdict = dict( [ ( fname[ : -trim ], f ) for fname, f in |
||
818 | inspect.getmembers( modules[ __name__ ], |
||
819 | inspect.isfunction ) |
||
820 | if fname.endswith( suffix ) ] )
|
||
821 | return fdict
|
||
822 | |||
823 | |||
824 | def testString(): |
||
825 | "Return string listing valid tests"
|
||
826 | return 'valid tests: ( %s )' % ' '.join( testDict().keys() ) |
||
827 | fa1758b9 | Bob Lantz | |
828 | 14903d6a | Bob Lantz | |
829 | 85dfac5c | Bob Lantz | def parseArgs(): |
830 | "Parse command line arguments and run"
|
||
831 | fce7f5c5 | Bob Lantz | global LogToConsole, NoKVM, Branch, Zip
|
832 | d4279559 | Bob Lantz | parser = argparse.ArgumentParser( description='Mininet VM build script',
|
833 | 7bd9a79b | Bob Lantz | epilog=buildFlavorString() + ' ' +
|
834 | testString() ) |
||
835 | d4279559 | Bob Lantz | parser.add_argument( '-v', '--verbose', action='store_true', |
836 | help='send VM output to console rather than log file' )
|
||
837 | parser.add_argument( '-d', '--depend', action='store_true', |
||
838 | 1dfa7776 | Bob Lantz | help='install dependencies for this script' )
|
839 | d4279559 | Bob Lantz | parser.add_argument( '-l', '--list', action='store_true', |
840 | 7bd9a79b | Bob Lantz | help='list valid build flavors and tests' )
|
841 | d4279559 | Bob Lantz | parser.add_argument( '-c', '--clean', action='store_true', |
842 | 85dfac5c | Bob Lantz | help='clean up leftover build junk (e.g. qemu-nbd)' )
|
843 | d4279559 | Bob Lantz | parser.add_argument( '-q', '--qcow2', action='store_true', |
844 | help='save qcow2 image rather than deleting it' )
|
||
845 | 0038720c | Bob Lantz | parser.add_argument( '-n', '--nokvm', action='store_true', |
846 | help="Don't use kvm - use tcg emulation instead" )
|
||
847 | 568f6424 | Bob Lantz | parser.add_argument( '-m', '--memory', metavar='MB', type=int, |
848 | default=1024, help='VM memory size in MB' ) |
||
849 | 7bd9a79b | Bob Lantz | parser.add_argument( '-i', '--image', metavar='image', default=[], |
850 | action='append',
|
||
851 | help='Boot and test an existing VM image' )
|
||
852 | parser.add_argument( '-t', '--test', metavar='test', default=[], |
||
853 | 0294f5ec | Bob Lantz | action='append',
|
854 | help='specify a test to run' )
|
||
855 | parser.add_argument( '-r', '--run', metavar='cmd', default='', |
||
856 | help='specify a command line to run before tests' )
|
||
857 | parser.add_argument( '-p', '--post', metavar='cmd', default='', |
||
858 | help='specify a command line to run after tests' )
|
||
859 | 7bd9a79b | Bob Lantz | parser.add_argument( '-b', '--branch', metavar='branch', |
860 | help='For an existing VM image, check out and install'
|
||
861 | ' this branch before testing' )
|
||
862 | 85dfac5c | Bob Lantz | parser.add_argument( 'flavor', nargs='*', |
863 | d4279559 | Bob Lantz | help='VM flavor(s) to build (e.g. raring32server)' )
|
864 | fce7f5c5 | Bob Lantz | parser.add_argument( '-z', '--zip', action='store_true', |
865 | 0294f5ec | Bob Lantz | help='archive .ovf and .vmdk into .zip file' )
|
866 | d4279559 | Bob Lantz | args = parser.parse_args() |
867 | 85dfac5c | Bob Lantz | if args.depend:
|
868 | depend() |
||
869 | if args.list:
|
||
870 | d4279559 | Bob Lantz | print buildFlavorString()
|
871 | 85dfac5c | Bob Lantz | if args.clean:
|
872 | cleanup() |
||
873 | d4279559 | Bob Lantz | if args.verbose:
|
874 | LogToConsole = True
|
||
875 | 0038720c | Bob Lantz | if args.nokvm:
|
876 | NoKVM = True
|
||
877 | 7bd9a79b | Bob Lantz | if args.branch:
|
878 | Branch = args.branch |
||
879 | fce7f5c5 | Bob Lantz | if args.zip:
|
880 | Zip = True
|
||
881 | e0b50c8a | Bob Lantz | if not args.test and not args.run and not args.post: |
882 | 0294f5ec | Bob Lantz | args.test = [ 'sanity', 'core' ] |
883 | d4279559 | Bob Lantz | for flavor in args.flavor: |
884 | fa1758b9 | Bob Lantz | if flavor not in isoURLs: |
885 | d4279559 | Bob Lantz | print "Unknown build flavor:", flavor |
886 | print buildFlavorString()
|
||
887 | 14903d6a | Bob Lantz | break
|
888 | b7548e68 | Bob Lantz | try:
|
889 | 568f6424 | Bob Lantz | build( flavor, tests=args.test, pre=args.run, post=args.post, |
890 | memory=args.memory ) |
||
891 | b7548e68 | Bob Lantz | except Exception as e: |
892 | log( '* BUILD FAILED with exception: ', e )
|
||
893 | exit( 1 ) |
||
894 | 7bd9a79b | Bob Lantz | for image in args.image: |
895 | 0294f5ec | Bob Lantz | bootAndRunTests( image, tests=args.test, pre=args.run, |
896 | 568f6424 | Bob Lantz | post=args.post, memory=args.memory) |
897 | 501a164e | Bob Lantz | if not ( args.depend or args.list or args.clean or args.flavor |
898 | 7bd9a79b | Bob Lantz | or args.image ):
|
899 | 85dfac5c | Bob Lantz | parser.print_help() |
900 | d4279559 | Bob Lantz | |
901 | 85dfac5c | Bob Lantz | |
902 | if __name__ == '__main__': |
||
903 | parseArgs() |