diff options
Diffstat (limited to '')
-rw-r--r-- | src/crimson/admin/CMakeLists.txt | 8 | ||||
-rw-r--r-- | src/crimson/admin/admin_socket.cc | 521 | ||||
-rw-r--r-- | src/crimson/admin/admin_socket.h | 201 | ||||
-rw-r--r-- | src/crimson/admin/osd_admin.cc | 191 | ||||
-rw-r--r-- | src/crimson/admin/osd_admin.h | 22 | ||||
-rw-r--r-- | src/crimson/admin/pg_commands.cc | 159 | ||||
-rw-r--r-- | src/crimson/admin/pg_commands.h | 10 |
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 |