Merge lp:~justin-fathomdb/nova/virtualbox-support into lp:~hudson-openstack/nova/trunk
- virtualbox-support
- Merge into trunk
Status: | Work in progress |
---|---|
Proposed branch: | lp:~justin-fathomdb/nova/virtualbox-support |
Merge into: | lp:~hudson-openstack/nova/trunk |
Prerequisite: | lp:~justin-fathomdb/nova/raw-disk-images |
Diff against target: |
1705 lines (+841/-171) 28 files modified
bin/nova-compute (+5/-6) dev-scripts/api.sh (+9/-0) dev-scripts/compute.sh (+19/-0) dev-scripts/create-image.sh (+17/-0) dev-scripts/down.sh (+8/-0) dev-scripts/network.sh (+7/-0) dev-scripts/objectstore.sh (+15/-0) dev-scripts/redis.sh (+3/-0) dev-scripts/restart.sh (+8/-0) dev-scripts/up.sh (+10/-0) dev-scripts/upload-image.sh (+13/-0) dev-scripts/volume.sh (+11/-0) nova/compute/disk.py (+27/-36) nova/compute/monitor.py (+1/-1) nova/compute/service.py (+19/-31) nova/network/service.py (+1/-1) nova/process.py (+31/-34) nova/rpc.py (+113/-13) nova/server.py (+1/-1) nova/service.py (+93/-36) nova/threads.py (+40/-0) nova/twistd.py (+4/-2) nova/utils.py (+16/-2) nova/virt/connection.py (+10/-2) nova/virt/images.py (+4/-5) nova/virt/libvirt_conn.py (+23/-0) nova/virt/virtualbox.py (+332/-0) nova/volume/service.py (+1/-1) |
To merge this branch: | bzr merge lp:~justin-fathomdb/nova/virtualbox-support |
Related bugs: | |
Related blueprints: |
Add virtualbox bindings
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Nova Core security contacts | Pending | ||
Review via email: mp+33067@code.launchpad.net |
Commit message
Description of the change
Adds initial experimental support for VirtualBox. Currently doesn't work, because VirtualBox doesn't expose an async API. Will work once the Twisted removal is complete. Won't break anything unless users launch with the (undocumented) connection type 'virtualbox'.
- 166. By justinsb
-
Merged with trunk & lp:~justin-fathomdb/nova/better-error-messages-on-process-fail
- 167. By justinsb
-
Merged with updated kiss-backend (twisted for some services)
- 168. By justinsb
-
Merged with latest kiss-backend
- 169. By justinsb
-
Merged with latest kiss-backend
- 170. By justinsb
-
Merged with latest kiss-backend
- 171. By justinsb
-
Merged with latest lp:~justin-fathomdb/nova/better-error-messages-on-process-fail
- 172. By justinsb
-
Merged latest kiss-backend
- 173. By justinsb
-
Merged latest improved error message
- 174. By justinsb
-
Merged latest kiss-backend
- 175. By justinsb
-
Adding dev-scripts to make development easier
- 176. By justinsb
-
Added network to up/down helper scripts
- 177. By justinsb
-
Merged with latest ~justin-
fathomdb/ nova/kiss- backend/ - 178. By justinsb
-
Built disk and launched VM
- 179. By justinsb
-
Don't partition the disk if using raw images
- 180. By justinsb
-
Merged with latest lp:~justin-fathomdb/nova/raw-disk-images (though I'm fighting a mini merge-battle with myself because the code actually depends on the twisted removal)
- 181. By justinsb
-
Actually, it looks like the twisted removal & virtualbox support may be inextricably linked...
justinsb (justin-fathomdb) wrote : | # |
The relational is the 'viral' nature of Twisted's async calls... when implementing an interface method that is currently synchronous (get_info, as I recall), that method needs to be made asynchronous if the implementation is now async (in this case, all VirtualBox methods must spawn the VirtualBoxManage command.) But then all its callers need to be made async, and then their callers etc. When I asked about this on the mailing list, it seems that the only thing to do was essentially to make _every_ method async, in case it ever called in future an interface method which had an async implementation. That's a pretty big lock-in to Twisted, and it seems that the momentum was moving against Twisted.
If this patch continued to work with Twisted, I think it counted it would need a dozen additional changes propagating new deferreds up the call chains.
Whatever the decision on Twisted, I'll work with it, but I do think we need _a_ decision. If it's to keep Twisted then I'll have to rework this patch, but that's OK... My vote is to go with Threads/Eventlet, because then the whole code isn't covered in Twisted-specifics and we have more options in future (Python Threads, Jython, Eventlet, source code translators) vs just Twisted.
Unmerged revisions
- 181. By justinsb
-
Actually, it looks like the twisted removal & virtualbox support may be inextricably linked...
- 180. By justinsb
-
Merged with latest lp:~justin-fathomdb/nova/raw-disk-images (though I'm fighting a mini merge-battle with myself because the code actually depends on the twisted removal)
- 179. By justinsb
-
Don't partition the disk if using raw images
- 178. By justinsb
-
Built disk and launched VM
- 177. By justinsb
-
Merged with latest ~justin-
fathomdb/ nova/kiss- backend/ - 176. By justinsb
-
Added network to up/down helper scripts
- 175. By justinsb
-
Adding dev-scripts to make development easier
- 174. By justinsb
-
Merged latest kiss-backend
- 173. By justinsb
-
Merged latest improved error message
- 172. By justinsb
-
Merged latest kiss-backend
Preview Diff
1 | === modified file 'bin/nova-compute' |
2 | --- bin/nova-compute 2010-08-19 00:39:12 +0000 |
3 | +++ bin/nova-compute 2010-08-21 14:09:42 +0000 |
4 | @@ -24,9 +24,8 @@ |
5 | from nova import twistd |
6 | from nova.compute import service |
7 | |
8 | - |
9 | -if __name__ == '__main__': |
10 | - twistd.serve(__file__) |
11 | - |
12 | -if __name__ == '__builtin__': |
13 | - application = service.ComputeService.create() # pylint: disable-msg=C0103 |
14 | +twistd.serve(__file__, twisted=False) |
15 | + |
16 | +service.ComputeService.create() |
17 | + |
18 | +service.ComputeService.main_loop() |
19 | |
20 | === added directory 'dev-scripts' |
21 | === added file 'dev-scripts/api.sh' |
22 | --- dev-scripts/api.sh 1970-01-01 00:00:00 +0000 |
23 | +++ dev-scripts/api.sh 2010-08-21 14:09:42 +0000 |
24 | @@ -0,0 +1,9 @@ |
25 | +#!/bin/bash |
26 | + |
27 | +. ~/openstack/conf |
28 | +cd ${NOVA_SRC_BASE} |
29 | + |
30 | +NOVA_API_ARGS="--ca_path=${CA_DIR} --keys_path=${KEYS_PATH} ${NETWORK_ARGS}" # --datastore_path=/var/lib/nova/keeper" |
31 | +echo NOVA_API_ARGS=${NOVA_API_ARGS} |
32 | +bin/nova-api ${NOVA_API_ARGS} --nodaemonize --verbose start |
33 | + |
34 | |
35 | === added file 'dev-scripts/compute.sh' |
36 | --- dev-scripts/compute.sh 1970-01-01 00:00:00 +0000 |
37 | +++ dev-scripts/compute.sh 2010-08-21 14:09:42 +0000 |
38 | @@ -0,0 +1,19 @@ |
39 | +#!/bin/bash |
40 | + |
41 | +. ~/openstack/conf |
42 | +cd ${NOVA_SRC_BASE} |
43 | + |
44 | +mkdir -p ${INSTANCES_PATH} |
45 | +mkdir -p ${NETWORKS_PATH} |
46 | +NOVA_COMPUTE_ARGS="--ca_path=${CA_DIR} --keys_path=${KEYS_PATH} ${NETWORK_ARGS} --images_path=${IMAGES_PATH} --use_s3=false --instances_path=${INSTANCES_PATH} --networks_path=${NETWORKS_PATH}" # --libvirt_xml_template=nova/compute/libvirt.xml.template --vpn_client_template=nova/cloudpipe/client.ovpn.template --credentials_template=nova/auth/novarc.template" |
47 | +# --datastore_path=/var/lib/nova/keeper" |
48 | + |
49 | +if [ -n $CONNECTION_TYPE ]; then |
50 | + NOVA_COMPUTE_ARGS="${NOVA_COMPUTE_ARGS} --connection_type=${CONNECTION_TYPE}" |
51 | +fi |
52 | + |
53 | +echo NOVA_COMPUTE_ARGS=${NOVA_COMPUTE_ARGS} |
54 | + |
55 | +bin/nova-compute ${NOVA_COMPUTE_ARGS} --nodaemon --verbose start |
56 | + |
57 | + |
58 | |
59 | === added file 'dev-scripts/create-image.sh' |
60 | --- dev-scripts/create-image.sh 1970-01-01 00:00:00 +0000 |
61 | +++ dev-scripts/create-image.sh 2010-08-21 14:09:42 +0000 |
62 | @@ -0,0 +1,17 @@ |
63 | +#!/bin/bash |
64 | + |
65 | +apt-get --yes install apt-proxy ubuntu-vm-builder |
66 | + |
67 | +mkdir -p ~/openstack/images |
68 | +cd ~/openstack/images |
69 | +# If you're on 32 bit CPU, or if you don't have HVM (?), use 32 bit... |
70 | +# If you're not running a big-memory machine, you should probably be using 32 bit anyway... |
71 | +ARCH=i386 |
72 | +sudo vmbuilder kvm ubuntu --addpkg openssh-server --install-mirror=http://127.0.0.1:9999/ubuntu --install-security-mirror=http://127.0.0.1:9999/ubuntu-security --debug --arch ${ARCH} |
73 | +cd ubuntu-kvm |
74 | +# Sneaky link to get around random naming |
75 | +ln -s *.qcow2 root.qcow2 |
76 | +qemu-img info root.qcow2 |
77 | +qemu-img convert -O vmdk root.qcow2 root.vmdk |
78 | +qemu-img info root.vmdk |
79 | + |
80 | |
81 | === added file 'dev-scripts/down.sh' |
82 | --- dev-scripts/down.sh 1970-01-01 00:00:00 +0000 |
83 | +++ dev-scripts/down.sh 2010-08-21 14:09:42 +0000 |
84 | @@ -0,0 +1,8 @@ |
85 | +#!/bin/bash |
86 | + |
87 | +pkill -f nova-api |
88 | +pkill -f nova-compute |
89 | +pkill -f nova-volume |
90 | +pkill -f nova-objectstore |
91 | +pkill -f nova-network |
92 | +pkill -f redis-server |
93 | |
94 | === added file 'dev-scripts/network.sh' |
95 | --- dev-scripts/network.sh 1970-01-01 00:00:00 +0000 |
96 | +++ dev-scripts/network.sh 2010-08-21 14:09:42 +0000 |
97 | @@ -0,0 +1,7 @@ |
98 | +#!/bin/bash |
99 | + |
100 | +. ~/openstack/conf |
101 | +cd ${NOVA_SRC_BASE} |
102 | + |
103 | +bin/nova-network --nodaemon --verbose |
104 | + |
105 | |
106 | === added file 'dev-scripts/objectstore.sh' |
107 | --- dev-scripts/objectstore.sh 1970-01-01 00:00:00 +0000 |
108 | +++ dev-scripts/objectstore.sh 2010-08-21 14:09:42 +0000 |
109 | @@ -0,0 +1,15 @@ |
110 | +#!/bin/bash |
111 | + |
112 | +. ~/openstack/conf |
113 | +cd ${NOVA_SRC_BASE} |
114 | + |
115 | +mkdir -p ${BUCKETS_PATH} |
116 | +mkdir -p ${IMAGES_PATH} |
117 | +NOVA_OBJECTSTORE_ARGS="--ca_path=${CA_DIR} --keys_path=${KEYS_PATH} ${NETWORK_ARGS} --images_path=${IMAGES_PATH} --buckets_path=${BUCKETS_PATH}" # --datastore_path=/var/lib/nova/keeper" |
118 | +# This hack is because otherwise the compute deamon also writes to twistd.log |
119 | +mkdir -p objectstorelogs |
120 | +cd objectstorelogs |
121 | +# PYTHONPATH needs to be set to '..' to find the modules when running this way.. (overriding the 'conf' file at the top) -- comstud |
122 | +PYTHONPATH=".." |
123 | +../bin/nova-objectstore ${NOVA_OBJECTSTORE_ARGS} --nodaemon --verbose start |
124 | + |
125 | |
126 | === added file 'dev-scripts/redis.sh' |
127 | --- dev-scripts/redis.sh 1970-01-01 00:00:00 +0000 |
128 | +++ dev-scripts/redis.sh 2010-08-21 14:09:42 +0000 |
129 | @@ -0,0 +1,3 @@ |
130 | +#!/bin/bash |
131 | + |
132 | +~/openstack/src/redis-latest/redis-server |
133 | |
134 | === added file 'dev-scripts/restart.sh' |
135 | --- dev-scripts/restart.sh 1970-01-01 00:00:00 +0000 |
136 | +++ dev-scripts/restart.sh 2010-08-21 14:09:42 +0000 |
137 | @@ -0,0 +1,8 @@ |
138 | +#!/bin/bash |
139 | + |
140 | +BASE=`dirname $0` |
141 | + |
142 | +${BASE}/down.sh |
143 | +sleep 1 |
144 | +${BASE}/up.sh |
145 | + |
146 | |
147 | === added file 'dev-scripts/up.sh' |
148 | --- dev-scripts/up.sh 1970-01-01 00:00:00 +0000 |
149 | +++ dev-scripts/up.sh 2010-08-21 14:09:42 +0000 |
150 | @@ -0,0 +1,10 @@ |
151 | +#!/bin/bash |
152 | + |
153 | +BASE=`dirname $0` |
154 | +screen -X screen -t redis ${BASE}/redis.sh |
155 | +screen -X screen -t api ${BASE}/api.sh |
156 | +screen -X screen -t object ${BASE}/objectstore.sh |
157 | +screen -X screen -t compute ${BASE}/compute.sh |
158 | +screen -X screen -t volume ${BASE}/volume.sh |
159 | +screen -X screen -t network ${BASE}/network.sh |
160 | + |
161 | |
162 | === added file 'dev-scripts/upload-image.sh' |
163 | --- dev-scripts/upload-image.sh 1970-01-01 00:00:00 +0000 |
164 | +++ dev-scripts/upload-image.sh 2010-08-21 14:09:42 +0000 |
165 | @@ -0,0 +1,13 @@ |
166 | +#!/bin/bash |
167 | + |
168 | +cd ~/openstack/images |
169 | +cd ubuntu-kvm |
170 | + |
171 | +mkdir -p image |
172 | +. ${NOVA_SRC_BASE}/me/novarc |
173 | +euca-bundle-image --image root.vmdk --destination ./image |
174 | +euca-upload-bundle --bucket image --manifest ./image/root.vmdk.manifest.xml |
175 | +EMI=`euca-register image/root.img.manifest.xml | awk '{print $2}'` |
176 | +echo $EMI |
177 | + |
178 | + |
179 | |
180 | === added file 'dev-scripts/volume.sh' |
181 | --- dev-scripts/volume.sh 1970-01-01 00:00:00 +0000 |
182 | +++ dev-scripts/volume.sh 2010-08-21 14:09:42 +0000 |
183 | @@ -0,0 +1,11 @@ |
184 | +#!/bin/bash |
185 | + |
186 | +. ~/openstack/conf |
187 | +cd ${NOVA_SRC_BASE} |
188 | + |
189 | +NOVA_VOLUME_ARGS="--storage_dev=${LOOPBACK_MOUNT}" |
190 | + |
191 | +#are you running from iSCSI? if not, comment out 2nd line and run first line: |
192 | +#bin/nova-volume ${NOVA_VOLUME_ARGS} --nodaemon --verbose start |
193 | +bin/nova-volume ${NOVA_VOLUME_ARGS} --nodaemon --iscsi_storage=true --verbose start |
194 | + |
195 | |
196 | === modified file 'nova/compute/disk.py' |
197 | --- nova/compute/disk.py 2010-08-21 14:09:42 +0000 |
198 | +++ nova/compute/disk.py 2010-08-21 14:09:42 +0000 |
199 | @@ -25,12 +25,9 @@ |
200 | import os |
201 | import tempfile |
202 | |
203 | -from twisted.internet import defer |
204 | - |
205 | from nova import exception |
206 | |
207 | |
208 | -@defer.inlineCallbacks |
209 | def partition(infile, outfile, local_bytes=0, local_type='ext2', execute=None): |
210 | """Takes a single partition represented by infile and writes a bootable |
211 | drive image into outfile. |
212 | @@ -66,27 +63,26 @@ |
213 | last_sector = local_last # e |
214 | |
215 | # create an empty file |
216 | - yield execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' |
217 | + execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' |
218 | % (outfile, last_sector, sector_size)) |
219 | |
220 | # make mbr partition |
221 | - yield execute('parted --script %s mklabel msdos' % outfile) |
222 | + execute('parted --script %s mklabel msdos' % outfile) |
223 | |
224 | # make primary partition |
225 | - yield execute('parted --script %s mkpart primary %ds %ds' |
226 | + execute('parted --script %s mkpart primary %ds %ds' |
227 | % (outfile, primary_first, primary_last)) |
228 | |
229 | # make local partition |
230 | if local_bytes > 0: |
231 | - yield execute('parted --script %s mkpartfs primary %s %ds %ds' |
232 | + execute('parted --script %s mkpartfs primary %s %ds %ds' |
233 | % (outfile, local_type, local_first, local_last)) |
234 | |
235 | # copy file into partition |
236 | - yield execute('dd if=%s of=%s bs=%d seek=%d conv=notrunc,fsync' |
237 | + execute('dd if=%s of=%s bs=%d seek=%d conv=notrunc,fsync' |
238 | % (infile, outfile, sector_size, primary_first)) |
239 | |
240 | |
241 | -@defer.inlineCallbacks |
242 | def inject_data( image, key=None, net=None, dns=None, |
243 | remove_network_udev=False, |
244 | partition=None, execute=None): |
245 | @@ -98,14 +94,14 @@ |
246 | If partition is not specified it mounts the image as a single partition. |
247 | |
248 | """ |
249 | - out, err = yield execute('sudo losetup --find --show %s' % image) |
250 | + out, err = execute('sudo losetup --find --show %s' % image) |
251 | if err: |
252 | raise exception.Error('Could not attach image to loopback: %s' % err) |
253 | device = out.strip() |
254 | try: |
255 | if not partition is None: |
256 | # create partition |
257 | - out, err = yield execute('sudo kpartx -a %s' % device) |
258 | + out, err = execute('sudo kpartx -a %s' % device) |
259 | if err: |
260 | raise exception.Error('Failed to load partition: %s' % err) |
261 | mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1], |
262 | @@ -121,12 +117,12 @@ |
263 | % mapped_device) |
264 | |
265 | # Configure ext2fs so that it doesn't auto-check every N boots |
266 | - out, err = yield execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) |
267 | + out, err = execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) |
268 | |
269 | tmpdir = tempfile.mkdtemp() |
270 | try: |
271 | # mount loopback to dir |
272 | - out, err = yield execute( |
273 | + out, err = execute( |
274 | 'sudo mount %s %s' % (mapped_device, tmpdir)) |
275 | if err: |
276 | raise exception.Error('Failed to mount filesystem: %s' % err) |
277 | @@ -134,53 +130,48 @@ |
278 | try: |
279 | if key: |
280 | # inject key file |
281 | - yield _inject_key_into_fs(key, tmpdir, execute=execute) |
282 | + _inject_key_into_fs(key, tmpdir, execute=execute) |
283 | if net: |
284 | - yield _inject_net_into_fs(net, tmpdir, execute=execute) |
285 | + _inject_net_into_fs(net, tmpdir, execute=execute) |
286 | if dns: |
287 | - yield _inject_dns_into_fs(dns, tmpdir, execute=execute) |
288 | + _inject_dns_into_fs(dns, tmpdir, execute=execute) |
289 | if remove_network_udev: |
290 | - yield _remove_network_udev(tmpdir, execute=execute) |
291 | + _remove_network_udev(tmpdir, execute=execute) |
292 | |
293 | finally: |
294 | # unmount device |
295 | - yield execute('sudo umount %s' % mapped_device) |
296 | + execute('sudo umount %s' % mapped_device) |
297 | finally: |
298 | # remove temporary directory |
299 | - yield execute('rmdir %s' % tmpdir) |
300 | + execute('rmdir %s' % tmpdir) |
301 | if not partition is None: |
302 | # remove partitions |
303 | - yield execute('sudo kpartx -d %s' % device) |
304 | + execute('sudo kpartx -d %s' % device) |
305 | finally: |
306 | # remove loopback |
307 | - yield execute('sudo losetup --detach %s' % device) |
308 | - |
309 | - |
310 | -@defer.inlineCallbacks |
311 | + execute('sudo losetup --detach %s' % device) |
312 | + |
313 | def _inject_key_into_fs(key, fs, execute=None): |
314 | sshdir = os.path.join(fs, 'root', '.ssh') |
315 | - yield execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter |
316 | - yield execute('sudo chown root %s' % sshdir) |
317 | - yield execute('sudo chmod 700 %s' % sshdir) |
318 | + execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter |
319 | + execute('sudo chown root %s' % sshdir) |
320 | + execute('sudo chmod 700 %s' % sshdir) |
321 | keyfile = os.path.join(sshdir, 'authorized_keys') |
322 | - yield execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n') |
323 | - |
324 | - |
325 | -@defer.inlineCallbacks |
326 | + execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n') |
327 | + |
328 | + |
329 | def _inject_net_into_fs(net, fs, execute=None): |
330 | netfile = os.path.join(fs, 'etc', 'network', 'interfaces') |
331 | - yield execute('sudo tee %s' % netfile, net) |
332 | + execute('sudo tee %s' % netfile, net) |
333 | |
334 | -@defer.inlineCallbacks |
335 | def _inject_dns_into_fs(dns, fs, execute=None): |
336 | dnsfile = os.path.join(fs, 'etc', 'resolv.conf') |
337 | - yield execute('sudo tee %s' % dnsfile, dns) |
338 | + execute('sudo tee %s' % dnsfile, dns) |
339 | |
340 | -@defer.inlineCallbacks |
341 | def _remove_network_udev(fs, execute=None): |
342 | # TODO(justinsb): This is correct for Ubuntu, but might not be right for |
343 | # other distros. There is a much bigger discussion to be had about what |
344 | # we inject and how we inject it. |
345 | rulesfile = os.path.join(fs, 'etc', 'udev', 'rules.d', '70-persistent-net.rules') |
346 | - yield execute('rm -f %s' % rulesfile) |
347 | + execute('rm -f %s' % rulesfile) |
348 | |
349 | |
350 | === modified file 'nova/compute/monitor.py' |
351 | --- nova/compute/monitor.py 2010-08-16 12:16:21 +0000 |
352 | +++ nova/compute/monitor.py 2010-08-21 14:09:42 +0000 |
353 | @@ -389,7 +389,7 @@ |
354 | return '%d:%d' % (rx, tx) |
355 | |
356 | |
357 | -class InstanceMonitor(object, service.Service): |
358 | +class InstanceMonitor(object, service.ThreadedService): |
359 | """ |
360 | Monitors the running instances of the current machine. |
361 | """ |
362 | |
363 | === modified file 'nova/compute/service.py' |
364 | --- nova/compute/service.py 2010-08-16 12:16:21 +0000 |
365 | +++ nova/compute/service.py 2010-08-21 14:09:42 +0000 |
366 | @@ -30,12 +30,8 @@ |
367 | import os |
368 | import sys |
369 | |
370 | -from twisted.internet import defer |
371 | -from twisted.internet import task |
372 | - |
373 | from nova import exception |
374 | from nova import flags |
375 | -from nova import process |
376 | from nova import service |
377 | from nova import utils |
378 | from nova.compute import disk |
379 | @@ -53,7 +49,7 @@ |
380 | 'where instances are stored on disk') |
381 | |
382 | |
383 | -class ComputeService(service.Service): |
384 | +class ComputeService(service.ThreadedService): |
385 | """ |
386 | Manages the running instances. |
387 | """ |
388 | @@ -67,7 +63,7 @@ |
389 | |
390 | def noop(self): |
391 | """ simple test of an AMQP message call """ |
392 | - return defer.succeed('PONG') |
393 | + return 'PONG' |
394 | |
395 | def get_instance(self, instance_id): |
396 | # inst = self.instdir.get(instance_id) |
397 | @@ -79,7 +75,7 @@ |
398 | @exception.wrap_exception |
399 | def adopt_instances(self): |
400 | """ if there are instances already running, adopt them """ |
401 | - return defer.succeed(0) |
402 | + return 0 |
403 | instance_names = self._conn.list_instances() |
404 | for name in instance_names: |
405 | try: |
406 | @@ -87,7 +83,7 @@ |
407 | new_inst.update_state() |
408 | except: |
409 | pass |
410 | - return defer.succeed(len(self._instances)) |
411 | + return len(self._instances) |
412 | |
413 | @exception.wrap_exception |
414 | def describe_instances(self): |
415 | @@ -97,7 +93,6 @@ |
416 | Instance.fromName(self._conn, inst['instance_id'])) |
417 | return retval |
418 | |
419 | - @defer.inlineCallbacks |
420 | def report_state(self, nodename, daemon): |
421 | # TODO(termie): make this pattern be more elegant. -todd |
422 | try: |
423 | @@ -111,7 +106,7 @@ |
424 | if not getattr(self, "model_disconnected", False): |
425 | self.model_disconnected = True |
426 | logging.exception("model server went away") |
427 | - yield |
428 | + |
429 | |
430 | @exception.wrap_exception |
431 | def run_instance(self, instance_id, **_kwargs): |
432 | @@ -161,7 +156,6 @@ |
433 | 'trying to reboot unknown instance: %s' % instance_id) |
434 | return instance.reboot() |
435 | |
436 | - @defer.inlineCallbacks |
437 | @exception.wrap_exception |
438 | def get_console_output(self, instance_id): |
439 | """ send the console output for an instance """ |
440 | @@ -171,34 +165,31 @@ |
441 | if not instance: |
442 | raise exception.Error( |
443 | 'trying to get console log for unknown: %s' % instance_id) |
444 | - rv = yield instance.console_output() |
445 | + rv = instance.console_output() |
446 | # TODO(termie): this stuff belongs in the API layer, no need to |
447 | # munge the data we send to ourselves |
448 | output = {"InstanceId" : instance_id, |
449 | "Timestamp" : "2", |
450 | "output" : base64.b64encode(rv)} |
451 | - defer.returnValue(output) |
452 | + return output |
453 | |
454 | - @defer.inlineCallbacks |
455 | @exception.wrap_exception |
456 | def attach_volume(self, instance_id = None, |
457 | volume_id = None, mountpoint = None): |
458 | volume = volume_service.get_volume(volume_id) |
459 | - yield self._init_aoe() |
460 | - yield process.simple_execute( |
461 | + self._init_aoe() |
462 | + utils.execute( |
463 | "sudo virsh attach-disk %s /dev/etherd/%s %s" % |
464 | (instance_id, |
465 | volume['aoe_device'], |
466 | mountpoint.rpartition('/dev/')[2])) |
467 | volume.finish_attach() |
468 | - defer.returnValue(True) |
469 | + return True |
470 | |
471 | - @defer.inlineCallbacks |
472 | def _init_aoe(self): |
473 | - yield process.simple_execute("sudo aoe-discover") |
474 | - yield process.simple_execute("sudo aoe-stat") |
475 | + utils.execute("sudo aoe-discover") |
476 | + utils.execute("sudo aoe-stat") |
477 | |
478 | - @defer.inlineCallbacks |
479 | @exception.wrap_exception |
480 | def detach_volume(self, instance_id, volume_id): |
481 | """ detach a volume from an instance """ |
482 | @@ -206,10 +197,10 @@ |
483 | # name without the leading /dev/ |
484 | volume = volume_service.get_volume(volume_id) |
485 | target = volume['mountpoint'].rpartition('/dev/')[2] |
486 | - yield process.simple_execute( |
487 | + utils.execute( |
488 | "sudo virsh detach-disk %s %s " % (instance_id, target)) |
489 | volume.finish_detach() |
490 | - defer.returnValue(True) |
491 | + return True |
492 | |
493 | |
494 | class Group(object): |
495 | @@ -317,7 +308,6 @@ |
496 | self.set_state(self.state) |
497 | self.datamodel.save() # Extra, but harmless |
498 | |
499 | - @defer.inlineCallbacks |
500 | @exception.wrap_exception |
501 | def destroy(self): |
502 | if self.is_destroyed(): |
503 | @@ -326,10 +316,9 @@ |
504 | ' instance: %s' % self.name) |
505 | |
506 | self.set_state(power_state.NOSTATE, 'shutting_down') |
507 | - yield self._conn.destroy(self) |
508 | + self._conn.destroy(self) |
509 | self.datamodel.destroy() |
510 | |
511 | - @defer.inlineCallbacks |
512 | @exception.wrap_exception |
513 | def reboot(self): |
514 | if not self.is_running(): |
515 | @@ -339,18 +328,17 @@ |
516 | |
517 | logging.debug('rebooting instance %s' % self.name) |
518 | self.set_state(power_state.NOSTATE, 'rebooting') |
519 | - yield self._conn.reboot(self) |
520 | + self._conn.reboot(self) |
521 | self.update_state() |
522 | |
523 | - @defer.inlineCallbacks |
524 | @exception.wrap_exception |
525 | def spawn(self): |
526 | self.set_state(power_state.NOSTATE, 'spawning') |
527 | logging.debug("Starting spawn in Instance") |
528 | try: |
529 | - yield self._conn.spawn(self) |
530 | + self._conn.spawn(self) |
531 | except Exception, ex: |
532 | - logging.debug(ex) |
533 | + logging.warn(ex) |
534 | self.set_state(power_state.SHUTDOWN) |
535 | self.update_state() |
536 | |
537 | @@ -364,4 +352,4 @@ |
538 | console = f.read() |
539 | else: |
540 | console = 'FAKE CONSOLE OUTPUT' |
541 | - return defer.succeed(console) |
542 | + return console |
543 | |
544 | === modified file 'nova/network/service.py' |
545 | --- nova/network/service.py 2010-08-18 15:44:24 +0000 |
546 | +++ nova/network/service.py 2010-08-21 14:09:42 +0000 |
547 | @@ -81,7 +81,7 @@ |
548 | return "networkhost:%s" % project_id |
549 | |
550 | |
551 | -class BaseNetworkService(service.Service): |
552 | +class BaseNetworkService(service.TwistedService): |
553 | """Implements common network service functionality |
554 | |
555 | This class must be subclassed. |
556 | |
557 | === modified file 'nova/process.py' |
558 | --- nova/process.py 2010-08-18 21:14:24 +0000 |
559 | +++ nova/process.py 2010-08-21 14:09:42 +0000 |
560 | @@ -29,28 +29,12 @@ |
561 | from twisted.internet import reactor |
562 | |
563 | from nova import flags |
564 | +from nova.utils import ProcessExecutionError |
565 | |
566 | FLAGS = flags.FLAGS |
567 | flags.DEFINE_integer('process_pool_size', 4, |
568 | 'Number of processes to use in the process pool') |
569 | |
570 | - |
571 | -# NOTE(termie): this is copied from twisted.internet.utils but since |
572 | -# they don't export it I've copied and modified |
573 | -class UnexpectedErrorOutput(IOError): |
574 | - """ |
575 | - Standard error data was received where it was not expected. This is a |
576 | - subclass of L{IOError} to preserve backward compatibility with the previous |
577 | - error behavior of L{getProcessOutput}. |
578 | - |
579 | - @ivar processEnded: A L{Deferred} which will fire when the process which |
580 | - produced the data on stderr has ended (exited and all file descriptors |
581 | - closed). |
582 | - """ |
583 | - def __init__(self, stdout=None, stderr=None): |
584 | - IOError.__init__(self, "got stdout: %r\nstderr: %r" % (stdout, stderr)) |
585 | - |
586 | - |
587 | # This is based on _BackRelay from twister.internal.utils, but modified to |
588 | # capture both stdout and stderr, without odd stderr handling, and also to |
589 | # handle stdin |
590 | @@ -62,22 +46,23 @@ |
591 | @ivar deferred: A L{Deferred} which will be called back with all of stdout |
592 | and all of stderr as well (as a tuple). C{terminate_on_stderr} is true |
593 | and any bytes are received over stderr, this will fire with an |
594 | - L{_UnexpectedErrorOutput} instance and the attribute will be set to |
595 | + L{_ProcessExecutionError} instance and the attribute will be set to |
596 | C{None}. |
597 | |
598 | @ivar onProcessEnded: If C{terminate_on_stderr} is false and bytes are |
599 | received over stderr, this attribute will refer to a L{Deferred} which |
600 | will be called back when the process ends. This C{Deferred} is also |
601 | - associated with the L{_UnexpectedErrorOutput} which C{deferred} fires |
602 | + associated with the L{_ProcessExecutionError} which C{deferred} fires |
603 | with earlier in this case so that users can determine when the process |
604 | has actually ended, in addition to knowing when bytes have been received |
605 | via stderr. |
606 | """ |
607 | |
608 | - def __init__(self, deferred, started_deferred=None, |
609 | + def __init__(self, deferred, cmd, started_deferred=None, |
610 | terminate_on_stderr=False, check_exit_code=True, |
611 | process_input=None): |
612 | self.deferred = deferred |
613 | + self.cmd = cmd |
614 | self.stdout = StringIO.StringIO() |
615 | self.stderr = StringIO.StringIO() |
616 | self.started_deferred = started_deferred |
617 | @@ -85,14 +70,18 @@ |
618 | self.check_exit_code = check_exit_code |
619 | self.process_input = process_input |
620 | self.on_process_ended = None |
621 | - |
622 | + |
623 | + def _build_execution_error(self, exit_code=None): |
624 | + return ProcessExecutionError( cmd=self.cmd, |
625 | + exit_code=exit_code, |
626 | + stdout=self.stdout.getvalue(), |
627 | + stderr=self.stderr.getvalue()) |
628 | + |
629 | def errReceived(self, text): |
630 | self.stderr.write(text) |
631 | if self.terminate_on_stderr and (self.deferred is not None): |
632 | self.on_process_ended = defer.Deferred() |
633 | - self.deferred.errback(UnexpectedErrorOutput( |
634 | - stdout=self.stdout.getvalue(), |
635 | - stderr=self.stderr.getvalue())) |
636 | + self.deferred.errback(self._build_execution_error()) |
637 | self.deferred = None |
638 | self.transport.loseConnection() |
639 | |
640 | @@ -102,15 +91,19 @@ |
641 | def processEnded(self, reason): |
642 | if self.deferred is not None: |
643 | stdout, stderr = self.stdout.getvalue(), self.stderr.getvalue() |
644 | - try: |
645 | - if self.check_exit_code: |
646 | - reason.trap(error.ProcessDone) |
647 | - self.deferred.callback((stdout, stderr)) |
648 | - except: |
649 | - # NOTE(justinsb): This logic is a little suspicious to me... |
650 | - # If the callback throws an exception, then errback will be |
651 | - # called also. However, this is what the unit tests test for... |
652 | - self.deferred.errback(UnexpectedErrorOutput(stdout, stderr)) |
653 | + exit_code = reason.value.exitCode |
654 | + if self.check_exit_code and exit_code <> 0: |
655 | + self.deferred.errback(self._build_execution_error(exit_code)) |
656 | + else: |
657 | + try: |
658 | + if self.check_exit_code: |
659 | + reason.trap(error.ProcessDone) |
660 | + self.deferred.callback((stdout, stderr)) |
661 | + except: |
662 | + # NOTE(justinsb): This logic is a little suspicious to me... |
663 | + # If the callback throws an exception, then errback will be |
664 | + # called also. However, this is what the unit tests test for... |
665 | + self.deferred.errback(self._build_execution_error(exit_code)) |
666 | elif self.on_process_ended is not None: |
667 | self.on_process_ended.errback(reason) |
668 | |
669 | @@ -131,8 +124,12 @@ |
670 | args = args and args or () |
671 | env = env and env and {} |
672 | deferred = defer.Deferred() |
673 | + cmd = executable |
674 | + if args: |
675 | + cmd = cmd + " " + ' '.join(args) |
676 | process_handler = BackRelayWithInput( |
677 | - deferred, |
678 | + deferred, |
679 | + cmd, |
680 | started_deferred=started_deferred, |
681 | check_exit_code=check_exit_code, |
682 | process_input=process_input, |
683 | |
684 | === modified file 'nova/rpc.py' |
685 | --- nova/rpc.py 2010-08-18 15:44:24 +0000 |
686 | +++ nova/rpc.py 2010-08-21 14:09:42 +0000 |
687 | @@ -1,8 +1,12 @@ |
688 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 |
689 | +from nova.threads import PollingThread |
690 | +from threading import Thread |
691 | +import threading |
692 | |
693 | # Copyright 2010 United States Government as represented by the |
694 | # Administrator of the National Aeronautics and Space Administration. |
695 | # All Rights Reserved. |
696 | +# Copyright 2010 FathomDB Inc. All rights reserved. |
697 | # |
698 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
699 | # not use this file except in compliance with the License. You may obtain |
700 | @@ -71,6 +75,14 @@ |
701 | del cls._instance |
702 | return cls.instance() |
703 | |
704 | +# Carrot is seriously FUBAR when it comes to threads |
705 | +# (1990 called and wants its libraries back) |
706 | +# It looks like it we simultaneously issue fetches from two threads, carrot crashes. |
707 | +# Also, we can't open a queue connection on one thread and then call from another. |
708 | +# We'll probably have to introduce a message queueing library that works |
709 | +# (or just wrap carrot) |
710 | +# Finding all the problems first, then going to decide... |
711 | +queue_lock = threading.Lock() |
712 | |
713 | class Consumer(messaging.Consumer): |
714 | """Consumer base class |
715 | @@ -100,6 +112,7 @@ |
716 | """Wraps the parent fetch with some logic for failed connections""" |
717 | # TODO(vish): the logic for failed connections and logging should be |
718 | # refactored into some sort of connection manager object |
719 | + LOG.debug("Doing fetch from queue") |
720 | try: |
721 | if self.failed_connection: |
722 | # NOTE(vish): conn is defined in the parent class, we can |
723 | @@ -107,16 +120,17 @@ |
724 | # pylint: disable-msg=W0201 |
725 | self.conn = Connection.recreate() |
726 | self.backend = self.conn.create_backend() |
727 | - super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) |
728 | + with queue_lock: |
729 | + super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) |
730 | if self.failed_connection: |
731 | - logging.error("Reconnected to queue") |
732 | + LOG.error("Reconnected to queue") |
733 | self.failed_connection = False |
734 | # NOTE(vish): This is catching all errors because we really don't |
735 | # exceptions to be logged 10 times a second if some |
736 | # persistent failure occurs. |
737 | except Exception: # pylint: disable-msg=W0703 |
738 | if not self.failed_connection: |
739 | - logging.exception("Failed to fetch message from queue") |
740 | + LOG.exception("Failed to fetch message from queue") |
741 | self.failed_connection = True |
742 | |
743 | def attach_to_twisted(self): |
744 | @@ -124,7 +138,44 @@ |
745 | loop = task.LoopingCall(self.fetch, enable_callbacks=True) |
746 | loop.start(interval=0.1) |
747 | |
748 | - |
749 | + def start_background_thread(self): |
750 | + consumer_thread = ConsumeQueueThread() |
751 | + consumer_thread.setName('Queue consumer thread') |
752 | + consumer_thread.task_interval = 0.1 |
753 | + consumer_thread.consumer = self |
754 | + consumer_thread.setDaemon(True) |
755 | + consumer_thread.start() |
756 | + return consumer_thread |
757 | + |
758 | +class ConsumeQueueThread(PollingThread): |
759 | + """ Thread that waits for queue messages """ |
760 | + # It would be very easy to convert this to be non-polling |
761 | + # (i.e. just call consumer.wait(), but we're cloning existing behaviour before improving it |
762 | + def _do_task(self): |
763 | + self._fetch_from_queue() |
764 | + |
765 | + def _fetch_from_queue(self): |
766 | + self.consumer.fetch(enable_callbacks=True) |
767 | + |
768 | +class ProcessMessageThread(Thread): |
769 | + """ Thread that processes a queue message """ |
770 | + def run(self): |
771 | + LOG.warning("Invoking %s %s", self.handler_function, self.handler_args) |
772 | + |
773 | + try: |
774 | + rval = self.handler_function(**self.handler_args) |
775 | + except Exception as e: |
776 | + LOG.exception("Error invoking message handler") |
777 | + if self.msg_id: |
778 | + msg_reply(self.msg_id, str(e)) |
779 | + else: |
780 | + LOG.warning("No msg_id, cannot send error message") |
781 | + |
782 | + LOG.warning("retval = %s", rval) |
783 | + |
784 | + if self.msg_id: |
785 | + msg_reply(self.msg_id, rval) |
786 | + |
787 | class Publisher(messaging.Publisher): |
788 | """Publisher base class""" |
789 | pass |
790 | @@ -144,9 +195,11 @@ |
791 | |
792 | class AdapterConsumer(TopicConsumer): |
793 | """Calls methods on a proxy object based on method and args""" |
794 | - def __init__(self, connection=None, topic="broadcast", proxy=None): |
795 | + def __init__(self, connection=None, topic="broadcast", proxy=None, |
796 | + twisted=True): |
797 | LOG.debug('Initing the Adapter Consumer for %s' % (topic)) |
798 | self.proxy = proxy |
799 | + self.twisted = twisted |
800 | super(AdapterConsumer, self).__init__(connection=connection, |
801 | topic=topic) |
802 | |
803 | @@ -177,12 +230,21 @@ |
804 | |
805 | node_func = getattr(self.proxy, str(method)) |
806 | node_args = dict((str(k), v) for k, v in args.iteritems()) |
807 | - # NOTE(vish): magic is fun! |
808 | - # pylint: disable-msg=W0142 |
809 | - d = defer.maybeDeferred(node_func, **node_args) |
810 | - if msg_id: |
811 | - d.addCallback(lambda rval: msg_reply(msg_id, rval, None)) |
812 | - d.addErrback(lambda e: msg_reply(msg_id, None, e)) |
813 | + |
814 | + if self.twisted: |
815 | + # NOTE(vish): magic is fun! |
816 | + # pylint: disable-msg=W0142 |
817 | + d = defer.maybeDeferred(node_func, **node_args) |
818 | + if msg_id: |
819 | + d.addCallback(lambda rval: msg_reply(msg_id, rval, None)) |
820 | + d.addErrback(lambda e: msg_reply(msg_id, None, e)) |
821 | + else: |
822 | + handler_thread = ProcessMessageThread() |
823 | + handler_thread.setName('Message handler: %s' % str(method)) |
824 | + handler_thread.handler_function = node_func |
825 | + handler_thread.handler_args = node_args |
826 | + handler_thread.msg_id = msg_id |
827 | + handler_thread.start() |
828 | return |
829 | |
830 | |
831 | @@ -227,14 +289,15 @@ |
832 | if failure: |
833 | message = failure.getErrorMessage() |
834 | traceback = failure.getTraceback() |
835 | - logging.error("Returning exception %s to caller", message) |
836 | - logging.error(traceback) |
837 | + LOG.error("Returning exception %s to caller", message) |
838 | + LOG.error(traceback) |
839 | failure = (failure.type.__name__, str(failure.value), traceback) |
840 | conn = Connection.instance() |
841 | publisher = DirectPublisher(connection=conn, msg_id=msg_id) |
842 | try: |
843 | publisher.send({'result': reply, 'failure': failure}) |
844 | except TypeError: |
845 | + LOG.exception("Error sending reply") |
846 | publisher.send( |
847 | {'result': dict((k, repr(v)) |
848 | for k, v in reply.__dict__.iteritems()), |
849 | @@ -338,3 +401,40 @@ |
850 | # topic and a json sting representing a dictionary |
851 | # for the method |
852 | send_message(sys.argv[1], json.loads(sys.argv[2])) |
853 | + |
854 | + |
855 | + |
856 | + |
857 | +class ConnectionManager(object): |
858 | + def __init__(self): |
859 | + self.connection_map_lock = threading.Lock() |
860 | + self.connection_map = {} |
861 | + |
862 | + def _build_key(self, config): |
863 | + key = tuple(sorted(config.items())) |
864 | + return key |
865 | + |
866 | + def _build_connection(self, config): |
867 | + LOG.debug('Building queue connection using %s', config) |
868 | + c = Connection(**config) |
869 | + c.queue_config = config |
870 | + c.connection_manager = self |
871 | + return c |
872 | + |
873 | + def get_connection(self, config): |
874 | + key = self._build_key(config) |
875 | + |
876 | + with self.connection_map_lock: |
877 | + c = self.connection_map.get(key) |
878 | + if not c: |
879 | + c = self._build_connection(config) |
880 | + self.connection_map[key] = c |
881 | + return c |
882 | + |
883 | + def invalidate_connection(self, connection): |
884 | + key = self._build_key(connection.queue_config) |
885 | + |
886 | + with self.connection_map_lock: |
887 | + c = self.connection_map.get(key) |
888 | + if c == connection: |
889 | + del self.connection_map[key] |
890 | |
891 | === modified file 'nova/server.py' |
892 | --- nova/server.py 2010-08-09 14:46:33 +0000 |
893 | +++ nova/server.py 2010-08-21 14:09:42 +0000 |
894 | @@ -22,7 +22,6 @@ |
895 | |
896 | import daemon |
897 | from daemon import pidlockfile |
898 | -import logging |
899 | import logging.handlers |
900 | import os |
901 | import signal |
902 | @@ -65,6 +64,7 @@ |
903 | time.sleep(0.1) |
904 | except OSError, err: |
905 | err = str(err) |
906 | + # Should this be >= 0 ? |
907 | if err.find("No such process") > 0: |
908 | if os.path.exists(pidfile): |
909 | os.remove(pidfile) |
910 | |
911 | === modified file 'nova/service.py' |
912 | --- nova/service.py 2010-07-23 22:27:18 +0000 |
913 | +++ nova/service.py 2010-08-21 14:09:42 +0000 |
914 | @@ -1,4 +1,5 @@ |
915 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 |
916 | +from nova.threads import PollingThread |
917 | |
918 | # Copyright 2010 United States Government as represented by the |
919 | # Administrator of the National Aeronautics and Space Administration. |
920 | @@ -23,10 +24,13 @@ |
921 | import inspect |
922 | import logging |
923 | import os |
924 | +import signal |
925 | +import time |
926 | |
927 | from twisted.internet import defer |
928 | from twisted.internet import task |
929 | from twisted.application import service |
930 | +from threading import Thread |
931 | |
932 | from nova import datastore |
933 | from nova import flags |
934 | @@ -40,64 +44,117 @@ |
935 | 'seconds between nodes reporting state to cloud', |
936 | lower_bound=1) |
937 | |
938 | -class Service(object, service.Service): |
939 | +def handle_sig_int(signum, frame): |
940 | + logging.info('Received SIGINT') |
941 | + ThreadedService.keep_running = False |
942 | + |
943 | +class ThreadedService(object): |
944 | """Base class for workers that run on hosts""" |
945 | - |
946 | + keep_running = True |
947 | + |
948 | @classmethod |
949 | def create(cls, |
950 | report_interval=None, # defaults to flag |
951 | bin_name=None, # defaults to basename of executable |
952 | topic=None): # defaults to basename - "nova-" part |
953 | + return _create_common(cls, report_interval=report_interval, |
954 | + bin_name=bin_name, topic=topic, twisted=False) |
955 | + |
956 | + @classmethod |
957 | + def main_loop(cls): |
958 | + signal.signal(signal.SIGINT, handle_sig_int) |
959 | + while ThreadedService.keep_running: |
960 | + time.sleep(1) |
961 | + |
962 | +def _create_common(cls, |
963 | + report_interval=None, # defaults to flag |
964 | + bin_name=None, # defaults to basename of executable |
965 | + topic=None, # defaults to basename - "nova-" part |
966 | + twisted=False): |
967 | + if True: # Hack to minimize diff |
968 | """Instantiates class and passes back application object""" |
969 | - if not report_interval: |
970 | - # NOTE(vish): set here because if it is set to flag in the |
971 | - # parameter list, it wrongly uses the default |
972 | - report_interval = FLAGS.report_interval |
973 | # NOTE(vish): magic to automatically determine bin_name and topic |
974 | if not bin_name: |
975 | bin_name = os.path.basename(inspect.stack()[-1][1]) |
976 | if not topic: |
977 | topic = bin_name.rpartition("nova-")[2] |
978 | - logging.warn("Starting %s node" % topic) |
979 | + logging.warn("Starting %s node", topic) |
980 | node_instance = cls() |
981 | |
982 | conn = rpc.Connection.instance() |
983 | consumer_all = rpc.AdapterConsumer( |
984 | connection=conn, |
985 | topic='%s' % topic, |
986 | - proxy=node_instance) |
987 | + proxy=node_instance, |
988 | + twisted=twisted) |
989 | |
990 | consumer_node = rpc.AdapterConsumer( |
991 | connection=conn, |
992 | topic='%s.%s' % (topic, FLAGS.node_name), |
993 | - proxy=node_instance) |
994 | - |
995 | - pulse = task.LoopingCall(node_instance.report_state, |
996 | - FLAGS.node_name, |
997 | - bin_name) |
998 | - pulse.start(interval=report_interval, now=False) |
999 | - |
1000 | - consumer_all.attach_to_twisted() |
1001 | - consumer_node.attach_to_twisted() |
1002 | - |
1003 | - # This is the parent service that twistd will be looking for when it |
1004 | - # parses this file, return it so that we can get it into globals below |
1005 | - application = service.Application(bin_name) |
1006 | - node_instance.setServiceParent(application) |
1007 | - return application |
1008 | + proxy=node_instance, |
1009 | + twisted=twisted) |
1010 | + |
1011 | + if not report_interval: |
1012 | + # NOTE(vish): set here because if it is set to flag in the |
1013 | + # parameter list, it wrongly uses the default |
1014 | + report_interval = FLAGS.report_interval |
1015 | + if twisted: |
1016 | + pulse = task.LoopingCall(node_instance._report_state_twisted, |
1017 | + FLAGS.node_name, |
1018 | + bin_name) |
1019 | + pulse.start(interval=report_interval, now=False) |
1020 | + |
1021 | + consumer_all.attach_to_twisted() |
1022 | + consumer_node.attach_to_twisted() |
1023 | + else: |
1024 | + report_state_thread = ReportStateThread() |
1025 | + report_state_thread.setName('Report state thread') |
1026 | + report_state_thread.task_interval = report_interval |
1027 | + report_state_thread.nodename = FLAGS.node_name |
1028 | + report_state_thread.daemon = bin_name |
1029 | + report_state_thread.setDaemon(True) |
1030 | + report_state_thread.start() |
1031 | + |
1032 | + consumer_all.start_background_thread() |
1033 | + consumer_node.start_background_thread() |
1034 | + |
1035 | + if twisted: |
1036 | + # This is the parent service that twistd will be looking for when it |
1037 | + # parses this file, return it so that we can get it into globals below |
1038 | + application = service.Application(bin_name) |
1039 | + node_instance.setServiceParent(application) |
1040 | + return application |
1041 | + |
1042 | +class TwistedService(object, service.Service): |
1043 | + @classmethod |
1044 | + def create(cls, |
1045 | + report_interval=None, # defaults to flag |
1046 | + bin_name=None, # defaults to basename of executable |
1047 | + topic=None): # defaults to basename - "nova-" part |
1048 | + return _create_common(cls, report_interval=report_interval, |
1049 | + bin_name=bin_name, topic=topic, twisted=True) |
1050 | + |
1051 | |
1052 | @defer.inlineCallbacks |
1053 | - def report_state(self, nodename, daemon): |
1054 | - # TODO(termie): make this pattern be more elegant. -todd |
1055 | - try: |
1056 | - record = model.Daemon(nodename, daemon) |
1057 | - record.heartbeat() |
1058 | - if getattr(self, "model_disconnected", False): |
1059 | - self.model_disconnected = False |
1060 | - logging.error("Recovered model server connection!") |
1061 | - |
1062 | - except datastore.ConnectionError, ex: |
1063 | - if not getattr(self, "model_disconnected", False): |
1064 | - self.model_disconnected = True |
1065 | - logging.exception("model server went away") |
1066 | + def _report_state_twisted(self, nodename, daemon): |
1067 | + _report_state_common(self, nodename, daemon) |
1068 | yield |
1069 | + |
1070 | +def _report_state_common(target, nodename, daemon): |
1071 | + # TODO(termie): make this pattern be more elegant. -todd |
1072 | + try: |
1073 | + record = model.Daemon(nodename, daemon) |
1074 | + record.heartbeat() |
1075 | + if getattr(target, "model_disconnected", False): |
1076 | + target.model_disconnected = False |
1077 | + logging.error("Recovered model server connection!") |
1078 | + except datastore.ConnectionError, ex: |
1079 | + if not getattr(target, "model_disconnected", False): |
1080 | + target.model_disconnected = True |
1081 | + logging.exception("model server went away") |
1082 | + |
1083 | +class ReportStateThread(PollingThread): |
1084 | + """ Thread that reports state back periodically (heartbeat) """ |
1085 | + def _do_task(self): |
1086 | + _report_state_common(self, self.nodename, self.daemon) |
1087 | + |
1088 | |
1089 | === added file 'nova/threads.py' |
1090 | --- nova/threads.py 1970-01-01 00:00:00 +0000 |
1091 | +++ nova/threads.py 2010-08-21 14:09:42 +0000 |
1092 | @@ -0,0 +1,40 @@ |
1093 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
1094 | + |
1095 | +# Copyright 2010 FathomDB Inc. |
1096 | +# All Rights Reserved. |
1097 | +# |
1098 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
1099 | +# not use this file except in compliance with the License. You may obtain |
1100 | +# a copy of the License at |
1101 | +# |
1102 | +# http://www.apache.org/licenses/LICENSE-2.0 |
1103 | +# |
1104 | +# Unless required by applicable law or agreed to in writing, software |
1105 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
1106 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
1107 | +# License for the specific language governing permissions and limitations |
1108 | +# under the License. |
1109 | + |
1110 | +from threading import Thread |
1111 | +import logging |
1112 | +import time |
1113 | + |
1114 | +""" |
1115 | +Helper functions for dealing with (simple python) threads |
1116 | +""" |
1117 | + |
1118 | +class PollingThread(Thread): |
1119 | + """ Thread that does something periodically """ |
1120 | + def run(self): |
1121 | + while True: |
1122 | + try: |
1123 | + self._run() |
1124 | + except Exception as e: |
1125 | + logging.exception("Error on thread %s", self.name) |
1126 | + |
1127 | + def _run(self): |
1128 | + time.sleep(self.task_interval) |
1129 | + self._do_task() |
1130 | + |
1131 | + def _do_task(self): |
1132 | + raise Exception("Must implement _do_task") |
1133 | |
1134 | === modified file 'nova/twistd.py' |
1135 | --- nova/twistd.py 2010-08-12 18:52:32 +0000 |
1136 | +++ nova/twistd.py 2010-08-21 14:09:42 +0000 |
1137 | @@ -193,6 +193,7 @@ |
1138 | time.sleep(0.1) |
1139 | except OSError, err: |
1140 | err = str(err) |
1141 | + # Should this be >= 0 ? |
1142 | if err.find("No such process") > 0: |
1143 | if os.path.exists(pidfile): |
1144 | os.remove(pidfile) |
1145 | @@ -201,7 +202,7 @@ |
1146 | sys.exit(1) |
1147 | |
1148 | |
1149 | -def serve(filename): |
1150 | +def serve(filename, twisted=True): |
1151 | logging.debug("Serving %s" % filename) |
1152 | name = os.path.basename(filename) |
1153 | OptionsClass = WrapTwistedOptions(TwistdServerOptions) |
1154 | @@ -256,4 +257,5 @@ |
1155 | for flag in FLAGS: |
1156 | logging.debug("%s : %s" % (flag, FLAGS.get(flag, None))) |
1157 | |
1158 | - twistd.runApp(options) |
1159 | + if twisted: |
1160 | + twistd.runApp(options) |
1161 | |
1162 | === modified file 'nova/utils.py' |
1163 | --- nova/utils.py 2010-08-18 21:14:24 +0000 |
1164 | +++ nova/utils.py 2010-08-21 14:09:42 +0000 |
1165 | @@ -36,6 +36,16 @@ |
1166 | FLAGS = flags.FLAGS |
1167 | TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" |
1168 | |
1169 | +class ProcessExecutionError(IOError): |
1170 | + def __init__( self, stdout=None, stderr=None, exit_code=None, cmd=None, |
1171 | + description=None): |
1172 | + if description is None: |
1173 | + description = "Unexpected error while running command." |
1174 | + if exit_code is None: |
1175 | + exit_code = '-' |
1176 | + message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % ( |
1177 | + description, cmd, exit_code, stdout, stderr) |
1178 | + IOError.__init__(self, message) |
1179 | |
1180 | def import_class(import_str): |
1181 | """Returns a class from a string including module and class""" |
1182 | @@ -59,6 +69,7 @@ |
1183 | execute("curl --fail %s -o %s" % (url, target)) |
1184 | |
1185 | def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): |
1186 | + logging.debug("Running command %s" % cmd) |
1187 | env = os.environ.copy() |
1188 | if addl_env: |
1189 | env.update(addl_env) |
1190 | @@ -73,8 +84,11 @@ |
1191 | if obj.returncode: |
1192 | logging.debug("Result was %s" % (obj.returncode)) |
1193 | if check_exit_code and obj.returncode <> 0: |
1194 | - raise Exception( "Unexpected exit code: %s. result=%s" |
1195 | - % (obj.returncode, result)) |
1196 | + (stdout, stderr) = result |
1197 | + raise ProcessExecutionError(exit_code=obj.returncode, |
1198 | + stdout=stdout, |
1199 | + stderr=stderr, |
1200 | + cmd=cmd) |
1201 | return result |
1202 | |
1203 | |
1204 | |
1205 | === modified file 'nova/virt/connection.py' |
1206 | --- nova/virt/connection.py 2010-08-13 10:51:33 +0000 |
1207 | +++ nova/virt/connection.py 2010-08-21 14:09:42 +0000 |
1208 | @@ -17,11 +17,14 @@ |
1209 | # License for the specific language governing permissions and limitations |
1210 | # under the License. |
1211 | |
1212 | +import logging |
1213 | +import sys |
1214 | + |
1215 | from nova import flags |
1216 | from nova.virt import fake |
1217 | from nova.virt import libvirt_conn |
1218 | from nova.virt import xenapi |
1219 | - |
1220 | +from nova.virt import virtualbox |
1221 | |
1222 | FLAGS = flags.FLAGS |
1223 | |
1224 | @@ -39,16 +42,21 @@ |
1225 | # TODO(termie): maybe lazy load after initial check for permissions |
1226 | # TODO(termie): check whether we can be disconnected |
1227 | t = FLAGS.connection_type |
1228 | + print "connection_type %s" % t |
1229 | if t == 'fake': |
1230 | conn = fake.get_connection(read_only) |
1231 | elif t == 'libvirt': |
1232 | conn = libvirt_conn.get_connection(read_only) |
1233 | elif t == 'xenapi': |
1234 | conn = xenapi.get_connection(read_only) |
1235 | + elif t == 'virtualbox': |
1236 | + conn = virtualbox.get_connection(read_only) |
1237 | else: |
1238 | - raise Exception('Unknown connection type "%s"' % t) |
1239 | + logging.error('Unknown connection type "%s"', t) |
1240 | + sys.exit(1) |
1241 | |
1242 | if conn is None: |
1243 | logging.error('Failed to open connection to the hypervisor') |
1244 | sys.exit(1) |
1245 | + |
1246 | return conn |
1247 | |
1248 | === modified file 'nova/virt/images.py' |
1249 | --- nova/virt/images.py 2010-08-18 21:14:24 +0000 |
1250 | +++ nova/virt/images.py 2010-08-21 14:09:42 +0000 |
1251 | @@ -29,6 +29,7 @@ |
1252 | from nova import process |
1253 | from nova.auth import manager |
1254 | from nova.auth import signer |
1255 | +from nova.auth import manager |
1256 | |
1257 | |
1258 | FLAGS = flags.FLAGS |
1259 | @@ -43,7 +44,6 @@ |
1260 | f = _fetch_local_image |
1261 | return f(image, path, user, project) |
1262 | |
1263 | - |
1264 | def _fetch_s3_image(image, path, user, project): |
1265 | url = image_url(image) |
1266 | |
1267 | @@ -53,11 +53,11 @@ |
1268 | headers = {} |
1269 | headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) |
1270 | |
1271 | - (_, _, url_path, _, _, _) = urlparse.urlparse(url) |
1272 | + uri = '/' + url.partition('/')[2] |
1273 | access = manager.AuthManager().get_access_key(user, project) |
1274 | signature = signer.Signer(user.secret.encode()).s3_authorization(headers, |
1275 | 'GET', |
1276 | - url_path) |
1277 | + uri) |
1278 | headers['Authorization'] = 'AWS %s:%s' % (access, signature) |
1279 | |
1280 | cmd = ['/usr/bin/curl', '--fail', '--silent', url] |
1281 | @@ -67,8 +67,7 @@ |
1282 | cmd += ['-o', path] |
1283 | return process.SharedPool().execute(executable=cmd[0], args=cmd[1:]) |
1284 | |
1285 | - |
1286 | -def _fetch_local_image(image, path, user, project): |
1287 | +def _fetch_local_image(image, path, _user, _project): |
1288 | source = _image_path('%s/image' % image) |
1289 | return process.simple_execute('cp %s %s' % (source, path)) |
1290 | |
1291 | |
1292 | === modified file 'nova/virt/libvirt_conn.py' |
1293 | --- nova/virt/libvirt_conn.py 2010-08-21 14:09:42 +0000 |
1294 | +++ nova/virt/libvirt_conn.py 2010-08-21 14:09:42 +0000 |
1295 | @@ -244,6 +244,8 @@ |
1296 | |
1297 | key = data['key_data'] |
1298 | net = None |
1299 | + dns = None |
1300 | + |
1301 | if data.get('inject_network', False): |
1302 | with open(FLAGS.injected_network_template) as f: |
1303 | net = f.read() % {'address': data['private_dns_name'], |
1304 | @@ -281,6 +283,7 @@ |
1305 | |
1306 | # TODO(termie): lazy lazy hack because xml is annoying |
1307 | xml_info['nova'] = json.dumps(instance.datamodel.copy()) |
1308 | + xml_info['type'] = FLAGS.libvirt_type |
1309 | |
1310 | if xml_info['kernel_id']: |
1311 | xml_info['kernel'] = xml_info['basepath'] + "/kernel" |
1312 | @@ -311,6 +314,12 @@ |
1313 | 'cpu_time': cpu_time} |
1314 | |
1315 | def get_disks(self, instance_id): |
1316 | + """ |
1317 | + Note that this function takes an instance ID, not an Instance, so |
1318 | + that it can be called by monitor. |
1319 | + |
1320 | + Returns a list of all block devices for this domain. |
1321 | + """ |
1322 | domain = self._conn.lookupByName(instance_id) |
1323 | # TODO(devcamcar): Replace libxml2 with etree. |
1324 | xml = domain.XMLDesc(0) |
1325 | @@ -347,6 +356,12 @@ |
1326 | return disks |
1327 | |
1328 | def get_interfaces(self, instance_id): |
1329 | + """ |
1330 | + Note that this function takes an instance ID, not an Instance, so |
1331 | + that it can be called by monitor. |
1332 | + |
1333 | + Returns a list of all network interfaces for this instance. |
1334 | + """ |
1335 | domain = self._conn.lookupByName(instance_id) |
1336 | # TODO(devcamcar): Replace libxml2 with etree. |
1337 | xml = domain.XMLDesc(0) |
1338 | @@ -383,9 +398,17 @@ |
1339 | return interfaces |
1340 | |
1341 | def block_stats(self, instance_id, disk): |
1342 | + """ |
1343 | + Note that this function takes an instance ID, not an Instance, so |
1344 | + that it can be called by monitor. |
1345 | + """ |
1346 | domain = self._conn.lookupByName(instance_id) |
1347 | return domain.blockStats(disk) |
1348 | |
1349 | def interface_stats(self, instance_id, interface): |
1350 | + """ |
1351 | + Note that this function takes an instance ID, not an Instance, so |
1352 | + that it can be called by monitor. |
1353 | + """ |
1354 | domain = self._conn.lookupByName(instance_id) |
1355 | return domain.interfaceStats(interface) |
1356 | |
1357 | === added file 'nova/virt/virtualbox.py' |
1358 | --- nova/virt/virtualbox.py 1970-01-01 00:00:00 +0000 |
1359 | +++ nova/virt/virtualbox.py 2010-08-21 14:09:42 +0000 |
1360 | @@ -0,0 +1,332 @@ |
1361 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
1362 | +import uuid |
1363 | +import time |
1364 | + |
1365 | +# Copyright 2010 FathomDB Inc |
1366 | +# All Rights Reserved. |
1367 | +# |
1368 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
1369 | +# not use this file except in compliance with the License. You may obtain |
1370 | +# a copy of the License at |
1371 | +# |
1372 | +# http://www.apache.org/licenses/LICENSE-2.0 |
1373 | +# |
1374 | +# Unless required by applicable law or agreed to in writing, software |
1375 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
1376 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
1377 | +# License for the specific language governing permissions and limitations |
1378 | +# under the License. |
1379 | + |
1380 | +""" |
1381 | +A connection to VirtualBox; this actually uses the VBoxManage command line tool |
1382 | +because libvirt is problematic with Virtualbox and on a Mac |
1383 | +""" |
1384 | + |
1385 | +import logging |
1386 | +import os.path |
1387 | +import shutil |
1388 | +import time |
1389 | + |
1390 | +from nova import exception |
1391 | +from nova import flags |
1392 | +from nova import utils |
1393 | +from nova.auth import manager |
1394 | +from nova.compute import disk |
1395 | +from nova.compute import instance_types |
1396 | +from nova.compute import power_state |
1397 | +from nova.virt import images |
1398 | + |
1399 | +from Cheetah.Template import Template |
1400 | + |
1401 | +FLAGS = flags.FLAGS |
1402 | +flags.DEFINE_string('virtualbox_bridge_adapter', |
1403 | + 'eth0', |
1404 | + 'Network adapter to use for VirtualBox bridged networking') |
1405 | + |
1406 | +def get_connection(read_only): |
1407 | + return VirtualBoxConnection(read_only) |
1408 | + |
1409 | + |
1410 | +class VirtualBoxConnection(object): |
1411 | + def __init__(self, read_only): |
1412 | + pass |
1413 | + |
1414 | + def list_instances(self): |
1415 | + out, err = utils.execute('VBoxManage --nologo list vms') |
1416 | + if err: |
1417 | + raise exception.Error('Could not list virtualbox vms: %s' % err) |
1418 | + |
1419 | + instance_ids = [] |
1420 | + for line in out.splitlines(): |
1421 | + tokens = line.split() |
1422 | + if len(tokens) == 2: |
1423 | + instance_id = tokens[0].strip('"') |
1424 | + instance_ids.append(instance_id) |
1425 | + |
1426 | + return instance_ids |
1427 | + |
1428 | + def destroy(self, instance): |
1429 | + _, err = utils.execute('VBoxManage --nologo controlvm %s poweroff' % instance.name) |
1430 | + if err: |
1431 | + # TODO(justinsb): If the instance was already stopped, we should accept that and continue... |
1432 | + raise exception.Error('Could not stop virtualbox vm: %s' % err) |
1433 | + |
1434 | + while True: |
1435 | + try: |
1436 | + instance.update_state() |
1437 | + if instance.state == power_state.SHUTOFF: |
1438 | + return |
1439 | + except Exception: |
1440 | + logging.exception("Error waiting for machine to shut down") |
1441 | + instance.set_state(power_state.SHUTOFF) |
1442 | + return |
1443 | + time.sleep(0.5) |
1444 | + |
1445 | + def _cleanup(self, instance): |
1446 | + target = os.path.abspath(instance.datamodel['basepath']) |
1447 | + logging.info("Deleting instance files at %s", target) |
1448 | + shutil.rmtree(target) |
1449 | + |
1450 | + @exception.wrap_exception |
1451 | + def reboot(self, instance): |
1452 | + _, err = utils.execute('VBoxManage --nologo controlvm %s reset' % instance.name) |
1453 | + if err: |
1454 | + raise exception.Error('Could not reset virtualbox vm: %s' % err) |
1455 | + # TODO(justinsb): Should we wait for restart? |
1456 | + |
1457 | + def _register_image(self, instance): |
1458 | + # See e.g. http://www.uhleeka.com/blog/2009/12/virtualbox-3-1-install-windows-xp-guest-on-ubuntu-9-04-host/ |
1459 | + instance_name = instance.name |
1460 | + base_folder = self.basepath(instance) |
1461 | + image_path = self._image_path(instance) |
1462 | + image_uuid = uuid.uuid4() |
1463 | + memory_size = 1024 |
1464 | + net_bridge_adapter = str(FLAGS.virtualbox_bridge_adapter) |
1465 | + |
1466 | + _, err = utils.execute('VBoxManage --nologo createvm --name %s --register --basefolder %s' % (instance_name, base_folder)) |
1467 | + if err: |
1468 | + raise exception.Error('Could not create virtualbox vm: %s' % err) |
1469 | + |
1470 | + _, err = utils.execute('VBoxManage --nologo storagectl %s --name "SATA Controller" --add sata --controller IntelAhci' % (instance_name, )) |
1471 | + if err: |
1472 | + raise exception.Error('Could not create SATA controller for virtualbox vm: %s' % err) |
1473 | + |
1474 | + # VirtualBox seems to pull a UUID from the disk. |
1475 | + # Thus we have to specify the UUID, or else VirtualBox refuses to |
1476 | + # register duplicate images because they have duplicate UUIDs |
1477 | + _, err = utils.execute('VBoxManage --nologo openmedium disk %s --uuid=%s' % (image_path, image_uuid)) |
1478 | + if err: |
1479 | + raise exception.Error('Could not register disk with virtualbox vm: %s' % err) |
1480 | + |
1481 | + _, err = utils.execute('VBoxManage --nologo storageattach %s --storagectl "SATA Controller" --port 0 --device 0 --type hdd --medium %s' % (instance_name, image_uuid)) |
1482 | + if err: |
1483 | + raise exception.Error('Could not attach disk to virtualbox vm: %s' % err) |
1484 | + |
1485 | + # TODO(justinsb): Do we need --sata on --sataportcount 30 ?? |
1486 | + cmd = 'VBoxManage --nologo modifyvm %s --memory %s --vram 64 --nic1 bridged --bridgeadapter1 %s' % (instance_name, memory_size, net_bridge_adapter) |
1487 | + _, err = utils.execute(cmd) |
1488 | + if err: |
1489 | + raise exception.Error('Could not configure virtualbox vm: %s' % err) |
1490 | + |
1491 | + def _start_image(self, instance): |
1492 | + instance_name = instance.name |
1493 | + _, err = utils.execute('VBoxManage --nologo startvm %s' % instance_name) |
1494 | + if err: |
1495 | + raise exception.Error('Could not start virtualbox instance %s: %s' % (instance_name, err)) |
1496 | + |
1497 | + @exception.wrap_exception |
1498 | + def spawn(self, instance): |
1499 | + instance.set_state(power_state.NOSTATE, 'launching') |
1500 | + |
1501 | + self._create_image(instance) |
1502 | + |
1503 | + self._register_image(instance) |
1504 | + |
1505 | + self._start_image(instance) |
1506 | + |
1507 | + # TODO(termie): this should actually register |
1508 | + # a callback to check for successful boot |
1509 | + logging.debug("Instance is running") |
1510 | + |
1511 | + while True: |
1512 | + try: |
1513 | + instance.update_state() |
1514 | + if instance.is_running(): |
1515 | + logging.debug('booted instance %s', instance.name) |
1516 | + return |
1517 | + except Exception as exn: |
1518 | + logging.error("_wait_for_boot exception %s", exn) |
1519 | + instance.set_state(power_state.SHUTDOWN) |
1520 | + logging.error('Failed to boot instance %s', instance.name) |
1521 | + return |
1522 | + time.sleep(0.5) |
1523 | + |
1524 | + |
1525 | + def _create_image(self, instance): |
1526 | + # syntactic nicety |
1527 | + data = instance.datamodel |
1528 | + basepath = lambda x = '': self.basepath(instance, x) |
1529 | + |
1530 | + # ensure directories exist and are writable |
1531 | + utils.execute('mkdir -p %s' % basepath()) |
1532 | + utils.execute('chmod 0777 %s' % basepath()) |
1533 | + |
1534 | + |
1535 | + user = manager.AuthManager().get_user(data['user_id']) |
1536 | + project = manager.AuthManager().get_project(data['project_id']) |
1537 | + |
1538 | + if not os.path.exists(basepath('disk')): |
1539 | + images.fetch(data['image_id'], basepath('disk-raw'), user, project) |
1540 | + |
1541 | + using_kernel = data['kernel_id'] and True |
1542 | + |
1543 | + if using_kernel: |
1544 | + if not os.path.exists(basepath('kernel')): |
1545 | + images.fetch(data['kernel_id'], basepath('kernel'), user, project) |
1546 | + if not os.path.exists(basepath('ramdisk')): |
1547 | + images.fetch(data['ramdisk_id'], basepath('ramdisk'), user, project) |
1548 | + |
1549 | + execute = lambda cmd, process_input = None: \ |
1550 | + utils.execute( cmd=cmd, |
1551 | + process_input=process_input, |
1552 | + check_exit_code=True) |
1553 | + |
1554 | + # For now, we assume that if we're not using a kernel, we're using a partitioned disk image |
1555 | + # where the target partition is the first partition |
1556 | + target_partition = None |
1557 | + if not using_kernel: |
1558 | + target_partition = "1" |
1559 | + |
1560 | + key = data['key_data'] |
1561 | + net = None |
1562 | + dns = None |
1563 | + |
1564 | + if data.get('inject_network', False): |
1565 | + network_info = { 'address': data['private_dns_name'], |
1566 | + 'network': data['network_network'], |
1567 | + 'netmask': data['network_netmask'], |
1568 | + 'gateway': data['network_gateway'], |
1569 | + 'broadcast': data['network_broadcast'], |
1570 | + 'dns': data['network_dns']} |
1571 | + |
1572 | + with open(FLAGS.injected_network_template) as f: |
1573 | + net = f.read() % network_info |
1574 | + |
1575 | + #with open(FLAGS.simple_network_dns_template) as f: |
1576 | + # dns = str(Template(f.read(), searchList=[ network_info ])) |
1577 | + |
1578 | + |
1579 | + if key or net or dns: |
1580 | + logging.info('Injecting data into image %s', data['image_id']) |
1581 | + try: |
1582 | + disk.inject_data(basepath('disk-raw'), key=key, net=net, dns=dns, remove_network_udev=True, partition=target_partition, execute=execute) |
1583 | + except Exception as e: |
1584 | + # This could be a windows image, or a vmdk format disk |
1585 | + logging.warn('Could not inject data; ignoring. (%s)', e) |
1586 | + |
1587 | + if using_kernel: |
1588 | + if os.path.exists(basepath('disk')): |
1589 | + utils.execute('rm -f %s' % basepath('disk')) |
1590 | + |
1591 | + disk_bytes = (instance_types.INSTANCE_TYPES[data['instance_type']]['local_gb'] |
1592 | + * 1024 * 1024 * 1024) |
1593 | + disk.partition( |
1594 | + basepath('disk-raw'), basepath('disk'), disk_bytes, execute=execute) |
1595 | + |
1596 | + |
1597 | + def _uses_kernel(self, instance): |
1598 | + if instance.datamodel['ramdisk_id'] or instance.datamodel['kernel_id']: |
1599 | + return True |
1600 | + return False |
1601 | + |
1602 | + def basepath(self, instance, path=''): |
1603 | + return os.path.abspath(os.path.join(instance.datamodel['basepath'], path)) |
1604 | + |
1605 | + def _image_path(self, instance): |
1606 | + if self._uses_kernel(instance): |
1607 | + return instance.datamodel['basepath'] + '/disk' |
1608 | + else: |
1609 | + return instance.datamodel['basepath'] + '/disk-raw' |
1610 | + |
1611 | + def _get_virtualbox_info(self, instance_id): |
1612 | + out, err = utils.execute('VBoxManage --nologo showvminfo %s --machinereadable' % instance_id, check_exit_code=False) |
1613 | + if out.find("VBOX_E_OBJECT_NOT_FOUND") != -1: |
1614 | + return {} |
1615 | + |
1616 | + if err: |
1617 | + raise exception.Error('Could not get virtualbox info: %s' % err) |
1618 | + # The output is a series of lines, each containing a key="value" pair |
1619 | + info_map = {} |
1620 | + for line in out.splitlines(): |
1621 | + line = line.strip() |
1622 | + if line == "": |
1623 | + continue |
1624 | + key, sep, value = line.partition('=') |
1625 | + if sep != '=': |
1626 | + if line.find('E_ACCESSDENIED') != -1: |
1627 | + if 'poweroff' == info_map.get('VMState', None): |
1628 | + # When the machine is being destroyed, we get some status and then E_ACCESSDENED; return what we've got |
1629 | + break |
1630 | + logging.info("Could not parse virtualbox line: %s", line) |
1631 | + raise exception.Error('Could not parse virtualbox info: %s' % out) |
1632 | + info_map[key] = value.strip('"') |
1633 | + |
1634 | + logging.debug("Virtual box info %s", info_map) |
1635 | + |
1636 | + return info_map |
1637 | + |
1638 | + def get_info(self, instance_id): |
1639 | + vb_info = self._get_virtualbox_info(instance_id) |
1640 | + |
1641 | + state = self.instance_state_from_virtualbox_state[vb_info.get('VMState', None)] |
1642 | + max_mem = vb_info.get('memory', 0) |
1643 | + mem = max_mem |
1644 | + num_cpu = vb_info.get('cpus', 0) |
1645 | + cpu_time = 0 |
1646 | + |
1647 | + return {'state': state, 'max_mem': max_mem, 'mem': mem, 'num_cpu': num_cpu, 'cpu_time': cpu_time} |
1648 | + |
1649 | + def get_disks(self, instance_id): |
1650 | + """ |
1651 | + Note that this function takes an instance ID, not an Instance, so |
1652 | + that it can be called by monitor. |
1653 | + |
1654 | + Returns a list of all block devices for this domain. |
1655 | + """ |
1656 | + raise Exception('Not yet implemented') |
1657 | + |
1658 | + |
1659 | + def get_interfaces(self, instance_id): |
1660 | + """ |
1661 | + Note that this function takes an instance ID, not an Instance, so |
1662 | + that it can be called by monitor. |
1663 | + |
1664 | + Returns a list of all network interfaces for this instance. |
1665 | + """ |
1666 | + raise Exception('Not yet implemented') |
1667 | + |
1668 | + |
1669 | + def block_stats(self, instance_id, disk): |
1670 | + """ |
1671 | + Note that this function takes an instance ID, not an Instance, so |
1672 | + that it can be called by monitor. |
1673 | + """ |
1674 | + raise Exception('Not yet implemented') |
1675 | + |
1676 | + |
1677 | + def interface_stats(self, instance_id, interface): |
1678 | + """ |
1679 | + Note that this function takes an instance ID, not an Instance, so |
1680 | + that it can be called by monitor. |
1681 | + """ |
1682 | + raise Exception('Not yet implemented') |
1683 | + |
1684 | + instance_state_from_virtualbox_state = { |
1685 | + None : power_state.NOSTATE, |
1686 | + 'poweroff': power_state.SHUTOFF, |
1687 | + 'starting': power_state.RUNNING, |
1688 | + 'running' : power_state.RUNNING, |
1689 | + #'Running' : power_state.RUNNING, |
1690 | + #'Paused' : power_state.PAUSED, |
1691 | + #'Crashed' : power_state.CRASHED |
1692 | + } |
1693 | |
1694 | === modified file 'nova/volume/service.py' |
1695 | --- nova/volume/service.py 2010-08-18 21:19:39 +0000 |
1696 | +++ nova/volume/service.py 2010-08-21 14:09:42 +0000 |
1697 | @@ -77,7 +77,7 @@ |
1698 | raise exception.Error("Volume does not exist") |
1699 | |
1700 | |
1701 | -class VolumeService(service.Service): |
1702 | +class VolumeService(service.TwistedService): |
1703 | """ |
1704 | There is one VolumeNode running on each host. |
1705 | However, each VolumeNode can report on the state of |
Twisted has no particular problem talking to a synchronous API. It can simply defer the call to a thread and get a response back as a callback once the call returns. What /is/ difficult is calling Twisted from a strictly synchronous application, but I don't see that as an issue here.
With that in mind, how is it that Twisted removal and VirtualBox support are related?