1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #include "crimson/admin/admin_socket.h"
6 #include <boost/algorithm/string/join.hpp>
7 #include <fmt/format.h>
8 #include <fmt/ranges.h>
9 #include <seastar/net/api.hh>
10 #include <seastar/net/inet_address.hh>
11 #include <seastar/core/future-util.hh>
12 #include <seastar/core/reactor.hh>
13 #include <seastar/core/sleep.hh>
14 #include <seastar/core/thread.hh>
15 #include <seastar/util/std-compat.hh>
17 #include "common/options.h"
18 #include "common/version.h"
19 #include "messages/MCommand.h"
20 #include "messages/MCommandReply.h"
21 #include "crimson/common/log.h"
22 #include "crimson/net/Socket.h"
24 using namespace crimson::common
;
25 using namespace std::literals
;
26 using ceph::common::cmdmap_from_json
;
27 using ceph::common::cmd_getval
;
28 using ceph::common::bad_cmd_get
;
29 using ceph::common::validate_cmd
;
30 using ceph::common::dump_cmd_and_help_to_json
;
33 seastar::logger
& logger()
35 return crimson::get_logger(ceph_subsys_osd
);
40 using std::string_view
;
41 using std::stringstream
;
42 using std::unique_ptr
;
44 namespace crimson::admin
{
46 tell_result_t::tell_result_t(int ret
, std::string
&& err
)
47 : ret
{ret
}, err(std::move(err
))
50 tell_result_t::tell_result_t(int ret
, std::string
&& err
, ceph::bufferlist
&& out
)
51 : ret
{ret
}, err(std::move(err
)), out(std::move(out
))
54 tell_result_t::tell_result_t(std::unique_ptr
<Formatter
> formatter
)
56 formatter
->flush(out
);
59 void AdminSocket::register_command(std::unique_ptr
<AdminSocketHook
>&& hook
)
61 auto prefix
= hook
->prefix
;
62 auto [it
, added
] = hooks
.emplace(prefix
, std::move(hook
));
64 logger().info("register_command(): {})", it
->first
);
67 auto AdminSocket::parse_cmd(const std::vector
<std::string
>& cmd
)
68 -> std::variant
<parsed_command_t
, tell_result_t
>
71 // - create the formatter specified by the cmd parameters
72 // - locate the "op-code" string (the 'prefix' segment)
73 // - prepare for command parameters extraction via cmdmap_t
79 // note that cmdmap_from_json() may throw on syntax issues
80 if (!cmdmap_from_json(cmd
, &cmdmap
, errss
)) {
81 logger().error("{}: incoming command error: {}", __func__
, errss
.str());
82 out
.append("error:"s
);
83 out
.append(errss
.str());
84 return tell_result_t
{-EINVAL
, "invalid json", std::move(out
)};
86 } catch (const std::runtime_error
& e
) {
87 logger().error("{}: incoming command syntax: {}", __func__
, cmd
);
88 out
.append(string
{e
.what()});
89 return tell_result_t
{-EINVAL
, "invalid json", std::move(out
)};
95 cmd_getval(cmdmap
, "format", format
);
96 cmd_getval(cmdmap
, "prefix", prefix
);
97 // tolerate old-style pg <pgid> command <args> style formatting
99 cmd_getval(cmdmap
, "cmd", prefix
);
101 } catch (const bad_cmd_get
& e
) {
102 logger().error("{}: invalid syntax: {}", __func__
, cmd
);
103 out
.append(string
{e
.what()});
104 return tell_result_t
{-EINVAL
, "invalid json", std::move(out
)};
107 // match the incoming op-code to one of the registered APIs
108 if (auto found
= hooks
.find(prefix
); found
!= hooks
.end()) {
109 return parsed_command_t
{ cmdmap
, format
, *found
->second
};
111 return tell_result_t
{-EINVAL
,
112 fmt::format("unknown command '{}'", prefix
),
117 seastar::future
<> AdminSocket::finalize_response(
118 seastar::output_stream
<char>& out
, ceph::bufferlist
&& msgs
)
120 string outbuf_cont
= msgs
.to_str();
121 if (outbuf_cont
.empty()) {
122 outbuf_cont
= " {} ";
124 uint32_t response_length
= htonl(outbuf_cont
.length());
125 logger().info("asok response length: {}", outbuf_cont
.length());
127 return out
.write(reinterpret_cast<char*>(&response_length
),
128 sizeof(response_length
))
129 .then([&out
, outbuf_cont
] { return out
.write(outbuf_cont
.c_str()); });
133 seastar::future
<> AdminSocket::handle_command(crimson::net::ConnectionRef conn
,
134 boost::intrusive_ptr
<MCommand
> m
)
136 return execute_command(m
->cmd
, std::move(m
->get_data())).then(
137 [conn
, tid
=m
->get_tid()](auto result
) {
138 auto [ret
, err
, out
] = std::move(result
);
139 auto reply
= crimson::make_message
<MCommandReply
>(ret
, err
);
141 reply
->set_data(out
);
142 return conn
->send(std::move(reply
));
146 seastar::future
<> AdminSocket::execute_line(std::string cmdline
,
147 seastar::output_stream
<char>& out
)
149 return execute_command({std::move(cmdline
)}, {}).then([&out
, this](auto result
) {
150 auto [ret
, stderr
, stdout
] = std::move(result
);
152 stdout
.append(fmt::format("ERROR: {}\n", cpp_strerror(ret
)));
153 stdout
.append(stderr
);
155 return finalize_response(out
, std::move(stdout
));
159 auto AdminSocket::execute_command(const std::vector
<std::string
>& cmd
,
160 ceph::bufferlist
&& buf
)
161 -> seastar::future
<tell_result_t
>
163 auto maybe_parsed
= parse_cmd(cmd
);
164 if (auto* parsed
= std::get_if
<parsed_command_t
>(&maybe_parsed
); parsed
) {
166 string desc
{parsed
->hook
.desc
};
167 if (!validate_cmd(desc
, parsed
->params
, os
)) {
168 logger().error("AdminSocket::execute_command: "
169 "failed to validate '{}': {}", cmd
, os
.str());
170 ceph::bufferlist out
;
172 return seastar::make_ready_future
<tell_result_t
>(
173 tell_result_t
{-EINVAL
, "invalid command json", std::move(out
)});
175 return parsed
->hook
.call(parsed
->params
, parsed
->format
, std::move(buf
));
177 auto& result
= std::get
<tell_result_t
>(maybe_parsed
);
178 return seastar::make_ready_future
<tell_result_t
>(std::move(result
));
182 // an input_stream consumer that reads buffer into a std::string up to the first
183 // '\0' which indicates the end of command
184 struct line_consumer
{
185 using tmp_buf
= seastar::temporary_buffer
<char>;
186 using consumption_result_type
=
187 typename
seastar::input_stream
<char>::consumption_result_type
;
189 seastar::future
<consumption_result_type
> operator()(tmp_buf
&& buf
) {
194 buf
.trim_front(consumed
);
195 return seastar::make_ready_future
<consumption_result_type
>(
196 consumption_result_type::stop_consuming_type(std::move(buf
)));
201 return seastar::make_ready_future
<consumption_result_type
>(
202 seastar::continue_consuming
{});
207 seastar::future
<> AdminSocket::handle_client(seastar::input_stream
<char>& in
,
208 seastar::output_stream
<char>& out
)
210 auto consumer
= seastar::make_shared
<line_consumer
>();
211 return in
.consume(*consumer
).then([consumer
, &out
, this] {
212 logger().debug("AdminSocket::handle_client: incoming asok string: {}",
214 return execute_line(consumer
->line
, out
);
221 }).handle_exception([](auto ep
) {
222 logger().debug("exception on {}: {}", __func__
, ep
);
226 seastar::future
<> AdminSocket::start(const std::string
& path
)
230 "{}: Admin Socket socket path missing from the configuration", __func__
);
231 return seastar::now();
234 logger().debug("{}: asok socket path={}", __func__
, path
);
235 auto sock_path
= seastar::socket_address
{ seastar::unix_domain_addr
{ path
} };
237 server_sock
= seastar::engine().listen(sock_path
);
238 } catch (const std::system_error
& e
) {
239 if (e
.code() == std::errc::address_in_use
) {
240 logger().debug("{}: Admin Socket socket path={} already exists, retrying",
242 return seastar::remove_file(path
).then([this, path
] {
247 logger().error("{}: unable to listen({}): {}", __func__
, path
, e
.what());
249 return seastar::make_ready_future
<>();
251 // listen in background
252 task
= seastar::keep_doing([this] {
253 return seastar::try_with_gate(stop_gate
, [this] {
254 assert(!connected_sock
.has_value());
255 return server_sock
->accept().then([this](seastar::accept_result acc
) {
256 connected_sock
= std::move(acc
.connection
);
257 return seastar::do_with(connected_sock
->input(),
258 connected_sock
->output(),
259 [this](auto& input
, auto& output
) mutable {
260 return handle_client(input
, output
);
262 assert(connected_sock
.has_value());
263 connected_sock
.reset();
265 }).handle_exception([this](auto ep
) {
266 if (!stop_gate
.is_closed()) {
267 logger().error("AdminSocket: terminated: {}", ep
);
271 }).handle_exception_type([](const seastar::gate_closed_exception
&) {
273 return seastar::remove_file(path
);
275 return seastar::make_ready_future
<>();
278 seastar::future
<> AdminSocket::stop()
281 return seastar::now();
283 server_sock
->abort_accept();
284 if (connected_sock
) {
285 connected_sock
->shutdown_input();
286 connected_sock
->shutdown_output();
288 return stop_gate
.close().then([this] {
289 assert(task
.has_value());
290 return task
->then([] {
291 logger().info("AdminSocket: stopped");
292 return seastar::now();
297 /////////////////////////////////////////
298 // the internal hooks
299 /////////////////////////////////////////
301 class VersionHook final
: public AdminSocketHook
{
304 : AdminSocketHook
{"version", "", "get ceph version"}
306 seastar::future
<tell_result_t
> call(const cmdmap_t
&,
307 std::string_view format
,
308 ceph::bufferlist
&&) const final
310 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
311 f
->open_object_section("version");
312 f
->dump_string("version", ceph_version_to_str());
313 f
->dump_string("release", ceph_release_to_str());
314 f
->dump_string("release_type", ceph_release_type());
316 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
321 Note that the git_version command is expected to return a 'version' JSON
324 class GitVersionHook final
: public AdminSocketHook
{
327 : AdminSocketHook
{"git_version", "", "get git sha1"}
329 seastar::future
<tell_result_t
> call(const cmdmap_t
&,
330 std::string_view format
,
331 ceph::bufferlist
&&) const final
333 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
334 f
->open_object_section("version");
335 f
->dump_string("git_version", git_version_to_str());
337 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
341 class HelpHook final
: public AdminSocketHook
{
342 const AdminSocket
& m_as
;
345 explicit HelpHook(const AdminSocket
& as
) :
346 AdminSocketHook
{"help", "", "list available commands"},
350 seastar::future
<tell_result_t
> call(const cmdmap_t
&,
351 std::string_view format
,
352 ceph::bufferlist
&&) const final
354 unique_ptr
<Formatter
> f
{Formatter::create(format
,
355 "json-pretty", "json-pretty")};
356 f
->open_object_section("help");
357 for (const auto& [prefix
, hook
] : m_as
) {
358 if (!hook
->help
.empty()) {
359 f
->dump_string(prefix
.data(), hook
->help
);
363 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
367 class GetdescsHook final
: public AdminSocketHook
{
368 const AdminSocket
& m_as
;
371 explicit GetdescsHook(const AdminSocket
& as
) :
372 AdminSocketHook
{"get_command_descriptions",
374 "list available commands"},
377 seastar::future
<tell_result_t
> call(const cmdmap_t
& cmdmap
,
378 std::string_view format
,
379 ceph::bufferlist
&&) const final
381 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
383 f
->open_object_section("command_descriptions");
384 for (const auto& [prefix
, hook
] : m_as
) {
385 auto secname
= fmt::format("cmd {:>03}", cmdnum
);
386 auto cmd
= fmt::format("{} {}", hook
->prefix
, hook
->desc
);
387 dump_cmd_and_help_to_json(f
.get(), CEPH_FEATURES_ALL
, secname
,
388 cmd
, std::string
{hook
->help
});
392 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
396 class InjectArgsHook final
: public AdminSocketHook
{
399 : AdminSocketHook
{"injectargs",
400 "name=injected_args,type=CephString,n=N",
401 "inject configuration arguments into running daemon"}
403 seastar::future
<tell_result_t
> call(const cmdmap_t
& cmdmap
,
404 std::string_view format
,
405 ceph::bufferlist
&&) const final
407 std::vector
<std::string
> argv
;
408 if (!cmd_getval(cmdmap
, "injected_args", argv
)) {
409 return seastar::make_ready_future
<tell_result_t
>();
411 const std::string args
= boost::algorithm::join(argv
, " ");
412 return local_conf().inject_args(args
).then([] {
413 return seastar::make_ready_future
<tell_result_t
>();
414 }).handle_exception_type([] (const std::invalid_argument
& e
) {
415 return seastar::make_ready_future
<tell_result_t
>(
416 tell_result_t
{-EINVAL
, e
.what()});
422 * listing the configuration values
424 class ConfigShowHook
: public AdminSocketHook
{
427 AdminSocketHook
{"config show",
429 "dump current config settings"}
431 seastar::future
<tell_result_t
> call(const cmdmap_t
&,
432 std::string_view format
,
433 ceph::bufferlist
&& input
) const final
435 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
436 f
->open_object_section("config_show");
437 local_conf().show_config(f
.get());
439 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
444 * fetching the value of a specific configuration item
446 class ConfigGetHook
: public AdminSocketHook
{
449 AdminSocketHook("config get",
450 "name=var,type=CephString",
451 "config get <field>: get the config value")
453 seastar::future
<tell_result_t
> call(const cmdmap_t
& cmdmap
,
454 std::string_view format
,
455 ceph::bufferlist
&& input
) const final
458 [[maybe_unused
]] bool found
= cmd_getval(cmdmap
, "var", var
);
460 std::string conf_val
;
461 if (int r
= local_conf().get_val(var
, &conf_val
); r
< 0) {
462 return seastar::make_ready_future
<tell_result_t
>(
463 tell_result_t
{r
, fmt::format("error getting {}: {}",
464 var
, cpp_strerror(r
))});
466 unique_ptr
<Formatter
> f
{Formatter::create(format
,
469 f
->open_object_section("config_get");
470 f
->dump_string(var
, conf_val
);
472 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
477 * setting the value of a specific configuration item (an example:
478 * {"prefix": "config set", "var":"debug_osd", "val": ["30/20"]} )
480 class ConfigSetHook
: public AdminSocketHook
{
483 : AdminSocketHook("config set",
484 "name=var,type=CephString "
485 "name=val,type=CephString,n=N",
486 "config set <field> <val> [<val> ...]: set a config variable")
488 seastar::future
<tell_result_t
> call(const cmdmap_t
& cmdmap
,
489 std::string_view format
,
490 ceph::bufferlist
&&) const final
493 std::vector
<std::string
> new_val
;
494 cmd_getval(cmdmap
, "var", var
);
495 cmd_getval(cmdmap
, "val", new_val
);
496 // val may be multiple words
497 const std::string joined_values
= boost::algorithm::join(new_val
, " ");
498 return local_conf().set_val(var
, joined_values
).then([format
] {
499 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
500 f
->open_object_section("config_set");
501 f
->dump_string("success", "");
503 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
504 }).handle_exception_type([](std::invalid_argument
& e
) {
505 return seastar::make_ready_future
<tell_result_t
>(
506 tell_result_t
{-EINVAL
, e
.what()});
512 * listing the configuration values
514 class ConfigHelpHook
: public AdminSocketHook
{
517 AdminSocketHook
{"config help",
519 "get config setting schema and descriptions"}
521 seastar::future
<tell_result_t
> call(const cmdmap_t
&,
522 std::string_view format
,
523 ceph::bufferlist
&& input
) const final
525 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
527 f
->open_array_section("options");
528 for (const auto &option
: ceph_options
) {
529 f
->dump_object("option", option
);
532 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
536 /// the hooks that are served directly by the admin_socket server
537 void AdminSocket::register_admin_commands()
539 register_command(std::make_unique
<VersionHook
>());
540 register_command(std::make_unique
<GitVersionHook
>());
541 register_command(std::make_unique
<HelpHook
>(*this));
542 register_command(std::make_unique
<GetdescsHook
>(*this));
543 register_command(std::make_unique
<ConfigGetHook
>());
544 register_command(std::make_unique
<ConfigSetHook
>());
545 register_command(std::make_unique
<ConfigShowHook
>());
546 register_command(std::make_unique
<ConfigHelpHook
>());
547 register_command(std::make_unique
<InjectArgsHook
>());
550 } // namespace crimson::admin