def test_parse_json_funcsigs(): commands = get_command_descriptions("all") cmd_json = parse_json_funcsigs(commands, 'cli') # syntax error https://github.com/ceph/ceph/pull/585 commands = get_command_descriptions("pull585") assert_raises(TypeError, parse_json_funcsigs, commands, 'cli')
def admin_socket(asok_path, cmd, fmt=''): """Send a daemon (--admin-daemon) command 'cmd'. asok_path is the path to the admin socket; cmd is a list of strings """ from ceph_argparse import parse_json_funcsigs from ceph_argparse import validate_command def do_sockio(path, cmd): """helper: do all the actual low-level stream I/O """ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: sock.connect(path) sock.sendall(cmd + '\0') len_str = sock.recv(4) if len(len_str) < 4: raise RuntimeError("no data returned from admin socket") l, = struct.unpack(">I", len_str) ret = '' got = 0 while got < l: bit = sock.recv(l - got) ret += bit got += len(bit) except Exception as e: raise AdminSocketError('exception: ' + str(e)) finally: sock.close() return ret try: cmd_json = do_sockio( asok_path, json.dumps({"prefix": "get_command_descriptions"})) except Exception as e: raise AdminSocketError('exception getting command descriptions: ' + str(e)) if cmd == 'get_command_descriptions': return cmd_json sigdict = parse_json_funcsigs(cmd_json, 'cli') valid_dict = validate_command(sigdict, cmd) if not valid_dict: raise AdminSocketError('invalid command') if fmt: valid_dict['format'] = fmt try: ret = do_sockio(asok_path, json.dumps(valid_dict)) except Exception as e: raise AdminSocketError('exception: ' + str(e)) return ret
def admin_socket(asok_path: str, cmd: str, format: Optional[str] = '') -> bytes: """ Send a daemon (--admin-daemon) command 'cmd'. asok_path is the path to the admin socket; cmd is a list of strings; format may be set to one of the formatted forms to get output in that form (daemon commands don't support 'plain' output). """ def do_sockio(path, cmd_bytes): """ helper: do all the actual low-level stream I/O """ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(path) try: sock.sendall(cmd_bytes + b'\0') len_str = sock.recv(4) if len(len_str) < 4: raise RuntimeError("no data returned from admin socket") l, = struct.unpack(">I", len_str) sock_ret = b'' got = 0 while got < l: # recv() receives signed int, i.e max 2GB # workaround by capping READ_CHUNK_SIZE per call. want = min(l - got, READ_CHUNK_SIZE) bit = sock.recv(want) sock_ret += bit got += len(bit) except Exception as sock_e: raise RuntimeError('exception: ' + str(sock_e)) return sock_ret try: cmd_json = do_sockio(asok_path, b'{"prefix": "get_command_descriptions"}') except Exception as e: raise RuntimeError('exception getting command descriptions: ' + str(e)) if cmd == 'get_command_descriptions': return cmd_json sigdict = parse_json_funcsigs(cmd_json.decode('utf-8'), 'cli') valid_dict = validate_command(sigdict, cmd) if not valid_dict: raise RuntimeError('invalid command') if format: valid_dict['format'] = format try: ret = do_sockio(asok_path, json.dumps(valid_dict).encode('utf-8')) except Exception as e: raise RuntimeError('exception: ' + str(e)) return ret
def admin_socket(asok_path, cmd, format=''): """ Send a daemon (--admin-daemon) command 'cmd'. asok_path is the path to the admin socket; cmd is a list of strings; format may be set to one of the formatted forms to get output in that form (daemon commands don't support 'plain' output). """ def do_sockio(path, cmd_bytes): """ helper: do all the actual low-level stream I/O """ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(path) try: sock.sendall(cmd_bytes + b'\0') len_str = sock.recv(4) if len(len_str) < 4: raise RuntimeError("no data returned from admin socket") l, = struct.unpack(">I", len_str) sock_ret = b'' got = 0 while got < l: # recv() receives signed int, i.e max 2GB # workaround by capping READ_CHUNK_SIZE per call. want = min(l - got, READ_CHUNK_SIZE) bit = sock.recv(want) sock_ret += bit got += len(bit) except Exception as sock_e: raise RuntimeError('exception: ' + str(sock_e)) return sock_ret try: cmd_json = do_sockio(asok_path, b'{"prefix": "get_command_descriptions"}') except Exception as e: raise RuntimeError('exception getting command descriptions: ' + str(e)) if cmd == 'get_command_descriptions': return cmd_json sigdict = parse_json_funcsigs(cmd_json.decode('utf-8'), 'cli') valid_dict = validate_command(sigdict, cmd) if not valid_dict: raise RuntimeError('invalid command') if format: valid_dict['format'] = format try: ret = do_sockio(asok_path, json.dumps(valid_dict).encode('utf-8')) except Exception as e: raise RuntimeError('exception: ' + str(e)) return ret
def admin_socket(asok_path, cmd, fmt=''): """ Send a daemon (--admin-daemon) command 'cmd'. asok_path is the path to the admin socket; cmd is a list of strings """ from ceph_argparse import parse_json_funcsigs, validate_command def do_sockio(path, cmd): """ helper: do all the actual low-level stream I/O """ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(path) try: sock.sendall(cmd + '\0') len_str = sock.recv(4) if len(len_str) < 4: raise RuntimeError("no data returned from admin socket") l, = struct.unpack(">I", len_str) ret = '' got = 0 while got < l: bit = sock.recv(l - got) ret += bit got += len(bit) except Exception as e: raise AdminSocketError('exception: ' + str(e)) return ret try: cmd_json = do_sockio(asok_path, json.dumps({"prefix": "get_command_descriptions"})) except Exception as e: raise AdminSocketError('exception getting command descriptions: ' + str(e)) if cmd == 'get_command_descriptions': return cmd_json sigdict = parse_json_funcsigs(cmd_json, 'cli') valid_dict = validate_command(sigdict, cmd) if not valid_dict: raise AdminSocketError('invalid command') if fmt: valid_dict['format'] = fmt try: ret = do_sockio(asok_path, json.dumps(valid_dict)) except Exception as e: raise AdminSocketError('exception: ' + str(e)) return ret
def admin_socket(asok_path, cmd, format=''): """ Send a daemon (--admin-daemon) command 'cmd'. asok_path is the path to the admin socket; cmd is a list of strings; format may be set to one of the formatted forms to get output in that form (daemon commands don't support 'plain' output). """ def do_sockio(path, cmd_bytes): """ helper: do all the actual low-level stream I/O """ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(path) try: sock.sendall(cmd_bytes + '\0') len_str = sock.recv(4) if len(len_str) < 4: raise RuntimeError("no data returned from admin socket") l, = struct.unpack(">I", len_str) sock_ret = '' got = 0 while got < l: bit = sock.recv(l - got) sock_ret += bit got += len(bit) except Exception as sock_e: raise RuntimeError('exception: ' + str(sock_e)) return sock_ret try: cmd_json = do_sockio(asok_path, json.dumps({"prefix": "get_command_descriptions"})) except Exception as e: raise RuntimeError('exception getting command descriptions: ' + str(e)) if cmd == 'get_command_descriptions': return cmd_json sigdict = parse_json_funcsigs(cmd_json, 'cli') valid_dict = validate_command(sigdict, cmd) if not valid_dict: raise RuntimeError('invalid command') if format: valid_dict['format'] = format try: ret = do_sockio(asok_path, json.dumps(valid_dict)) except Exception as e: raise RuntimeError('exception: ' + str(e)) return ret
def get_command_descriptions(cluster, target=("mon", "")): ret, outbuf, outs = json_command(cluster, target, prefix="get_command_descriptions", timeout=30) if ret: err = "Can't get command descriptions: {0}".format(outs) app.logger.error(err) raise EnvironmentError(ret, err) try: sigdict = parse_json_funcsigs(outbuf, "rest") except Exception as e: err = "Can't parse command descriptions: {}".format(e) app.logger.error(err) raise EnvironmentError(err) return sigdict
def get_command_descriptions(cluster, target=('mon', '')): ret, outbuf, outs = json_command(cluster, target, prefix='get_command_descriptions', timeout=30) if ret: err = "Can't get command descriptions: {0}".format(outs) app.logger.error(err) raise EnvironmentError(ret, err) try: sigdict = parse_json_funcsigs(outbuf, 'rest') except Exception as e: err = "Can't parse command descriptions: {}".format(e) app.logger.error(err) raise EnvironmentError(err) return sigdict
import json def get_command_descriptions(what): buffer = os.popen("./get_command_descriptions " + "--" + what + " 2>&1 | grep cmd000").read() return re.sub(r'^.*?(\{.*\})', '\g<1>', buffer) def test_parse_json_funcsigs(): commands = get_command_descriptions("all") cmd_json = parse_json_funcsigs(commands, 'cli') # syntax error https://github.com/ceph/ceph/pull/585 commands = get_command_descriptions("pull585") assert_raises(TypeError, parse_json_funcsigs, commands, 'cli') sigdict = parse_json_funcsigs(get_command_descriptions("all"), 'cli') class TestArgparse: def assert_valid_command(self, args): result = validate_command(sigdict, args) assert_not_in(result, [None, {}]) def check_1_natural_arg(self, prefix, command): self.assert_valid_command([prefix, command, '1']) assert_equal({}, validate_command(sigdict, [prefix, command])) assert_equal({}, validate_command(sigdict, [prefix, command, '-1'])) assert_equal({}, validate_command(sigdict, [prefix, command, '1', '1']))
def get_command_descriptions(what): return os.popen("./get_command_descriptions " + "--" + what).read() def test_parse_json_funcsigs(): commands = get_command_descriptions("all") cmd_json = parse_json_funcsigs(commands, 'cli') # syntax error https://github.com/ceph/ceph/pull/585 commands = get_command_descriptions("pull585") assert_raises(TypeError, parse_json_funcsigs, commands, 'cli') sigdict = parse_json_funcsigs(get_command_descriptions("all"), 'cli') class TestArgparse: def assert_valid_command(self, args): result = validate_command(sigdict, args) assert_not_equal(result, None) assert_not_equal(result, {}) def check_1_natural_arg(self, prefix, command): self.assert_valid_command([prefix, command, '1']) assert_equal({}, validate_command(sigdict, [prefix, command])) assert_equal({}, validate_command(sigdict, [prefix, command, '-1'])) assert_equal({}, validate_command(sigdict, [prefix, command, '1', '1']))
def main(): # 这里是从环境变量中拿CEPH_ARGS # 因为这里存在一种用法:比如把日志打印到stderr # export CEPH_ARGS="--log-to-stderr" ceph_args = os.environ.get('CEPH_ARGS') # 如果这里有针对ceph的参数,那么需要进行处理。 # 一般而言,这里是没有这些参数的。 if ceph_args: if "injectargs" in sys.argv: i = sys.argv.index("injectargs") sys.argv = sys.argv[:i] + ceph_args.split() + sys.argv[i:] else: sys.argv.extend(ceph_args.split()) parser, parsed_args, childargs = parse_cmdargs() # 如果只是查看版本,那么直接输出版本,然后退出。 if parsed_args.version: print 'ceph version {0} ({1})'.format(CEPH_GIT_NICE_VER, CEPH_GIT_VER) return 0 # verbose是进入一种交互式的命令行方式,类似于直接运行python。 global verbose verbose = parsed_args.verbose if verbose: print >> sys.stderr, "parsed_args: {0}, childargs: {1}".format(parsed_args, childargs) # 这里只是一个客户端工具,是不能加上--admin-socket参数来运行。 # 守护进程,比如ceph-mon,ceph-osd才可以用这种方式来运行。 if parsed_args.admin_socket_nope: print >> sys.stderr, '--admin-socket is used by daemons; '\ 'you probably mean --admin-daemon/daemon' return 1 # pass on --id, --name, --conf name = 'client.admin' # 这里获取需要认证的客户端用户名。一般是采用client.admin。 # 如果用了id,那么就直接用id。 if parsed_args.client_id: name = 'client.' + parsed_args.client_id # 如果给了名字,那么就直接使用名字。 if parsed_args.client_name: name = parsed_args.client_name # default '' means default conf search # 这里设置使用的配置文件,一般是/etc/ceph/ceph.conf。 conffile = '' 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 # 这里设置socket。在运行ceph -s的时候这段代码没什么用,不会用到。 sockpath = None if parsed_args.admin_socket: sockpath = parsed_args.admin_socket elif len(childargs) > 0 and childargs[0] == "daemon": # Treat "daemon <path>" or "daemon <name>" like --admin_daemon <path> if len(childargs) > 2: if childargs[1].find('/') >= 0: sockpath = childargs[1] else: # try resolve daemon name try: sockpath = ceph_conf(parsed_args, 'admin_socket', childargs[1]) except Exception as e: print >> sys.stderr, \ 'Can\'t get admin socket path: ' + str(e) return errno.EINVAL # for both: childargs = childargs[2:] else: print >> sys.stderr, 'daemon requires at least 3 arguments' return errno.EINVAL # ceph -s的时候,会直到这里来执行。 if sockpath: try: print admin_socket(sockpath, childargs, format) except Exception as e: print >> sys.stderr, 'admin_socket: {0}'.format(e) return errno.EINVAL return 0 timeout = None if parsed_args.cluster_timeout: timeout = parsed_args.cluster_timeout # basic help if parsed_args.help: do_basic_help(parser, childargs) # handle any 'generic' ceph arguments that we didn't parse here global cluster_handle # rados.Rados() will call rados_create2, and then read the conf file, # and then set the keys from the dict. So we must do these # "pre-file defaults" first (see common_preinit in librados) conf_defaults = { 'log_to_stderr':'true', 'err_to_stderr':'true', 'log_flush_on_exit':'true', } if 'injectargs' in childargs: position = childargs.index('injectargs') injectargs = childargs[position:] childargs = childargs[:position] if verbose: print >> sys.stderr, 'Separate childargs {0} from injectargs {1}'.\ format(childargs, injectargs) else: injectargs = None clustername = 'ceph' if parsed_args.cluster: clustername = parsed_args.cluster try: cluster_handle = rados.Rados(name=name, clustername=clustername, conf_defaults=conf_defaults, conffile=conffile) retargs = cluster_handle.conf_parse_argv(childargs) except rados.Error as e: print >> sys.stderr, 'Error initializing cluster client: {0}'.\ format(repr(e)) return 1 childargs = retargs if not childargs: childargs = [] # -- means "stop parsing args", but we don't want to see it either if '--' in childargs: childargs.remove('--') 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 >> sys.stderr, '"{0} tell" is deprecated; try "tell {0}.<id>" instead (id can be "*") '.format(childargs[0]) return 1 if parsed_args.help: # short default timeout for -h if not timeout: timeout = 5 hdr('Monitor commands:') print '[Contacting monitor, timeout after %d seconds]' % timeout if childargs and childargs[0] == 'ping': if len(childargs) < 2: print >> sys.stderr, '"ping" requires a monitor name as argument: "ping mon.<id>"' return 1 try: if childargs and childargs[0] == 'ping': return ping_monitor(cluster_handle, childargs[1]) cluster_handle.connect(timeout=timeout) except KeyboardInterrupt: print >> sys.stderr, 'Cluster connection aborted' return 1 except Exception as e: print >> sys.stderr, 'Error connecting to cluster: {0}'.\ format(e.__class__.__name__) return 1 if parsed_args.help: return do_extended_help(parser, childargs) # implement -w/--watch_* # This is ugly, but Namespace() isn't quite rich enough. level = '' for k, v in parsed_args._get_kwargs(): if k.startswith('watch') and v: if k == 'watch': level = 'info' else: level = k.replace('watch_', '') if level: # an awfully simple callback def watch_cb(arg, line, who, stamp_sec, stamp_nsec, seq, level, msg): print line sys.stdout.flush() # first do a ceph status ret, outbuf, outs = json_command(cluster_handle, prefix='status') if ret == -errno.EINVAL: # try old mon ret, outbuf, outs = send_command(cluster_handle, cmd=['status']) # old mon returns status to outs...ick if ret == 0: outbuf += outs if ret: print >> sys.stderr, "status query failed: ", outs return ret print outbuf # this instance keeps the watch connection alive, but is # otherwise unused logwatch = rados.MonitorLog(cluster_handle, level, watch_cb, 0) # loop forever letting watch_cb print lines try: signal.pause() except KeyboardInterrupt: # or until ^C, at least return 0 # read input file, if any inbuf = '' if parsed_args.input_file: try: with open(parsed_args.input_file, 'r') as f: inbuf = f.read() except Exception as e: print >> sys.stderr, 'Can\'t open input file {0}: {1}'.format(parsed_args.input_file, e) return 1 # prepare output file, if any if parsed_args.output_file: try: outf = open(parsed_args.output_file, 'w') except Exception as e: print >> sys.stderr, \ 'Can\'t open output file {0}: {1}'.\ format(parsed_args.output_file, e) return 1 # -s behaves like a command (ceph status). if parsed_args.status: childargs.insert(0, 'status') try: target = find_cmd_target(childargs) except Exception as e: print >> sys.stderr, \ 'error handling command target: {0}'.format(e) return 1 # 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. is_tell = False if len(childargs) and childargs[0] == 'tell': childargs = childargs[2:] is_tell = True if is_tell: if injectargs: childargs = injectargs if not len(childargs): print >> sys.stderr, \ 'Cannot use \'tell\' with interactive mode' return errno.EINVAL # fetch JSON sigs from command # each line contains one command signature (a placeholder name # of the form 'cmdNNN' followed by an array of argument descriptors) # as part of the validated argument JSON object targets = [target] if target[1] == '*': if target[0] == 'osd': targets = [(target[0], o) for o in osdids()] elif target[0] == 'mon': targets = [(target[0], m) for m in monids()] final_ret = 0 for target in targets: # prettify? prefix output with target, if there was a wildcard used prefix = '' suffix = '' if not parsed_args.output_file and len(targets) > 1: prefix = '{0}.{1}: '.format(*target) suffix = '\n' ret, outbuf, outs = json_command(cluster_handle, target=target, prefix='get_command_descriptions') compat = False if ret == -errno.EINVAL: # send command to old monitor or OSD if verbose: print prefix + '{0} to old {1}'.format(' '.join(childargs), target[0]) compat = True if parsed_args.output_format: childargs.extend(['--format', parsed_args.output_format]) ret, outbuf, outs = send_command(cluster_handle, target, childargs, inbuf) if ret == -errno.EINVAL: # did we race with a mon upgrade? try again! ret, outbuf, outs = json_command(cluster_handle, target=target, prefix='get_command_descriptions') if ret == 0: compat = False # yep, carry on if not compat: if ret: if ret < 0: outs = 'problem getting command descriptions from {0}.{1}'.format(*target) else: sigdict = parse_json_funcsigs(outbuf, 'cli') if parsed_args.completion: return complete(sigdict, childargs, target) ret, outbuf, outs = new_style_command(parsed_args, childargs, target, sigdict, inbuf, verbose) # debug tool: send any successful command *again* to # verify that it is idempotent. if not ret and 'CEPH_CLI_TEST_DUP_COMMAND' in os.environ: ret, outbuf, outs = new_style_command(parsed_args, childargs, target, sigdict, inbuf, verbose) if ret < 0: ret = -ret print >> sys.stderr, prefix + 'Second attempt of previously successful command failed with {0}: {1}'.format(errno.errorcode[ret], outs) if ret < 0: ret = -ret print >> sys.stderr, prefix + 'Error {0}: {1}'.format(errno.errorcode[ret], outs) if len(targets) > 1: final_ret = ret else: return ret # this assumes outs never has useful command output, only status if compat: if ret == 0: # old cli/mon would send status string to stdout on non-error print outs else: if outs: print >> sys.stderr, prefix + outs if (parsed_args.output_file): outf.write(outbuf) else: # hack: old code printed status line before many json outputs # (osd dump, etc.) that consumers know to ignore. Add blank line # to satisfy consumers that skip the first line, but not annoy # consumers that don't. if parsed_args.output_format and \ parsed_args.output_format.startswith('json') and \ not compat: sys.stdout.write('\n') # if we are prettifying things, normalize newlines. sigh. if suffix != '': outbuf = outbuf.rstrip() if outbuf != '': sys.stdout.write(prefix + outbuf + suffix) sys.stdout.flush() if (parsed_args.output_file): outf.close() if final_ret: return final_ret return 0
def help_for_sigs(sigs, partial=None): sys.stdout.write(format_help(parse_json_funcsigs(sigs, 'cli'), partial=partial))