Revision d5d76ab2
b/lib/utils/process.py | ||
---|---|---|
142 | 142 |
|
143 | 143 |
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False, |
144 | 144 |
interactive=False, timeout=None, noclose_fds=None, |
145 |
_postfork_fn=None): |
|
145 |
input_fd=None, _postfork_fn=None):
|
|
146 | 146 |
"""Execute a (shell) command. |
147 | 147 |
|
148 | 148 |
The command should not read from its standard input, as it will be |
... | ... | |
170 | 170 |
@type noclose_fds: list |
171 | 171 |
@param noclose_fds: list of additional (fd >=3) file descriptors to leave |
172 | 172 |
open for the child process |
173 |
@type input_fd: C{file}-like object or numeric file descriptor |
|
174 |
@param input_fd: File descriptor for process' standard input |
|
173 | 175 |
@param _postfork_fn: Callback run after fork but before timeout (unittest) |
174 | 176 |
@rtype: L{RunResult} |
175 | 177 |
@return: RunResult instance |
... | ... | |
183 | 185 |
raise errors.ProgrammerError("Parameters 'output' and 'interactive' can" |
184 | 186 |
" not be provided at the same time") |
185 | 187 |
|
188 |
if not (output is None or input_fd is None): |
|
189 |
# The current logic in "_RunCmdFile", which is used when output is defined, |
|
190 |
# does not support input files (not hard to implement, though) |
|
191 |
raise errors.ProgrammerError("Parameters 'output' and 'input_fd' can" |
|
192 |
" not be used at the same time") |
|
193 |
|
|
186 | 194 |
if isinstance(cmd, basestring): |
187 | 195 |
strcmd = cmd |
188 | 196 |
shell = True |
... | ... | |
202 | 210 |
if output is None: |
203 | 211 |
out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd, |
204 | 212 |
interactive, timeout, |
205 |
noclose_fds, |
|
213 |
noclose_fds, input_fd,
|
|
206 | 214 |
_postfork_fn=_postfork_fn) |
207 | 215 |
else: |
208 | 216 |
assert _postfork_fn is None, \ |
209 | 217 |
"_postfork_fn not supported if output provided" |
218 |
assert input_fd is None |
|
210 | 219 |
timeout_action = _TIMEOUT_NONE |
211 | 220 |
status = _RunCmdFile(cmd, cmd_env, shell, output, cwd, noclose_fds) |
212 | 221 |
out = err = "" |
... | ... | |
481 | 490 |
|
482 | 491 |
|
483 | 492 |
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout, noclose_fds, |
493 |
input_fd, |
|
484 | 494 |
_linger_timeout=constants.CHILD_LINGER_TIMEOUT, |
485 | 495 |
_postfork_fn=None): |
486 | 496 |
"""Run a command and return its output. |
... | ... | |
500 | 510 |
@type noclose_fds: list |
501 | 511 |
@param noclose_fds: list of additional (fd >=3) file descriptors to leave |
502 | 512 |
open for the child process |
513 |
@type input_fd: C{file}-like object or numeric file descriptor |
|
514 |
@param input_fd: File descriptor for process' standard input |
|
503 | 515 |
@param _postfork_fn: Function run after fork but before timeout (unittest) |
504 | 516 |
@rtype: tuple |
505 | 517 |
@return: (out, err, status) |
... | ... | |
507 | 519 |
""" |
508 | 520 |
poller = select.poll() |
509 | 521 |
|
510 |
stderr = subprocess.PIPE |
|
511 |
stdout = subprocess.PIPE |
|
512 |
stdin = subprocess.PIPE |
|
513 |
|
|
514 | 522 |
if interactive: |
515 |
stderr = stdout = stdin = None |
|
523 |
stderr = None |
|
524 |
stdout = None |
|
525 |
else: |
|
526 |
stderr = subprocess.PIPE |
|
527 |
stdout = subprocess.PIPE |
|
528 |
|
|
529 |
if input_fd: |
|
530 |
stdin = input_fd |
|
531 |
elif interactive: |
|
532 |
stdin = None |
|
533 |
else: |
|
534 |
stdin = subprocess.PIPE |
|
516 | 535 |
|
517 | 536 |
if noclose_fds: |
518 | 537 |
preexec_fn = lambda: CloseFDs(noclose_fds) |
... | ... | |
549 | 568 |
|
550 | 569 |
timeout_action = _TIMEOUT_NONE |
551 | 570 |
|
571 |
# subprocess: "If the stdin argument is PIPE, this attribute is a file object |
|
572 |
# that provides input to the child process. Otherwise, it is None." |
|
573 |
assert (stdin == subprocess.PIPE) ^ (child.stdin is None), \ |
|
574 |
"subprocess' stdin did not behave as documented" |
|
575 |
|
|
552 | 576 |
if not interactive: |
553 |
child.stdin.close() |
|
577 |
if child.stdin is not None: |
|
578 |
child.stdin.close() |
|
554 | 579 |
poller.register(child.stdout, select.POLLIN) |
555 | 580 |
poller.register(child.stderr, select.POLLIN) |
556 | 581 |
fdmap = { |
b/test/ganeti.utils.process_unittest.py | ||
---|---|---|
274 | 274 |
(out, err, status, ta) = \ |
275 | 275 |
utils.process._RunCmdPipe(cmd, {}, False, "/", False, |
276 | 276 |
timeout, [self.proc_ready_helper.write_fd], |
277 |
None, |
|
277 | 278 |
_linger_timeout=0.2, |
278 | 279 |
_postfork_fn=self.proc_ready_helper.Ready) |
279 | 280 |
self.assert_(status < 0) |
... | ... | |
377 | 378 |
finally: |
378 | 379 |
temp.close() |
379 | 380 |
|
381 |
def testNoInputRead(self): |
|
382 |
testfile = self._TestDataFilename("cert1.pem") |
|
383 |
|
|
384 |
result = utils.RunCmd(["cat"], timeout=10.0) |
|
385 |
self.assertFalse(result.failed) |
|
386 |
self.assertEqual(result.stderr, "") |
|
387 |
self.assertEqual(result.stdout, "") |
|
388 |
|
|
389 |
def testInputFileHandle(self): |
|
390 |
testfile = self._TestDataFilename("cert1.pem") |
|
391 |
|
|
392 |
result = utils.RunCmd(["cat"], input_fd=open(testfile, "r")) |
|
393 |
self.assertFalse(result.failed) |
|
394 |
self.assertEqual(result.stdout, utils.ReadFile(testfile)) |
|
395 |
self.assertEqual(result.stderr, "") |
|
396 |
|
|
397 |
def testInputNumericFileDescriptor(self): |
|
398 |
testfile = self._TestDataFilename("cert2.pem") |
|
399 |
|
|
400 |
fh = open(testfile, "r") |
|
401 |
try: |
|
402 |
result = utils.RunCmd(["cat"], input_fd=fh.fileno()) |
|
403 |
finally: |
|
404 |
fh.close() |
|
405 |
|
|
406 |
self.assertFalse(result.failed) |
|
407 |
self.assertEqual(result.stdout, utils.ReadFile(testfile)) |
|
408 |
self.assertEqual(result.stderr, "") |
|
409 |
|
|
410 |
def testInputWithCloseFds(self): |
|
411 |
testfile = self._TestDataFilename("cert1.pem") |
|
412 |
|
|
413 |
temp = open(self.fname, "r+") |
|
414 |
try: |
|
415 |
temp.write("test283523367") |
|
416 |
temp.seek(0) |
|
417 |
|
|
418 |
result = utils.RunCmd(["/bin/bash", "-c", |
|
419 |
("cat && read -u %s; echo $REPLY" % |
|
420 |
temp.fileno())], |
|
421 |
input_fd=open(testfile, "r"), |
|
422 |
noclose_fds=[temp.fileno()]) |
|
423 |
self.assertFalse(result.failed) |
|
424 |
self.assertEqual(result.stdout.strip(), |
|
425 |
utils.ReadFile(testfile) + "test283523367") |
|
426 |
self.assertEqual(result.stderr, "") |
|
427 |
finally: |
|
428 |
temp.close() |
|
429 |
|
|
430 |
def testOutputAndInteractive(self): |
|
431 |
self.assertRaises(errors.ProgrammerError, utils.RunCmd, |
|
432 |
[], output=self.fname, interactive=True) |
|
433 |
|
|
434 |
def testOutputAndInput(self): |
|
435 |
self.assertRaises(errors.ProgrammerError, utils.RunCmd, |
|
436 |
[], output=self.fname, input_fd=open(self.fname)) |
|
437 |
|
|
380 | 438 |
|
381 | 439 |
class TestRunParts(testutils.GanetiTestCase): |
382 | 440 |
"""Testing case for the RunParts function""" |
Also available in: Unified diff