]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/crimson/admin/admin_socket.cc
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / crimson / admin / admin_socket.cc
index 1c677ff10795784fab7f29e01317b87fb62c6ab9..852185af18d9547851b74902d20065ae5546f3ba 100644 (file)
@@ -3,25 +3,23 @@
 
 #include "crimson/admin/admin_socket.h"
 
+#include <boost/algorithm/string/join.hpp>
 #include <fmt/format.h>
 #include <seastar/net/api.hh>
 #include <seastar/net/inet_address.hh>
+#include <seastar/core/future-util.hh>
 #include <seastar/core/reactor.hh>
 #include <seastar/core/sleep.hh>
 #include <seastar/core/thread.hh>
 #include <seastar/util/std-compat.hh>
 
 #include "common/version.h"
+#include "messages/MCommand.h"
+#include "messages/MCommandReply.h"
 #include "crimson/common/log.h"
 #include "crimson/net/Socket.h"
 
 using namespace crimson::common;
-/**
- *  A Crimson-wise version of the admin socket - implementation file
- *
- *  \todo handle the unlinking of the admin socket. Note that 'cleanup_files'
- *  at-exit functionality is not yet implemented in Crimson.
- */
 
 namespace {
 seastar::logger& logger()
@@ -32,6 +30,19 @@ seastar::logger& logger()
 
 namespace crimson::admin {
 
+tell_result_t::tell_result_t(int ret, std::string&& err)
+  : ret{ret}, err(std::move(err))
+{}
+
+tell_result_t::tell_result_t(int ret, std::string&& err, ceph::bufferlist&& out)
+  : ret{ret}, err(std::move(err)), out(std::move(out))
+{}
+
+tell_result_t::tell_result_t(std::unique_ptr<Formatter> formatter)
+{
+  formatter->flush(out);
+}
+
 seastar::future<>
 AdminSocket::register_command(std::unique_ptr<AdminSocketHook>&& hook)
 {
@@ -51,28 +62,29 @@ AdminSocket::register_command(std::unique_ptr<AdminSocketHook>&& hook)
 /*
  * Note: parse_cmd() is executed with servers_tbl_rwlock held as shared
  */
-AdminSocket::maybe_parsed_t AdminSocket::parse_cmd(std::string cmd,
-                                                  ceph::bufferlist& out)
+auto AdminSocket::parse_cmd(const std::vector<std::string>& cmd)
+  -> std::variant<parsed_command_t, tell_result_t>
 {
   // preliminaries:
   //   - create the formatter specified by the cmd parameters
   //   - locate the "op-code" string (the 'prefix' segment)
   //   - prepare for command parameters extraction via cmdmap_t
   cmdmap_t cmdmap;
+  ceph::bufferlist out;
 
   try {
     stringstream errss;
     //  note that cmdmap_from_json() may throw on syntax issues
-    if (!cmdmap_from_json({cmd}, &cmdmap, errss)) {
+    if (!cmdmap_from_json(cmd, &cmdmap, errss)) {
       logger().error("{}: incoming command error: {}", __func__, errss.str());
       out.append("error:"s);
       out.append(errss.str());
-      return maybe_parsed_t{ std::nullopt };
+      return tell_result_t{-EINVAL, "invalid json", std::move(out)};
     }
-  } catch (std::runtime_error& e) {
+  } catch (const std::runtime_error& e) {
     logger().error("{}: incoming command syntax: {}", __func__, cmd);
-    out.append("error: command syntax"s);
-    return maybe_parsed_t{ std::nullopt };
+    out.append(string{e.what()});
+    return tell_result_t{-EINVAL, "invalid json", std::move(out)};
   }
 
   string format;
@@ -82,43 +94,17 @@ AdminSocket::maybe_parsed_t AdminSocket::parse_cmd(std::string cmd,
     cmd_getval(cmdmap, "prefix", prefix);
   } catch (const bad_cmd_get& e) {
     logger().error("{}: invalid syntax: {}", __func__, cmd);
-    out.append("error: command syntax: missing 'prefix'"s);
-    return maybe_parsed_t{ std::nullopt };
-  }
-
-  if (prefix.empty()) {
-    // no command identified
-    out.append("error: no command identified"s);
-    return maybe_parsed_t{ std::nullopt };
+    out.append(string{e.what()});
+    return tell_result_t{-EINVAL, "invalid json", std::move(out)};
   }
 
   // match the incoming op-code to one of the registered APIs
   if (auto found = hooks.find(prefix); found != hooks.end()) {
     return parsed_command_t{ cmdmap, format, *found->second };
   } else {
-    return maybe_parsed_t{ std::nullopt };
-  }
-}
-
-/*
- * Note: validate_command() is executed with servers_tbl_rwlock held as shared
- */
-bool AdminSocket::validate_command(const parsed_command_t& parsed,
-                                   const std::string& command_text,
-                                   ceph::bufferlist& out) const
-{
-  logger().info("{}: validating {} against:{}", __func__, command_text,
-                parsed.hook.desc);
-
-  stringstream os;  // for possible validation error messages
-  if (validate_cmd(nullptr, std::string{parsed.hook.desc}, parsed.parameters, os)) {
-    return true;
-  } else {
-    os << "error: command validation failure ";
-    logger().error("{}: validation failure (incoming:{}) {}", __func__,
-                  command_text, os.str());
-    out.append(os);
-    return false;
+    return tell_result_t{-EINVAL,
+                         fmt::format("unknown command '{}'", prefix),
+                         std::move(out)};
   }
 }
 
@@ -136,25 +122,56 @@ seastar::future<> AdminSocket::finalize_response(
     .then([&out, outbuf_cont] { return out.write(outbuf_cont.c_str()); });
 }
 
+
+seastar::future<> AdminSocket::handle_command(crimson::net::ConnectionRef conn,
+                                             boost::intrusive_ptr<MCommand> m)
+{
+  return execute_command(m->cmd, std::move(m->get_data())).then(
+    [conn, tid=m->get_tid()](auto result) {
+    auto [ret, err, out] = std::move(result);
+    auto reply = make_message<MCommandReply>(ret, err);
+    reply->set_tid(tid);
+    reply->set_data(out);
+    return conn->send(reply);
+  });
+}
+
 seastar::future<> AdminSocket::execute_line(std::string cmdline,
                                             seastar::output_stream<char>& out)
+{
+  return execute_command({cmdline}, {}).then([&out, this](auto result) {
+     auto [ret, stderr, stdout] = std::move(result);
+     if (ret < 0) {
+       stdout.append(fmt::format("ERROR: {}\n", cpp_strerror(ret)));
+       stdout.append(stderr);
+     }
+     return finalize_response(out, std::move(stdout));
+  });
+}
+
+auto AdminSocket::execute_command(const std::vector<std::string>& cmd,
+                                 ceph::bufferlist&& buf)
+  -> seastar::future<tell_result_t>
 {
   return seastar::with_shared(servers_tbl_rwlock,
-                             [this, cmdline, &out]() mutable {
-    ceph::bufferlist err;
-    auto parsed = parse_cmd(cmdline, err);
-    if (!parsed.has_value() ||
-       !validate_command(*parsed, cmdline, err)) {
-      return finalize_response(out, std::move(err));
+                             [cmd, buf=std::move(buf), this]() mutable {
+    auto maybe_parsed = parse_cmd(cmd);
+    if (auto parsed = std::get_if<parsed_command_t>(&maybe_parsed); parsed) {
+      stringstream os;
+      string desc{parsed->hook.desc};
+      if (!validate_cmd(nullptr, desc, parsed->params, os)) {
+       logger().error("AdminSocket::execute_command: "
+                      "failed to validate '{}': {}", cmd, os.str());
+       ceph::bufferlist out;
+       out.append(os);
+       return seastar::make_ready_future<tell_result_t>(
+          tell_result_t{-EINVAL, "invalid command json", std::move(out)});
+      }
+      return parsed->hook.call(parsed->params, parsed->format, std::move(buf));
+    } else {
+      auto& result = std::get<tell_result_t>(maybe_parsed);
+      return seastar::make_ready_future<tell_result_t>(std::move(result));
     }
-    return parsed->hook.call(parsed->hook.prefix,
-                            parsed->format,
-                            parsed->parameters).then(
-      [this, &out](auto result) {
-       // add 'failed' to the contents of out_buf? not what
-       // happens in the old code
-        return finalize_response(out, std::move(result));
-      });
   });
 }
 
@@ -199,8 +216,7 @@ seastar::future<> AdminSocket::handle_client(seastar::input_stream<char>& in,
     return in.close();
   }).handle_exception([](auto ep) {
     logger().debug("exception on {}: {}", __func__, ep);
-    return seastar::make_ready_future<>();
-  }).discard_result();
+  });
 }
 
 seastar::future<> AdminSocket::start(const std::string& path)
@@ -213,9 +229,15 @@ seastar::future<> AdminSocket::start(const std::string& path)
 
   logger().debug("{}: asok socket path={}", __func__, path);
   auto sock_path = seastar::socket_address{ seastar::unix_domain_addr{ path } };
-  server_sock = seastar::engine().listen(sock_path);
+  try {
+    server_sock = seastar::engine().listen(sock_path);
+  } catch (const std::system_error& e) {
+    logger().error("{}: unable to listen({}): {}", __func__, path, e.what());
+    server_sock.reset();
+    return seastar::make_ready_future<>();
+  }
   // listen in background
-  std::ignore = seastar::do_until(
+  task = seastar::do_until(
     [this] { return stop_gate.is_closed(); },
     [this] {
       return seastar::with_gate(stop_gate, [this] {
@@ -236,11 +258,9 @@ seastar::future<> AdminSocket::start(const std::string& path)
           }
         });
       });
-    }).then([] {
-      logger().debug("AdminSocket::init(): admin-sock thread terminated");
-      return seastar::now();
+    }).finally([path] {
+      return seastar::remove_file(path);
     });
-
   return seastar::make_ready_future<>();
 }
 
@@ -250,13 +270,17 @@ seastar::future<> AdminSocket::stop()
     return seastar::now();
   }
   server_sock->abort_accept();
-  server_sock.reset();
   if (connected_sock) {
     connected_sock->shutdown_input();
     connected_sock->shutdown_output();
-    connected_sock.reset();
   }
-  return stop_gate.close();
+  return stop_gate.close().then([this] {
+    assert(task.has_value());
+    return task->then([] {
+      logger().info("AdminSocket: stopped");
+      return seastar::now();
+    });
+  });
 }
 
 /////////////////////////////////////////
@@ -266,11 +290,11 @@ seastar::future<> AdminSocket::stop()
 class VersionHook final : public AdminSocketHook {
  public:
   VersionHook()
-    : AdminSocketHook{"version", "version", "get ceph version"}
+    : AdminSocketHook{"version", "", "get ceph version"}
   {}
-  seastar::future<bufferlist> call(std::string_view,
-                                  std::string_view format,
-                                  const cmdmap_t&) const final
+  seastar::future<tell_result_t> call(const cmdmap_t&,
+                                     std::string_view format,
+                                     ceph::bufferlist&&) const final
   {
     unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
     f->open_object_section("version");
@@ -278,9 +302,7 @@ class VersionHook final : public AdminSocketHook {
     f->dump_string("release", ceph_release_to_str());
     f->dump_string("release_type", ceph_release_type());
     f->close_section();
-    bufferlist out;
-    f->flush(out);
-    return seastar::make_ready_future<bufferlist>(std::move(out));
+    return seastar::make_ready_future<tell_result_t>(std::move(f));
   }
 };
 
@@ -291,19 +313,17 @@ class VersionHook final : public AdminSocketHook {
 class GitVersionHook final : public AdminSocketHook {
  public:
   GitVersionHook()
-    : AdminSocketHook{"git_version", "git_version", "get git sha1"}
+    : AdminSocketHook{"git_version", "", "get git sha1"}
   {}
-  seastar::future<bufferlist> call(std::string_view command,
-                                  std::string_view format,
-                                  const cmdmap_t&) const final
+  seastar::future<tell_result_t> call(const cmdmap_t&,
+                                     std::string_view format,
+                                     ceph::bufferlist&&) const final
   {
     unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
     f->open_object_section("version");
     f->dump_string("git_version", git_version_to_str());
     f->close_section();
-    ceph::bufferlist out;
-    f->flush(out);
-    return seastar::make_ready_future<bufferlist>(std::move(out));
+    return seastar::make_ready_future<tell_result_t>(std::move(f));
   }
 };
 
@@ -312,16 +332,16 @@ class HelpHook final : public AdminSocketHook {
 
  public:
   explicit HelpHook(const AdminSocket& as) :
-    AdminSocketHook{"help", "help", "list available commands"},
+    AdminSocketHook{"help", "", "list available commands"},
     m_as{as}
   {}
 
-  seastar::future<bufferlist> call(std::string_view command,
-                                  std::string_view format,
-                                  const cmdmap_t& cmdmap) const final
+  seastar::future<tell_result_t> call(const cmdmap_t&,
+                                     std::string_view format,
+                                     ceph::bufferlist&&) const final
   {
     return seastar::with_shared(m_as.servers_tbl_rwlock,
-                               [this, format] {
+                               [format, this] {
       unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
       f->open_object_section("help");
       for (const auto& [prefix, hook] : m_as) {
@@ -330,9 +350,7 @@ class HelpHook final : public AdminSocketHook {
        }
       }
       f->close_section();
-      ceph::bufferlist out;
-      f->flush(out);
-      return seastar::make_ready_future<bufferlist>(std::move(out));
+      return seastar::make_ready_future<tell_result_t>(std::move(f));
     });
   }
 };
@@ -343,29 +361,142 @@ class GetdescsHook final : public AdminSocketHook {
  public:
   explicit GetdescsHook(const AdminSocket& as) :
     AdminSocketHook{"get_command_descriptions",
-                   "get_command_descriptions",
+                   "",
                    "list available commands"},
     m_as{ as } {}
 
-  seastar::future<bufferlist> call(std::string_view command,
-                                  std::string_view format,
-                                  const cmdmap_t& cmdmap) const final
+  seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
+                                     std::string_view format,
+                                     ceph::bufferlist&&) const final
   {
-    unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
-    return seastar::with_shared(m_as.servers_tbl_rwlock, [this, f=move(f)] {
+    return seastar::with_shared(m_as.servers_tbl_rwlock, [format, this] {
+      unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
       int cmdnum = 0;
       f->open_object_section("command_descriptions");
       for (const auto& [prefix, hook] : m_as) {
        auto secname = fmt::format("cmd {:>03}", cmdnum);
+        auto cmd = fmt::format("{} {}", hook->prefix, hook->desc);
         dump_cmd_and_help_to_json(f.get(), CEPH_FEATURES_ALL, secname,
-                                  std::string{hook->desc},
-                                 std::string{hook->help});
+                                  cmd, std::string{hook->help});
         cmdnum++;
       }
       f->close_section();
-      ceph::bufferlist out;
-      f->flush(out);
-      return seastar::make_ready_future<bufferlist>(std::move(out));
+      return seastar::make_ready_future<tell_result_t>(std::move(f));
+    });
+  }
+};
+
+class InjectArgsHook final : public AdminSocketHook {
+public:
+  InjectArgsHook()
+    : AdminSocketHook{"injectargs",
+                      "name=injected_args,type=CephString,n=N",
+                      "inject configuration arguments into running daemon"}
+  {}
+  seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
+                                     std::string_view format,
+                                     ceph::bufferlist&&) const final
+  {
+    std::vector<std::string> argv;
+    if (!cmd_getval(cmdmap, "injected_args", argv)) {
+      return seastar::make_ready_future<tell_result_t>();
+    }
+    const std::string args = boost::algorithm::join(argv, " ");
+    return local_conf().inject_args(args).then([] {
+      return seastar::make_ready_future<tell_result_t>();
+    }).handle_exception_type([] (const std::invalid_argument& e) {
+      return seastar::make_ready_future<tell_result_t>(
+        tell_result_t{-EINVAL, e.what()});
+    });
+  }
+};
+
+/**
+ * listing the configuration values
+ */
+class ConfigShowHook : public AdminSocketHook {
+public:
+  ConfigShowHook() :
+    AdminSocketHook{"config show",
+                    "",
+                    "dump current config settings"}
+  {}
+  seastar::future<tell_result_t> call(const cmdmap_t&,
+                                      std::string_view format,
+                                      ceph::bufferlist&& input) const final
+  {
+    unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
+    f->open_object_section("config_show");
+    local_conf().show_config(f.get());
+    f->close_section();
+    return seastar::make_ready_future<tell_result_t>(std::move(f));
+  }
+};
+
+/**
+ * fetching the value of a specific configuration item
+ */
+class ConfigGetHook : public AdminSocketHook {
+public:
+  ConfigGetHook() :
+    AdminSocketHook("config get",
+                    "name=var,type=CephString",
+                    "config get <field>: get the config value")
+  {}
+  seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
+                                      std::string_view format,
+                                      ceph::bufferlist&& input) const final
+  {
+    std::string var;
+    [[maybe_unused]] bool found = cmd_getval(cmdmap, "var", var);
+    assert(found);
+    std::string conf_val;
+    if (int r = local_conf().get_val(var, &conf_val); r < 0) {
+      return seastar::make_ready_future<tell_result_t>(
+        tell_result_t{r, fmt::format("error getting {}: {}",
+                                     var, cpp_strerror(r))});
+    }
+    unique_ptr<Formatter> f{Formatter::create(format,
+                                              "json-pretty",
+                                              "json-pretty")};
+    f->open_object_section("config_get");
+    f->dump_string(var, conf_val);
+    f->close_section();
+    return seastar::make_ready_future<tell_result_t>(std::move(f));
+  }
+};
+
+/**
+ * setting the value of a specific configuration item (an example:
+ * {"prefix": "config set", "var":"debug_osd", "val": ["30/20"]} )
+ */
+class ConfigSetHook : public AdminSocketHook {
+public:
+  ConfigSetHook()
+    : AdminSocketHook("config set",
+                      "name=var,type=CephString "
+                      "name=val,type=CephString,n=N",
+                      "config set <field> <val> [<val> ...]: set a config variable")
+  {}
+  seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
+                                      std::string_view format,
+                                      ceph::bufferlist&&) const final
+  {
+    std::string var;
+    std::vector<std::string> new_val;
+    cmd_getval(cmdmap, "var", var);
+    cmd_getval(cmdmap, "val", new_val);
+    // val may be multiple words
+    const std::string joined_values = boost::algorithm::join(new_val, " ");
+    return local_conf().set_val(var, joined_values).then([format] {
+      unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
+      f->open_object_section("config_set");
+      f->dump_string("success", "");
+      f->close_section();
+      return seastar::make_ready_future<tell_result_t>(std::move(f));
+    }).handle_exception_type([](std::invalid_argument& e) {
+      return seastar::make_ready_future<tell_result_t>(
+        tell_result_t{-EINVAL, e.what()});
     });
   }
 };
@@ -377,7 +508,14 @@ seastar::future<> AdminSocket::register_admin_commands()
     register_command(std::make_unique<VersionHook>()),
     register_command(std::make_unique<GitVersionHook>()),
     register_command(std::make_unique<HelpHook>(*this)),
-    register_command(std::make_unique<GetdescsHook>(*this)));
+    register_command(std::make_unique<GetdescsHook>(*this)),
+    register_command(std::make_unique<ConfigGetHook>()),
+    register_command(std::make_unique<ConfigSetHook>()),
+    register_command(std::make_unique<ConfigShowHook>()),
+    register_command(std::make_unique<InjectArgsHook>())
+  ).then_unpack([] {
+    return seastar::now();
+  });
 }
 
 }  // namespace crimson::admin