Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#!/usr/bin/python
"""
build.py: build a Mininet VM
Basic idea:
prepare
- download cloud image if it's missing
- write-protect it
build
-> create cow disk for vm
-> boot it in qemu/kvm with text /serial console
-> install Mininet
test
-> make codecheck
-> make test
release
-> shut down VM
-> shrink-wrap VM
-> upload to storage
Notes du jour:
- our infrastructure is currently based on 12.04 LTS, so we
can't rely on cloud-localds which is only in 12.10+
- as a result, we should download the tar image, extract it,
and boot with tty0 as the console (I think)
- and we'll manually add the mininet user to it
- and use pexpect to interact with it on the serial console
Something to think about:
Maybe download the cloud image and customize it so that
it is an actual usable/bootable image???
"""
import os
from os.path import exists, splitext
from glob import glob
from urllib import urlretrieve
from subprocess import check_output, call, Popen, PIPE
from tempfile import mkdtemp, NamedTemporaryFile
from sys import exit
from time import time
# boot can be slooooow!!!! need to debug/optimize somehow
TIMEOUT=120
VMImageDir = os.environ[ 'HOME' ] + '/vm-images'
ImageURLBase = {
'raring-server-amd64':
'http://cloud-images.ubuntu.com/raring/current/'
'raring-server-cloudimg-amd64'
}
def run( cmd, **kwargs ):
"Convenient interface to check_output"
print cmd
cmd = cmd.split()
return check_output( cmd, **kwargs )
def srun( cmd, **kwargs ):
"Run + sudo"
return run( 'sudo ' + cmd, **kwargs )
# Install necessary packages
print '* Installing package dependencies'
packages = ( )
run( 'sudo apt-get -y update' )
run( 'sudo apt-get install -y'
' kvm cloud-utils genisoimage qemu-kvm qemu-utils'
' e2fsprogs '
' landscape-client'
' python-setuptools' )
run( 'sudo easy_install pexpect' )
import pexpect
def popen( cmd ):
"Convenient interface to popen"
print cmd
cmd = cmd.split()
return Popen( cmd )
def remove( fname ):
"rm -f fname"
return run( 'rm -f %s' % fname )
def imageURL( image ):
"Return base URL for VM image"
return ImageURLBase[ image ]
def imagePath( image ):
"Return base pathname for VM image files"
url = imageURL( image )
fname = url.split( '/' )[ -1 ]
path = os.path.join( VMImageDir, fname )
return path
def fetchImage( image, path=None ):
"Fetch base VM image if it's not there already"
if not path:
path = imagePath( image )
tgz = path + '.tar.gz'
disk = path + '.img'
kernel = path + '-vmlinuz-generic'
floppy = path + '-floppy'
if exists( disk ) and exists( kernel ):
print '* Found', disk, 'and', kernel
else:
dir = os.path.dirname( path )
run( 'mkdir -p %s' % dir )
if not os.path.exists( tgz ):
url = imageURL( image ) + '.tar.gz'
print '* Retrieving', url
urlretrieve( url, tgz )
print '* Extracting', tgz
run( 'tar -C %s -xzf %s' % ( dir, tgz ) )
# Make sure Mininet user is there
os.chmod( disk, 0664 )
addMininetUser( disk )
# Write-protect disk image so it remains somewhat pristine;
# We will not use it directly but will use a COW disk
print '* Write-protecting disk image', disk
os.chmod( disk, 0444 )
return disk, kernel
def addTo( file, line ):
"Add line to file if it's not there already"
if call( [ 'sudo', 'grep', line, file ] ) != 0:
call( 'echo "%s" | sudo tee -a %s' % ( line, file ), shell=True )
def disableCloud( bind ):
"Disable cloud junk for disk mounted at bind"
print '* Disabling cloud startup scripts'
modules = glob( '%s/etc/init/cloud*.conf' % bind )
for module in modules:
path, ext = splitext( module )
override = path + '.override'
call( 'echo manual | sudo tee ' + override, shell=True )
def addMininetUser( img ):
"Add mininet user/group to root filesystem image"
print '* Adding mininet user to', img
# 1. We bind-mount / into a temporary directory, and
# then mount the volume's /etc and /home on top of it!
bind = mkdtemp()
mnt = mkdtemp()
srun( 'mount -B / ' + bind )
srun( 'mount %s %s' % ( img, mnt ) )
srun( 'mount -B %s/etc %s/etc' % ( mnt, bind ) )
srun( 'mount -B %s/home %s/home' % ( mnt, bind ) )
def chroot( cmd ):
"Chroot into bind mount and run command"
call( 'sudo chroot %s ' % bind + cmd, shell=True )
# 2. Next, we delete any old mininet user and add a new one
chroot( 'deluser mininet' )
chroot( 'useradd --create-home mininet' )
print '* Setting password'
call( 'echo mininet:mininet | sudo chroot %s chpasswd -c SHA512'
% bind, shell=True )
# 2a. Add mininet to sudoers
addTo( bind + '/etc/sudoers', 'mininet ALL=NOPASSWD: ALL' )
# 2b. Disable cloud junk
disableCloud( bind )
chroot( 'sudo update-rc.d landscape-client disable' )
# 2c. Add serial getty
print '* Adding getty on ttyS0'
chroot( 'cp /etc/init/tty1.conf /etc/init/ttyS0.conf' )
chroot( 'sed -i "s/tty1/ttyS0/g" /etc/init/ttyS0.conf' )
# 3. Lastly, we umount and clean up everything
run( 'sync' )
srun( 'umount %s/etc ' % bind )
srun( 'umount %s/home ' % bind )
srun( 'umount %s' % bind )
srun( 'umount ' + mnt )
run( 'rmdir ' + mnt )
# 4. Just to make sure, we check the filesystem
run( 'e2fsck -y ' + img )
def makeCOWDisk( image ):
"Create new COW disk for image"
disk, kernel = fetchImage( image )
cow = disk + '.qcow2'
print '* Creating COW disk', cow
remove( cow )
run( 'qemu-img create -f qcow2 -b %s %s' % ( disk, cow ) )
print '* Resizing COW disk and file system'
run( 'qemu-img resize %s +8G' % cow )
srun( 'modprobe nbd')
srun( 'qemu-nbd -c /dev/nbd0 ' + cow )
srun( 'e2fsck -fy /dev/nbd0' )
srun( 'resize2fs /dev/nbd0' )
srun( 'qemu-nbd -d /dev/nbd0' )
return cow, kernel
def boot( cow, kernel ):
"""Boot qemu/kvm with a COW disk and local/user data store
returns: popen object to qemu process"""
if 'amd64' in cow:
kvm = 'qemu-system-x86_64'
elif 'i386' in cow:
kvm = 'qemu-system-i386'
else:
print "Error: can't discern CPU for image", cow
exit
# was -nographic
# was -net=%net
# ' -net nic,model=virtio'
# ' -netdev tap,id=mininet0,ifname=%s,script=no ' % tap +
cmd = [ 'sudo', kvm,
'-machine', 'accel=kvm',
'-nographic',
'-m', '512',
'-k', 'en-us',
'-kernel', kernel,
'-drive', 'file=%s,if=virtio' % cow,
'-append',
' "root=/dev/vda'
' init=/sbin/init'
# ' init=/usr/lib/cloud-init/uncloud-init'
# ' ds=nocloud'
# ' --verbose'
' console=ttyS0"'
]
cmd = ' '.join( cmd )
print '* STARTING VM'
print cmd
vm = pexpect.spawn( cmd, timeout=TIMEOUT )
logfile = NamedTemporaryFile( prefix='mn-build-expect' )
print '* Logging results to', logfile.name
vm.logfile_read = logfile
return vm, logfile
def interact( vm ):
"Interact with vm, which is a pexpect object"
prompt = '\$ '
print '* Waiting for login prompt'
vm.expect( 'login: ' )
print '* Logging in'
vm.sendline( 'mininet' )
print '* Waiting for password prompt'
vm.expect( 'Password: ' )
print '* Sending password'
vm.sendline( 'mininet' )
print '* Waiting for login...'
vm.expect( prompt )
print '* Sending hostname command'
vm.sendline( 'hostname' )
print '* Waiting for output'
vm.expect( prompt )
print '* Fetching Mininet VM install script'
vm.sendline( 'wget '
'https://raw.github.com/mininet/mininet/master/util/vm/'
'install-mininet-vm.sh' )
vm.expect( prompt )
print '* Running VM install script'
vm.sendline( 'bash install-mininet-vm.sh' )
print '* Waiting for script to complete... '
vm.expect( 'Done preparing Mininet' )
print '* Completed successfully'
vm.expect( prompt )
print '* Testing Mininet'
vm.sendline( 'sudo mn --test pingall' )
vm.expect( '0% dropped' )
print '* Basic test succeeded'
vm.expect( prompt )
print '* Running make test'
vm.sendline( 'cd ~/mininet; sudo make test' )
vm.expect( 'OK' )
print '* Basic test succeeded'
vm.expect( 'OK' )
print '* Hi-Fi test succeeded'
vm.expect( prompt )
print '* Shutting down'
vm.sendline( 'sync; sudo shutdown -h now' )
print '* Waiting for EOF/shutdown'
vm.read()
print '* Interaction complete'
image = 'raring-server-amd64'
start = time()
cow, kernel = makeCOWDisk( image )
print '* VM image for', image, 'created as', cow
vm, logfile = boot( cow, kernel )
interact( vm )
logfile.close()
end = time()
elapsed = end - start
print '* Results logged to', logfile.name
print '* Completed in %.2f seconds' % elapsed
print '* DONE!!!!! :D'