]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
common,ceph: add output file switch to dump json to
authorPatrick Donnelly <pdonnell@redhat.com>
Wed, 1 May 2024 19:09:33 +0000 (15:09 -0400)
committerPatrick Donnelly <pdonnell@ibm.com>
Mon, 3 Mar 2025 20:00:54 +0000 (15:00 -0500)
The `ceph tell mds.X cache dump` and `ceph tell mds.X ops` commands have a
useful `--path` argument that directs the daemon to save the dump of the output
to a file local the daemon. This has several advantages:

* We don't need to construct the JSON output in memory, which may be many gigabytes.
* Streaming writes to a local file is significantly faster than sending the data over the ceph messenger.
* The command spends as little time as possible holding relevant locks.

However, only some commands support this and we could make it generic to the
admin socket interface. So, add a generic --daemon-output-file argument to
achieve this.

The main concern with this is security (telling the daemon to write to
arbitrary files) but this is gated by the session caps which require
"allow_all" and that's typically only valid for the client.admin.

Fixes: https://tracker.ceph.com/issues/65747
Signed-off-by: Patrick Donnelly <pdonnell@redhat.com>
(cherry picked from commit a3414684974380c1d335f1fcafc5245dbde1c0d6)

src/ceph.in
src/common/Formatter.h
src/common/admin_socket.cc

index 11a76511a8ec2b758afcb2ca05e39df19e3b0a60..51743dd9ae8c928cf36f9589c52e3428624d99dd 100755 (executable)
@@ -336,6 +336,8 @@ def parse_cmdargs(args=None, target='') -> Tuple[argparse.ArgumentParser,
     parser.add_argument('--concise', dest='verbose', action="store_false",
                         help="make less verbose")
 
+    parser.add_argument('--daemon-output-file', dest='daemon_output_file',
+                        help="output file location local to the daemon for JSON produced by tell commands")
     parser.add_argument('-f', '--format', choices=['json', 'json-pretty',
                         'xml', 'xml-pretty', 'plain', 'yaml'],
                         help="Note: yaml is only valid for orch commands", dest='output_format')
@@ -580,6 +582,8 @@ def do_command(parsed_args, target, cmdargs, sigdict, inbuf, verbose):
     if valid_dict:
         if parsed_args.output_format:
             valid_dict['format'] = parsed_args.output_format
+        if parsed_args.daemon_output_file:
+            valid_dict['output-file'] = parsed_args.daemon_output_file
         if verbose:
             print("Submitting command: ", valid_dict, file=sys.stderr)
     else:
index 4bfb5c41e08d50d6d731e01d0b060c6dd0e090d6..3e94c687114c661ee3c02e109fb7bb47a96d3a0f 100644 (file)
@@ -272,6 +272,9 @@ public:
     int get_len() const override {
       return file.tellp();
     }
+    std::ofstream const& get_ofstream() const {
+      return file;
+    }
 
 protected:
     std::ostream& get_ss() override {
index 58ea36c86e8ac3a4308a82dd8cef80d20c333323..1e73ce0836a92e7e38ad30b2266e1a23b3f2fb22 100644 (file)
@@ -15,6 +15,8 @@
 #include <sys/un.h>
 #include <optional>
 
+#include <stdlib.h>
+
 #include "common/admin_socket.h"
 #include "common/admin_socket_client.h"
 #include "common/dout.h"
@@ -506,7 +508,46 @@ void AdminSocket::execute_command(
                     empty);
   }
 
-  auto f = Formatter::create(format, "json-pretty", "json-pretty");
+  ldout(m_cct, 20) << __func__ << ": format is " << format << " prefix is " << prefix << dendl;
+
+  string output;
+  try {
+    cmd_getval(cmdmap, "output-file", output);
+    if (!output.empty()) {
+      ldout(m_cct, 20) << __func__ << ": output file is " << output << dendl;
+    }
+  } catch (const bad_cmd_get& e) {
+    output = "";
+  }
+
+  if (output == ":tmp:") {
+    auto path = m_cct->_conf.get_val<std::string>("tmp_file_template");
+    if (int fd = mkstemp(path.data()); fd >= 0) {
+      close(fd);
+      output = path;
+      ldout(m_cct, 20) << __func__ << ": output file created in tmp_dir is " << output << dendl;
+    } else {
+      return on_finish(-errno, "temporary output file could not be opened", empty);
+    }
+  }
+
+  Formatter* f;
+  if (!output.empty()) {
+    if (!(format == "json" || format == "json-pretty")) {
+      return on_finish(-EINVAL, "unsupported format for --output-file", empty);
+    }
+    ldout(m_cct, 10) << __func__ << ": opening file for json output: " << output << dendl;
+    bool pretty = (format == "json-pretty");
+    auto* jff = new JSONFormatterFile(output, pretty);
+    auto&& of = jff->get_ofstream();
+    if (!of.is_open()) {
+      delete jff;
+      return on_finish(-EIO, "output file could not be opened", empty);
+    }
+    f = jff;
+  } else {
+    f = Formatter::create(format, "json-pretty", "json-pretty");
+  }
 
   auto [retval, hook] = find_matched_hook(prefix, cmdmap);
   switch (retval) {
@@ -524,10 +565,27 @@ void AdminSocket::execute_command(
 
   hook->call_async(
     prefix, cmdmap, f, inbl,
-    [f, on_finish](int r, std::string_view err, bufferlist& out) {
+    [f, output, on_finish, m_cct=m_cct](int r, std::string_view err, bufferlist& out) {
       // handle either existing output in bufferlist *or* via formatter
-      if (r >= 0 && out.length() == 0) {
-       f->flush(out);
+      ldout(m_cct, 10) << __func__ << ": command completed with result " << r << dendl;
+      if (auto* jff = dynamic_cast<JSONFormatterFile*>(f); jff != nullptr) {
+        ldout(m_cct, 25) << __func__ << ": flushing file" << dendl;
+        jff->flush();
+        auto* outf = new JSONFormatter(true);
+        outf->open_object_section("result");
+        outf->dump_string("path", output);
+        outf->dump_int("result", r);
+        outf->dump_string("output", out.to_str());
+        outf->dump_int("len", jff->get_len());
+        outf->close_section();
+        CachedStackStringStream css;
+        outf->flush(*css);
+        delete outf;
+        out.clear();
+        out.append(css->strv());
+      } else if (r >= 0 && out.length() == 0) {
+        ldout(m_cct, 25) << __func__ << ": out is empty, dumping formatter" << dendl;
+        f->flush(out);
       }
       delete f;
       on_finish(r, err, out);