Merge lp:~justin-fathomdb/nova/check-subprocess-exit-code into lp:~hudson-openstack/nova/trunk

Proposed by justinsb
Status: Merged
Approved by: Jay Pipes
Approved revision: 166
Merged at revision: 251
Proposed branch: lp:~justin-fathomdb/nova/check-subprocess-exit-code
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 543 lines (+140/-135)
12 files modified
bin/nova-import-canonical-imagestore (+3/-3)
bin/nova-manage (+2/-1)
nova/cloudpipe/bootscript.sh (+2/-2)
nova/objectstore/image.py (+12/-3)
nova/process.py (+85/-100)
nova/tests/network_unittest.py (+0/-1)
nova/tests/process_unittest.py (+1/-1)
nova/utils.py (+14/-8)
nova/virt/images.py (+1/-1)
nova/virt/libvirt_conn.py (+3/-3)
nova/volume/service.py (+13/-7)
tools/install_venv.py (+4/-5)
To merge this branch: bzr merge lp:~justin-fathomdb/nova/check-subprocess-exit-code
Reviewer Review Type Date Requested Status
termie (community) Approve
justinsb (community) Approve
Vish Ishaya (community) Approve
Review via email: mp+30707@code.launchpad.net

Commit message

Check exit codes when spawning processes by default
Also pass --fail to curl so that it sets exit code when download fails

Description of the change

Check exit codes when spawning processes by default
Also pass --fail to curl so that it sets exit code when download fails

To post a comment you must log in.
Revision history for this message
Vish Ishaya (vishvananda) wrote :

Any reason why you didn't make the change in process.py as well? A lot of the shell commands are called with process.simple_execute

review: Needs Information
Revision history for this message
Vish Ishaya (vishvananda) wrote :

In fact, I'm hoping we can get rid of runthis in favor of process.execute at least. After my refactors I think it is only used in a few places, perhaps utils.execute will disappear eventually as well

Revision history for this message
justinsb (justin-fathomdb) wrote :

Didn't make the changes in process.py because I didn't realize that processes were called from there as well... I'll make the similar change to process.py

Revision history for this message
justinsb (justin-fathomdb) wrote :

Now checking the exit code in the twisted code also.

Also incorporated passing the --fail flag to curl so that it sets the exit code. Seems to make sense here, in that we're now checking error codes, so we want curl to set them!

review: Needs Resubmitting
Revision history for this message
Vish Ishaya (vishvananda) wrote :

looks good. I requested a review from Termie, since he wrote the process.py code. Also, i noticed a few problems with nova-volume's use of simple_execute. I had to add an error_ok or two because some of the shell calls were returning output on stderr. We might need to run through creating a few volumes with my branch lp:~vishvananda/nova/fix-volumes and see if we need check_error_code=False on any of the commands. I only noticed this stuff when actually creating volumes (i.e. not using fakes).

Revision history for this message
justinsb (justin-fathomdb) wrote :

Yes - it definitely needs an Twisted expert's eye on it. I certainly haven't run through every code path, so odds are the error checking will surface more issues and break things, but I think that at this stage of the project that's probably the right thing to do - we at least want to know when things are failing! I found some problems with the virtualenv stuff, for example, which otherwise were going unnoticed, and probably resulting in mixed versions of python libs etc...

Revision history for this message
termie (termie) wrote :

* Please change all variable and parameter names to match Twisted's (camelCase)

* When you write a note about something (you have a comment in an exception below) please use

NOTE(justinsb): comment text

rather than

comment text (justinsb)

* Please make the lines fit in 80 characters.

* Your comment at the beginning of BackRelay has odd spacing.

* Technically the pip change (good catch) should be a different branch, it is fine now that it is already in there but please keep changes as small and specific as possible.

In general I don't think we should change the behavior of how "errortoo" works.

Specifically I don't think the majority of the changes in the twisted code are necessary, it looks like simply making a failure state in processEnded if checkExitCode is true and the exit code is non-zero is enough. I agree that we should also always store the stderr separately in addition to what twisted is doing so that we can give it back when the exit code is non-zero, but that should be an add-on to existing behavior, not a modification.

review: Needs Fixing
Revision history for this message
justinsb (justin-fathomdb) wrote :

Agree that the pip comment should be in a different branch, as probably should the --fail argument to curl; just trying to minimize the overhead involved in patch submission.

On the changes to the twisted code, there were intended to fix the problems with checking stderr & exit code, that were documented in the code itself. It also felt cleaner to have one class than two, particularly as some methods in the base class appeared to be broken, but were never called as the derived class overrides those method. I'm happy to reapply my fixes against a branch if you want to submit something more along your lines of thinking.

The other comments seem to be about formatting. Can you please point me to the formatting rules we're using? Ideally (from my point of view) we could just all agree on a vim configuration (or some other editor) and post that for Python noobs such as myself.

Not sure I agree on use of camel case because Twisted does it, given that the rest of the code seems to use underscores, and Twisted might well be ripped out anyway. But this is the sort of thing that will be covered by the project formatting rules, so we don't need to debate it :-)

If someone can point me to the formatting rules for vim that we're using, I'll auto-format the code and resubmit.

Revision history for this message
justinsb (justin-fathomdb) wrote :

Ping!

review: Needs Resubmitting
Revision history for this message
justinsb (justin-fathomdb) wrote :

Fixes for greater compliance with pep8/pylint style checks.

In places where nova's code style was incompatible with Twisted's code style (camelCase), I used camelCase only where we had to for interactions with Twisted (e.g. method overrides, specifying arguments), and this style is confined to the process.py module and mostly to the BackRelayWithInput class.

review: Needs Resubmitting
Revision history for this message
Vish Ishaya (vishvananda) wrote :

Cleanup here is good enough for me. Be nice if termie approved as well. There have been a few additions/changes to commands since this was written, so it may be that this will need to be updated before merging (specifically I'm thinking about volume/service.py)

review: Approve
Revision history for this message
termie (termie) wrote :

I still don't agree that these changes are necessary, can you point out a use case where the existing code is not functional? I suspect there is a way to solve your issues that don't involve modifying this code.

review: Disapprove
Revision history for this message
justinsb (justin-fathomdb) wrote :

Sure - the primary case I was trying to fix when messing with the Twisted code (which is what I think you're questioning), is the one in this comment:

# NOTE(termie): current behavior means if error_ok is True
# we won't throw an error even if the process
# exited with a non-0 status, so you can't be
# okay with stderr output and not with bad exit
# codes.

I cleaned up the logic so that we can still check the exit code, whether or not the process outputs on stderr.

Revision history for this message
Eric Day (eday) wrote :

Any more feedback from termie on the last comment? We should merge this if it's still possible, or reject/remove from the merge queue if not.

Revision history for this message
termie (termie) wrote :

weird, I got Eric's message but not the message from Justin. Sorry for the late reply, I expected to get an email about it.

Justin: That is a NOTE not a TODO, I don't see any use cases where this behavior is a problem, what I am asking you for is a use case that we have right now where we can't do what we want because of this behavior. I think the change you are proposing increases the complexity of the code significantly and would rather it not happen unless there is a good reason for it.

Revision history for this message
justinsb (justin-fathomdb) wrote :

I don't believe a use-case should be needed for merging in a patch that adds correct error checking, but anyway...

From nova/volume/service.py:
        # NOTE(vish): these commands sometimes sends output to stderr for warnings
        yield process.simple_execute("sudo vblade-persist auto all", error_ok=1)
        yield process.simple_execute("sudo vblade-persist start all", error_ok=1)

In other words, there was crummy stderr/exit code checking code (I think it was grabbed out of Twisted?) Though this was known, there was a multi-line note instead of a (shorter) fix for it. As inevitably happens, the bug eventually caused problems - in this case, for vish in the storage service, but the only way to work around it was to mark stderr as being OK, or to fix the problem and risk a conflict with this merge-in-progress. Which means we now probably have to fix nova/volume/service.py as well (to state that stderr is OK but a non-zero exit code is not).

When I see technical debt in code I'm working on, I clean it up.

I believe the new code is much cleaner, so I don't know what you can point to that makes you believe it to be more complex. The old code used dynamic class modification (errReceivedIsBad, errReceivedIsGood), two classes where only the derived class was actually used, had some probable syntax errors flagged by pylint (but the methods were overridden in the derived class so were never actually called), and did a questionable dynamic import of twisted.internet.reactor if the reactor parameter to getProcessOutput was None. I don't believe there are any 'tricks' at all in the new code - it should be surprise-free. I think I've also reduced lines of code and have much greater pylint compliance.

Finally, given that it seems unlikely that Twisted will survive for long in Nova, why are we nit-picking? Isn't proper error checking more important, so we can start getting nova working solidly and get more features working? I have a huge backlog of patches (not least a mostly working abstraction of the Redis data store and a SQL alternative implementation), but everything depends on this branch because - frankly - I find it really difficult to make progress when there's no error checking when spawning processes.

Revision history for this message
termie (termie) wrote :
Download full text (3.6 KiB)

On Wed, Aug 18, 2010 at 12:24 AM, justinsb <email address hidden> wrote:

> I don't believe a use-case should be needed for merging in a patch that
> adds correct error checking, but anyway...
>

Correct is what does what we need, without a use case it is just an opinion.

>
> >From nova/volume/service.py:
> # NOTE(vish): these commands sometimes sends output to stderr for
> warnings
> yield process.simple_execute("sudo vblade-persist auto all",
> error_ok=1)
> yield process.simple_execute("sudo vblade-persist start all",
> error_ok=1)

In other words, there was crummy stderr/exit code checking code (I think it
> was grabbed out of Twisted?)

Thanks this was what I was asking for. I'd also add that it is bad form to
call code 'crummy' but I sense that you are frustrated at the moment, so I
hope that if whoever wrote it sees this they won't take personal offense.

> Though this was known, there was a multi-line note instead of a (shorter)
> fix for it. As inevitably happens, the bug eventually caused problems - in
> this case, for vish in the storage service, but the only way to work around
> it was to mark stderr as being OK, or to fix the problem and risk a conflict
> with this merge-in-progress. Which means we now probably have to fix
> nova/volume/service.py as well (to state that stderr is OK but a non-zero
> exit code is not).
>
> When I see technical debt in code I'm working on, I clean it up.
>
> I believe the new code is much cleaner, so I don't know what you can point
> to that makes you believe it to be more complex. The old code used dynamic
> class modification (errReceivedIsBad, errReceivedIsGood), two classes where
> only the derived class was actually used, had some probable syntax errors
> flagged by pylint (but the methods were overridden in the derived class so
> were never actually called), and did a questionable dynamic import of
> twisted.internet.reactor if the reactor parameter to getProcessOutput was
> None. I don't believe there are any 'tricks' at all in the new code - it
> should be surprise-free. I think I've also reduced lines of code and have
> much greater pylint compliance.
>
> Finally, given that it seems unlikely that Twisted will survive for long in
> Nova, why are we nit-picking? Isn't proper error checking more important,
> so we can start getting nova working solidly and get more features working?
> I have a huge backlog of patches (not least a mostly working abstraction of
> the Redis data store and a SQL alternative implementation), but everything
> depends on this branch because - frankly - I find it really difficult to
> make progress when there's no error checking when spawning processes.
>

No need to get upset with me Justin. I apologize that I was slow on the
review, I haven't been receiving emails from any of your updates until this
one and Launchpad's workflow has yet to sink in with me.

In summary what I've understood from your comment:

1. We have a specific use case where an external tool is outputting on
stderr even though it has succeeded and therefore there is need to change
the behavior of this code.

2. You feel that the change is a general improvement in...

Read more...

Revision history for this message
termie (termie) :
review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :

Attempt to merge lp:~justin-fathomdb/nova/check-subprocess-exit-code into lp:nova failed due to merge conflicts:

text conflict in bin/nova-manage
text conflict in nova/utils.py
text conflict in nova/volume/service.py
text conflict in tools/install_venv.py

Revision history for this message
justinsb (justin-fathomdb) wrote :

Merged with trunk; changed error_ok to terminate_on_stderr=False because this is what I believe is wanted (this affects a shell to vgcreate/lvcreate, so this patch would probably have helped with Bug 620027).

Also fixed a unit test that was calling release_ip it didn't get from issue_ip. Though the unit test was wrong, we weren't detecting the failure. As Ewan noticed in the most recent mailing list thread, the network unit tests are unusually tolerant of being FUBAR, this is probably why - they weren't checking exit codes!

Revision history for this message
justinsb (justin-fathomdb) :
review: Approve
Revision history for this message
termie (termie) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/nova-import-canonical-imagestore'
2--- bin/nova-import-canonical-imagestore 2010-08-08 02:51:17 +0000
3+++ bin/nova-import-canonical-imagestore 2010-08-18 21:36:47 +0000
4@@ -56,21 +56,21 @@
5 for f in img['files']:
6 if f['kind'] == 'kernel':
7 dest = os.path.join(tempdir, 'kernel')
8- subprocess.call(['curl', f['url'], '-o', dest])
9+ subprocess.call(['curl', '--fail', f['url'], '-o', dest])
10 kernel_id = image.Image.add(dest,
11 description='kernel/' + img['title'], kernel=True)
12
13 for f in img['files']:
14 if f['kind'] == 'ramdisk':
15 dest = os.path.join(tempdir, 'ramdisk')
16- subprocess.call(['curl', f['url'], '-o', dest])
17+ subprocess.call(['curl', '--fail', f['url'], '-o', dest])
18 ramdisk_id = image.Image.add(dest,
19 description='ramdisk/' + img['title'], ramdisk=True)
20
21 for f in img['files']:
22 if f['kind'] == 'image':
23 dest = os.path.join(tempdir, 'image')
24- subprocess.call(['curl', f['url'], '-o', dest])
25+ subprocess.call(['curl', '--fail', f['url'], '-o', dest])
26 ramdisk_id = image.Image.add(dest,
27 description=img['title'], kernel=kernel_id, ramdisk=ramdisk_id)
28
29
30=== modified file 'bin/nova-manage'
31--- bin/nova-manage 2010-08-13 21:09:30 +0000
32+++ bin/nova-manage 2010-08-18 21:36:47 +0000
33@@ -56,7 +56,8 @@
34 vpn = self._vpn_for(project.id)
35 if vpn:
36 command = "ping -c1 -w1 %s > /dev/null; echo $?"
37- out, _err = utils.execute(command % vpn['private_dns_name'])
38+ out, _err = utils.execute( command % vpn['private_dns_name'],
39+ check_exit_code=False)
40 if out.strip() == '0':
41 net = 'up'
42 else:
43
44=== modified file 'nova/cloudpipe/bootscript.sh'
45--- nova/cloudpipe/bootscript.sh 2010-07-20 22:05:02 +0000
46+++ nova/cloudpipe/bootscript.sh 2010-08-18 21:36:47 +0000
47@@ -44,8 +44,8 @@
48
49 # SIGN the csr and save as server.crt
50 # CURL fetch to the supervisor, POSTing the CSR text, saving the result as the CRT file
51-curl $SUPERVISOR -d "cert=$CSRTEXT" > /etc/openvpn/server.crt
52-curl $SUPERVISOR/getca/ > /etc/openvpn/ca.crt
53+curl --fail $SUPERVISOR -d "cert=$CSRTEXT" > /etc/openvpn/server.crt
54+curl --fail $SUPERVISOR/getca/ > /etc/openvpn/ca.crt
55
56 # Customize the server.conf.template
57 cd /etc/openvpn
58
59=== modified file 'nova/objectstore/image.py'
60--- nova/objectstore/image.py 2010-08-16 12:16:21 +0000
61+++ nova/objectstore/image.py 2010-08-18 21:36:47 +0000
62@@ -232,13 +232,22 @@
63
64 @staticmethod
65 def decrypt_image(encrypted_filename, encrypted_key, encrypted_iv, cloud_private_key, decrypted_filename):
66- key, err = utils.execute('openssl rsautl -decrypt -inkey %s' % cloud_private_key, encrypted_key)
67+ key, err = utils.execute(
68+ 'openssl rsautl -decrypt -inkey %s' % cloud_private_key,
69+ process_input=encrypted_key,
70+ check_exit_code=False)
71 if err:
72 raise exception.Error("Failed to decrypt private key: %s" % err)
73- iv, err = utils.execute('openssl rsautl -decrypt -inkey %s' % cloud_private_key, encrypted_iv)
74+ iv, err = utils.execute(
75+ 'openssl rsautl -decrypt -inkey %s' % cloud_private_key,
76+ process_input=encrypted_iv,
77+ check_exit_code=False)
78 if err:
79 raise exception.Error("Failed to decrypt initialization vector: %s" % err)
80- out, err = utils.execute('openssl enc -d -aes-128-cbc -in %s -K %s -iv %s -out %s' % (encrypted_filename, key, iv, decrypted_filename))
81+ _out, err = utils.execute(
82+ 'openssl enc -d -aes-128-cbc -in %s -K %s -iv %s -out %s'
83+ % (encrypted_filename, key, iv, decrypted_filename),
84+ check_exit_code=False)
85 if err:
86 raise exception.Error("Failed to decrypt image file %s : %s" % (encrypted_filename, err))
87
88
89=== modified file 'nova/process.py'
90--- nova/process.py 2010-08-16 12:16:21 +0000
91+++ nova/process.py 2010-08-18 21:36:47 +0000
92@@ -2,6 +2,7 @@
93
94 # Copyright 2010 United States Government as represented by the
95 # Administrator of the National Aeronautics and Space Administration.
96+# Copyright 2010 FathomDB Inc.
97 # All Rights Reserved.
98 #
99 # Licensed under the Apache License, Version 2.0 (the "License"); you may
100@@ -20,17 +21,12 @@
101 Process pool, still buggy right now.
102 """
103
104-import logging
105-import multiprocessing
106 import StringIO
107
108 from twisted.internet import defer
109 from twisted.internet import error
110-from twisted.internet import process
111 from twisted.internet import protocol
112 from twisted.internet import reactor
113-from twisted.internet import threads
114-from twisted.python import failure
115
116 from nova import flags
117
118@@ -55,111 +51,100 @@
119 IOError.__init__(self, "got stdout: %r\nstderr: %r" % (stdout, stderr))
120
121
122-# NOTE(termie): this too
123-class _BackRelay(protocol.ProcessProtocol):
124+# This is based on _BackRelay from twister.internal.utils, but modified to
125+# capture both stdout and stderr, without odd stderr handling, and also to
126+# handle stdin
127+class BackRelayWithInput(protocol.ProcessProtocol):
128 """
129 Trivial protocol for communicating with a process and turning its output
130 into the result of a L{Deferred}.
131
132 @ivar deferred: A L{Deferred} which will be called back with all of stdout
133- and, if C{errortoo} is true, all of stderr as well (mixed together in
134- one string). If C{errortoo} is false and any bytes are received over
135- stderr, this will fire with an L{_UnexpectedErrorOutput} instance and
136- the attribute will be set to C{None}.
137+ and all of stderr as well (as a tuple). C{terminate_on_stderr} is true
138+ and any bytes are received over stderr, this will fire with an
139+ L{_UnexpectedErrorOutput} instance and the attribute will be set to
140+ C{None}.
141
142- @ivar onProcessEnded: If C{errortoo} is false and bytes are received over
143- stderr, this attribute will refer to a L{Deferred} which will be called
144- back when the process ends. This C{Deferred} is also associated with
145- the L{_UnexpectedErrorOutput} which C{deferred} fires with earlier in
146- this case so that users can determine when the process has actually
147- ended, in addition to knowing when bytes have been received via stderr.
148+ @ivar onProcessEnded: If C{terminate_on_stderr} is false and bytes are
149+ received over stderr, this attribute will refer to a L{Deferred} which
150+ will be called back when the process ends. This C{Deferred} is also
151+ associated with the L{_UnexpectedErrorOutput} which C{deferred} fires
152+ with earlier in this case so that users can determine when the process
153+ has actually ended, in addition to knowing when bytes have been received
154+ via stderr.
155 """
156
157- def __init__(self, deferred, errortoo=0):
158+ def __init__(self, deferred, started_deferred=None,
159+ terminate_on_stderr=False, check_exit_code=True,
160+ process_input=None):
161 self.deferred = deferred
162- self.s = StringIO.StringIO()
163- if errortoo:
164- self.errReceived = self.errReceivedIsGood
165- else:
166- self.errReceived = self.errReceivedIsBad
167-
168- def errReceivedIsBad(self, text):
169- if self.deferred is not None:
170- self.onProcessEnded = defer.Deferred()
171- err = UnexpectedErrorOutput(text, self.onProcessEnded)
172- self.deferred.errback(failure.Failure(err))
173+ self.stdout = StringIO.StringIO()
174+ self.stderr = StringIO.StringIO()
175+ self.started_deferred = started_deferred
176+ self.terminate_on_stderr = terminate_on_stderr
177+ self.check_exit_code = check_exit_code
178+ self.process_input = process_input
179+ self.on_process_ended = None
180+
181+ def errReceived(self, text):
182+ self.stderr.write(text)
183+ if self.terminate_on_stderr and (self.deferred is not None):
184+ self.on_process_ended = defer.Deferred()
185+ self.deferred.errback(UnexpectedErrorOutput(
186+ stdout=self.stdout.getvalue(),
187+ stderr=self.stderr.getvalue()))
188 self.deferred = None
189 self.transport.loseConnection()
190
191- def errReceivedIsGood(self, text):
192- self.s.write(text)
193-
194 def outReceived(self, text):
195- self.s.write(text)
196-
197- def processEnded(self, reason):
198- if self.deferred is not None:
199- self.deferred.callback(self.s.getvalue())
200- elif self.onProcessEnded is not None:
201- self.onProcessEnded.errback(reason)
202-
203-
204-class BackRelayWithInput(_BackRelay):
205- def __init__(self, deferred, startedDeferred=None, error_ok=0,
206- input=None):
207- # Twisted doesn't use new-style classes in most places :(
208- _BackRelay.__init__(self, deferred, errortoo=error_ok)
209- self.error_ok = error_ok
210- self.input = input
211- self.stderr = StringIO.StringIO()
212- self.startedDeferred = startedDeferred
213-
214- def errReceivedIsBad(self, text):
215- self.stderr.write(text)
216- self.transport.loseConnection()
217-
218- def errReceivedIsGood(self, text):
219- self.stderr.write(text)
220-
221- def connectionMade(self):
222- if self.startedDeferred:
223- self.startedDeferred.callback(self)
224- if self.input:
225- self.transport.write(self.input)
226- self.transport.closeStdin()
227-
228- def processEnded(self, reason):
229- if self.deferred is not None:
230- stdout, stderr = self.s.getvalue(), self.stderr.getvalue()
231+ self.stdout.write(text)
232+
233+ def processEnded(self, reason):
234+ if self.deferred is not None:
235+ stdout, stderr = self.stdout.getvalue(), self.stderr.getvalue()
236 try:
237- # NOTE(termie): current behavior means if error_ok is True
238- # we won't throw an error even if the process
239- # exited with a non-0 status, so you can't be
240- # okay with stderr output and not with bad exit
241- # codes.
242- if not self.error_ok:
243+ if self.check_exit_code:
244 reason.trap(error.ProcessDone)
245 self.deferred.callback((stdout, stderr))
246 except:
247+ # NOTE(justinsb): This logic is a little suspicious to me...
248+ # If the callback throws an exception, then errback will be
249+ # called also. However, this is what the unit tests test for...
250 self.deferred.errback(UnexpectedErrorOutput(stdout, stderr))
251-
252-
253-def getProcessOutput(executable, args=None, env=None, path=None, reactor=None,
254- error_ok=0, input=None, startedDeferred=None):
255- if reactor is None:
256- from twisted.internet import reactor
257+ elif self.on_process_ended is not None:
258+ self.on_process_ended.errback(reason)
259+
260+
261+ def connectionMade(self):
262+ if self.started_deferred:
263+ self.started_deferred.callback(self)
264+ if self.process_input:
265+ self.transport.write(self.process_input)
266+ self.transport.closeStdin()
267+
268+def get_process_output(executable, args=None, env=None, path=None,
269+ process_reactor=None, check_exit_code=True,
270+ process_input=None, started_deferred=None,
271+ terminate_on_stderr=False):
272+ if process_reactor is None:
273+ process_reactor = reactor
274 args = args and args or ()
275 env = env and env and {}
276- d = defer.Deferred()
277- p = BackRelayWithInput(
278- d, startedDeferred=startedDeferred, error_ok=error_ok, input=input)
279+ deferred = defer.Deferred()
280+ process_handler = BackRelayWithInput(
281+ deferred,
282+ started_deferred=started_deferred,
283+ check_exit_code=check_exit_code,
284+ process_input=process_input,
285+ terminate_on_stderr=terminate_on_stderr)
286 # NOTE(vish): commands come in as unicode, but self.executes needs
287 # strings or process.spawn raises a deprecation warning
288 executable = str(executable)
289 if not args is None:
290 args = [str(x) for x in args]
291- reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path)
292- return d
293+ process_reactor.spawnProcess( process_handler, executable,
294+ (executable,)+tuple(args), env, path)
295+ return deferred
296
297
298 class ProcessPool(object):
299@@ -185,26 +170,26 @@
300 return self.execute(executable, args, **kw)
301
302 def execute(self, *args, **kw):
303- d = self._pool.acquire()
304+ deferred = self._pool.acquire()
305
306- def _associateProcess(proto):
307- d.process = proto.transport
308+ def _associate_process(proto):
309+ deferred.process = proto.transport
310 return proto.transport
311
312 started = defer.Deferred()
313- started.addCallback(_associateProcess)
314- kw.setdefault('startedDeferred', started)
315-
316- d.process = None
317- d.started = started
318-
319- d.addCallback(lambda _: getProcessOutput(*args, **kw))
320- d.addBoth(self._release)
321- return d
322-
323- def _release(self, rv=None):
324+ started.addCallback(_associate_process)
325+ kw.setdefault('started_deferred', started)
326+
327+ deferred.process = None
328+ deferred.started = started
329+
330+ deferred.addCallback(lambda _: get_process_output(*args, **kw))
331+ deferred.addBoth(self._release)
332+ return deferred
333+
334+ def _release(self, retval=None):
335 self._pool.release()
336- return rv
337+ return retval
338
339
340 class SharedPool(object):
341
342=== modified file 'nova/tests/network_unittest.py'
343--- nova/tests/network_unittest.py 2010-08-18 15:44:24 +0000
344+++ nova/tests/network_unittest.py 2010-08-18 21:36:47 +0000
345@@ -166,7 +166,6 @@
346 release_ip(mac3, address3, hostname, net.bridge_name)
347 net = model.get_project_network(self.projects[0].id, "default")
348 self.service.deallocate_fixed_ip(firstaddress)
349- release_ip(mac, firstaddress, hostname, net.bridge_name)
350
351 def test_vpn_ip_and_port_looks_valid(self):
352 """Ensure the vpn ip and port are reasonable"""
353
354=== modified file 'nova/tests/process_unittest.py'
355--- nova/tests/process_unittest.py 2010-07-29 08:08:31 +0000
356+++ nova/tests/process_unittest.py 2010-08-18 21:36:47 +0000
357@@ -48,7 +48,7 @@
358
359 def test_execute_stderr(self):
360 pool = process.ProcessPool(2)
361- d = pool.simple_execute('cat BAD_FILE', error_ok=1)
362+ d = pool.simple_execute('cat BAD_FILE', check_exit_code=False)
363 def _check(rv):
364 self.assertEqual(rv[0], '')
365 self.assert_('No such file' in rv[1])
366
367=== modified file 'nova/utils.py'
368--- nova/utils.py 2010-08-16 12:16:21 +0000
369+++ nova/utils.py 2010-08-18 21:36:47 +0000
370@@ -56,23 +56,25 @@
371 # c.perform()
372 # c.close()
373 # fp.close()
374- execute("curl %s -o %s" % (url, target))
375-
376-
377-def execute(cmd, input=None, addl_env=None):
378+ execute("curl --fail %s -o %s" % (url, target))
379+
380+def execute(cmd, process_input=None, addl_env=None, check_exit_code=True):
381 env = os.environ.copy()
382 if addl_env:
383 env.update(addl_env)
384 obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
385 stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
386 result = None
387- if input != None:
388- result = obj.communicate(input)
389+ if process_input != None:
390+ result = obj.communicate(process_input)
391 else:
392 result = obj.communicate()
393 obj.stdin.close()
394 if obj.returncode:
395 logging.debug("Result was %s" % (obj.returncode))
396+ if check_exit_code and obj.returncode <> 0:
397+ raise Exception( "Unexpected exit code: %s. result=%s"
398+ % (obj.returncode, result))
399 return result
400
401
402@@ -98,9 +100,13 @@
403 return arg
404
405
406-def runthis(prompt, cmd):
407+def runthis(prompt, cmd, check_exit_code = True):
408 logging.debug("Running %s" % (cmd))
409- logging.debug(prompt % (subprocess.call(cmd.split(" "))))
410+ exit_code = subprocess.call(cmd.split(" "))
411+ logging.debug(prompt % (exit_code))
412+ if check_exit_code and exit_code <> 0:
413+ raise Exception( "Unexpected exit code: %s from cmd: %s"
414+ % (exit_code, cmd))
415
416
417 def generate_uid(topic, size=8):
418
419=== modified file 'nova/virt/images.py'
420--- nova/virt/images.py 2010-08-17 11:53:30 +0000
421+++ nova/virt/images.py 2010-08-18 21:36:47 +0000
422@@ -60,7 +60,7 @@
423 url_path)
424 headers['Authorization'] = 'AWS %s:%s' % (access, signature)
425
426- cmd = ['/usr/bin/curl', '--silent', url]
427+ cmd = ['/usr/bin/curl', '--fail', '--silent', url]
428 for (k,v) in headers.iteritems():
429 cmd += ['-H', '%s: %s' % (k,v)]
430
431
432=== modified file 'nova/virt/libvirt_conn.py'
433--- nova/virt/libvirt_conn.py 2010-08-17 11:53:30 +0000
434+++ nova/virt/libvirt_conn.py 2010-08-18 21:36:47 +0000
435@@ -227,10 +227,10 @@
436 if not os.path.exists(basepath('ramdisk')):
437 yield images.fetch(data['ramdisk_id'], basepath('ramdisk'), user, project)
438
439- execute = lambda cmd, input=None: \
440+ execute = lambda cmd, process_input=None: \
441 process.simple_execute(cmd=cmd,
442- input=input,
443- error_ok=1)
444+ process_input=process_input,
445+ check_exit_code=True)
446
447 key = data['key_data']
448 net = None
449
450=== modified file 'nova/volume/service.py'
451--- nova/volume/service.py 2010-08-16 12:16:21 +0000
452+++ nova/volume/service.py 2010-08-18 21:36:47 +0000
453@@ -131,8 +131,10 @@
454 if FLAGS.fake_storage:
455 return
456 # NOTE(vish): these commands sometimes sends output to stderr for warnings
457- yield process.simple_execute("sudo vblade-persist auto all", error_ok=1)
458- yield process.simple_execute("sudo vblade-persist start all", error_ok=1)
459+ yield process.simple_execute( "sudo vblade-persist auto all",
460+ terminate_on_stderr=False)
461+ yield process.simple_execute( "sudo vblade-persist start all",
462+ terminate_on_stderr=False)
463
464 @defer.inlineCallbacks
465 def _init_volume_group(self):
466@@ -247,13 +249,14 @@
467 "sudo lvcreate -L %s -n %s %s" % (sizestr,
468 self['volume_id'],
469 FLAGS.volume_group),
470- error_ok=1)
471+ terminate_on_stderr=False)
472
473 @defer.inlineCallbacks
474 def _delete_lv(self):
475 yield process.simple_execute(
476 "sudo lvremove -f %s/%s" % (FLAGS.volume_group,
477- self['volume_id']), error_ok=1)
478+ self['volume_id']),
479+ terminate_on_stderr=False)
480
481 @property
482 def __devices_key(self):
483@@ -281,7 +284,8 @@
484 self['blade_id'],
485 FLAGS.aoe_eth_dev,
486 FLAGS.volume_group,
487- self['volume_id']), error_ok=1)
488+ self['volume_id']),
489+ terminate_on_stderr=False)
490
491 @defer.inlineCallbacks
492 def _remove_export(self):
493@@ -294,10 +298,12 @@
494 def _exec_remove_export(self):
495 yield process.simple_execute(
496 "sudo vblade-persist stop %s %s" % (self['shelf_id'],
497- self['blade_id']), error_ok=1)
498+ self['blade_id']),
499+ terminate_on_stderr=False)
500 yield process.simple_execute(
501 "sudo vblade-persist destroy %s %s" % (self['shelf_id'],
502- self['blade_id']), error_ok=1)
503+ self['blade_id']),
504+ terminate_on_stderr=False)
505
506
507 class FakeVolume(Volume):
508
509=== modified file 'tools/install_venv.py'
510--- tools/install_venv.py 2010-08-17 15:03:15 +0000
511+++ tools/install_venv.py 2010-08-18 21:36:47 +0000
512@@ -37,7 +37,7 @@
513 sys.exit(1)
514
515
516-def run_command(cmd, redirect_output=True, error_ok=False):
517+def run_command(cmd, redirect_output=True, check_exit_code=True):
518 """
519 Runs a command in an out-of-process shell, returning the
520 output of that command. Working directory is ROOT.
521@@ -49,19 +49,18 @@
522
523 proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
524 output = proc.communicate()[0]
525- if not error_ok and proc.returncode != 0:
526+ if check_exit_code and proc.returncode != 0:
527 die('Command "%s" failed.\n%s', ' '.join(cmd), output)
528 return output
529
530
531-HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install']).strip())
532-HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv']).strip())
533+HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install'], check_exit_code=False).strip())
534+HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv'], check_exit_code=False).strip())
535
536
537 def check_dependencies():
538 """Make sure virtualenv is in the path."""
539
540- print 'Checking for virtualenv...',
541 if not HAS_VIRTUALENV:
542 print 'not found.'
543 # Try installing it via easy_install...