From: Joao Eduardo Luis Date: Wed, 24 Dec 2014 02:02:32 +0000 (+0000) Subject: tools: ceph-monstore-tool: a cli that works X-Git-Tag: v0.92~87^2~5 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=fe662c5ae208b9623bfa7b1141d11ce3e3094ea5;p=ceph.git tools: ceph-monstore-tool: a cli that works Fixes: #10341 Signed-off-by: Joao Eduardo Luis --- diff --git a/src/tools/ceph_monstore_tool.cc b/src/tools/ceph_monstore_tool.cc index ea8402b79352..541416f1b9a4 100644 --- a/src/tools/ceph_monstore_tool.cc +++ b/src/tools/ceph_monstore_tool.cc @@ -94,59 +94,145 @@ public: } }; + +/** + * usage: ceph-monstore-tool [options] + * + * commands: + * + * store-copy < --out arg > + * dump-keys + * compact + * getmonmap < --out arg [ --version arg ] > + * getosdmap < --out arg [ --version arg ] > + * dump-paxos <--dump-start VER> <--dump-end VER> + * dump-trace < --trace-file arg > + * replay-trace + * random-gen + * + * wanted syntax: + * + * ceph-monstore-tool PATH CMD [options] + * + * ceph-monstore-tool PATH store-copy + * ceph-monstore-tool PATH dump-keys + * ceph-monstore-tool PATH compact + * ceph-monstore-tool PATH get monmap [VER] + * ceph-monstore-tool PATH get osdmap [VER] + * ceph-monstore-tool PATH dump-paxos STARTVER ENDVER + * + * + */ +void usage(const char *n, po::options_description &d) +{ + std::cerr << + "usage: " << n << " [args|options]\n" + << "\n" + << "Commands:\n" + << " store-copy PATH copies store to PATH\n" + << " compact compacts the store\n" + << " get monmap [-- options] get monmap (version VER if specified)\n" + << " (default: last committed)\n" + << " get osdmap [-- options] get osdmap (version VER if specified)\n" + << " (default: last committed)\n" + << " get mdsmap [-- options] get mdsmap (version VER if specified)\n" + << " (default: last committed)\n" + << " dump-keys dumps store keys to FILE\n" + << " (default: stdout)\n" + << " dump-paxos [-- options] dump paxos transactions\n" + << " (dump-paxos -- --help for more info)\n" + << " dump-trace FILE [-- options] dump contents of trace file FILE\n" + << " (dump-trace -- --help for more info)\n" + << " replay-trace FILE [-- options] replay trace from FILE\n" + << " (replay-trace -- --help for more info)\n" + << " random-gen [-- options] add randomly generated ops to the store\n" + << " (random-gen -- --help for more info)\n" + << std::endl; + std::cerr << d << std::endl; + std::cerr + << "\nPlease Note:\n" + << "* Ceph-specific options should be in the format --option-name=VAL\n" + << " (specifically, do not forget the '='!!)\n" + << "* Command-specific options need to be passed after a '--'\n" + << " e.g., 'get monmap -- --version 10 --out /tmp/foo'" + << std::endl; +} + int main(int argc, char **argv) { + int err = 0; po::options_description desc("Allowed options"); - int version = -1; - string store_path, cmd, out_path, tfile; - unsigned dstart = 0; - unsigned dstop = ~0; - unsigned num_replays = 1; - unsigned tsize = 200; - unsigned tvalsize = 1024; - unsigned ntrans = 100; + string store_path, cmd; + vector subcmds; desc.add_options() - ("help", "produce help message") - ("mon-store-path", po::value(&store_path), - "path to mon directory, mandatory") - ("out", po::value(&out_path), - "out path") - ("version", po::value(&version), - "version requested") - ("trace-file", po::value(&tfile), - "trace file") - ("dump-start", po::value(&dstart), - "transaction num to start dumping at") - ("dump-end", po::value(&dstop), - "transaction num to stop dumping at") - ("num-replays", po::value(&num_replays), - "number of times to replay") - ("trans-size", po::value(&tsize), - "keys to write in each transaction") - ("trans-val-size", po::value(&tvalsize), - "val to write in each key") - ("num-trans", po::value(&ntrans), - "number of transactions to run") + ("help,h", "produce help message") + ; + + /* Dear Future Developer: + * + * for further improvement, should you need to pass specific options to + * a command (e.g., get osdmap VER --hex), you can expand the current + * format by creating additional 'po::option_description' and passing + * 'subcmds' to 'po::command_line_parser', much like what is currently + * done by default. However, beware: in order to differentiate a + * command-specific option from the generic/global options, you will need + * to pass '--' in the command line (so that the first parser, the one + * below, assumes it has reached the end of all options); e.g., + * 'get osdmap VER -- --hex'. Not pretty; far from intuitive; it was as + * far as I got with this library. Improvements on this format will be + * left as an excercise for the reader. -Joao + */ + po::options_description positional_desc("Positional argument options"); + positional_desc.add_options() + ("store-path", po::value(&store_path), + "path to monitor's store") ("command", po::value(&cmd), - "command") + "Command") + ("subcmd", po::value >(&subcmds), + "Command arguments/Sub-Commands") ; - po::positional_options_description p; - p.add("command", 1); - p.add("version", 1); + po::positional_options_description positional; + positional.add("store-path", 1); + positional.add("command", 1); + positional.add("subcmd", -1); + + po::options_description all_desc("All options"); + all_desc.add(desc).add(positional_desc); vector ceph_option_strings; po::variables_map vm; try { po::parsed_options parsed = - po::command_line_parser(argc, argv).options(desc).positional(p).allow_unregistered().run(); + po::command_line_parser(argc, argv). + options(all_desc). + positional(positional). + allow_unregistered().run(); + po::store( parsed, vm); po::notify(vm); + // Specifying po::include_positional would have our positional arguments + // being collected (thus being part of ceph_option_strings and eventually + // passed on to global_init() below). + // Instead we specify po::exclude_positional, which has the upside of + // completely avoid this, but the downside of having to specify ceph + // options as --VAR=VAL (note the '='); otherwise we will capture the + // positional 'VAL' as belonging to us, never being collected. ceph_option_strings = po::collect_unrecognized(parsed.options, - po::include_positional); + po::exclude_positional); + } catch(po::error &e) { - std::cerr << e.what() << std::endl; + std::cerr << "error: " << e.what() << std::endl; + return 1; + } + + // parse command structure before calling global_init() and friends. + + if (vm.empty() || vm.count("help") || + store_path.empty() || cmd.empty() || + *cmd.begin() == '-') { + usage(argv[0], desc); return 1; } @@ -165,38 +251,18 @@ int main(int argc, char **argv) { g_ceph_context->_conf->apply_changes(NULL); g_conf = g_ceph_context->_conf; - if (vm.count("help")) { - std::cerr << desc << std::endl; - return 1; - } - - int fd; - if (vm.count("out")) { - if ((fd = open(out_path.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) { - int _err = errno; - if (_err != EISDIR) { - std::cerr << "Couldn't open " << out_path << ": " << cpp_strerror(_err) << std::endl; - return 1; - } - } - } else { - fd = STDOUT_FILENO; - } - - if (fd < 0 && cmd != "store-copy") { - std::cerr << "error: '" << out_path << "' is a directory!" << std::endl; - return 1; - } - + // this is where we'll write *whatever*, on a per-command basis. + // not all commands require some place to write their things. MonitorDBStore st(store_path); if (store_path.size()) { stringstream ss; int r = st.open(ss); if (r < 0) { std::cerr << ss.str() << std::endl; - goto done; + return EINVAL; } } + if (cmd == "dump-keys") { KeyValueDB::WholeSpaceIterator iter = st.get_iterator(); while (iter->valid()) { @@ -206,50 +272,110 @@ int main(int argc, char **argv) { } } else if (cmd == "compact") { st.compact(); - } else if (cmd == "getmonmap") { - assert(fd >= 0); - if (!store_path.size()) { - std::cerr << "need mon store path" << std::endl; - std::cerr << desc << std::endl; + } else if (cmd == "get") { + unsigned v = 0; + string outpath; + string map_type; + // visible options for this command + po::options_description op_desc("Allowed 'get' options"); + op_desc.add_options() + ("help,h", "produce this help message") + ("out,o", po::value(&outpath), + "output file (default: stdout)") + ("version,v", po::value(&v), + "map version to obtain") + ; + // this is going to be a positional argument; we don't want to show + // it as an option during --help, but we do want to have it captured + // when parsing. + po::options_description hidden_op_desc("Hidden 'get' options"); + hidden_op_desc.add_options() + ("map-type", po::value(&map_type), + "map-type") + ; + po::positional_options_description op_positional; + op_positional.add("map-type", 1); + + // op_desc_all will aggregate all visible and hidden options for parsing. + // when we call 'usage()' we just pass 'op_desc', as that's the description + // holding the visible options. + po::options_description op_desc_all; + op_desc_all.add(op_desc).add(hidden_op_desc); + + po::variables_map op_vm; + try { + po::parsed_options op_parsed = po::command_line_parser(subcmds). + options(op_desc_all).positional(op_positional).run(); + po::store(op_parsed, op_vm); + po::notify(op_vm); + } catch (po::error &e) { + std::cerr << "error: " << e.what() << std::endl; + err = EINVAL; goto done; } - version_t v; - if (version <= 0) { - v = st.get("monmap", "last_committed"); - } else { - v = version; - } - bufferlist bl; - /// XXX: this is not ok, osdmap and full should be abstracted somewhere - int r = st.get("monmap", v, bl); - if (r < 0) { - std::cerr << "Error getting map: " << cpp_strerror(r) << std::endl; + if (op_vm.count("help") || map_type.empty()) { + usage(argv[0], op_desc); + err = 0; goto done; } - bl.write_fd(fd); - } else if (cmd == "getosdmap") { - if (!store_path.size()) { - std::cerr << "need mon store path" << std::endl; - std::cerr << desc << std::endl; - goto done; + + if (v <= 0) { + v = st.get(map_type, "last_committed"); } - version_t v; - if (version == -1) { - v = st.get("osdmap", "last_committed"); - } else { - v = version; + + int fd = ::open(outpath.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (fd < 0) { + std::cerr << "error opening output file: " + << cpp_strerror(errno) << std::endl; + err = EINVAL; + goto done; } bufferlist bl; - /// XXX: this is not ok, osdmap and full should be abstracted somewhere - int r = st.get("osdmap", st.combine_strings("full", v), bl); + int r = 0; + if (map_type == "osdmap") { + r = st.get(map_type, st.combine_strings("full", v), bl); + } else { + r = st.get(map_type, v, bl); + } if (r < 0) { std::cerr << "Error getting map: " << cpp_strerror(r) << std::endl; + err = EINVAL; + ::close(fd); goto done; } bl.write_fd(fd); } else if (cmd == "dump-paxos") { + unsigned dstart = 0; + unsigned dstop = ~0; + po::options_description op_desc("Allowed 'dump-paxos' options"); + op_desc.add_options() + ("help,h", "produce this help message") + ("start,s", po::value(&dstart), + "starting version (default: 0)") + ("end,e", po::value(&dstop), + "finish version (default: ~0)") + ; + + po::variables_map op_vm; + try { + po::parsed_options op_parsed = po::command_line_parser(subcmds). + options(op_desc).run(); + po::store(op_parsed, op_vm); + po::notify(op_vm); + } catch (po::error &e) { + std::cerr << "error: " << e.what() << std::endl; + err = EINVAL; + goto done; + } + + if (op_vm.count("help")) { + usage(argv[0], op_desc); + err = 0; + goto done; + } + for (version_t v = dstart; v <= dstop; ++v) { bufferlist bl; st.get("paxos", v, bl); @@ -263,12 +389,61 @@ int main(int argc, char **argv) { f.flush(cout); } } else if (cmd == "dump-trace") { - if (tfile.empty()) { - std::cerr << "Need trace_file" << std::endl; - std::cerr << desc << std::endl; + unsigned dstart = 0; + unsigned dstop = ~0; + string outpath; + + // visible options for this command + po::options_description op_desc("Allowed 'dump-trace' options"); + op_desc.add_options() + ("help,h", "produce this help message") + ("start,s", po::value(&dstart), + "starting version (default: 0)") + ("end,e", po::value(&dstop), + "finish version (default: ~0)") + ; + // this is going to be a positional argument; we don't want to show + // it as an option during --help, but we do want to have it captured + // when parsing. + po::options_description hidden_op_desc("Hidden 'dump-trace' options"); + hidden_op_desc.add_options() + ("out,o", po::value(&outpath), + "file to write the dump to") + ; + po::positional_options_description op_positional; + op_positional.add("out", 1); + + // op_desc_all will aggregate all visible and hidden options for parsing. + // when we call 'usage()' we just pass 'op_desc', as that's the description + // holding the visible options. + po::options_description op_desc_all; + op_desc_all.add(op_desc).add(hidden_op_desc); + + po::variables_map op_vm; + try { + po::parsed_options op_parsed = po::command_line_parser(subcmds). + options(op_desc_all).positional(op_positional).run(); + po::store(op_parsed, op_vm); + po::notify(op_vm); + } catch (po::error &e) { + std::cerr << "error: " << e.what() << std::endl; + err = EINVAL; + goto done; + } + + if (op_vm.count("help")) { + usage(argv[0], op_desc); + err = 0; + goto done; + } + + if (outpath.empty()) { + usage(argv[0], op_desc); + err = EINVAL; goto done; } - TraceIter iter(tfile.c_str()); + + TraceIter iter(outpath.c_str()); iter.init(); while (true) { if (!iter.valid()) @@ -286,19 +461,59 @@ int main(int argc, char **argv) { } std::cerr << "Read up to transaction " << iter.num() << std::endl; } else if (cmd == "replay-trace") { - if (!store_path.size()) { - std::cerr << "need mon store path" << std::endl; - std::cerr << desc << std::endl; + string inpath; + unsigned num_replays = 1; + // visible options for this command + po::options_description op_desc("Allowed 'replay-trace' options"); + op_desc.add_options() + ("help,h", "produce this help message") + ("num-replays,n", po::value(&num_replays), + "finish version (default: 1)") + ; + // this is going to be a positional argument; we don't want to show + // it as an option during --help, but we do want to have it captured + // when parsing. + po::options_description hidden_op_desc("Hidden 'replay-trace' options"); + hidden_op_desc.add_options() + ("in,i", po::value(&inpath), + "file to write the dump to") + ; + po::positional_options_description op_positional; + op_positional.add("in", 1); + + // op_desc_all will aggregate all visible and hidden options for parsing. + // when we call 'usage()' we just pass 'op_desc', as that's the description + // holding the visible options. + po::options_description op_desc_all; + op_desc_all.add(op_desc).add(hidden_op_desc); + + po::variables_map op_vm; + try { + po::parsed_options op_parsed = po::command_line_parser(subcmds). + options(op_desc_all).positional(op_positional).run(); + po::store(op_parsed, op_vm); + po::notify(op_vm); + } catch (po::error &e) { + std::cerr << "error: " << e.what() << std::endl; + err = EINVAL; + goto done; + } + + if (op_vm.count("help")) { + usage(argv[0], op_desc); + err = 0; goto done; } - if (tfile.empty()) { - std::cerr << "Need trace_file" << std::endl; - std::cerr << desc << std::endl; + + if (inpath.empty()) { + usage(argv[0], op_desc); + err = EINVAL; goto done; } + unsigned num = 0; for (unsigned i = 0; i < num_replays; ++i) { - TraceIter iter(tfile.c_str()); + TraceIter iter(inpath.c_str()); iter.init(); while (true) { if (!iter.valid()) @@ -311,11 +526,38 @@ int main(int argc, char **argv) { std::cerr << "Read up to transaction " << iter.num() << std::endl; } } else if (cmd == "random-gen") { - if (!store_path.size()) { - std::cerr << "need mon store path" << std::endl; - std::cerr << desc << std::endl; + unsigned tsize = 200; + unsigned tvalsize = 1024; + unsigned ntrans = 100; + po::options_description op_desc("Allowed 'random-gen' options"); + op_desc.add_options() + ("help,h", "produce this help message") + ("num-keys,k", po::value(&tsize), + "keys to write in each transaction (default: 200)") + ("size,s", po::value(&tvalsize), + "size (in bytes) of the value to write in each key (default: 1024)") + ("ntrans,n", po::value(&ntrans), + "number of transactions to run (default: 100)") + ; + + po::variables_map op_vm; + try { + po::parsed_options op_parsed = po::command_line_parser(subcmds). + options(op_desc).run(); + po::store(op_parsed, op_vm); + po::notify(op_vm); + } catch (po::error &e) { + std::cerr << "error: " << e.what() << std::endl; + err = EINVAL; goto done; } + + if (op_vm.count("help")) { + usage(argv[0], op_desc); + err = 0; + goto done; + } + unsigned num = 0; for (unsigned i = 0; i < ntrans; ++i) { std::cerr << "Applying trans " << i << std::endl; @@ -334,23 +576,14 @@ int main(int argc, char **argv) { st.apply_transaction(t); } } else if (cmd == "store-copy") { - if (!store_path.size()) { - std::cerr << "need mon store path to copy from" << std::endl; - std::cerr << desc << std::endl; - goto done; - } - if (!out_path.size()) { - std::cerr << "need mon store path to copy to (--out )" - << std::endl; - std::cerr << desc << std::endl; - goto done; - } - if (fd > 0) { - std::cerr << "supplied out path '" << out_path << "' is not a directory" - << std::endl; + if (subcmds.size() < 1 || subcmds[0].empty()) { + usage(argv[0], desc); + err = EINVAL; goto done; } + string out_path = subcmds[0]; + MonitorDBStore out_store(out_path); { stringstream ss; @@ -401,13 +634,11 @@ int main(int argc, char **argv) { << std::endl; } else { std::cerr << "Unrecognized command: " << cmd << std::endl; + usage(argv[0], desc); goto done; } done: st.close(); - if (vm.count("out") && fd > 0) { - ::close(fd); - } - return 0; + return err; }