#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()
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)
{
/*
* 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;
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)};
}
}
.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));
- });
});
}
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)
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] {
}
});
});
- }).then([] {
- logger().debug("AdminSocket::init(): admin-sock thread terminated");
- return seastar::now();
+ }).finally([path] {
+ return seastar::remove_file(path);
});
-
return seastar::make_ready_future<>();
}
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();
+ });
+ });
}
/////////////////////////////////////////
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");
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));
}
};
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));
}
};
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) {
}
}
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));
});
}
};
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()});
});
}
};
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