]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
tools: ceph-monstore-tool: a cli that works
authorJoao Eduardo Luis <joao@redhat.com>
Wed, 24 Dec 2014 02:02:32 +0000 (02:02 +0000)
committerJoao Eduardo Luis <joao@redhat.com>
Mon, 29 Dec 2014 17:50:28 +0000 (17:50 +0000)
Fixes: #10341
Signed-off-by: Joao Eduardo Luis <joao@redhat.com>
src/tools/ceph_monstore_tool.cc

index ea8402b79352c0048dab923b6ea34575b8580eba..541416f1b9a445f42fd49186ec58ca9a5404562c 100644 (file)
@@ -94,59 +94,145 @@ public:
   }
 };
 
+
+/**
+ * usage: ceph-monstore-tool <store-path> <command> [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 <PATH2 | -o PATH2>
+ * 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 << " <store-path> <cmd> [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<string> subcmds;
   desc.add_options()
-    ("help", "produce help message")
-    ("mon-store-path", po::value<string>(&store_path),
-     "path to mon directory, mandatory")
-    ("out", po::value<string>(&out_path),
-     "out path")
-    ("version", po::value<int>(&version),
-     "version requested")
-    ("trace-file", po::value<string>(&tfile),
-     "trace file")
-    ("dump-start", po::value<unsigned>(&dstart),
-     "transaction num to start dumping at")
-    ("dump-end", po::value<unsigned>(&dstop),
-     "transaction num to stop dumping at")
-    ("num-replays", po::value<unsigned>(&num_replays),
-     "number of times to replay")
-    ("trans-size", po::value<unsigned>(&tsize),
-     "keys to write in each transaction")
-    ("trans-val-size", po::value<unsigned>(&tvalsize),
-     "val to write in each key")
-    ("num-trans", po::value<unsigned>(&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<string>(&store_path),
+     "path to monitor's store")
     ("command", po::value<string>(&cmd),
-     "command")
+     "Command")
+    ("subcmd", po::value<vector<string> >(&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<string> 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<string>(&outpath),
+       "output file (default: stdout)")
+      ("version,v", po::value<unsigned>(&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<string>(&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<unsigned>(&dstart),
+       "starting version (default: 0)")
+      ("end,e", po::value<unsigned>(&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<unsigned>(&dstart),
+       "starting version (default: 0)")
+      ("end,e", po::value<unsigned>(&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<string>(&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<unsigned>(&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<string>(&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<unsigned>(&tsize),
+       "keys to write in each transaction (default: 200)")
+      ("size,s", po::value<unsigned>(&tvalsize),
+       "size (in bytes) of the value to write in each key (default: 1024)")
+      ("ntrans,n", po::value<unsigned>(&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 <mon_data_dir>)"
-                << 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;
 }