mirror of
https://github.com/ceph/ceph
synced 2025-03-31 07:53:23 +00:00
Check for errors on remote commands.
This commit is contained in:
parent
87d7192c89
commit
df84f4e052
@ -8,8 +8,9 @@ import shutil
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class RemoteProcess(object):
|
||||
__slots__ = ['stdin', 'stdout', 'stderr', '_get_exitstatus']
|
||||
def __init__(self, stdin, stdout, stderr, get_exitstatus):
|
||||
__slots__ = ['command', 'stdin', 'stdout', 'stderr', '_get_exitstatus']
|
||||
def __init__(self, command, stdin, stdout, stderr, get_exitstatus):
|
||||
self.command = command
|
||||
self.stdin = stdin
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
@ -44,6 +45,7 @@ def execute(client, args):
|
||||
cmd = ' '.join(pipes.quote(a) for a in args)
|
||||
(in_, out, err) = client.exec_command(cmd)
|
||||
r = RemoteProcess(
|
||||
command=cmd,
|
||||
stdin=in_,
|
||||
stdout=out,
|
||||
stderr=err,
|
||||
@ -74,10 +76,43 @@ def copy_file_to(f, dst):
|
||||
handler = shutil.copyfileobj
|
||||
return handler(f, dst)
|
||||
|
||||
|
||||
class CommandFailedError(Exception):
|
||||
def __init__(self, command, exitstatus):
|
||||
self.command = command
|
||||
self.exitstatus = exitstatus
|
||||
|
||||
def __str__(self):
|
||||
return "Command failed with status {status}: {command!r}".format(
|
||||
status=self.exitstatus,
|
||||
command=self.command,
|
||||
)
|
||||
|
||||
|
||||
class CommandCrashedError(Exception):
|
||||
def __init__(self, command):
|
||||
self.command = command
|
||||
|
||||
def __str__(self):
|
||||
return "Command crashed: {command!r}".format(
|
||||
command=self.command,
|
||||
)
|
||||
|
||||
|
||||
class ConnectionLostError(Exception):
|
||||
def __init__(self, command):
|
||||
self.command = command
|
||||
|
||||
def __str__(self):
|
||||
return "SSH connection was lost: {command!r}".format(
|
||||
command=self.command,
|
||||
)
|
||||
|
||||
def run(
|
||||
client, args,
|
||||
stdin=None, stdout=None, stderr=None,
|
||||
logger=None,
|
||||
check_status=True,
|
||||
):
|
||||
"""
|
||||
Run a command remotely.
|
||||
@ -89,6 +124,7 @@ def run(
|
||||
:param stdout: What to do with standard output. Either a file-like object, a `logging.Logger`, or `None` for copying to default log.
|
||||
:param stderr: What to do with standard error. See `stdout`.
|
||||
:param logger: If logging, write stdout/stderr to "out" and "err" children of this logger. Defaults to logger named after this module.
|
||||
:param check_status: Whether to raise CalledProcessError on non-zero exit status, and . Defaults to True. All signals and connection loss are made to look like SIGHUP.
|
||||
"""
|
||||
r = execute(client, args)
|
||||
|
||||
@ -108,4 +144,19 @@ def run(
|
||||
g_err.get()
|
||||
g_in.get()
|
||||
|
||||
return r.exitstatus
|
||||
status = r.exitstatus
|
||||
if check_status:
|
||||
if status is None:
|
||||
# command either died due to a signal, or the connection
|
||||
# was lost
|
||||
transport = client.get_transport()
|
||||
if not transport.is_active():
|
||||
# look like we lost the connection
|
||||
raise ConnectionLostError(command=r.command)
|
||||
|
||||
# connection seems healthy still, assuming it was a
|
||||
# signal; sadly SSH does not tell us which signal
|
||||
raise CommandCrashedError(command=r.command)
|
||||
if status != 0:
|
||||
raise CommandFailedError(command=r.command, exitstatus=status)
|
||||
return status
|
||||
|
@ -6,6 +6,8 @@ import logging
|
||||
|
||||
from .. import run
|
||||
|
||||
from .util import assert_raises
|
||||
|
||||
|
||||
@nose.with_setup(fudge.clear_expectations)
|
||||
@fudge.with_fakes
|
||||
@ -30,18 +32,100 @@ def test_run_log_simple():
|
||||
log_out.expects('log').with_args(logging.INFO, 'bar')
|
||||
channel = fudge.Fake('channel')
|
||||
out.has_attr(channel=channel)
|
||||
channel.expects('recv_exit_status').with_args().returns(42)
|
||||
channel.expects('recv_exit_status').with_args().returns(0)
|
||||
got = run.run(
|
||||
client=ssh,
|
||||
logger=logger,
|
||||
args=['foo', 'bar baz'],
|
||||
)
|
||||
eq(got, 0)
|
||||
|
||||
|
||||
@nose.with_setup(fudge.clear_expectations)
|
||||
@fudge.with_fakes
|
||||
def test_run_status_bad():
|
||||
ssh = fudge.Fake('SSHConnection')
|
||||
cmd = ssh.expects('exec_command')
|
||||
cmd.with_args("foo")
|
||||
in_ = fudge.Fake('ChannelFile').is_a_stub()
|
||||
out = fudge.Fake('ChannelFile').is_a_stub()
|
||||
err = fudge.Fake('ChannelFile').is_a_stub()
|
||||
cmd.returns((in_, out, err))
|
||||
out.expects('xreadlines').with_args().returns([])
|
||||
err.expects('xreadlines').with_args().returns([])
|
||||
logger = fudge.Fake('logger').is_a_stub()
|
||||
channel = fudge.Fake('channel')
|
||||
out.has_attr(channel=channel)
|
||||
channel.expects('recv_exit_status').with_args().returns(42)
|
||||
e = assert_raises(
|
||||
run.CommandFailedError,
|
||||
run.run,
|
||||
client=ssh,
|
||||
logger=logger,
|
||||
args=['foo'],
|
||||
)
|
||||
eq(e.command, 'foo')
|
||||
eq(e.exitstatus, 42)
|
||||
eq(str(e), "Command failed with status 42: 'foo'")
|
||||
|
||||
|
||||
@nose.with_setup(fudge.clear_expectations)
|
||||
@fudge.with_fakes
|
||||
def test_run_status_bad_nocheck():
|
||||
ssh = fudge.Fake('SSHConnection')
|
||||
cmd = ssh.expects('exec_command')
|
||||
cmd.with_args("foo")
|
||||
in_ = fudge.Fake('ChannelFile').is_a_stub()
|
||||
out = fudge.Fake('ChannelFile').is_a_stub()
|
||||
err = fudge.Fake('ChannelFile').is_a_stub()
|
||||
cmd.returns((in_, out, err))
|
||||
out.expects('xreadlines').with_args().returns([])
|
||||
err.expects('xreadlines').with_args().returns([])
|
||||
logger = fudge.Fake('logger').is_a_stub()
|
||||
channel = fudge.Fake('channel')
|
||||
out.has_attr(channel=channel)
|
||||
channel.expects('recv_exit_status').with_args().returns(42)
|
||||
got = run.run(
|
||||
client=ssh,
|
||||
logger=logger,
|
||||
args=['foo'],
|
||||
check_status=False,
|
||||
)
|
||||
eq(got, 42)
|
||||
|
||||
|
||||
@nose.with_setup(fudge.clear_expectations)
|
||||
@fudge.with_fakes
|
||||
def test_run_crash_status():
|
||||
def test_run_status_crash():
|
||||
ssh = fudge.Fake('SSHConnection')
|
||||
cmd = ssh.expects('exec_command')
|
||||
cmd.with_args("foo")
|
||||
in_ = fudge.Fake('ChannelFile').is_a_stub()
|
||||
out = fudge.Fake('ChannelFile').is_a_stub()
|
||||
err = fudge.Fake('ChannelFile').is_a_stub()
|
||||
cmd.returns((in_, out, err))
|
||||
out.expects('xreadlines').with_args().returns([])
|
||||
err.expects('xreadlines').with_args().returns([])
|
||||
logger = fudge.Fake('logger').is_a_stub()
|
||||
channel = fudge.Fake('channel')
|
||||
out.has_attr(channel=channel)
|
||||
channel.expects('recv_exit_status').with_args().returns(-1)
|
||||
transport = ssh.expects('get_transport').with_args().returns_fake()
|
||||
transport.expects('is_active').with_args().returns(True)
|
||||
e = assert_raises(
|
||||
run.CommandCrashedError,
|
||||
run.run,
|
||||
client=ssh,
|
||||
logger=logger,
|
||||
args=['foo'],
|
||||
)
|
||||
eq(e.command, 'foo')
|
||||
eq(str(e), "Command crashed: 'foo'")
|
||||
|
||||
|
||||
@nose.with_setup(fudge.clear_expectations)
|
||||
@fudge.with_fakes
|
||||
def test_run_status_crash_nocheck():
|
||||
ssh = fudge.Fake('SSHConnection')
|
||||
cmd = ssh.expects('exec_command')
|
||||
cmd.with_args("foo")
|
||||
@ -59,5 +143,61 @@ def test_run_crash_status():
|
||||
client=ssh,
|
||||
logger=logger,
|
||||
args=['foo'],
|
||||
check_status=False,
|
||||
)
|
||||
assert got is None
|
||||
|
||||
|
||||
@nose.with_setup(fudge.clear_expectations)
|
||||
@fudge.with_fakes
|
||||
def test_run_status_lost():
|
||||
ssh = fudge.Fake('SSHConnection')
|
||||
cmd = ssh.expects('exec_command')
|
||||
cmd.with_args("foo")
|
||||
in_ = fudge.Fake('ChannelFile').is_a_stub()
|
||||
out = fudge.Fake('ChannelFile').is_a_stub()
|
||||
err = fudge.Fake('ChannelFile').is_a_stub()
|
||||
cmd.returns((in_, out, err))
|
||||
out.expects('xreadlines').with_args().returns([])
|
||||
err.expects('xreadlines').with_args().returns([])
|
||||
logger = fudge.Fake('logger').is_a_stub()
|
||||
channel = fudge.Fake('channel')
|
||||
out.has_attr(channel=channel)
|
||||
channel.expects('recv_exit_status').with_args().returns(-1)
|
||||
transport = ssh.expects('get_transport').with_args().returns_fake()
|
||||
transport.expects('is_active').with_args().returns(False)
|
||||
e = assert_raises(
|
||||
run.ConnectionLostError,
|
||||
run.run,
|
||||
client=ssh,
|
||||
logger=logger,
|
||||
args=['foo'],
|
||||
)
|
||||
|
||||
eq(e.command, 'foo')
|
||||
eq(str(e), "SSH connection was lost: 'foo'")
|
||||
|
||||
|
||||
@nose.with_setup(fudge.clear_expectations)
|
||||
@fudge.with_fakes
|
||||
def test_run_status_lost_nocheck():
|
||||
ssh = fudge.Fake('SSHConnection')
|
||||
cmd = ssh.expects('exec_command')
|
||||
cmd.with_args("foo")
|
||||
in_ = fudge.Fake('ChannelFile').is_a_stub()
|
||||
out = fudge.Fake('ChannelFile').is_a_stub()
|
||||
err = fudge.Fake('ChannelFile').is_a_stub()
|
||||
cmd.returns((in_, out, err))
|
||||
out.expects('xreadlines').with_args().returns([])
|
||||
err.expects('xreadlines').with_args().returns([])
|
||||
logger = fudge.Fake('logger').is_a_stub()
|
||||
channel = fudge.Fake('channel')
|
||||
out.has_attr(channel=channel)
|
||||
channel.expects('recv_exit_status').with_args().returns(-1)
|
||||
got = run.run(
|
||||
client=ssh,
|
||||
logger=logger,
|
||||
args=['foo'],
|
||||
check_status=False,
|
||||
)
|
||||
assert got is None
|
||||
|
Loading…
Reference in New Issue
Block a user