-#!@PYTHON_EXECUTABLE@
+#!@Python3_EXECUTABLE@
# -*- mode:python -*-
# vim: ts=4 sw=4 smarttab expandtab
#
Foundation. See file COPYING.
"""
-from __future__ import print_function
-import codecs
+from time import sleep
import grp
import os
import pwd
+import re
+import shutil
+import stat
import sys
+import time
import platform
+from typing import Dict, List, Sequence, Tuple
+
try:
input = raw_input
except NameError:
CEPH_RELEASE_NAME = "@CEPH_RELEASE_NAME@"
CEPH_RELEASE_TYPE = "@CEPH_RELEASE_TYPE@"
-# Flags from src/mon/Monitor.h
-FLAG_NOFORWARD = (1 << 0)
-FLAG_OBSOLETE = (1 << 1)
-FLAG_DEPRECATED = (1 << 2)
-
# priorities from src/common/perf_counters.h
PRIO_CRITICAL = 10
PRIO_INTERESTING = 8
DEVMODEMSG = '*** DEVELOPER MODE: setting PATH, PYTHONPATH and LD_LIBRARY_PATH ***'
-def respawn_in_path(lib_path, pybind_path, pythonlib_path):
- execv_cmd = ['python']
- if 'CEPH_DBG' in os.environ:
- execv_cmd += ['-mpdb']
+def add_to_ld_path(path_name, path):
+ paths = re.split('[ :]', os.environ.get(path_name, ''))
+ if path in paths:
+ return 0
+ else:
+ paths.insert(0, path)
+ os.environ[path_name] = ':'.join(paths)
+ return 1
+
+def respawn_in_path(lib_path, pybind_path, pythonlib_path, asan_lib_path):
if platform.system() == "Darwin":
lib_path_var = "DYLD_LIBRARY_PATH"
else:
lib_path_var = "LD_LIBRARY_PATH"
- py_binary = os.environ.get("PYTHON", "python")
-
- if lib_path_var in os.environ:
- if lib_path not in os.environ[lib_path_var]:
- os.environ[lib_path_var] += ':' + lib_path
- if "CEPH_DEV" not in os.environ:
- print(DEVMODEMSG, file=sys.stderr)
- os.execvp(py_binary, execv_cmd + sys.argv)
- else:
- os.environ[lib_path_var] = lib_path
+ ld_paths_changed = 0
+ preload_libcxx = os.environ.get('CEPH_PRELOAD_LIBCXX')
+ if preload_libcxx:
+ ld_paths_changed += add_to_ld_path('LD_PRELOAD', preload_libcxx)
+ if asan_lib_path:
+ ld_paths_changed += add_to_ld_path('LD_PRELOAD', asan_lib_path)
+ ld_paths_changed += add_to_ld_path(lib_path_var, lib_path)
+ if ld_paths_changed > 0:
if "CEPH_DEV" not in os.environ:
print(DEVMODEMSG, file=sys.stderr)
- os.execvp(py_binary, execv_cmd + sys.argv)
- sys.path.insert(0, os.path.join(MYDIR, pybind_path))
- sys.path.insert(0, os.path.join(MYDIR, pythonlib_path))
+ execv_cmd = []
+ if 'CEPH_DBG' in os.environ:
+ execv_cmd += ['@Python3_EXECUTABLE@', '-mpdb']
+ execv_cmd += sys.argv
+ os.execvp(execv_cmd[0], execv_cmd)
+ else:
+ sys.path.insert(0, pybind_path)
+ sys.path.insert(0, pythonlib_path)
def get_pythonlib_dir():
"""Returns the name of a distutils build directory"""
return "lib.{version[0]}".format(version=sys.version_info)
+
+def get_cmake_variables(*names):
+ vars = dict((name, None) for name in names)
+ for line in open(os.path.join(MYPDIR, "CMakeCache.txt")):
+ # parse lines like "WITH_ASAN:BOOL=ON"
+ for name in names:
+ if line.startswith("{}:".format(name)):
+ type_value = line.split(":")[1].strip()
+ t, v = type_value.split("=")
+ if t == 'BOOL':
+ v = v.upper() in ('TRUE', '1', 'Y', 'YES', 'ON')
+ vars[name] = v
+ break
+ if all(vars.values()):
+ break
+ return [vars[name] for name in names]
+
+
if os.path.exists(os.path.join(MYPDIR, "CMakeCache.txt")) \
and os.path.exists(os.path.join(MYPDIR, "bin/init-ceph")):
- src_path = None
- for l in open(os.path.join(MYPDIR, "CMakeCache.txt")):
- if l.startswith("ceph_SOURCE_DIR:STATIC="):
- src_path = l.split("=")[1].strip()
-
+ src_path, with_asan, asan_lib_path = \
+ get_cmake_variables("ceph_SOURCE_DIR", "WITH_ASAN", "ASAN_LIBRARY")
if src_path is None:
# Huh, maybe we're not really in a cmake environment?
pass
pythonlib_path = os.path.join(lib_path,
"cython_modules",
get_pythonlib_dir())
-
- respawn_in_path(lib_path, pybind_path, pythonlib_path)
+ respawn_in_path(lib_path, pybind_path, pythonlib_path,
+ asan_lib_path if with_asan else None)
if 'PATH' in os.environ and bin_path not in os.environ['PATH']:
- os.environ['PATH'] += ':' + bin_path
+ os.environ['PATH'] = os.pathsep.join([bin_path, os.environ['PATH']])
import argparse
import errno
from ceph_argparse import \
concise_sig, descsort_key, parse_json_funcsigs, \
- matchnum, validate_command, find_cmd_target, \
- send_command, json_command, run_in_thread
+ validate_command, find_cmd_target, \
+ json_command, run_in_thread, Flag
from ceph_daemon import admin_socket, DaemonWatcher, Termsize
verbose = False
cluster_handle = None
-# Always use Unicode (UTF-8) for stdout
-if sys.version_info[0] >= 3:
- raw_stdout = sys.stdout.buffer
- raw_stderr = sys.stderr.buffer
-else:
- raw_stdout = sys.__stdout__
- raw_stderr = sys.__stderr__
- sys.stdout = codecs.getwriter('utf-8')(raw_stdout)
- sys.stderr = codecs.getwriter('utf-8')(raw_stderr)
-
def raw_write(buf):
sys.stdout.flush()
- raw_stdout.write(buf)
+ sys.stdout.buffer.write(buf)
def osdids():
d = json.loads(outbuf.decode('utf-8'))
l = []
l.append(d['active_name'])
- for i in d['standbys']:
- l.append(i['name'])
+ # we can only send tell commands to the active mgr
+ #for i in d['standbys']:
+ # l.append(i['name'])
return l
}
-def parse_cmdargs(args=None, target=''):
+def parse_cmdargs(args=None, target='') -> Tuple[argparse.ArgumentParser,
+ argparse.Namespace,
+ List[str]]:
+ """
+ Consume generic arguments from the start of the ``args``
+ list. Call this first to handle arguments that are not
+ handled by a command description provided by the server.
+
+ :returns: three tuple of ArgumentParser instance, Namespace instance
+ containing parsed values, and list of un-handled arguments
+ """
# alias: let the line-wrapping be sane
AP = argparse.ArgumentParser
parser.add_argument('--cluster', help='cluster name')
parser.add_argument('--admin-daemon', dest='admin_socket',
- help='submit admin-socket commands (\"help\" for help')
+ help='submit admin-socket commands (\"help\" for help)')
parser.add_argument('-s', '--status', action='store_true',
help='show cluster status')
parser.add_argument('--watch-error', action='store_true',
help='watch error events')
- parser.add_argument('--watch-channel', dest="watch_channel",
- help="which log channel to follow " \
- "when using -w/--watch. One of ['cluster', 'audit', '*'",
- default='cluster')
+ parser.add_argument('-W', '--watch-channel', dest="watch_channel",
+ help="watch live cluster changes on a specific channel "
+ "(e.g., cluster, audit, cephadm, or '*' for all)")
parser.add_argument('--version', '-v', action="store_true", help="display version")
parser.add_argument('--verbose', action="store_true", help="make verbose")
help="make less verbose")
parser.add_argument('-f', '--format', choices=['json', 'json-pretty',
- 'xml', 'xml-pretty', 'plain'], dest='output_format')
+ 'xml', 'xml-pretty', 'plain', 'yaml'], dest='output_format')
parser.add_argument('--connect-timeout', dest='cluster_timeout',
type=int,
help='set a timeout for connecting to the cluster')
+ parser.add_argument('--block', action='store_true',
+ help='block until completion (scrub and deep-scrub only)')
+ parser.add_argument('--period', '-p', default=1, type=float,
+ help='polling period, default 1.0 second (for ' \
+ 'polling commands only)')
+
# returns a Namespace with the parsed args, and a list of all extras
parsed_args, extras = parser.parse_known_args(args)
""", file=sys.stdout)
-def do_extended_help(parser, args, target, partial):
+def do_extended_help(parser, args, target, partial) -> int:
def help_for_sigs(sigs, partial=None):
- sys.stdout.write(format_help(parse_json_funcsigs(sigs, 'cli'),
- partial=partial))
+ try:
+ while True:
+ out = format_help(parse_json_funcsigs(sigs, 'cli'),
+ partial=partial)
+ if not out and partial:
+ # shorten partial until we get at least one matching command prefix
+ partial = ' '.join(partial.split()[:-1])
+ continue
+ sys.stdout.write(out)
+ break
+ except BrokenPipeError:
+ pass
def help_for_target(target, partial=None):
# wait for osdmap because we know this is sent after the mgrmap
prefix='get_command_descriptions',
timeout=10)
if ret:
- print("couldn't get command descriptions for {0}: {1} ({2})".
- format(target, outs, ret), file=sys.stderr)
+ if (ret == -errno.EPERM or ret == -errno.EACCES) and target[0] in ('osd', 'mds'):
+ print("Permission denied. Check that your user has 'allow *' "
+ "capabilities for the target daemon type.", file=sys.stderr)
+ elif ret == -errno.EPERM:
+ print("Permission denied. Check your user has proper "
+ "capabilities configured", file=sys.stderr)
+ else:
+ print("couldn't get command descriptions for {0}: {1} ({2})".
+ format(target, outs, ret), file=sys.stderr)
return ret
else:
return help_for_sigs(outbuf.decode('utf-8'), partial)
yield result
- raise StopIteration
-
-def format_help(cmddict, partial=None):
+def format_help(cmddict, partial=None) -> str:
"""
Formats all the cmdsigs and helptexts from cmddict into a sorted-by-
cmdsig 2-column display, with each column wrapped and indented to
if not cmd['help']:
continue
flags = cmd.get('flags', 0)
- if flags & (FLAG_OBSOLETE | FLAG_DEPRECATED):
+ if flags & (Flag.OBSOLETE | Flag.DEPRECATED | Flag.HIDDEN):
continue
concise = concise_sig(cmd['sig'])
if partial and not concise.startswith(partial):
return fullusage
-def ceph_conf(parsed_args, field, name):
- args = ['ceph-conf']
+def ceph_conf(parsed_args, field, name, pid=None):
+ cmd = 'ceph-conf'
+ bindir = os.path.dirname(__file__)
+ if shutil.which(cmd):
+ args = [cmd]
+ elif shutil.which(cmd, path=bindir):
+ args = [os.path.join(bindir, cmd)]
+ else:
+ raise RuntimeError('"ceph-conf" not found')
if name:
args.extend(['--name', name])
+ if pid:
+ args.extend(['--pid', pid])
# add any args in GLOBAL_ARGS
for key, val in GLOBAL_ARGS.items():
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
outdata, errdata = p.communicate()
- if len(errdata):
+ if p.returncode != 0:
raise RuntimeError('unable to get conf option %s for %s: %s' % (field, name, errdata))
return outdata.rstrip()
+
PROMPT = 'ceph> '
if sys.stdin.isatty():
return line
-def new_style_command(parsed_args, cmdargs, target, sigdict, inbuf, verbose):
+def do_command(parsed_args, target, cmdargs, sigdict, inbuf, verbose):
+ ''' Validate a command, and handle the polling flag '''
+
+ valid_dict = validate_command(sigdict, cmdargs, verbose)
+ # Validate input args against list of sigs
+ if valid_dict:
+ if parsed_args.output_format:
+ valid_dict['format'] = parsed_args.output_format
+ if verbose:
+ print("Submitting command: ", valid_dict, file=sys.stderr)
+ else:
+ return -errno.EINVAL, '', 'invalid command'
+
+ next_header_print = 0
+ # Set extra options for polling commands only:
+ if valid_dict.get('poll', False):
+ valid_dict['width'] = Termsize().cols
+ while True:
+ try:
+ # Only print the header for polling commands
+ if next_header_print == 0 and valid_dict.get('poll', False):
+ valid_dict['print_header'] = True
+ next_header_print = Termsize().rows - 3
+ next_header_print -= 1
+ ret, outbuf, outs = json_command(cluster_handle, target=target,
+ argdict=valid_dict, inbuf=inbuf, verbose=verbose)
+ if valid_dict.get('poll', False):
+ valid_dict['print_header'] = False
+ if not valid_dict.get('poll', False):
+ # Don't print here if it's not a polling command
+ break
+ if ret:
+ ret = abs(ret)
+ print('Error: {0} {1}'.format(ret, errno.errorcode.get(ret, 'Unknown')),
+ file=sys.stderr)
+ break
+ if outbuf:
+ print(outbuf.decode('utf-8'))
+ if outs:
+ print(outs, file=sys.stderr)
+ if parsed_args.period <= 0:
+ break
+ sleep(parsed_args.period)
+ except KeyboardInterrupt:
+ print('Interrupted')
+ return errno.EINTR, '', ''
+ if ret == errno.ETIMEDOUT:
+ ret = -ret
+ if not outs:
+ outs = ("Connection timed out. Please check the client's " +
+ "permission and connection.")
+ return ret, outbuf, outs
+
+
+def new_style_command(parsed_args,
+ cmdargs,
+ target,
+ sigdict,
+ inbuf, verbose) -> Tuple[int, bytes, str]:
"""
Do new-style command dance.
target: daemon to receive command: mon (any) or osd.N
sig = cmd['sig']
print('{0}: {1}'.format(cmdtag, concise_sig(sig)))
- if True:
- if cmdargs:
- # Validate input args against list of sigs
- valid_dict = validate_command(sigdict, cmdargs, verbose)
- if valid_dict:
- if parsed_args.output_format:
- valid_dict['format'] = parsed_args.output_format
- else:
- return -errno.EINVAL, '', 'invalid command'
- else:
- if sys.stdin.isatty():
- # do the command-interpreter looping
- # for input to do readline cmd editing
- import readline # noqa
+ if cmdargs:
+ # Non interactive mode
+ ret, outbuf, outs = do_command(parsed_args, target, cmdargs, sigdict, inbuf, verbose)
+ else:
+ # Interactive mode (ceph cli)
+ if sys.stdin.isatty():
+ # do the command-interpreter looping
+ # for input to do readline cmd editing
+ import readline # noqa
- while True:
+ while True:
+ try:
interactive_input = read_input()
- if interactive_input is None:
- return 0, '', ''
- cmdargs = parse_cmdargs(shlex.split(interactive_input))[2]
- try:
- target = find_cmd_target(cmdargs)
- except Exception as e:
- print('error handling command target: {0}'.format(e),
- file=sys.stderr)
- continue
- if len(cmdargs) and cmdargs[0] == 'tell':
- print('Can not use \'tell\' in interactive mode.',
- file=sys.stderr)
- continue
- valid_dict = validate_command(sigdict, cmdargs, verbose)
- if valid_dict:
- if parsed_args.output_format:
- valid_dict['format'] = parsed_args.output_format
- if verbose:
- print("Submitting command: ", valid_dict, file=sys.stderr)
- ret, outbuf, outs = json_command(cluster_handle,
- target=target,
- argdict=valid_dict)
- if ret:
- ret = abs(ret)
- print('Error: {0} {1}'.format(ret, errno.errorcode.get(ret, 'Unknown')),
- file=sys.stderr)
- if outbuf:
- print(outbuf)
- if outs:
- print('Status:\n', outs, file=sys.stderr)
- else:
- print("Invalid command", file=sys.stderr)
+ except EOFError:
+ # leave user an uncluttered prompt
+ return 0, b'\n', ''
+ if interactive_input is None:
+ return 0, b'', ''
+ cmdargs = parse_cmdargs(shlex.split(interactive_input))[2]
+ try:
+ target = find_cmd_target(cmdargs)
+ except Exception as e:
+ print('error handling command target: {0}'.format(e),
+ file=sys.stderr)
+ continue
+ if len(cmdargs) and cmdargs[0] == 'tell':
+ print('Can not use \'tell\' in interactive mode.',
+ file=sys.stderr)
+ continue
+ ret, outbuf, outs = do_command(parsed_args, target, cmdargs,
+ sigdict, inbuf, verbose)
+ if ret < 0:
+ ret = -ret
+ errstr = errno.errorcode.get(ret, 'Unknown')
+ print('Error {0}: {1}'.format(errstr, outs), file=sys.stderr)
+ else:
+ if outs:
+ print(outs, file=sys.stderr)
+ if outbuf:
+ print(outbuf.decode('utf-8'))
- if verbose:
- print("Submitting command: ", valid_dict, file=sys.stderr)
- return json_command(cluster_handle, target=target, argdict=valid_dict,
- inbuf=inbuf)
+ return ret, outbuf, outs
def complete(sigdict, args, target):
"""
# XXX this looks a lot like the front of validate_command(). Refactor?
- complete_verbose = 'COMPVERBOSE' in os.environ
-
# Repulsive hack to handle tell: lop off 'tell' and target
# and validate the rest of the command. 'target' is already
# determined in our callers, so it's ok to remove it here.
match_count = 0
comps = []
for cmdtag, cmd in sigdict.items():
+ flags = cmd.get('flags', 0)
+ if flags & (Flag.OBSOLETE | Flag.HIDDEN):
+ continue
sig = cmd['sig']
j = 0
# iterate over all arguments, except last one
return 0
+def get_admin_socket(parsed_args, name):
+ path = ceph_conf(parsed_args, 'admin_socket', name)
+ try:
+ if stat.S_ISSOCK(os.stat(path).st_mode):
+ return path
+ except OSError:
+ pass
+ # try harder, probably the "name" option is in the form of
+ # "${name}.${pid}"?
+ parts = name.rsplit('.', 1)
+ if len(parts) > 1 and parts[-1].isnumeric():
+ name, pid = parts
+ return ceph_conf(parsed_args, 'admin_socket', name, pid)
+ else:
+ return path
+
+
def maybe_daemon_command(parsed_args, childargs):
"""
Check if --admin-socket, daemon, or daemonperf command
else:
# try resolve daemon name
try:
- sockpath = ceph_conf(parsed_args, 'admin_socket',
- childargs[1])
+ sockpath = get_admin_socket(parsed_args, childargs[1])
except Exception as e:
print('Can\'t get admin socket path: ' + str(e), file=sys.stderr)
return True, errno.EINVAL
return False
-def daemonperf(childargs, sockpath):
+def daemonperf(childargs: Sequence[str], sockpath: str):
"""
Handle daemonperf command; returns errno or 0
return 0
+def get_scrub_timestamps(childargs: Sequence[str]) -> Dict[str,
+ Tuple[str, str]]:
+ last_scrub_stamp = "last_" + childargs[1].replace('-', '_') + "_stamp"
+ results = dict()
+ scruball = False
+ if childargs[2] in ['all', 'any', '*']:
+ scruball = True
+ devnull = open(os.devnull, 'w')
+ out = subprocess.check_output(['ceph', 'pg', 'dump', '--format=json-pretty'],
+ stderr=devnull)
+ try:
+ pgstats = json.loads(out)['pg_map']['pg_stats']
+ except KeyError:
+ pgstats = json.loads(out)['pg_stats']
+ for stat in pgstats:
+ if scruball or stat['up_primary'] == int(childargs[2]):
+ scrub_tuple = (stat['up_primary'], stat[last_scrub_stamp])
+ results[stat['pgid']] = scrub_tuple
+ return results
+
+
+def check_scrub_stamps(waitdata, currdata):
+ for pg in waitdata.keys():
+ # Try to handle the case where a pg may not exist in current results
+ if pg in currdata and waitdata[pg][1] == currdata[pg][1]:
+ return False
+ return True
+
+
+def waitscrub(childargs, waitdata):
+ print('Waiting for {0} to complete...'.format(childargs[1]), file=sys.stdout)
+ currdata = get_scrub_timestamps(childargs)
+ while not check_scrub_stamps(waitdata, currdata):
+ time.sleep(3)
+ currdata = get_scrub_timestamps(childargs)
+ print('{0} completed'.format(childargs[1]), file=sys.stdout)
+
+
+def wait(childargs: Sequence[str], waitdata):
+ if childargs[1] in ['scrub', 'deep-scrub']:
+ waitscrub(childargs, waitdata)
+
+
def main():
ceph_args = os.environ.get('CEPH_ARGS')
if ceph_args:
CEPH_RELEASE_TYPE)) # noqa
return 0
+ # --watch-channel|-W implies -w
+ if parsed_args.watch_channel:
+ parsed_args.watch = True
+ elif parsed_args.watch and not parsed_args.watch_channel:
+ parsed_args.watch_channel = 'cluster'
+
global verbose
verbose = parsed_args.verbose
if parsed_args.client_name:
name = parsed_args.client_name
- # default '' means default conf search
- conffile = ''
+ conffile = rados.Rados.DEFAULT_CONF_FILES
if parsed_args.cephconf:
conffile = parsed_args.cephconf
# For now, --admin-daemon is handled as usual. Try it
# first in case we can't connect() to the cluster
- format = parsed_args.output_format
-
done, ret = maybe_daemon_command(parsed_args, childargs)
if done:
return ret
if injectargs and '--' in injectargs:
injectargs.remove('--')
- # special deprecation warning for 'ceph <type> tell'
- # someday 'mds' will be here too
- if (len(childargs) >= 2 and
- childargs[0] in ['mon', 'osd'] and
- childargs[1] == 'tell'):
- print('"{0} tell" is deprecated; try "tell {0}.<id> <command> [options...]" instead (id can be "*") '.format(childargs[0]),
- file=sys.stderr)
- return 1
+ block = False
+ waitdata = dict()
+ if parsed_args.block:
+ if (len(childargs) >= 2 and
+ childargs[0] == 'osd' and
+ childargs[1] in ['deep-scrub', 'scrub']):
+ block = True
+ waitdata = get_scrub_timestamps(childargs)
if parsed_args.help:
# short default timeout for -h
return 1
if parsed_args.help:
- hdr('Monitor commands:')
+ target = None
+ if len(childargs) >= 2 and childargs[0] == 'tell':
+ target = childargs[1].split('.', 1)
+ if not validate_target(target):
+ print('target {0} doesn\'t exist; please pass correct target to tell command (e.g., mon.a, osd.1, mds.a, mgr)'.format(childargs[1]), file=sys.stderr)
+ return 1
+ childargs = childargs[2:]
+ hdr('Tell %s commands:' % target[0])
+ else:
+ hdr('Monitor commands:')
+ target = ('mon', '')
if verbose:
print('[Contacting monitor, timeout after %d seconds]' % timeout)
- return do_extended_help(parser, childargs, ('mon', ''), ' '.join(childargs))
+ return do_extended_help(parser, childargs, target, ' '.join(childargs))
# implement "tell service.id help"
if len(childargs) >= 3 and childargs[0] == 'tell' and childargs[2] == 'help':
- target = childargs[1].split('.')
+ target = childargs[1].split('.', 1)
if validate_target(target):
+ hdr('Tell %s commands' % target[0])
return do_extended_help(parser, childargs, target, None)
else:
print('target {0} doesn\'t exists, please pass correct target to tell command, such as mon.a/'
'osd.1/mds.a/mgr'.format(childargs[1]), file=sys.stderr)
return 1
+
# implement -w/--watch_*
# This is ugly, but Namespace() isn't quite rich enough.
level = ''
# an awfully simple callback
def watch_cb(arg, line, channel, name, who, stamp_sec, stamp_nsec, seq, level, msg):
# Filter on channel
- if (channel == parsed_args.watch_channel or \
- parsed_args.watch_channel == "*"):
- print(line)
+ channel = channel.decode('utf-8')
+ if parsed_args.watch_channel in (channel, '*'):
+ print(line.decode('utf-8'))
sys.stdout.flush()
# first do a ceph status
if ret:
print("status query failed: ", outs, file=sys.stderr)
return ret
- print(outbuf)
+ print(outbuf.decode('utf-8'))
# this instance keeps the watch connection alive, but is
# otherwise unused
if parsed_args.input_file:
try:
if parsed_args.input_file == '-':
- inbuf = sys.stdin.read()
+ inbuf = sys.stdin.buffer.read()
else:
with open(parsed_args.input_file, 'rb') as f:
inbuf = f.read()
if parsed_args.output_file:
try:
if parsed_args.output_file == '-':
- outf = sys.stdout
+ outf = sys.stdout.buffer
else:
outf = open(parsed_args.output_file, 'wb')
except Exception as e:
if ret:
where = '{0}.{1}'.format(*target)
if ret > 0:
- raise RuntimeError('Unexpeceted return code from {0}: {1}'.
+ raise RuntimeError('Unexpected return code from {0}: {1}'.
format(where, ret))
outs = 'problem getting command descriptions from {0}'.format(where)
else:
errno.errorcode.get(ret, 'Unknown'), outs),
file=sys.stderr)
- if ret < 0:
- ret = -ret
- errstr = errno.errorcode.get(ret, 'Unknown')
- print(u'Error {0}: {1}'.format(errstr, outs), file=sys.stderr)
- if len(targets) > 1:
- final_ret = ret
- else:
- return ret
-
- if outs:
- print(prefix + outs, file=sys.stderr)
-
sys.stdout.flush()
if parsed_args.output_file:
except IOError as e:
if e.errno != errno.EPIPE:
raise e
+ final_e = None
+ try:
+ sys.stdout.flush()
+ except IOError as e:
+ if e.errno != errno.EPIPE:
+ final_e = e
- sys.stdout.flush()
+ if ret < 0:
+ ret = -ret
+ errstr = errno.errorcode.get(ret, 'Unknown')
+ print('Error {0}: {1}'.format(errstr, outs), file=sys.stderr)
+ final_ret = ret
+ elif outs:
+ print(prefix + outs, file=sys.stderr)
+
+ if final_e:
+ raise final_e
+
+ # Block until command completion (currently scrub and deep_scrub only)
+ if block:
+ wait(childargs, waitdata)
if parsed_args.output_file and parsed_args.output_file != '-':
outf.close()
return 0
if __name__ == '__main__':
- retval = main()
- # shutdown explicitly; Rados() does not
- if cluster_handle:
- run_in_thread(cluster_handle.shutdown)
- sys.exit(retval)
+ try:
+ retval = main()
+ # shutdown explicitly; Rados() does not
+ if retval == 0 and cluster_handle:
+ run_in_thread(cluster_handle.shutdown)
+ except KeyboardInterrupt:
+ print('Interrupted')
+ retval = errno.EINTR
+
+ if retval:
+ # flush explicitly because we aren't exiting in the usual way
+ sys.stdout.flush()
+ sys.stderr.flush()
+ os._exit(retval)
+ else:
+ sys.exit(retval)