3 # vim: ts=4 sw=4 smarttab expandtab
5 # Processed in Makefile to add python #! line and version variable
11 ceph.in becomes ceph, the command-line management tool for Ceph clusters.
12 This is a replacement for tools/ceph.cc and tools/common.cc.
14 Copyright (C) 2013 Inktank Storage, Inc.
16 This is free software; you can redistribute it and/or
17 modify it under the terms of the GNU General Public
18 License version 2, as published by the Free Software
19 Foundation. See file COPYING.
22 from time import sleep
37 CEPH_GIT_VER = "@CEPH_GIT_VER@"
38 CEPH_GIT_NICE_VER = "@CEPH_GIT_NICE_VER@"
39 CEPH_RELEASE = "@CEPH_RELEASE@"
40 CEPH_RELEASE_NAME = "@CEPH_RELEASE_NAME@"
41 CEPH_RELEASE_TYPE = "@CEPH_RELEASE_TYPE@"
43 # priorities from src/common/perf_counters.h
47 PRIO_UNINTERESTING = 2
50 PRIO_DEFAULT = PRIO_INTERESTING
52 # Make life easier on developers:
53 # If our parent dir contains CMakeCache.txt and bin/init-ceph,
54 # assume we're running from a build dir (i.e. src/build/bin/ceph)
55 # and tweak sys.path and LD_LIBRARY_PATH to use built files.
56 # Since this involves re-execing, if CEPH_DBG is set in the environment
57 # re-exec with -mpdb. Also, if CEPH_DEV is in the env, suppress
58 # the warning message about the DEVELOPER MODE.
60 MYPATH = os.path.abspath(__file__)
61 MYDIR = os.path.dirname(MYPATH)
62 MYPDIR = os.path.dirname(MYDIR)
63 DEVMODEMSG = '*** DEVELOPER MODE: setting PATH, PYTHONPATH and LD_LIBRARY_PATH ***'
66 def respawn_in_path(lib_path, pybind_path, pythonlib_path, asan_lib_path):
68 if 'CEPH_DBG' in os.environ:
69 execv_cmd += ['@Python3_EXECUTABLE@', '-mpdb']
71 if platform.system() == "Darwin":
72 lib_path_var = "DYLD_LIBRARY_PATH"
74 lib_path_var = "LD_LIBRARY_PATH"
78 os.environ['LD_PRELOAD'] = asan_lib_path
79 if lib_path_var in os.environ:
80 if lib_path not in os.environ[lib_path_var]:
81 os.environ[lib_path_var] += ':' + lib_path
82 if "CEPH_DEV" not in os.environ:
83 print(DEVMODEMSG, file=sys.stderr)
84 os.execvp(execv_cmd[0], execv_cmd)
86 os.environ[lib_path_var] = lib_path
87 if "CEPH_DEV" not in os.environ:
88 print(DEVMODEMSG, file=sys.stderr)
89 os.execvp(execv_cmd[0], execv_cmd)
90 sys.path.insert(0, pybind_path)
91 sys.path.insert(0, pythonlib_path)
94 def get_pythonlib_dir():
95 """Returns the name of a distutils build directory"""
96 return "lib.{version[0]}".format(version=sys.version_info)
99 def get_cmake_variables(*names):
100 vars = dict((name, None) for name in names)
101 for line in open(os.path.join(MYPDIR, "CMakeCache.txt")):
102 # parse lines like "WITH_ASAN:BOOL=ON"
104 if line.startswith("{}:".format(name)):
105 type_value = line.split(":")[1].strip()
106 t, v = type_value.split("=")
108 v = v.upper() in ('TRUE', '1', 'Y', 'YES', 'ON')
111 if all(vars.values()):
113 return [vars[name] for name in names]
116 if os.path.exists(os.path.join(MYPDIR, "CMakeCache.txt")) \
117 and os.path.exists(os.path.join(MYPDIR, "bin/init-ceph")):
118 src_path, with_asan, asan_lib_path = \
119 get_cmake_variables("ceph_SOURCE_DIR", "WITH_ASAN", "ASAN_LIBRARY")
121 # Huh, maybe we're not really in a cmake environment?
124 # Developer mode, but in a cmake build dir instead of the src dir
125 lib_path = os.path.join(MYPDIR, "lib")
126 bin_path = os.path.join(MYPDIR, "bin")
127 pybind_path = os.path.join(src_path, "src", "pybind")
128 pythonlib_path = os.path.join(lib_path,
131 respawn_in_path(lib_path, pybind_path, pythonlib_path,
132 asan_lib_path if with_asan else None)
134 if 'PATH' in os.environ and bin_path not in os.environ['PATH']:
135 os.environ['PATH'] = os.pathsep.join([bin_path, os.environ['PATH']])
146 from ceph_argparse import \
147 concise_sig, descsort_key, parse_json_funcsigs, \
148 validate_command, find_cmd_target, \
149 json_command, run_in_thread, Flag
151 from ceph_daemon import admin_socket, DaemonWatcher, Termsize
153 # just a couple of globals
156 cluster_handle = None
161 sys.stdout.buffer.write(rados.cstr(buf, ''))
165 ret, outbuf, outs = json_command(cluster_handle, prefix='osd ls')
167 raise RuntimeError('Can\'t contact mon for osd list')
168 return [line.decode('utf-8') for line in outbuf.split(b'\n') if line]
172 ret, outbuf, outs = json_command(cluster_handle, prefix='mon dump',
173 argdict={'format': 'json'})
175 raise RuntimeError('Can\'t contact mon for mon list')
176 d = json.loads(outbuf.decode('utf-8'))
177 return [m['name'] for m in d['mons']]
181 ret, outbuf, outs = json_command(cluster_handle, prefix='fs dump',
182 argdict={'format': 'json'})
184 raise RuntimeError('Can\'t contact mon for mds list')
185 d = json.loads(outbuf.decode('utf-8'))
187 for info in d['standbys']:
188 l.append(info['name'])
189 for fs in d['filesystems']:
190 for info in fs['mdsmap']['info'].values():
191 l.append(info['name'])
196 ret, outbuf, outs = json_command(cluster_handle, prefix='mgr dump',
197 argdict={'format': 'json'})
199 raise RuntimeError('Can\'t contact mon for mgr list')
201 d = json.loads(outbuf.decode('utf-8'))
203 l.append(d['active_name'])
204 # we can only send tell commands to the active mgr
205 #for i in d['standbys']:
206 # l.append(i['name'])
210 def ids_by_service(service):
211 ids = {"mon": monids,
215 return ids[service]()
218 def validate_target(target):
220 this function will return true iff target is a correct
221 target, such as mon.a/osd.2/mds.a/mgr.
223 target: array, likes ['osd', '2']
224 return: bool, or raise RuntimeError
228 # for case "service.id"
229 service_name, service_id = target[0], target[1]
231 exist_ids = ids_by_service(service_name)
233 print('WARN: {0} is not a legal service name, should be one of mon/osd/mds/mgr'.format(service_name),
237 if service_id in exist_ids or len(exist_ids) > 0 and service_id == '*':
240 print('WARN: the service id you provided does not exist. service id should '
241 'be one of {0}.'.format('/'.join(exist_ids)), file=sys.stderr)
244 elif len(target) == 1 and target[0] in ['mgr', 'mon']:
247 print('WARN: \"{0}\" is not a legal target. it should be one of mon.<id>/osd.<int>/mds.<id>/mgr'.format('.'.join(target)), file=sys.stderr)
251 # these args must be passed to all child programs
254 'client_name': '--name',
255 'cluster': '--cluster',
256 'cephconf': '--conf',
260 def parse_cmdargs(args=None, target=''):
262 Consume generic arguments from the start of the ``args``
263 list. Call this first to handle arguments that are not
264 handled by a command description provided by the server.
266 :returns: three tuple of ArgumentParser instance, Namespace instance
267 containing parsed values, and list of un-handled arguments
269 # alias: let the line-wrapping be sane
270 AP = argparse.ArgumentParser
272 # format our own help
273 parser = AP(description='Ceph administration tool', add_help=False)
275 parser.add_argument('--completion', action='store_true',
276 help=argparse.SUPPRESS)
278 parser.add_argument('-h', '--help', help='request mon help',
281 parser.add_argument('-c', '--conf', dest='cephconf',
282 help='ceph configuration file')
283 parser.add_argument('-i', '--in-file', dest='input_file',
284 help='input file, or "-" for stdin')
285 parser.add_argument('-o', '--out-file', dest='output_file',
286 help='output file, or "-" for stdout')
287 parser.add_argument('--setuser', dest='setuser',
288 help='set user file permission')
289 parser.add_argument('--setgroup', dest='setgroup',
290 help='set group file permission')
291 parser.add_argument('--id', '--user', dest='client_id',
292 help='client id for authentication')
293 parser.add_argument('--name', '-n', dest='client_name',
294 help='client name for authentication')
295 parser.add_argument('--cluster', help='cluster name')
297 parser.add_argument('--admin-daemon', dest='admin_socket',
298 help='submit admin-socket commands (\"help\" for help)')
300 parser.add_argument('-s', '--status', action='store_true',
301 help='show cluster status')
303 parser.add_argument('-w', '--watch', action='store_true',
304 help='watch live cluster changes')
305 parser.add_argument('--watch-debug', action='store_true',
306 help='watch debug events')
307 parser.add_argument('--watch-info', action='store_true',
308 help='watch info events')
309 parser.add_argument('--watch-sec', action='store_true',
310 help='watch security events')
311 parser.add_argument('--watch-warn', action='store_true',
312 help='watch warn events')
313 parser.add_argument('--watch-error', action='store_true',
314 help='watch error events')
316 parser.add_argument('-W', '--watch-channel', dest="watch_channel",
317 help="watch live cluster changes on a specific channel "
318 "(e.g., cluster, audit, cephadm, or '*' for all)")
320 parser.add_argument('--version', '-v', action="store_true", help="display version")
321 parser.add_argument('--verbose', action="store_true", help="make verbose")
322 parser.add_argument('--concise', dest='verbose', action="store_false",
323 help="make less verbose")
325 parser.add_argument('-f', '--format', choices=['json', 'json-pretty',
326 'xml', 'xml-pretty', 'plain', 'yaml'], dest='output_format')
328 parser.add_argument('--connect-timeout', dest='cluster_timeout',
330 help='set a timeout for connecting to the cluster')
332 parser.add_argument('--block', action='store_true',
333 help='block until completion (scrub and deep-scrub only)')
334 parser.add_argument('--period', '-p', default=1, type=float,
335 help='polling period, default 1.0 second (for ' \
336 'polling commands only)')
338 # returns a Namespace with the parsed args, and a list of all extras
339 parsed_args, extras = parser.parse_known_args(args)
341 return parser, parsed_args, extras
345 print('\n', s, '\n', '=' * len(s))
348 def do_basic_help(parser, args):
350 Print basic parser help
351 If the cluster is available, get and print monitor help
353 hdr('General usage:')
355 print_locally_handled_command_help()
358 def print_locally_handled_command_help():
359 hdr("Local commands:")
361 ping <mon.id> Send simple presence/life test to a mon
362 <mon.id> may be 'mon.*' for all mons
363 daemon {type.id|path} <cmd>
364 Same as --admin-daemon, but auto-find admin socket
365 daemonperf {type.id | path} [stat-pats] [priority] [<interval>] [<count>]
366 daemonperf {type.id | path} list|ls [stat-pats] [priority]
367 Get selected perf stats from daemon/admin socket
368 Optional shell-glob comma-delim match string stat-pats
369 Optional selection priority (can abbreviate name):
370 critical, interesting, useful, noninteresting, debug
371 List shows a table of all available stats
372 Run <count> times (default forever),
373 once per <interval> seconds (default 1)
374 """, file=sys.stdout)
377 def do_extended_help(parser, args, target, partial):
378 def help_for_sigs(sigs, partial=None):
380 sys.stdout.write(format_help(parse_json_funcsigs(sigs, 'cli'),
382 except BrokenPipeError:
385 def help_for_target(target, partial=None):
386 # wait for osdmap because we know this is sent after the mgrmap
387 # and monmap (it's alphabetical).
388 cluster_handle.wait_for_latest_osdmap()
389 ret, outbuf, outs = json_command(cluster_handle, target=target,
390 prefix='get_command_descriptions',
393 if (ret == -errno.EPERM or ret == -errno.EACCES) and target[0] in ('osd', 'mds'):
394 print("Permission denied. Check that your user has 'allow *' "
395 "capabilities for the target daemon type.", file=sys.stderr)
396 elif ret == -errno.EPERM:
397 print("Permission denied. Check your user has proper "
398 "capabilities configured", file=sys.stderr)
400 print("couldn't get command descriptions for {0}: {1} ({2})".
401 format(target, outs, ret), file=sys.stderr)
404 return help_for_sigs(outbuf.decode('utf-8'), partial)
406 assert(cluster_handle.state == "connected")
407 return help_for_target(target, partial)
409 DONTSPLIT = string.ascii_letters + '{[<>]}'
412 def wrap(s, width, indent):
414 generator to transform s into a sequence of strings width or shorter,
415 for wrapping text to a specific column width.
416 Attempt to break on anything but DONTSPLIT characters.
417 indent is amount to indent 2nd-through-nth lines.
419 so "long string long string long string" width=11 indent=1 becomes
420 'long string', ' long string', ' long string' so that it can be printed
433 # no splitting; just possibly indent
440 while (splitpos > 0) and (s[splitpos-1] in DONTSPLIT):
447 # prior result means we're mid-iteration, indent
450 # first time, set leader and width for next
451 leader = ' ' * indent
452 width -= 1 # for subsequent space additions
454 # remove any leading spaces in this chunk of s
455 result += s[:splitpos].lstrip()
461 def format_help(cmddict, partial=None):
463 Formats all the cmdsigs and helptexts from cmddict into a sorted-by-
464 cmdsig 2-column display, with each column wrapped and indented to
465 fit into (terminal_width / 2) characters.
469 for cmd in sorted(cmddict.values(), key=descsort_key):
473 flags = cmd.get('flags', 0)
474 if flags & (Flag.OBSOLETE | Flag.DEPRECATED | Flag.HIDDEN):
476 concise = concise_sig(cmd['sig'])
477 if partial and not concise.startswith(partial):
479 width = Termsize().cols - 1 # 1 for the line between sig and help
480 sig_width = int(width / 2)
481 # make sure width == sig_width + help_width, even (width % 2 > 0)
482 help_width = int(width / 2) + (width % 2)
483 siglines = [l for l in wrap(concise, sig_width, 1)]
484 helplines = [l for l in wrap(cmd['help'], help_width, 1)]
486 # make lists the same length
487 maxlen = max(len(siglines), len(helplines))
488 siglines.extend([''] * (maxlen - len(siglines)))
489 helplines.extend([''] * (maxlen - len(helplines)))
491 # so we can zip them for output
492 for s, h in zip(siglines, helplines):
493 fullusage += '{s:{w}s} {h}\n'.format(s=s, h=h, w=sig_width)
498 def ceph_conf(parsed_args, field, name):
500 bindir = os.path.dirname(__file__)
501 if shutil.which(cmd):
503 elif shutil.which(cmd, path=bindir):
504 args = [os.path.join(bindir, cmd)]
506 raise RuntimeError('"ceph-conf" not found')
509 args.extend(['--name', name])
511 # add any args in GLOBAL_ARGS
512 for key, val in GLOBAL_ARGS.items():
513 # ignore name in favor of argument name, if any
514 if name and key == 'client_name':
516 if getattr(parsed_args, key):
517 args.extend([val, getattr(parsed_args, key)])
519 args.extend(['--show-config-value', field])
520 p = subprocess.Popen(
522 stdout=subprocess.PIPE,
523 stderr=subprocess.PIPE)
524 outdata, errdata = p.communicate()
525 if p.returncode != 0:
526 raise RuntimeError('unable to get conf option %s for %s: %s' % (field, name, errdata))
527 return outdata.rstrip()
531 if sys.stdin.isatty():
534 line = input(PROMPT).rstrip()
535 if line in ['q', 'quit', 'Q', 'exit']:
542 line = sys.stdin.readline()
550 def do_command(parsed_args, target, cmdargs, sigdict, inbuf, verbose):
551 ''' Validate a command, and handle the polling flag '''
553 valid_dict = validate_command(sigdict, cmdargs, verbose)
554 # Validate input args against list of sigs
556 if parsed_args.output_format:
557 valid_dict['format'] = parsed_args.output_format
559 print("Submitting command: ", valid_dict, file=sys.stderr)
561 return -errno.EINVAL, '', 'invalid command'
563 next_header_print = 0
564 # Set extra options for polling commands only:
565 if valid_dict.get('poll', False):
566 valid_dict['width'] = Termsize().cols
569 # Only print the header for polling commands
570 if next_header_print == 0 and valid_dict.get('poll', False):
571 valid_dict['print_header'] = True
572 next_header_print = Termsize().rows - 3
573 next_header_print -= 1
574 ret, outbuf, outs = json_command(cluster_handle, target=target,
575 argdict=valid_dict, inbuf=inbuf, verbose=verbose)
576 if valid_dict.get('poll', False):
577 valid_dict['print_header'] = False
578 if not valid_dict.get('poll', False):
579 # Don't print here if it's not a polling command
583 print('Error: {0} {1}'.format(ret, errno.errorcode.get(ret, 'Unknown')),
587 print(outbuf.decode('utf-8'))
589 print(outs, file=sys.stderr)
590 if parsed_args.period <= 0:
592 sleep(parsed_args.period)
593 except KeyboardInterrupt:
595 return errno.EINTR, '', ''
596 if ret == errno.ETIMEDOUT:
599 outs = ("Connection timed out. Please check the client's " +
600 "permission and connection.")
601 return ret, outbuf, outs
604 def new_style_command(parsed_args, cmdargs, target, sigdict, inbuf, verbose):
606 Do new-style command dance.
607 target: daemon to receive command: mon (any) or osd.N
608 sigdict - the parsed output from the new monitor describing commands
609 inbuf - any -i input file data
613 for cmdtag in sorted(sigdict.keys()):
614 cmd = sigdict[cmdtag]
616 print('{0}: {1}'.format(cmdtag, concise_sig(sig)))
620 # Non interactive mode
621 ret, outbuf, outs = do_command(parsed_args, target, cmdargs, sigdict, inbuf, verbose)
623 # Interactive mode (ceph cli)
624 if sys.stdin.isatty():
625 # do the command-interpreter looping
626 # for input to do readline cmd editing
627 import readline # noqa
631 interactive_input = read_input()
633 # leave user an uncluttered prompt
635 if interactive_input is None:
637 cmdargs = parse_cmdargs(shlex.split(interactive_input))[2]
639 target = find_cmd_target(cmdargs)
640 except Exception as e:
641 print('error handling command target: {0}'.format(e),
644 if len(cmdargs) and cmdargs[0] == 'tell':
645 print('Can not use \'tell\' in interactive mode.',
648 ret, outbuf, outs = do_command(parsed_args, target, cmdargs,
649 sigdict, inbuf, verbose)
652 errstr = errno.errorcode.get(ret, 'Unknown')
653 print('Error {0}: {1}'.format(errstr, outs), file=sys.stderr)
656 print(outs, file=sys.stderr)
658 print(outbuf.decode('utf-8'))
660 return ret, outbuf, outs
663 def complete(sigdict, args, target):
665 Command completion. Match as much of [args] as possible,
666 and print every possible match separated by newlines.
669 # XXX this looks a lot like the front of validate_command(). Refactor?
671 # Repulsive hack to handle tell: lop off 'tell' and target
672 # and validate the rest of the command. 'target' is already
673 # determined in our callers, so it's ok to remove it here.
674 if len(args) and args[0] == 'tell':
676 # look for best match, accumulate possibles in bestcmds
677 # (so we can maybe give a more-useful error message)
681 for cmdtag, cmd in sigdict.items():
682 flags = cmd.get('flags', 0)
683 if flags & (Flag.OBSOLETE | Flag.HIDDEN):
687 # iterate over all arguments, except last one
688 for arg in args[0:-1]:
690 # an out of argument definitions
692 found_match = arg in sig[j].complete(arg)
693 if not found_match and sig[j].req:
694 # no elements that match
699 # successfully matched all - except last one - arguments
700 if j < len(sig) and len(args) > 0:
701 comps += sig[j].complete(args[-1])
706 if match_count == 1 and len(comps) == 0:
707 # only one command matched and no hints yet => add help
708 comps = comps + [' ', '#'+match_cmd['help']]
709 print('\n'.join(sorted(set(comps))))
713 def ping_monitor(cluster_handle, name, timeout):
714 if 'mon.' not in name:
715 print('"ping" expects a monitor to ping; try "ping mon.<id>"', file=sys.stderr)
718 mon_id = name[len('mon.'):]
720 run_in_thread(cluster_handle.connect, timeout=timeout)
722 s = run_in_thread(cluster_handle.ping_monitor, m)
724 print("mon.{0}".format(m) + '\n' + "Error connecting to monitor.")
726 print("mon.{0}".format(m) + '\n' + s)
728 s = run_in_thread(cluster_handle.ping_monitor, mon_id)
733 def maybe_daemon_command(parsed_args, childargs):
735 Check if --admin-socket, daemon, or daemonperf command
736 if it is, returns (boolean handled, return code if handled == True)
741 if parsed_args.admin_socket:
742 sockpath = parsed_args.admin_socket
743 elif len(childargs) > 0 and childargs[0] in ["daemon", "daemonperf"]:
744 daemon_perf = (childargs[0] == "daemonperf")
745 # Treat "daemon <path>" or "daemon <name>" like --admin_daemon <path>
746 # Handle "daemonperf <path>" the same but requires no trailing args
747 require_args = 2 if daemon_perf else 3
748 if len(childargs) >= require_args:
749 if childargs[1].find('/') >= 0:
750 sockpath = childargs[1]
752 # try resolve daemon name
754 sockpath = ceph_conf(parsed_args, 'admin_socket',
756 except Exception as e:
757 print('Can\'t get admin socket path: ' + str(e), file=sys.stderr)
758 return True, errno.EINVAL
760 childargs = childargs[2:]
762 print('{0} requires at least {1} arguments'.format(childargs[0], require_args),
764 return True, errno.EINVAL
766 if sockpath and daemon_perf:
767 return True, daemonperf(childargs, sockpath)
770 raw_write(admin_socket(sockpath, childargs, parsed_args.output_format))
771 except Exception as e:
772 print('admin_socket: {0}'.format(e), file=sys.stderr)
773 return True, errno.EINVAL
787 def daemonperf(childargs, sockpath):
789 Handle daemonperf command; returns errno or 0
791 daemonperf <daemon> [priority string] [statpats] [interval] [count]
792 daemonperf <daemon> list|ls [statpats]
801 def prio_from_name(arg):
804 'critical': PRIO_CRITICAL,
805 'interesting': PRIO_INTERESTING,
806 'useful': PRIO_USEFUL,
807 'uninteresting': PRIO_UNINTERESTING,
808 'debugonly': PRIO_DEBUGONLY,
814 for name, val in PRIOMAP.items():
815 if name.startswith(arg):
819 # consume and analyze non-numeric args
820 while len(childargs) and not isnum(childargs[0]):
821 arg = childargs.pop(0)
823 if arg in ['list', 'ls']:
827 prio = prio_from_name(arg)
832 statpats = arg.split(',')
835 priority = PRIO_DEFAULT
837 if len(childargs) > 0:
839 interval = float(childargs.pop(0))
843 print('daemonperf: interval should be a positive number', file=sys.stderr)
846 if len(childargs) > 0:
847 arg = childargs.pop(0)
848 if (not isnum(arg)) or (int(arg) < 0):
849 print('daemonperf: count should be a positive integer', file=sys.stderr)
853 watcher = DaemonWatcher(sockpath, statpats, priority)
857 watcher.run(interval, count)
861 def get_scrub_timestamps(childargs):
862 last_scrub_stamp = "last_" + childargs[1].replace('-', '_') + "_stamp"
865 if childargs[2] in ['all', 'any', '*']:
867 devnull = open(os.devnull, 'w')
868 out = subprocess.check_output(['ceph', 'pg', 'dump', '--format=json-pretty'],
871 pgstats = json.loads(out)['pg_map']['pg_stats']
873 pgstats = json.loads(out)['pg_stats']
875 if scruball or stat['up_primary'] == int(childargs[2]):
876 scrub_tuple = (stat['up_primary'], stat[last_scrub_stamp])
877 results[stat['pgid']] = scrub_tuple
880 def check_scrub_stamps(waitdata, currdata):
881 for pg in waitdata.keys():
882 # Try to handle the case where a pg may not exist in current results
883 if pg in currdata and waitdata[pg][1] == currdata[pg][1]:
887 def waitscrub(childargs, waitdata):
888 print('Waiting for {0} to complete...'.format(childargs[1]), file=sys.stdout)
889 currdata = get_scrub_timestamps(childargs)
890 while not check_scrub_stamps(waitdata, currdata):
892 currdata = get_scrub_timestamps(childargs)
893 print('{0} completed'.format(childargs[1]), file=sys.stdout)
895 def wait(childargs, waitdata):
896 if childargs[1] in ['scrub', 'deep-scrub']:
897 waitscrub(childargs, waitdata)
901 ceph_args = os.environ.get('CEPH_ARGS')
903 if "injectargs" in sys.argv:
904 i = sys.argv.index("injectargs")
905 sys.argv = sys.argv[:i] + ceph_args.split() + sys.argv[i:]
907 sys.argv.extend([arg for arg in ceph_args.split()
908 if '--admin-socket' not in arg])
909 parser, parsed_args, childargs = parse_cmdargs()
911 if parsed_args.version:
912 print('ceph version {0} ({1}) {2} ({3})'.format(
916 CEPH_RELEASE_TYPE)) # noqa
919 # --watch-channel|-W implies -w
920 if parsed_args.watch_channel:
921 parsed_args.watch = True
922 elif parsed_args.watch and not parsed_args.watch_channel:
923 parsed_args.watch_channel = 'cluster'
926 verbose = parsed_args.verbose
929 print("parsed_args: {0}, childargs: {1}".format(parsed_args, childargs), file=sys.stderr)
931 # pass on --id, --name, --conf
932 name = 'client.admin'
933 if parsed_args.client_id:
934 name = 'client.' + parsed_args.client_id
935 if parsed_args.client_name:
936 name = parsed_args.client_name
938 # default '' means default conf search
940 if parsed_args.cephconf:
941 conffile = parsed_args.cephconf
942 # For now, --admin-daemon is handled as usual. Try it
943 # first in case we can't connect() to the cluster
945 done, ret = maybe_daemon_command(parsed_args, childargs)
950 if parsed_args.cluster_timeout:
951 timeout = parsed_args.cluster_timeout
955 do_basic_help(parser, childargs)
957 # handle any 'generic' ceph arguments that we didn't parse here
958 global cluster_handle
960 # rados.Rados() will call rados_create2, and then read the conf file,
961 # and then set the keys from the dict. So we must do these
962 # "pre-file defaults" first (see common_preinit in librados)
964 'log_to_stderr': 'true',
965 'err_to_stderr': 'true',
966 'log_flush_on_exit': 'true',
969 if 'injectargs' in childargs:
970 position = childargs.index('injectargs')
971 injectargs = childargs[position:]
972 childargs = childargs[:position]
974 print('Separate childargs {0} from injectargs {1}'.format(childargs, injectargs),
980 if parsed_args.cluster:
981 clustername = parsed_args.cluster
984 cluster_handle = run_in_thread(rados.Rados,
985 name=name, clustername=clustername,
986 conf_defaults=conf_defaults,
988 retargs = run_in_thread(cluster_handle.conf_parse_argv, childargs)
989 except rados.Error as e:
990 print('Error initializing cluster client: {0!r}'.format(e), file=sys.stderr)
997 # -- means "stop parsing args", but we don't want to see it either
998 if '--' in childargs:
999 childargs.remove('--')
1000 if injectargs and '--' in injectargs:
1001 injectargs.remove('--')
1005 if parsed_args.block:
1006 if (len(childargs) >= 2 and
1007 childargs[0] == 'osd' and
1008 childargs[1] in ['deep-scrub', 'scrub']):
1010 waitdata = get_scrub_timestamps(childargs)
1012 if parsed_args.help:
1013 # short default timeout for -h
1017 if childargs and childargs[0] == 'ping' and not parsed_args.help:
1018 if len(childargs) < 2:
1019 print('"ping" requires a monitor name as argument: "ping mon.<id>"', file=sys.stderr)
1021 if parsed_args.completion:
1022 # for completion let timeout be really small
1025 if childargs and childargs[0] == 'ping' and not parsed_args.help:
1026 return ping_monitor(cluster_handle, childargs[1], timeout)
1027 result = run_in_thread(cluster_handle.connect, timeout=timeout)
1028 if type(result) is tuple and result[0] == -errno.EINTR:
1029 print('Cluster connection interrupted or timed out', file=sys.stderr)
1031 except KeyboardInterrupt:
1032 print('Cluster connection aborted', file=sys.stderr)
1034 except rados.PermissionDeniedError as e:
1035 print(str(e), file=sys.stderr)
1037 except Exception as e:
1038 print(str(e), file=sys.stderr)
1041 if parsed_args.help:
1043 if len(childargs) >= 2 and childargs[0] == 'tell':
1044 target = childargs[1].split('.', 1)
1045 if not validate_target(target):
1046 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)
1048 childargs = childargs[2:]
1049 hdr('Tell %s commands:' % target[0])
1051 hdr('Monitor commands:')
1052 target = ('mon', '')
1054 print('[Contacting monitor, timeout after %d seconds]' % timeout)
1056 return do_extended_help(parser, childargs, target, ' '.join(childargs))
1058 # implement "tell service.id help"
1059 if len(childargs) >= 3 and childargs[0] == 'tell' and childargs[2] == 'help':
1060 target = childargs[1].split('.', 1)
1061 if validate_target(target):
1062 hdr('Tell %s commands' % target[0])
1063 return do_extended_help(parser, childargs, target, None)
1065 print('target {0} doesn\'t exists, please pass correct target to tell command, such as mon.a/'
1066 'osd.1/mds.a/mgr'.format(childargs[1]), file=sys.stderr)
1069 # implement -w/--watch_*
1070 # This is ugly, but Namespace() isn't quite rich enough.
1072 for k, v in parsed_args._get_kwargs():
1073 if k.startswith('watch') and v:
1076 elif k != "watch_channel":
1077 level = k.replace('watch_', '')
1079 # an awfully simple callback
1080 def watch_cb(arg, line, channel, name, who, stamp_sec, stamp_nsec, seq, level, msg):
1082 channel = channel.decode('utf-8')
1083 if parsed_args.watch_channel in (channel, '*'):
1084 print(line.decode('utf-8'))
1087 # first do a ceph status
1088 ret, outbuf, outs = json_command(cluster_handle, prefix='status')
1090 print("status query failed: ", outs, file=sys.stderr)
1092 print(outbuf.decode('utf-8'))
1094 # this instance keeps the watch connection alive, but is
1096 run_in_thread(cluster_handle.monitor_log2, level, watch_cb, 0)
1098 # loop forever letting watch_cb print lines
1101 except KeyboardInterrupt:
1102 # or until ^C, at least
1105 # read input file, if any
1107 if parsed_args.input_file:
1109 if parsed_args.input_file == '-':
1110 inbuf = sys.stdin.buffer.read()
1112 with open(parsed_args.input_file, 'rb') as f:
1114 except Exception as e:
1115 print('Can\'t open input file {0}: {1}'.format(parsed_args.input_file, e), file=sys.stderr)
1118 # prepare output file, if any
1119 if parsed_args.output_file:
1121 if parsed_args.output_file == '-':
1122 outf = sys.stdout.buffer
1124 outf = open(parsed_args.output_file, 'wb')
1125 except Exception as e:
1126 print('Can\'t open output file {0}: {1}'.format(parsed_args.output_file, e), file=sys.stderr)
1128 if parsed_args.setuser:
1130 ownerid = pwd.getpwnam(parsed_args.setuser).pw_uid
1131 os.fchown(outf.fileno(), ownerid, -1)
1132 except OSError as e:
1133 print('Failed to change user ownership of {0} to {1}: {2}'.format(outf, parsed_args.setuser, e))
1135 if parsed_args.setgroup:
1137 groupid = grp.getgrnam(parsed_args.setgroup).gr_gid
1138 os.fchown(outf.fileno(), -1, groupid)
1139 except OSError as e:
1140 print('Failed to change group ownership of {0} to {1}: {2}'.format(outf, parsed_args.setgroup, e))
1143 # -s behaves like a command (ceph status).
1144 if parsed_args.status:
1145 childargs.insert(0, 'status')
1148 target = find_cmd_target(childargs)
1149 except Exception as e:
1150 print('error handling command target: {0}'.format(e), file=sys.stderr)
1153 # Repulsive hack to handle tell: lop off 'tell' and target
1154 # and validate the rest of the command. 'target' is already
1155 # determined in our callers, so it's ok to remove it here.
1157 if len(childargs) and childargs[0] == 'tell':
1158 childargs = childargs[2:]
1163 childargs = injectargs
1164 if not len(childargs):
1165 print('"{0} tell" requires additional arguments.'.format(sys.argv[0]),
1166 'Try "{0} tell <name> <command> [options...]" instead.'.format(sys.argv[0]),
1170 # fetch JSON sigs from command
1171 # each line contains one command signature (a placeholder name
1172 # of the form 'cmdNNN' followed by an array of argument descriptors)
1173 # as part of the validated argument JSON object
1175 if target[1] == '*':
1177 targets = [(service, o) for o in ids_by_service(service)]
1182 for target in targets:
1183 # prettify? prefix output with target, if there was a wildcard used
1186 if not parsed_args.output_file and len(targets) > 1:
1187 prefix = '{0}.{1}: '.format(*target)
1190 ret, outbuf, outs = json_command(cluster_handle, target=target,
1191 prefix='get_command_descriptions')
1193 where = '{0}.{1}'.format(*target)
1195 raise RuntimeError('Unexpected return code from {0}: {1}'.
1197 outs = 'problem getting command descriptions from {0}'.format(where)
1199 sigdict = parse_json_funcsigs(outbuf.decode('utf-8'), 'cli')
1201 if parsed_args.completion:
1202 return complete(sigdict, childargs, target)
1204 ret, outbuf, outs = new_style_command(parsed_args, childargs,
1205 target, sigdict, inbuf,
1208 # debug tool: send any successful command *again* to
1209 # verify that it is idempotent.
1210 if not ret and 'CEPH_CLI_TEST_DUP_COMMAND' in os.environ:
1211 ret, outbuf, outs = new_style_command(parsed_args, childargs,
1212 target, sigdict, inbuf,
1217 'Second attempt of previously successful command '
1218 'failed with {0}: {1}'.format(
1219 errno.errorcode.get(ret, 'Unknown'), outs),
1224 errstr = errno.errorcode.get(ret, 'Unknown')
1225 print('Error {0}: {1}'.format(errstr, outs), file=sys.stderr)
1226 if len(targets) > 1:
1232 print(prefix + outs, file=sys.stderr)
1236 if parsed_args.output_file:
1239 # hack: old code printed status line before many json outputs
1240 # (osd dump, etc.) that consumers know to ignore. Add blank line
1241 # to satisfy consumers that skip the first line, but not annoy
1242 # consumers that don't.
1243 if parsed_args.output_format and \
1244 parsed_args.output_format.startswith('json'):
1247 # if we are prettifying things, normalize newlines. sigh.
1249 outbuf = outbuf.rstrip()
1252 print(prefix, end='')
1253 # Write directly to binary stdout
1255 print(suffix, end='')
1256 except IOError as e:
1257 if e.errno != errno.EPIPE:
1261 except IOError as e:
1262 if e.errno != errno.EPIPE:
1266 # Block until command completion (currently scrub and deep_scrub only)
1268 wait(childargs, waitdata)
1270 if parsed_args.output_file and parsed_args.output_file != '-':
1278 if __name__ == '__main__':
1281 # shutdown explicitly; Rados() does not
1282 if retval == 0 and cluster_handle:
1283 run_in_thread(cluster_handle.shutdown)
1284 except KeyboardInterrupt:
1285 print('Interrupted')
1286 retval = errno.EINTR
1289 # flush explicitly because we aren't exiting in the usual way