]> git.proxmox.com Git - ceph.git/blob - ceph/src/crimson/admin/admin_socket.cc
import 15.2.0 Octopus source
[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 <fmt/format.h>
7 #include <seastar/net/api.hh>
8 #include <seastar/net/inet_address.hh>
9 #include <seastar/core/reactor.hh>
10 #include <seastar/core/sleep.hh>
11 #include <seastar/core/thread.hh>
12 #include <seastar/util/std-compat.hh>
13
14 #include "common/version.h"
15 #include "crimson/common/log.h"
16 #include "crimson/net/Socket.h"
17
18 using namespace crimson::common;
19 /**
20 * A Crimson-wise version of the admin socket - implementation file
21 *
22 * \todo handle the unlinking of the admin socket. Note that 'cleanup_files'
23 * at-exit functionality is not yet implemented in Crimson.
24 */
25
26 namespace {
27 seastar::logger& logger()
28 {
29 return crimson::get_logger(ceph_subsys_osd);
30 }
31 } // namespace
32
33 namespace crimson::admin {
34
35 seastar::future<>
36 AdminSocket::register_command(std::unique_ptr<AdminSocketHook>&& hook)
37 {
38 return seastar::with_lock(servers_tbl_rwlock,
39 [this, hook = std::move(hook)]() mutable {
40 auto prefix = hook->prefix;
41 auto [it, added] = hooks.emplace(prefix, std::move(hook));
42 // was this server tag already registered?
43 assert(added);
44 if (added) {
45 logger().info("register_command(): {})", it->first);
46 }
47 return seastar::now();
48 });
49 }
50
51 /*
52 * Note: parse_cmd() is executed with servers_tbl_rwlock held as shared
53 */
54 AdminSocket::maybe_parsed_t AdminSocket::parse_cmd(std::string cmd,
55 ceph::bufferlist& out)
56 {
57 // preliminaries:
58 // - create the formatter specified by the cmd parameters
59 // - locate the "op-code" string (the 'prefix' segment)
60 // - prepare for command parameters extraction via cmdmap_t
61 cmdmap_t cmdmap;
62
63 try {
64 stringstream errss;
65 // note that cmdmap_from_json() may throw on syntax issues
66 if (!cmdmap_from_json({cmd}, &cmdmap, errss)) {
67 logger().error("{}: incoming command error: {}", __func__, errss.str());
68 out.append("error:"s);
69 out.append(errss.str());
70 return maybe_parsed_t{ std::nullopt };
71 }
72 } catch (std::runtime_error& e) {
73 logger().error("{}: incoming command syntax: {}", __func__, cmd);
74 out.append("error: command syntax"s);
75 return maybe_parsed_t{ std::nullopt };
76 }
77
78 string format;
79 string prefix;
80 try {
81 cmd_getval(cmdmap, "format", format);
82 cmd_getval(cmdmap, "prefix", prefix);
83 } catch (const bad_cmd_get& e) {
84 logger().error("{}: invalid syntax: {}", __func__, cmd);
85 out.append("error: command syntax: missing 'prefix'"s);
86 return maybe_parsed_t{ std::nullopt };
87 }
88
89 if (prefix.empty()) {
90 // no command identified
91 out.append("error: no command identified"s);
92 return maybe_parsed_t{ std::nullopt };
93 }
94
95 // match the incoming op-code to one of the registered APIs
96 if (auto found = hooks.find(prefix); found != hooks.end()) {
97 return parsed_command_t{ cmdmap, format, *found->second };
98 } else {
99 return maybe_parsed_t{ std::nullopt };
100 }
101 }
102
103 /*
104 * Note: validate_command() is executed with servers_tbl_rwlock held as shared
105 */
106 bool AdminSocket::validate_command(const parsed_command_t& parsed,
107 const std::string& command_text,
108 ceph::bufferlist& out) const
109 {
110 logger().info("{}: validating {} against:{}", __func__, command_text,
111 parsed.hook.desc);
112
113 stringstream os; // for possible validation error messages
114 if (validate_cmd(nullptr, std::string{parsed.hook.desc}, parsed.parameters, os)) {
115 return true;
116 } else {
117 os << "error: command validation failure ";
118 logger().error("{}: validation failure (incoming:{}) {}", __func__,
119 command_text, os.str());
120 out.append(os);
121 return false;
122 }
123 }
124
125 seastar::future<> AdminSocket::finalize_response(
126 seastar::output_stream<char>& out, ceph::bufferlist&& msgs)
127 {
128 string outbuf_cont = msgs.to_str();
129 if (outbuf_cont.empty()) {
130 outbuf_cont = " {} ";
131 }
132 uint32_t response_length = htonl(outbuf_cont.length());
133 logger().info("asok response length: {}", outbuf_cont.length());
134
135 return out.write((char*)&response_length, sizeof(uint32_t))
136 .then([&out, outbuf_cont] { return out.write(outbuf_cont.c_str()); });
137 }
138
139 seastar::future<> AdminSocket::execute_line(std::string cmdline,
140 seastar::output_stream<char>& out)
141 {
142 return seastar::with_shared(servers_tbl_rwlock,
143 [this, cmdline, &out]() mutable {
144 ceph::bufferlist err;
145 auto parsed = parse_cmd(cmdline, err);
146 if (!parsed.has_value() ||
147 !validate_command(*parsed, cmdline, err)) {
148 return finalize_response(out, std::move(err));
149 }
150 return parsed->hook.call(parsed->hook.prefix,
151 parsed->format,
152 parsed->parameters).then(
153 [this, &out](auto result) {
154 // add 'failed' to the contents of out_buf? not what
155 // happens in the old code
156 return finalize_response(out, std::move(result));
157 });
158 });
159 }
160
161 // an input_stream consumer that reads buffer into a std::string up to the first
162 // '\0' which indicates the end of command
163 struct line_consumer {
164 using tmp_buf = seastar::temporary_buffer<char>;
165 using consumption_result_type =
166 typename seastar::input_stream<char>::consumption_result_type;
167
168 seastar::future<consumption_result_type> operator()(tmp_buf&& buf) {
169 size_t consumed = 0;
170 for (auto c : buf) {
171 consumed++;
172 if (c == '\0') {
173 buf.trim_front(consumed);
174 return seastar::make_ready_future<consumption_result_type>(
175 consumption_result_type::stop_consuming_type(std::move(buf)));
176 } else {
177 line.push_back(c);
178 }
179 }
180 return seastar::make_ready_future<consumption_result_type>(
181 seastar::continue_consuming{});
182 }
183 std::string line;
184 };
185
186 seastar::future<> AdminSocket::handle_client(seastar::input_stream<char>& in,
187 seastar::output_stream<char>& out)
188 {
189 auto consumer = seastar::make_shared<line_consumer>();
190 return in.consume(*consumer).then([consumer, &out, this] {
191 logger().debug("AdminSocket::handle_client: incoming asok string: {}",
192 consumer->line);
193 return execute_line(consumer->line, out);
194 }).then([&out] {
195 return out.flush();
196 }).finally([&out] {
197 return out.close();
198 }).then([&in] {
199 return in.close();
200 }).handle_exception([](auto ep) {
201 logger().debug("exception on {}: {}", __func__, ep);
202 return seastar::make_ready_future<>();
203 }).discard_result();
204 }
205
206 seastar::future<> AdminSocket::start(const std::string& path)
207 {
208 if (path.empty()) {
209 logger().error(
210 "{}: Admin Socket socket path missing from the configuration", __func__);
211 return seastar::now();
212 }
213
214 logger().debug("{}: asok socket path={}", __func__, path);
215 auto sock_path = seastar::socket_address{ seastar::unix_domain_addr{ path } };
216 server_sock = seastar::engine().listen(sock_path);
217 // listen in background
218 std::ignore = seastar::do_until(
219 [this] { return stop_gate.is_closed(); },
220 [this] {
221 return seastar::with_gate(stop_gate, [this] {
222 assert(!connected_sock.has_value());
223 return server_sock->accept().then([this](seastar::accept_result acc) {
224 connected_sock = std::move(acc.connection);
225 return seastar::do_with(connected_sock->input(),
226 connected_sock->output(),
227 [this](auto& input, auto& output) mutable {
228 return handle_client(input, output);
229 }).finally([this] {
230 assert(connected_sock.has_value());
231 connected_sock.reset();
232 });
233 }).handle_exception([this](auto ep) {
234 if (!stop_gate.is_closed()) {
235 logger().error("AdminSocket: terminated: {}", ep);
236 }
237 });
238 });
239 }).then([] {
240 logger().debug("AdminSocket::init(): admin-sock thread terminated");
241 return seastar::now();
242 });
243
244 return seastar::make_ready_future<>();
245 }
246
247 seastar::future<> AdminSocket::stop()
248 {
249 if (!server_sock) {
250 return seastar::now();
251 }
252 server_sock->abort_accept();
253 server_sock.reset();
254 if (connected_sock) {
255 connected_sock->shutdown_input();
256 connected_sock->shutdown_output();
257 connected_sock.reset();
258 }
259 return stop_gate.close();
260 }
261
262 /////////////////////////////////////////
263 // the internal hooks
264 /////////////////////////////////////////
265
266 class VersionHook final : public AdminSocketHook {
267 public:
268 VersionHook()
269 : AdminSocketHook{"version", "version", "get ceph version"}
270 {}
271 seastar::future<bufferlist> call(std::string_view,
272 std::string_view format,
273 const cmdmap_t&) const final
274 {
275 unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
276 f->open_object_section("version");
277 f->dump_string("version", ceph_version_to_str());
278 f->dump_string("release", ceph_release_to_str());
279 f->dump_string("release_type", ceph_release_type());
280 f->close_section();
281 bufferlist out;
282 f->flush(out);
283 return seastar::make_ready_future<bufferlist>(std::move(out));
284 }
285 };
286
287 /**
288 Note that the git_version command is expected to return a 'version' JSON
289 segment.
290 */
291 class GitVersionHook final : public AdminSocketHook {
292 public:
293 GitVersionHook()
294 : AdminSocketHook{"git_version", "git_version", "get git sha1"}
295 {}
296 seastar::future<bufferlist> call(std::string_view command,
297 std::string_view format,
298 const cmdmap_t&) const final
299 {
300 unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
301 f->open_object_section("version");
302 f->dump_string("git_version", git_version_to_str());
303 f->close_section();
304 ceph::bufferlist out;
305 f->flush(out);
306 return seastar::make_ready_future<bufferlist>(std::move(out));
307 }
308 };
309
310 class HelpHook final : public AdminSocketHook {
311 const AdminSocket& m_as;
312
313 public:
314 explicit HelpHook(const AdminSocket& as) :
315 AdminSocketHook{"help", "help", "list available commands"},
316 m_as{as}
317 {}
318
319 seastar::future<bufferlist> call(std::string_view command,
320 std::string_view format,
321 const cmdmap_t& cmdmap) const final
322 {
323 return seastar::with_shared(m_as.servers_tbl_rwlock,
324 [this, format] {
325 unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
326 f->open_object_section("help");
327 for (const auto& [prefix, hook] : m_as) {
328 if (!hook->help.empty()) {
329 f->dump_string(prefix.data(), hook->help);
330 }
331 }
332 f->close_section();
333 ceph::bufferlist out;
334 f->flush(out);
335 return seastar::make_ready_future<bufferlist>(std::move(out));
336 });
337 }
338 };
339
340 class GetdescsHook final : public AdminSocketHook {
341 const AdminSocket& m_as;
342
343 public:
344 explicit GetdescsHook(const AdminSocket& as) :
345 AdminSocketHook{"get_command_descriptions",
346 "get_command_descriptions",
347 "list available commands"},
348 m_as{ as } {}
349
350 seastar::future<bufferlist> call(std::string_view command,
351 std::string_view format,
352 const cmdmap_t& cmdmap) const final
353 {
354 unique_ptr<Formatter> f{Formatter::create(format, "json-pretty", "json-pretty")};
355 return seastar::with_shared(m_as.servers_tbl_rwlock, [this, f=move(f)] {
356 int cmdnum = 0;
357 f->open_object_section("command_descriptions");
358 for (const auto& [prefix, hook] : m_as) {
359 auto secname = fmt::format("cmd {:>03}", cmdnum);
360 dump_cmd_and_help_to_json(f.get(), CEPH_FEATURES_ALL, secname,
361 std::string{hook->desc},
362 std::string{hook->help});
363 cmdnum++;
364 }
365 f->close_section();
366 ceph::bufferlist out;
367 f->flush(out);
368 return seastar::make_ready_future<bufferlist>(std::move(out));
369 });
370 }
371 };
372
373 /// the hooks that are served directly by the admin_socket server
374 seastar::future<> AdminSocket::register_admin_commands()
375 {
376 return seastar::when_all_succeed(
377 register_command(std::make_unique<VersionHook>()),
378 register_command(std::make_unique<GitVersionHook>()),
379 register_command(std::make_unique<HelpHook>(*this)),
380 register_command(std::make_unique<GetdescsHook>(*this)));
381 }
382
383 } // namespace crimson::admin