]> git.proxmox.com Git - ceph.git/blob - ceph/src/crimson/admin/admin_socket.cc
add stop-gap to fix compat with CPUs not supporting SSE 4.1
[ceph.git] / ceph / src / crimson / admin / admin_socket.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #include "crimson/admin/admin_socket.h"
5
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>
16
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"
23
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;
31
32 namespace {
33 seastar::logger& logger()
34 {
35 return crimson::get_logger(ceph_subsys_osd);
36 }
37 } // namespace
38
39 using std::string;
40 using std::string_view;
41 using std::stringstream;
42 using std::unique_ptr;
43
44 namespace crimson::admin {
45
46 tell_result_t::tell_result_t(int ret, std::string&& err)
47 : ret{ret}, err(std::move(err))
48 {}
49
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))
52 {}
53
54 tell_result_t::tell_result_t(std::unique_ptr<Formatter> formatter)
55 {
56 formatter->flush(out);
57 }
58
59 void AdminSocket::register_command(std::unique_ptr<AdminSocketHook>&& hook)
60 {
61 auto prefix = hook->prefix;
62 auto [it, added] = hooks.emplace(prefix, std::move(hook));
63 assert(added);
64 logger().info("register_command(): {})", it->first);
65 }
66
67 auto AdminSocket::parse_cmd(const std::vector<std::string>& cmd)
68 -> std::variant<parsed_command_t, tell_result_t>
69 {
70 // preliminaries:
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
74 cmdmap_t cmdmap;
75 ceph::bufferlist out;
76
77 try {
78 stringstream errss;
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)};
85 }
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)};
90 }
91
92 string format;
93 string prefix;
94 try {
95 cmd_getval(cmdmap, "format", format);
96 cmd_getval(cmdmap, "prefix", prefix);
97 // tolerate old-style pg <pgid> command <args> style formatting
98 if (prefix == "pg") {
99 cmd_getval(cmdmap, "cmd", prefix);
100 }
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)};
105 }
106
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 };
110 } else {
111 return tell_result_t{-EINVAL,
112 fmt::format("unknown command '{}'", prefix),
113 std::move(out)};
114 }
115 }
116
117 seastar::future<> AdminSocket::finalize_response(
118 seastar::output_stream<char>& out, ceph::bufferlist&& msgs)
119 {
120 string outbuf_cont = msgs.to_str();
121 if (outbuf_cont.empty()) {
122 outbuf_cont = " {} ";
123 }
124 uint32_t response_length = htonl(outbuf_cont.length());
125 logger().info("asok response length: {}", outbuf_cont.length());
126
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()); });
130 }
131
132
133 seastar::future<> AdminSocket::handle_command(crimson::net::ConnectionRef conn,
134 boost::intrusive_ptr<MCommand> m)
135 {
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);
140 reply->set_tid(tid);
141 reply->set_data(out);
142 return conn->send(std::move(reply));
143 });
144 }
145
146 seastar::future<> AdminSocket::execute_line(std::string cmdline,
147 seastar::output_stream<char>& out)
148 {
149 return execute_command({std::move(cmdline)}, {}).then([&out, this](auto result) {
150 auto [ret, stderr, stdout] = std::move(result);
151 if (ret < 0) {
152 stdout.append(fmt::format("ERROR: {}\n", cpp_strerror(ret)));
153 stdout.append(stderr);
154 }
155 return finalize_response(out, std::move(stdout));
156 });
157 }
158
159 auto AdminSocket::execute_command(const std::vector<std::string>& cmd,
160 ceph::bufferlist&& buf)
161 -> seastar::future<tell_result_t>
162 {
163 auto maybe_parsed = parse_cmd(cmd);
164 if (auto* parsed = std::get_if<parsed_command_t>(&maybe_parsed); parsed) {
165 stringstream os;
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;
171 out.append(os);
172 return seastar::make_ready_future<tell_result_t>(
173 tell_result_t{-EINVAL, "invalid command json", std::move(out)});
174 }
175 return parsed->hook.call(parsed->params, parsed->format, std::move(buf));
176 } else {
177 auto& result = std::get<tell_result_t>(maybe_parsed);
178 return seastar::make_ready_future<tell_result_t>(std::move(result));
179 }
180 }
181
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;
188
189 seastar::future<consumption_result_type> operator()(tmp_buf&& buf) {
190 size_t consumed = 0;
191 for (auto c : buf) {
192 consumed++;
193 if (c == '\0') {
194 buf.trim_front(consumed);
195 return seastar::make_ready_future<consumption_result_type>(
196 consumption_result_type::stop_consuming_type(std::move(buf)));
197 } else {
198 line.push_back(c);
199 }
200 }
201 return seastar::make_ready_future<consumption_result_type>(
202 seastar::continue_consuming{});
203 }
204 std::string line;
205 };
206
207 seastar::future<> AdminSocket::handle_client(seastar::input_stream<char>& in,
208 seastar::output_stream<char>& out)
209 {
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: {}",
213 consumer->line);
214 return execute_line(consumer->line, out);
215 }).then([&out] {
216 return out.flush();
217 }).finally([&out] {
218 return out.close();
219 }).then([&in] {
220 return in.close();
221 }).handle_exception([](auto ep) {
222 logger().debug("exception on {}: {}", __func__, ep);
223 });
224 }
225
226 seastar::future<> AdminSocket::start(const std::string& path)
227 {
228 if (path.empty()) {
229 logger().error(
230 "{}: Admin Socket socket path missing from the configuration", __func__);
231 return seastar::now();
232 }
233
234 logger().debug("{}: asok socket path={}", __func__, path);
235 auto sock_path = seastar::socket_address{ seastar::unix_domain_addr{ path } };
236 try {
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());
240 server_sock.reset();
241 return seastar::make_ready_future<>();
242 }
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);
253 }).finally([this] {
254 assert(connected_sock.has_value());
255 connected_sock.reset();
256 });
257 }).handle_exception([this](auto ep) {
258 if (!stop_gate.is_closed()) {
259 logger().error("AdminSocket: terminated: {}", ep);
260 }
261 });
262 });
263 }).handle_exception_type([](const seastar::gate_closed_exception&) {
264 }).finally([path] {
265 return seastar::remove_file(path);
266 });
267 return seastar::make_ready_future<>();
268 }
269
270 seastar::future<> AdminSocket::stop()
271 {
272 if (!server_sock) {
273 return seastar::now();
274 }
275 server_sock->abort_accept();
276 if (connected_sock) {
277 connected_sock->shutdown_input();
278 connected_sock->shutdown_output();
279 }
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();
285 });
286 });
287 }
288
289 /////////////////////////////////////////
290 // the internal hooks
291 /////////////////////////////////////////
292
293 class VersionHook final : public AdminSocketHook {
294 public:
295 VersionHook()
296 : AdminSocketHook{"version", "", "get ceph version"}
297 {}
298 seastar::future<tell_result_t> call(const cmdmap_t&,
299 std::string_view format,
300 ceph::bufferlist&&) const final
301 {
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());
307 f->close_section();
308 return seastar::make_ready_future<tell_result_t>(std::move(f));
309 }
310 };
311
312 /**
313 Note that the git_version command is expected to return a 'version' JSON
314 segment.
315 */
316 class GitVersionHook final : public AdminSocketHook {
317 public:
318 GitVersionHook()
319 : AdminSocketHook{"git_version", "", "get git sha1"}
320 {}
321 seastar::future<tell_result_t> call(const cmdmap_t&,
322 std::string_view format,
323 ceph::bufferlist&&) const final
324 {
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());
328 f->close_section();
329 return seastar::make_ready_future<tell_result_t>(std::move(f));
330 }
331 };
332
333 class HelpHook final : public AdminSocketHook {
334 const AdminSocket& m_as;
335
336 public:
337 explicit HelpHook(const AdminSocket& as) :
338 AdminSocketHook{"help", "", "list available commands"},
339 m_as{as}
340 {}
341
342 seastar::future<tell_result_t> call(const cmdmap_t&,
343 std::string_view format,
344 ceph::bufferlist&&) const final
345 {
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);
352 }
353 }
354 f->close_section();
355 return seastar::make_ready_future<tell_result_t>(std::move(f));
356 }
357 };
358
359 class GetdescsHook final : public AdminSocketHook {
360 const AdminSocket& m_as;
361
362 public:
363 explicit GetdescsHook(const AdminSocket& as) :
364 AdminSocketHook{"get_command_descriptions",
365 "",
366 "list available commands"},
367 m_as{ as } {}
368
369 seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
370 std::string_view format,
371 ceph::bufferlist&&) const final
372 {
373 unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
374 int cmdnum = 0;
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});
381 cmdnum++;
382 }
383 f->close_section();
384 return seastar::make_ready_future<tell_result_t>(std::move(f));
385 }
386 };
387
388 class InjectArgsHook final : public AdminSocketHook {
389 public:
390 InjectArgsHook()
391 : AdminSocketHook{"injectargs",
392 "name=injected_args,type=CephString,n=N",
393 "inject configuration arguments into running daemon"}
394 {}
395 seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
396 std::string_view format,
397 ceph::bufferlist&&) const final
398 {
399 std::vector<std::string> argv;
400 if (!cmd_getval(cmdmap, "injected_args", argv)) {
401 return seastar::make_ready_future<tell_result_t>();
402 }
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()});
409 });
410 }
411 };
412
413 /**
414 * listing the configuration values
415 */
416 class ConfigShowHook : public AdminSocketHook {
417 public:
418 ConfigShowHook() :
419 AdminSocketHook{"config show",
420 "",
421 "dump current config settings"}
422 {}
423 seastar::future<tell_result_t> call(const cmdmap_t&,
424 std::string_view format,
425 ceph::bufferlist&& input) const final
426 {
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());
430 f->close_section();
431 return seastar::make_ready_future<tell_result_t>(std::move(f));
432 }
433 };
434
435 /**
436 * fetching the value of a specific configuration item
437 */
438 class ConfigGetHook : public AdminSocketHook {
439 public:
440 ConfigGetHook() :
441 AdminSocketHook("config get",
442 "name=var,type=CephString",
443 "config get <field>: get the config value")
444 {}
445 seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
446 std::string_view format,
447 ceph::bufferlist&& input) const final
448 {
449 std::string var;
450 [[maybe_unused]] bool found = cmd_getval(cmdmap, "var", var);
451 assert(found);
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))});
457 }
458 unique_ptr<Formatter> f{Formatter::create(format,
459 "json-pretty",
460 "json-pretty")};
461 f->open_object_section("config_get");
462 f->dump_string(var, conf_val);
463 f->close_section();
464 return seastar::make_ready_future<tell_result_t>(std::move(f));
465 }
466 };
467
468 /**
469 * setting the value of a specific configuration item (an example:
470 * {"prefix": "config set", "var":"debug_osd", "val": ["30/20"]} )
471 */
472 class ConfigSetHook : public AdminSocketHook {
473 public:
474 ConfigSetHook()
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")
479 {}
480 seastar::future<tell_result_t> call(const cmdmap_t& cmdmap,
481 std::string_view format,
482 ceph::bufferlist&&) const final
483 {
484 std::string var;
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", "");
494 f->close_section();
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()});
499 });
500 }
501 };
502
503 /**
504 * listing the configuration values
505 */
506 class ConfigHelpHook : public AdminSocketHook {
507 public:
508 ConfigHelpHook() :
509 AdminSocketHook{"config help",
510 "",
511 "get config setting schema and descriptions"}
512 {}
513 seastar::future<tell_result_t> call(const cmdmap_t&,
514 std::string_view format,
515 ceph::bufferlist&& input) const final
516 {
517 unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
518 // Output all
519 f->open_array_section("options");
520 for (const auto &option : ceph_options) {
521 f->dump_object("option", option);
522 }
523 f->close_section();
524 return seastar::make_ready_future<tell_result_t>(std::move(f));
525 }
526 };
527
528 /// the hooks that are served directly by the admin_socket server
529 void AdminSocket::register_admin_commands()
530 {
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>());
540 }
541
542 } // namespace crimson::admin