summaryrefslogtreecommitdiffstats
path: root/src/crimson/admin
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/crimson/admin/CMakeLists.txt8
-rw-r--r--src/crimson/admin/admin_socket.cc521
-rw-r--r--src/crimson/admin/admin_socket.h201
-rw-r--r--src/crimson/admin/osd_admin.cc191
-rw-r--r--src/crimson/admin/osd_admin.h22
-rw-r--r--src/crimson/admin/pg_commands.cc159
-rw-r--r--src/crimson/admin/pg_commands.h10
7 files changed, 1112 insertions, 0 deletions
diff --git a/src/crimson/admin/CMakeLists.txt b/src/crimson/admin/CMakeLists.txt
new file mode 100644
index 000000000..aa0771735
--- /dev/null
+++ b/src/crimson/admin/CMakeLists.txt
@@ -0,0 +1,8 @@
+add_library(crimson-admin STATIC
+ admin_socket.cc
+ osd_admin.cc
+ pg_commands.cc)
+
+target_link_libraries(crimson-admin
+ crimson::cflags
+ Boost::MPL)
diff --git a/src/crimson/admin/admin_socket.cc b/src/crimson/admin/admin_socket.cc
new file mode 100644
index 000000000..852185af1
--- /dev/null
+++ b/src/crimson/admin/admin_socket.cc
@@ -0,0 +1,521 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#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;
+
+namespace {
+seastar::logger& logger()
+{
+ return crimson::get_logger(ceph_subsys_osd);
+}
+} // namespace
+
+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)
+{
+ return seastar::with_lock(servers_tbl_rwlock,
+ [this, hook = std::move(hook)]() mutable {
+ auto prefix = hook->prefix;
+ auto [it, added] = hooks.emplace(prefix, std::move(hook));
+ // was this server tag already registered?
+ assert(added);
+ if (added) {
+ logger().info("register_command(): {})", it->first);
+ }
+ return seastar::now();
+ });
+}
+
+/*
+ * Note: parse_cmd() is executed with servers_tbl_rwlock held as shared
+ */
+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)) {
+ logger().error("{}: incoming command error: {}", __func__, errss.str());
+ out.append("error:"s);
+ out.append(errss.str());
+ return tell_result_t{-EINVAL, "invalid json", std::move(out)};
+ }
+ } catch (const std::runtime_error& e) {
+ logger().error("{}: incoming command syntax: {}", __func__, cmd);
+ out.append(string{e.what()});
+ return tell_result_t{-EINVAL, "invalid json", std::move(out)};
+ }
+
+ string format;
+ string prefix;
+ try {
+ cmd_getval(cmdmap, "format", format);
+ cmd_getval(cmdmap, "prefix", prefix);
+ } catch (const bad_cmd_get& e) {
+ logger().error("{}: invalid syntax: {}", __func__, cmd);
+ 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 tell_result_t{-EINVAL,
+ fmt::format("unknown command '{}'", prefix),
+ std::move(out)};
+ }
+}
+
+seastar::future<> AdminSocket::finalize_response(
+ seastar::output_stream<char>& out, ceph::bufferlist&& msgs)
+{
+ string outbuf_cont = msgs.to_str();
+ if (outbuf_cont.empty()) {
+ outbuf_cont = " {} ";
+ }
+ uint32_t response_length = htonl(outbuf_cont.length());
+ logger().info("asok response length: {}", outbuf_cont.length());
+
+ return out.write((char*)&response_length, sizeof(uint32_t))
+ .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,
+ [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));
+ }
+ });
+}
+
+// an input_stream consumer that reads buffer into a std::string up to the first
+// '\0' which indicates the end of command
+struct line_consumer {
+ using tmp_buf = seastar::temporary_buffer<char>;
+ using consumption_result_type =
+ typename seastar::input_stream<char>::consumption_result_type;
+
+ seastar::future<consumption_result_type> operator()(tmp_buf&& buf) {
+ size_t consumed = 0;
+ for (auto c : buf) {
+ consumed++;
+ if (c == '\0') {
+ buf.trim_front(consumed);
+ return seastar::make_ready_future<consumption_result_type>(
+ consumption_result_type::stop_consuming_type(std::move(buf)));
+ } else {
+ line.push_back(c);
+ }
+ }
+ return seastar::make_ready_future<consumption_result_type>(
+ seastar::continue_consuming{});
+ }
+ std::string line;
+};
+
+seastar::future<> AdminSocket::handle_client(seastar::input_stream<char>& in,
+ seastar::output_stream<char>& out)
+{
+ auto consumer = seastar::make_shared<line_consumer>();
+ return in.consume(*consumer).then([consumer, &out, this] {
+ logger().debug("AdminSocket::handle_client: incoming asok string: {}",
+ consumer->line);
+ return execute_line(consumer->line, out);
+ }).then([&out] {
+ return out.flush();
+ }).finally([&out] {
+ return out.close();
+ }).then([&in] {
+ return in.close();
+ }).handle_exception([](auto ep) {
+ logger().debug("exception on {}: {}", __func__, ep);
+ });
+}
+
+seastar::future<> AdminSocket::start(const std::string& path)
+{
+ if (path.empty()) {
+ logger().error(
+ "{}: Admin Socket socket path missing from the configuration", __func__);
+ return seastar::now();
+ }
+
+ logger().debug("{}: asok socket path={}", __func__, path);
+ auto sock_path = seastar::socket_address{ seastar::unix_domain_addr{ 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
+ task = seastar::do_until(
+ [this] { return stop_gate.is_closed(); },
+ [this] {
+ return seastar::with_gate(stop_gate, [this] {
+ assert(!connected_sock.has_value());
+ return server_sock->accept().then([this](seastar::accept_result acc) {
+ connected_sock = std::move(acc.connection);
+ return seastar::do_with(connected_sock->input(),
+ connected_sock->output(),
+ [this](auto& input, auto& output) mutable {
+ return handle_client(input, output);
+ }).finally([this] {
+ assert(connected_sock.has_value());
+ connected_sock.reset();
+ });
+ }).handle_exception([this](auto ep) {
+ if (!stop_gate.is_closed()) {
+ logger().error("AdminSocket: terminated: {}", ep);
+ }
+ });
+ });
+ }).finally([path] {
+ return seastar::remove_file(path);
+ });
+ return seastar::make_ready_future<>();
+}
+
+seastar::future<> AdminSocket::stop()
+{
+ if (!server_sock) {
+ return seastar::now();
+ }
+ server_sock->abort_accept();
+ if (connected_sock) {
+ connected_sock->shutdown_input();
+ connected_sock->shutdown_output();
+ }
+ return stop_gate.close().then([this] {
+ assert(task.has_value());
+ return task->then([] {
+ logger().info("AdminSocket: stopped");
+ return seastar::now();
+ });
+ });
+}
+
+/////////////////////////////////////////
+// the internal hooks
+/////////////////////////////////////////
+
+class VersionHook final : public AdminSocketHook {
+ public:
+ VersionHook()
+ : AdminSocketHook{"version", "", "get ceph version"}
+ {}
+ 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("version", ceph_version_to_str());
+ f->dump_string("release", ceph_release_to_str());
+ f->dump_string("release_type", ceph_release_type());
+ f->close_section();
+ return seastar::make_ready_future<tell_result_t>(std::move(f));
+ }
+};
+
+/**
+ Note that the git_version command is expected to return a 'version' JSON
+ segment.
+*/
+class GitVersionHook final : public AdminSocketHook {
+ public:
+ GitVersionHook()
+ : AdminSocketHook{"git_version", "", "get git sha1"}
+ {}
+ 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();
+ return seastar::make_ready_future<tell_result_t>(std::move(f));
+ }
+};
+
+class HelpHook final : public AdminSocketHook {
+ const AdminSocket& m_as;
+
+ public:
+ explicit HelpHook(const AdminSocket& as) :
+ AdminSocketHook{"help", "", "list available commands"},
+ m_as{as}
+ {}
+
+ 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,
+ [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) {
+ if (!hook->help.empty()) {
+ f->dump_string(prefix.data(), hook->help);
+ }
+ }
+ f->close_section();
+ return seastar::make_ready_future<tell_result_t>(std::move(f));
+ });
+ }
+};
+
+class GetdescsHook final : public AdminSocketHook {
+ const AdminSocket& m_as;
+
+ public:
+ explicit GetdescsHook(const AdminSocket& as) :
+ AdminSocketHook{"get_command_descriptions",
+ "",
+ "list available commands"},
+ m_as{ as } {}
+
+ seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
+ std::string_view format,
+ ceph::bufferlist&&) const final
+ {
+ 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,
+ cmd, std::string{hook->help});
+ cmdnum++;
+ }
+ f->close_section();
+ 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()});
+ });
+ }
+};
+
+/// the hooks that are served directly by the admin_socket server
+seastar::future<> AdminSocket::register_admin_commands()
+{
+ return seastar::when_all_succeed(
+ 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<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
diff --git a/src/crimson/admin/admin_socket.h b/src/crimson/admin/admin_socket.h
new file mode 100644
index 000000000..a842b62a2
--- /dev/null
+++ b/src/crimson/admin/admin_socket.h
@@ -0,0 +1,201 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#pragma once
+
+/**
+ A Crimson-wise version of the src/common/admin_socket.h
+
+ Note: assumed to be running on a single core.
+*/
+#include <map>
+#include <string>
+#include <string_view>
+
+#include <seastar/core/future.hh>
+#include <seastar/core/gate.hh>
+#include <seastar/core/iostream.hh>
+#include <seastar/core/shared_mutex.hh>
+#include <seastar/core/shared_ptr.hh>
+#include <seastar/net/api.hh>
+
+#include "common/cmdparse.h"
+#include "include/buffer.h"
+#include "crimson/net/Fwd.h"
+
+using namespace std::literals;
+
+class MCommand;
+
+namespace crimson::admin {
+
+class AdminSocket;
+
+struct tell_result_t {
+ int ret = 0;
+ std::string err;
+ ceph::bufferlist out;
+ tell_result_t() = default;
+ tell_result_t(int ret, std::string&& err);
+ tell_result_t(int ret, std::string&& err, ceph::bufferlist&& out);
+ /**
+ * create a \c tell_result_t indicating the successful completion
+ * of command
+ *
+ * \param formatter the content of formatter will be flushed to the
+ * output buffer
+ */
+ tell_result_t(std::unique_ptr<Formatter> formatter);
+};
+
+/**
+ * An abstract class to be inherited by implementations of asock hooks
+ */
+class AdminSocketHook {
+ public:
+ AdminSocketHook(std::string_view prefix,
+ std::string_view desc,
+ std::string_view help) :
+ prefix{prefix}, desc{desc}, help{help}
+ {}
+ /**
+ * handle command defined by cmdmap
+ *
+ * \param cmdmap dictionary holding the named parameters
+ * \param format the expected format of the output
+ * \param input the binary input of the command
+ * \pre \c cmdmap should be validated with \c desc
+ * \retval an instance of \c tell_result_t
+ * \note a negative \c ret should be set to indicate that the hook fails to
+ * fulfill the command either because of an invalid input or other
+ * failures. in that case, a brief reason of the failure should
+ * noted in \c err in the returned value
+ */
+ virtual seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
+ std::string_view format,
+ ceph::bufferlist&& input) const = 0;
+ virtual ~AdminSocketHook() {}
+ const std::string_view prefix;
+ const std::string_view desc;
+ const std::string_view help;
+};
+
+class AdminSocket : public seastar::enable_lw_shared_from_this<AdminSocket> {
+ public:
+ AdminSocket() = default;
+ ~AdminSocket() = default;
+
+ AdminSocket(const AdminSocket&) = delete;
+ AdminSocket& operator=(const AdminSocket&) = delete;
+ AdminSocket(AdminSocket&&) = delete;
+ AdminSocket& operator=(AdminSocket&&) = delete;
+
+ using hook_server_tag = const void*;
+
+ /**
+ * create the async Seastar thread that handles asok commands arriving
+ * over the socket.
+ */
+ seastar::future<> start(const std::string& path);
+
+ seastar::future<> stop();
+
+ /**
+ * register an admin socket hook
+ *
+ * Commands (APIs) are registered under a command string. Incoming
+ * commands are split by spaces and matched against the longest
+ * registered command. For example, if 'foo' and 'foo bar' are
+ * registered, and an incoming command is 'foo bar baz', it is
+ * matched with 'foo bar', while 'foo fud' will match 'foo'.
+ *
+ * \param hook a hook which includes its identifying command string, the
+ * expected call syntax, and some help text.
+ *
+ * A note regarding the help text: if empty, command will not be
+ * included in 'help' output.
+ */
+ seastar::future<> register_command(std::unique_ptr<AdminSocketHook>&& hook);
+
+ /**
+ * Registering the APIs that are served directly by the admin_socket server.
+ */
+ seastar::future<> register_admin_commands();
+ /**
+ * handle a command message by replying an MCommandReply with the same tid
+ *
+ * \param conn connection over which the incoming command message is received
+ * \param m message carrying the command vector and optional input buffer
+ */
+ seastar::future<> handle_command(crimson::net::ConnectionRef conn,
+ boost::intrusive_ptr<MCommand> m);
+
+private:
+ /**
+ * the result of analyzing an incoming command, and locating it in
+ * the registered APIs collection.
+ */
+ struct parsed_command_t {
+ cmdmap_t params;
+ std::string format;
+ const AdminSocketHook& hook;
+ };
+ // and the shorthand:
+ seastar::future<> handle_client(seastar::input_stream<char>& inp,
+ seastar::output_stream<char>& out);
+
+ seastar::future<> execute_line(std::string cmdline,
+ seastar::output_stream<char>& out);
+
+ seastar::future<> finalize_response(seastar::output_stream<char>& out,
+ ceph::bufferlist&& msgs);
+
+ seastar::future<tell_result_t> execute_command(const std::vector<std::string>& cmd,
+ ceph::bufferlist&& buf);
+
+ std::optional<seastar::future<>> task;
+ std::optional<seastar::server_socket> server_sock;
+ std::optional<seastar::connected_socket> connected_sock;
+
+ /**
+ * stopping incoming ASOK requests at shutdown
+ */
+ seastar::gate stop_gate;
+
+ /**
+ * parse the incoming command vector, find a registered hook by looking up by
+ * its prefix, perform sanity checks on the parsed parameters with the hook's
+ * command description
+ *
+ * \param cmd a vector of string which presents a command
+ * \retval on success, a \c parsed_command_t is returned, tell_result_t with
+ * detailed error messages is returned otherwise
+ */
+ std::variant<parsed_command_t, tell_result_t>
+ parse_cmd(const std::vector<std::string>& cmd);
+
+ /**
+ * The servers table is protected by a rw-lock, to be acquired exclusively
+ * only when registering or removing a server.
+ * The lock is locked-shared when executing any hook.
+ */
+ mutable seastar::shared_mutex servers_tbl_rwlock;
+ using hooks_t = std::map<std::string_view, std::unique_ptr<AdminSocketHook>>;
+ hooks_t hooks;
+
+ public:
+ /**
+ * iterator support
+ */
+ hooks_t::const_iterator begin() const {
+ return hooks.cbegin();
+ }
+ hooks_t::const_iterator end() const {
+ return hooks.cend();
+ }
+
+ friend class AdminSocketTest;
+ friend class HelpHook;
+ friend class GetdescsHook;
+};
+
+} // namespace crimson::admin
diff --git a/src/crimson/admin/osd_admin.cc b/src/crimson/admin/osd_admin.cc
new file mode 100644
index 000000000..ce6b6695d
--- /dev/null
+++ b/src/crimson/admin/osd_admin.cc
@@ -0,0 +1,191 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "crimson/admin/osd_admin.h"
+#include <string>
+#include <string_view>
+
+#include <fmt/format.h>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/future.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/scollectd_api.hh>
+
+#include "common/config.h"
+#include "crimson/admin/admin_socket.h"
+#include "crimson/common/log.h"
+#include "crimson/osd/exceptions.h"
+#include "crimson/osd/osd.h"
+
+using crimson::osd::OSD;
+using namespace crimson::common;
+
+namespace crimson::admin {
+
+using crimson::common::local_conf;
+
+template <class Hook, class... Args>
+std::unique_ptr<AdminSocketHook> make_asok_hook(Args&&... args)
+{
+ return std::make_unique<Hook>(std::forward<Args>(args)...);
+}
+
+/**
+ * An OSD admin hook: OSD status
+ */
+class OsdStatusHook : public AdminSocketHook {
+public:
+ explicit OsdStatusHook(const crimson::osd::OSD& osd) :
+ AdminSocketHook{"status", "", "OSD status"},
+ osd(osd)
+ {}
+ 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("status");
+ osd.dump_status(f.get());
+ f->close_section();
+ return seastar::make_ready_future<tell_result_t>(std::move(f));
+ }
+private:
+ const crimson::osd::OSD& osd;
+};
+template std::unique_ptr<AdminSocketHook>
+make_asok_hook<OsdStatusHook>(const crimson::osd::OSD& osd);
+
+/**
+ * An OSD admin hook: send beacon
+ */
+class SendBeaconHook : public AdminSocketHook {
+public:
+ explicit SendBeaconHook(crimson::osd::OSD& osd) :
+ AdminSocketHook{"send_beacon",
+ "",
+ "send OSD beacon to mon immediately"},
+ osd(osd)
+ {}
+ seastar::future<tell_result_t> call(const cmdmap_t&,
+ std::string_view format,
+ ceph::bufferlist&& input) const final
+ {
+ return osd.send_beacon().then([] {
+ return seastar::make_ready_future<tell_result_t>();
+ });
+ }
+private:
+ crimson::osd::OSD& osd;
+};
+template std::unique_ptr<AdminSocketHook>
+make_asok_hook<SendBeaconHook>(crimson::osd::OSD& osd);
+
+/**
+ * send the latest pg stats to mgr
+ */
+class FlushPgStatsHook : public AdminSocketHook {
+public:
+ explicit FlushPgStatsHook(crimson::osd::OSD& osd) :
+ AdminSocketHook("flush_pg_stats",
+ "",
+ "flush pg stats"),
+ osd{osd}
+ {}
+ seastar::future<tell_result_t> call(const cmdmap_t&,
+ std::string_view format,
+ ceph::bufferlist&& input) const final
+ {
+ uint64_t seq = osd.send_pg_stats();
+ unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
+ f->dump_unsigned("stat_seq", seq);
+ return seastar::make_ready_future<tell_result_t>(std::move(f));
+ }
+
+private:
+ crimson::osd::OSD& osd;
+};
+template std::unique_ptr<AdminSocketHook> make_asok_hook<FlushPgStatsHook>(crimson::osd::OSD& osd);
+
+/// dump the history of PGs' peering state
+class DumpPGStateHistory final: public AdminSocketHook {
+public:
+ explicit DumpPGStateHistory(const crimson::osd::OSD &osd) :
+ AdminSocketHook{"dump_pgstate_history",
+ "",
+ "dump history of PGs' peering state"},
+ osd{osd}
+ {}
+ seastar::future<tell_result_t> call(const cmdmap_t&,
+ std::string_view format,
+ ceph::bufferlist&& input) const final
+ {
+ std::unique_ptr<Formatter> f{Formatter::create(format,
+ "json-pretty",
+ "json-pretty")};
+ f->open_object_section("pgstate_history");
+ osd.dump_pg_state_history(f.get());
+ f->close_section();
+ return seastar::make_ready_future<tell_result_t>(std::move(f));
+ }
+private:
+ const crimson::osd::OSD& osd;
+};
+template std::unique_ptr<AdminSocketHook> make_asok_hook<DumpPGStateHistory>(const crimson::osd::OSD& osd);
+
+/**
+ * A CephContext admin hook: calling assert (if allowed by
+ * 'debug_asok_assert_abort')
+ */
+class AssertAlwaysHook : public AdminSocketHook {
+public:
+ AssertAlwaysHook() :
+ AdminSocketHook{"assert",
+ "",
+ "asserts"}
+ {}
+ seastar::future<tell_result_t> call(const cmdmap_t&,
+ std::string_view format,
+ ceph::bufferlist&& input) const final
+ {
+ if (local_conf().get_val<bool>("debug_asok_assert_abort")) {
+ ceph_assert_always(0);
+ return seastar::make_ready_future<tell_result_t>();
+ } else {
+ return seastar::make_ready_future<tell_result_t>(
+ tell_result_t{-EPERM, "configuration set to disallow asok assert"});
+ }
+ }
+};
+template std::unique_ptr<AdminSocketHook> make_asok_hook<AssertAlwaysHook>();
+
+/**
+* A Seastar admin hook: fetching the values of configured metrics
+*/
+class SeastarMetricsHook : public AdminSocketHook {
+public:
+ SeastarMetricsHook() :
+ AdminSocketHook("perf dump_seastar",
+ "",
+ "dump current configured seastar metrics and their values")
+ {}
+ seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
+ std::string_view format,
+ ceph::bufferlist&& input) const final
+ {
+ std::unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
+ f->open_object_section("perf_dump_seastar");
+ for (const auto& mf : seastar::scollectd::get_value_map()) {
+ for (const auto& m : mf.second) {
+ if (m.second && m.second->is_enabled()) {
+ auto& metric_function = m.second->get_function();
+ f->dump_float(m.second->get_id().full_name(), metric_function().d());
+ }
+ }
+ }
+ f->close_section();
+ return seastar::make_ready_future<tell_result_t>(std::move(f));
+ }
+};
+template std::unique_ptr<AdminSocketHook> make_asok_hook<SeastarMetricsHook>();
+
+} // namespace crimson::admin
diff --git a/src/crimson/admin/osd_admin.h b/src/crimson/admin/osd_admin.h
new file mode 100644
index 000000000..395042ea8
--- /dev/null
+++ b/src/crimson/admin/osd_admin.h
@@ -0,0 +1,22 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#pragma once
+
+#include <memory>
+
+#include "admin_socket.h"
+
+namespace crimson::admin {
+
+class AssertAlwaysHook;
+class FlushPgStatsHook;
+class OsdStatusHook;
+class SendBeaconHook;
+class DumpPGStateHistory;
+class SeastarMetricsHook;
+
+
+template<class Hook, class... Args>
+std::unique_ptr<AdminSocketHook> make_asok_hook(Args&&... args);
+
+} // namespace crimson::admin
diff --git a/src/crimson/admin/pg_commands.cc b/src/crimson/admin/pg_commands.cc
new file mode 100644
index 000000000..dacfd515d
--- /dev/null
+++ b/src/crimson/admin/pg_commands.cc
@@ -0,0 +1,159 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "crimson/admin/pg_commands.h"
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include <fmt/format.h>
+#include <seastar/core/future.hh>
+
+#include "crimson/admin/admin_socket.h"
+#include "crimson/osd/osd.h"
+#include "crimson/osd/pg.h"
+
+
+using crimson::osd::OSD;
+using crimson::osd::PG;
+using namespace crimson::common;
+
+
+namespace crimson::admin::pg {
+
+class PGCommand : public AdminSocketHook {
+public:
+ // TODO: const correctness of osd
+ PGCommand(crimson::osd::OSD& osd,
+ std::string_view prefix,
+ std::string_view desc,
+ std::string_view help)
+ : AdminSocketHook{prefix, desc, help}, osd {osd}
+ {}
+ seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
+ std::string_view format,
+ ceph::bufferlist&& input) const final
+ {
+ // we have "ceph tell <pgid> <cmd>". and it is the ceph cli's responsibility
+ // to add "pgid" to the cmd dict. as rados_pg_command() does not set it for
+ // us. moreover, and "pgid" is not listed in the command description, as user
+ // command format does not follow the convention of "<prefix> [<args>,...]"
+ // so we have to verify it on the server side.
+ std::string pgid_str;
+ pg_t pgid;
+ if (!cmd_getval(cmdmap, "pgid", pgid_str)) {
+ return seastar::make_ready_future<tell_result_t>(
+ tell_result_t{-EINVAL, "no pgid specified"});
+ } else if (!pgid.parse(pgid_str.c_str())) {
+ return seastar::make_ready_future<tell_result_t>(
+ tell_result_t{-EINVAL, fmt::format("couldn't parse pgid '{}'", pgid_str)});
+ }
+ // am i the primary for this pg?
+ const auto osdmap = osd.get_shard_services().get_osdmap();
+ spg_t spg_id;
+ if (!osdmap->get_primary_shard(pgid, &spg_id)) {
+ return seastar::make_ready_future<tell_result_t>(tell_result_t{
+ -ENOENT, fmt::format("pgid '{}' does not exist", pgid_str)});
+ }
+ Ref<PG> pg = osd.get_pg(spg_id);
+ if (!pg) {
+ return seastar::make_ready_future<tell_result_t>(tell_result_t{
+ -ENOENT, fmt::format("i don't have pgid '{}'", spg_id)});
+ }
+ if (!pg->is_primary()) {
+ return seastar::make_ready_future<tell_result_t>(tell_result_t{
+ -EAGAIN, fmt::format("not primary for pgid '{}'", spg_id)});
+ }
+ return this->do_command(pg, cmdmap, format, std::move(input));
+ }
+
+private:
+ virtual seastar::future<tell_result_t>
+ do_command(Ref<PG> pg,
+ const cmdmap_t& cmdmap,
+ std::string_view format,
+ ceph::bufferlist&& input) const = 0;
+
+ OSD& osd;
+};
+
+class QueryCommand final : public PGCommand {
+public:
+ // TODO: const correctness of osd
+ explicit QueryCommand(crimson::osd::OSD& osd) :
+ PGCommand{osd,
+ "query",
+ "",
+ "show details of a specific pg"}
+ {}
+private:
+ seastar::future<tell_result_t>
+ do_command(Ref<PG> pg,
+ const cmdmap_t&,
+ std::string_view format,
+ ceph::bufferlist&& input) const final
+ {
+ std::unique_ptr<Formatter> f{Formatter::create(format,
+ "json-pretty",
+ "json-pretty")};
+ f->open_object_section("pg");
+ pg->dump_primary(f.get());
+ f->close_section();
+ return seastar::make_ready_future<tell_result_t>(std::move(f));
+ }
+};
+
+class MarkUnfoundLostCommand final : public PGCommand {
+public:
+ explicit MarkUnfoundLostCommand(crimson::osd::OSD& osd) :
+ PGCommand{osd,
+ "mark_unfound_lost",
+ "name=pgid,type=CephPgid,req=false"
+ " name=mulcmd,type=CephChoices,strings=revert|delete",
+ "mark all unfound objects in this pg as lost, either"
+ " removing or reverting to a prior version if one is"
+ " available"}
+ {}
+ seastar::future<tell_result_t>
+ do_command(Ref<PG> pg,
+ const cmdmap_t& cmdmap,
+ std::string_view,
+ ceph::bufferlist&&) const final
+ {
+ // what to do with the unfound object specifically.
+ std::string cmd;
+ int op = -1;
+ cmd_getval(cmdmap, "mulcmd", cmd);
+ if (cmd == "revert") {
+ op = pg_log_entry_t::LOST_REVERT;
+ } else if (cmd == "delete") {
+ op = pg_log_entry_t::LOST_DELETE;
+ } else {
+ return seastar::make_ready_future<tell_result_t>(tell_result_t{
+ -EINVAL, "mode must be 'revert' or 'delete'; mark not yet implemented"});
+ }
+ return pg->mark_unfound_lost(op).then([] {
+ // TODO
+ return seastar::make_ready_future<tell_result_t>();
+ });
+ }
+};
+
+} // namespace crimson::admin::pg
+
+namespace crimson::admin {
+
+template <class Hook, class... Args>
+std::unique_ptr<AdminSocketHook> make_asok_hook(Args&&... args)
+{
+ return std::make_unique<Hook>(std::forward<Args>(args)...);
+}
+
+template std::unique_ptr<AdminSocketHook>
+make_asok_hook<crimson::admin::pg::QueryCommand>(crimson::osd::OSD& osd);
+
+template std::unique_ptr<AdminSocketHook>
+make_asok_hook<crimson::admin::pg::MarkUnfoundLostCommand>(crimson::osd::OSD& osd);
+
+} // namespace crimson::admin
diff --git a/src/crimson/admin/pg_commands.h b/src/crimson/admin/pg_commands.h
new file mode 100644
index 000000000..873b3c923
--- /dev/null
+++ b/src/crimson/admin/pg_commands.h
@@ -0,0 +1,10 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+#pragma once
+
+namespace crimson::admin::pg {
+
+class QueryCommand;
+class MarkUnfoundLostCommand;
+
+} // namespace crimson::admin::pg