Merge lp:~justin-fathomdb/nova/check-subprocess-exit-code into lp:~hudson-openstack/nova/trunk
- check-subprocess-exit-code
- Merge into trunk
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 | ||||
Related bugs: |
|
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
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
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
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!
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_
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...
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.
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.
justinsb (justin-fathomdb) wrote : | # |
Ping!
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.
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)
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.
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.
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.
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.
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/
# NOTE(vish): these commands sometimes sends output to stderr for warnings
yield process.
yield process.
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/
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.
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.
termie (termie) wrote : | # |
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/
> # NOTE(vish): these commands sometimes sends output to stderr for
> warnings
> yield process.
> error_ok=1)
> yield process.
> 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/
> 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.
> 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...
termie (termie) : | # |
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/
text conflict in tools/install_
justinsb (justin-fathomdb) wrote : | # |
Merged with trunk; changed error_ok to terminate_
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!
justinsb (justin-fathomdb) : | # |
termie (termie) : | # |
Preview Diff
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... |
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