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 logger().error("{}: unable to listen({}): {}", __func__
, path
, e
.what());
241 return seastar::make_ready_future
<>();
243 // listen in background
244 task
= seastar::keep_doing([this] {
245 return seastar::try_with_gate(stop_gate
, [this] {
246 assert(!connected_sock
.has_value());
247 return server_sock
->accept().then([this](seastar::accept_result acc
) {
248 connected_sock
= std::move(acc
.connection
);
249 return seastar::do_with(connected_sock
->input(),
250 connected_sock
->output(),
251 [this](auto& input
, auto& output
) mutable {
252 return handle_client(input
, output
);
254 assert(connected_sock
.has_value());
255 connected_sock
.reset();
257 }).handle_exception([this](auto ep
) {
258 if (!stop_gate
.is_closed()) {
259 logger().error("AdminSocket: terminated: {}", ep
);
263 }).handle_exception_type([](const seastar::gate_closed_exception
&) {
265 return seastar::remove_file(path
);
267 return seastar::make_ready_future
<>();
270 seastar::future
<> AdminSocket::stop()
273 return seastar::now();
275 server_sock
->abort_accept();
276 if (connected_sock
) {
277 connected_sock
->shutdown_input();
278 connected_sock
->shutdown_output();
280 return stop_gate
.close().then([this] {
281 assert(task
.has_value());
282 return task
->then([] {
283 logger().info("AdminSocket: stopped");
284 return seastar::now();
289 /////////////////////////////////////////
290 // the internal hooks
291 /////////////////////////////////////////
293 class VersionHook final
: public AdminSocketHook
{
296 : AdminSocketHook
{"version", "", "get ceph version"}
298 seastar::future
<tell_result_t
> call(const cmdmap_t
&,
299 std::string_view format
,
300 ceph::bufferlist
&&) const final
302 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
303 f
->open_object_section("version");
304 f
->dump_string("version", ceph_version_to_str());
305 f
->dump_string("release", ceph_release_to_str());
306 f
->dump_string("release_type", ceph_release_type());
308 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
313 Note that the git_version command is expected to return a 'version' JSON
316 class GitVersionHook final
: public AdminSocketHook
{
319 : AdminSocketHook
{"git_version", "", "get git sha1"}
321 seastar::future
<tell_result_t
> call(const cmdmap_t
&,
322 std::string_view format
,
323 ceph::bufferlist
&&) const final
325 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
326 f
->open_object_section("version");
327 f
->dump_string("git_version", git_version_to_str());
329 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
333 class HelpHook final
: public AdminSocketHook
{
334 const AdminSocket
& m_as
;
337 explicit HelpHook(const AdminSocket
& as
) :
338 AdminSocketHook
{"help", "", "list available commands"},
342 seastar::future
<tell_result_t
> call(const cmdmap_t
&,
343 std::string_view format
,
344 ceph::bufferlist
&&) const final
346 unique_ptr
<Formatter
> f
{Formatter::create(format
,
347 "json-pretty", "json-pretty")};
348 f
->open_object_section("help");
349 for (const auto& [prefix
, hook
] : m_as
) {
350 if (!hook
->help
.empty()) {
351 f
->dump_string(prefix
.data(), hook
->help
);
355 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
359 class GetdescsHook final
: public AdminSocketHook
{
360 const AdminSocket
& m_as
;
363 explicit GetdescsHook(const AdminSocket
& as
) :
364 AdminSocketHook
{"get_command_descriptions",
366 "list available commands"},
369 seastar::future
<tell_result_t
> call(const cmdmap_t
& cmdmap
,
370 std::string_view format
,
371 ceph::bufferlist
&&) const final
373 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
375 f
->open_object_section("command_descriptions");
376 for (const auto& [prefix
, hook
] : m_as
) {
377 auto secname
= fmt::format("cmd {:>03}", cmdnum
);
378 auto cmd
= fmt::format("{} {}", hook
->prefix
, hook
->desc
);
379 dump_cmd_and_help_to_json(f
.get(), CEPH_FEATURES_ALL
, secname
,
380 cmd
, std::string
{hook
->help
});
384 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
388 class InjectArgsHook final
: public AdminSocketHook
{
391 : AdminSocketHook
{"injectargs",
392 "name=injected_args,type=CephString,n=N",
393 "inject configuration arguments into running daemon"}
395 seastar::future
<tell_result_t
> call(const cmdmap_t
& cmdmap
,
396 std::string_view format
,
397 ceph::bufferlist
&&) const final
399 std::vector
<std::string
> argv
;
400 if (!cmd_getval(cmdmap
, "injected_args", argv
)) {
401 return seastar::make_ready_future
<tell_result_t
>();
403 const std::string args
= boost::algorithm::join(argv
, " ");
404 return local_conf().inject_args(args
).then([] {
405 return seastar::make_ready_future
<tell_result_t
>();
406 }).handle_exception_type([] (const std::invalid_argument
& e
) {
407 return seastar::make_ready_future
<tell_result_t
>(
408 tell_result_t
{-EINVAL
, e
.what()});
414 * listing the configuration values
416 class ConfigShowHook
: public AdminSocketHook
{
419 AdminSocketHook
{"config show",
421 "dump current config settings"}
423 seastar::future
<tell_result_t
> call(const cmdmap_t
&,
424 std::string_view format
,
425 ceph::bufferlist
&& input
) const final
427 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
428 f
->open_object_section("config_show");
429 local_conf().show_config(f
.get());
431 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
436 * fetching the value of a specific configuration item
438 class ConfigGetHook
: public AdminSocketHook
{
441 AdminSocketHook("config get",
442 "name=var,type=CephString",
443 "config get <field>: get the config value")
445 seastar::future
<tell_result_t
> call(const cmdmap_t
& cmdmap
,
446 std::string_view format
,
447 ceph::bufferlist
&& input
) const final
450 [[maybe_unused
]] bool found
= cmd_getval(cmdmap
, "var", var
);
452 std::string conf_val
;
453 if (int r
= local_conf().get_val(var
, &conf_val
); r
< 0) {
454 return seastar::make_ready_future
<tell_result_t
>(
455 tell_result_t
{r
, fmt::format("error getting {}: {}",
456 var
, cpp_strerror(r
))});
458 unique_ptr
<Formatter
> f
{Formatter::create(format
,
461 f
->open_object_section("config_get");
462 f
->dump_string(var
, conf_val
);
464 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
469 * setting the value of a specific configuration item (an example:
470 * {"prefix": "config set", "var":"debug_osd", "val": ["30/20"]} )
472 class ConfigSetHook
: public AdminSocketHook
{
475 : AdminSocketHook("config set",
476 "name=var,type=CephString "
477 "name=val,type=CephString,n=N",
478 "config set <field> <val> [<val> ...]: set a config variable")
480 seastar::future
<tell_result_t
> call(const cmdmap_t
& cmdmap
,
481 std::string_view format
,
482 ceph::bufferlist
&&) const final
485 std::vector
<std::string
> new_val
;
486 cmd_getval(cmdmap
, "var", var
);
487 cmd_getval(cmdmap
, "val", new_val
);
488 // val may be multiple words
489 const std::string joined_values
= boost::algorithm::join(new_val
, " ");
490 return local_conf().set_val(var
, joined_values
).then([format
] {
491 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
492 f
->open_object_section("config_set");
493 f
->dump_string("success", "");
495 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
496 }).handle_exception_type([](std::invalid_argument
& e
) {
497 return seastar::make_ready_future
<tell_result_t
>(
498 tell_result_t
{-EINVAL
, e
.what()});
504 * listing the configuration values
506 class ConfigHelpHook
: public AdminSocketHook
{
509 AdminSocketHook
{"config help",
511 "get config setting schema and descriptions"}
513 seastar::future
<tell_result_t
> call(const cmdmap_t
&,
514 std::string_view format
,
515 ceph::bufferlist
&& input
) const final
517 unique_ptr
<Formatter
> f
{Formatter::create(format
, "json-pretty", "json-pretty")};
519 f
->open_array_section("options");
520 for (const auto &option
: ceph_options
) {
521 f
->dump_object("option", option
);
524 return seastar::make_ready_future
<tell_result_t
>(std::move(f
));
528 /// the hooks that are served directly by the admin_socket server
529 void AdminSocket::register_admin_commands()
531 register_command(std::make_unique
<VersionHook
>());
532 register_command(std::make_unique
<GitVersionHook
>());
533 register_command(std::make_unique
<HelpHook
>(*this));
534 register_command(std::make_unique
<GetdescsHook
>(*this));
535 register_command(std::make_unique
<ConfigGetHook
>());
536 register_command(std::make_unique
<ConfigSetHook
>());
537 register_command(std::make_unique
<ConfigShowHook
>());
538 register_command(std::make_unique
<ConfigHelpHook
>());
539 register_command(std::make_unique
<InjectArgsHook
>());
542 } // namespace crimson::admin