]> git.proxmox.com Git - ceph.git/blame - ceph/src/mgr/ActivePyModules.cc
bump version to 15.2.4-pve1
[ceph.git] / ceph / src / mgr / ActivePyModules.cc
CommitLineData
7c673cae
FG
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
3/*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2014 John Spray <john.spray@inktank.com>
7 *
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
12 */
13
14// Include this first to get python headers earlier
31f18b77 15#include "Gil.h"
7c673cae 16
7c673cae
FG
17#include "common/errno.h"
18#include "include/stringify.h"
19
20#include "PyFormatter.h"
21
22#include "osd/OSDMap.h"
23#include "mon/MonMap.h"
24
25#include "mgr/MgrContext.h"
26
3efd9988 27// For ::config_prefix
11fdf7f2 28#include "PyModule.h"
3efd9988
FG
29#include "PyModuleRegistry.h"
30
31#include "ActivePyModules.h"
9f95a23c 32#include "DaemonKey.h"
11fdf7f2 33#include "DaemonServer.h"
7c673cae
FG
34
35#define dout_context g_ceph_context
36#define dout_subsys ceph_subsys_mgr
7c673cae 37#undef dout_prefix
9f95a23c 38#define dout_prefix *_dout << "mgr " << __func__ << " "
7c673cae 39
11fdf7f2
TL
40ActivePyModules::ActivePyModules(PyModuleConfig &module_config_,
41 std::map<std::string, std::string> store_data,
3efd9988 42 DaemonStateIndex &ds, ClusterState &cs,
11fdf7f2
TL
43 MonClient &mc, LogChannelRef clog_,
44 LogChannelRef audit_clog_, Objecter &objecter_,
45 Client &client_, Finisher &f, DaemonServer &server,
46 PyModuleRegistry &pmr)
47 : module_config(module_config_), daemon_state(ds), cluster_state(cs),
48 monc(mc), clog(clog_), audit_clog(audit_clog_), objecter(objecter_),
49 client(client_), finisher(f),
81eedcae 50 cmd_finisher(g_ceph_context, "cmd_finisher", "cmdfin"),
9f95a23c 51 server(server), py_module_registry(pmr)
11fdf7f2
TL
52{
53 store_cache = std::move(store_data);
81eedcae 54 cmd_finisher.start();
11fdf7f2 55}
7c673cae 56
3efd9988 57ActivePyModules::~ActivePyModules() = default;
7c673cae 58
3efd9988 59void ActivePyModules::dump_server(const std::string &hostname,
7c673cae
FG
60 const DaemonStateCollection &dmc,
61 Formatter *f)
62{
63 f->dump_string("hostname", hostname);
64 f->open_array_section("services");
65 std::string ceph_version;
66
9f95a23c
TL
67 for (const auto &[key, state] : dmc) {
68 std::lock_guard l(state->lock);
7c673cae
FG
69 // TODO: pick the highest version, and make sure that
70 // somewhere else (during health reporting?) we are
71 // indicating to the user if we see mixed versions
9f95a23c
TL
72 auto ver_iter = state->metadata.find("ceph_version");
73 if (ver_iter != state->metadata.end()) {
74 ceph_version = state->metadata.at("ceph_version");
7c673cae
FG
75 }
76
77 f->open_object_section("service");
9f95a23c
TL
78 f->dump_string("type", key.type);
79 f->dump_string("id", key.name);
7c673cae
FG
80 f->close_section();
81 }
82 f->close_section();
83
84 f->dump_string("ceph_version", ceph_version);
85}
86
87
88
3efd9988 89PyObject *ActivePyModules::get_server_python(const std::string &hostname)
7c673cae
FG
90{
91 PyThreadState *tstate = PyEval_SaveThread();
11fdf7f2 92 std::lock_guard l(lock);
7c673cae
FG
93 PyEval_RestoreThread(tstate);
94 dout(10) << " (" << hostname << ")" << dendl;
95
96 auto dmc = daemon_state.get_by_server(hostname);
97
98 PyFormatter f;
99 dump_server(hostname, dmc, &f);
100 return f.get();
101}
102
103
3efd9988 104PyObject *ActivePyModules::list_servers_python()
7c673cae 105{
11fdf7f2 106 PyFormatter f(false, true);
7c673cae 107 PyThreadState *tstate = PyEval_SaveThread();
7c673cae
FG
108 dout(10) << " >" << dendl;
109
11fdf7f2 110 daemon_state.with_daemons_by_server([this, &f, &tstate]
3efd9988 111 (const std::map<std::string, DaemonStateCollection> &all) {
11fdf7f2
TL
112 PyEval_RestoreThread(tstate);
113
3efd9988
FG
114 for (const auto &i : all) {
115 const auto &hostname = i.first;
7c673cae 116
3efd9988
FG
117 f.open_object_section("server");
118 dump_server(hostname, i.second, &f);
119 f.close_section();
120 }
121 });
7c673cae
FG
122
123 return f.get();
124}
125
3efd9988
FG
126PyObject *ActivePyModules::get_metadata_python(
127 const std::string &svc_type,
224ce89b 128 const std::string &svc_id)
7c673cae 129{
9f95a23c 130 auto metadata = daemon_state.get(DaemonKey{svc_type, svc_id});
3efd9988
FG
131 if (metadata == nullptr) {
132 derr << "Requested missing service " << svc_type << "." << svc_id << dendl;
133 Py_RETURN_NONE;
134 }
135
11fdf7f2 136 std::lock_guard l(metadata->lock);
7c673cae
FG
137 PyFormatter f;
138 f.dump_string("hostname", metadata->hostname);
139 for (const auto &i : metadata->metadata) {
140 f.dump_string(i.first.c_str(), i.second);
141 }
142
143 return f.get();
144}
145
3efd9988
FG
146PyObject *ActivePyModules::get_daemon_status_python(
147 const std::string &svc_type,
224ce89b
WB
148 const std::string &svc_id)
149{
9f95a23c 150 auto metadata = daemon_state.get(DaemonKey{svc_type, svc_id});
3efd9988
FG
151 if (metadata == nullptr) {
152 derr << "Requested missing service " << svc_type << "." << svc_id << dendl;
153 Py_RETURN_NONE;
154 }
155
11fdf7f2 156 std::lock_guard l(metadata->lock);
224ce89b
WB
157 PyFormatter f;
158 for (const auto &i : metadata->service_status) {
159 f.dump_string(i.first.c_str(), i.second);
160 }
161 return f.get();
162}
7c673cae 163
3efd9988 164PyObject *ActivePyModules::get_python(const std::string &what)
7c673cae 165{
11fdf7f2
TL
166 PyFormatter f;
167
168 // Drop the GIL, as most of the following blocks will block on
169 // a mutex -- they are all responsible for re-taking the GIL before
170 // touching the PyFormatter instance or returning from the function.
7c673cae 171 PyThreadState *tstate = PyEval_SaveThread();
7c673cae
FG
172
173 if (what == "fs_map") {
11fdf7f2
TL
174 cluster_state.with_fsmap([&f, &tstate](const FSMap &fsmap) {
175 PyEval_RestoreThread(tstate);
7c673cae
FG
176 fsmap.dump(&f);
177 });
178 return f.get();
179 } else if (what == "osdmap_crush_map_text") {
180 bufferlist rdata;
11fdf7f2
TL
181 cluster_state.with_osdmap([&rdata, &tstate](const OSDMap &osd_map){
182 PyEval_RestoreThread(tstate);
183 osd_map.crush->encode(rdata, CEPH_FEATURES_SUPPORTED_DEFAULT);
7c673cae
FG
184 });
185 std::string crush_text = rdata.to_str();
9f95a23c 186 return PyUnicode_FromString(crush_text.c_str());
7c673cae 187 } else if (what.substr(0, 7) == "osd_map") {
11fdf7f2
TL
188 cluster_state.with_osdmap([&f, &what, &tstate](const OSDMap &osd_map){
189 PyEval_RestoreThread(tstate);
7c673cae
FG
190 if (what == "osd_map") {
191 osd_map.dump(&f);
192 } else if (what == "osd_map_tree") {
193 osd_map.print_tree(&f, nullptr);
194 } else if (what == "osd_map_crush") {
195 osd_map.crush->dump(&f);
196 }
197 });
198 return f.get();
eafe8130
TL
199 } else if (what == "modified_config_options") {
200 PyEval_RestoreThread(tstate);
201 auto all_daemons = daemon_state.get_all();
202 set<string> names;
203 for (auto& [key, daemon] : all_daemons) {
204 std::lock_guard l(daemon->lock);
205 for (auto& [name, valmap] : daemon->config) {
206 names.insert(name);
207 }
208 }
209 f.open_array_section("options");
210 for (auto& name : names) {
211 f.dump_string("name", name);
212 }
213 f.close_section();
214 return f.get();
1adf2230 215 } else if (what.substr(0, 6) == "config") {
11fdf7f2 216 PyEval_RestoreThread(tstate);
1adf2230 217 if (what == "config_options") {
11fdf7f2 218 g_conf().config_options(&f);
1adf2230 219 } else if (what == "config") {
11fdf7f2 220 g_conf().show_config(&f);
1adf2230 221 }
7c673cae
FG
222 return f.get();
223 } else if (what == "mon_map") {
7c673cae 224 cluster_state.with_monmap(
11fdf7f2
TL
225 [&f, &tstate](const MonMap &monmap) {
226 PyEval_RestoreThread(tstate);
7c673cae
FG
227 monmap.dump(&f);
228 }
229 );
230 return f.get();
224ce89b 231 } else if (what == "service_map") {
224ce89b 232 cluster_state.with_servicemap(
11fdf7f2
TL
233 [&f, &tstate](const ServiceMap &service_map) {
234 PyEval_RestoreThread(tstate);
224ce89b
WB
235 service_map.dump(&f);
236 }
237 );
238 return f.get();
7c673cae 239 } else if (what == "osd_metadata") {
224ce89b 240 auto dmc = daemon_state.get_by_service("osd");
11fdf7f2
TL
241 PyEval_RestoreThread(tstate);
242
9f95a23c
TL
243 for (const auto &[key, state] : dmc) {
244 std::lock_guard l(state->lock);
245 f.open_object_section(key.name.c_str());
246 f.dump_string("hostname", state->hostname);
247 for (const auto &[name, val] : state->metadata) {
248 f.dump_string(name.c_str(), val);
249 }
250 f.close_section();
251 }
252 return f.get();
253 } else if (what == "mds_metadata") {
254 auto dmc = daemon_state.get_by_service("mds");
255 PyEval_RestoreThread(tstate);
256
257 for (const auto &[key, state] : dmc) {
258 std::lock_guard l(state->lock);
259 f.open_object_section(key.name.c_str());
260 f.dump_string("hostname", state->hostname);
261 for (const auto &[name, val] : state->metadata) {
262 f.dump_string(name.c_str(), val);
7c673cae
FG
263 }
264 f.close_section();
265 }
266 return f.get();
267 } else if (what == "pg_summary") {
7c673cae 268 cluster_state.with_pgmap(
11fdf7f2
TL
269 [&f, &tstate](const PGMap &pg_map) {
270 PyEval_RestoreThread(tstate);
271
7c673cae
FG
272 std::map<std::string, std::map<std::string, uint32_t> > osds;
273 std::map<std::string, std::map<std::string, uint32_t> > pools;
274 std::map<std::string, uint32_t> all;
275 for (const auto &i : pg_map.pg_stat) {
276 const auto pool = i.first.m_pool;
277 const std::string state = pg_state_string(i.second.state);
278 // Insert to per-pool map
279 pools[stringify(pool)][state]++;
280 for (const auto &osd_id : i.second.acting) {
281 osds[stringify(osd_id)][state]++;
282 }
283 all[state]++;
284 }
285 f.open_object_section("by_osd");
286 for (const auto &i : osds) {
287 f.open_object_section(i.first.c_str());
288 for (const auto &j : i.second) {
289 f.dump_int(j.first.c_str(), j.second);
290 }
291 f.close_section();
292 }
293 f.close_section();
294 f.open_object_section("by_pool");
295 for (const auto &i : pools) {
296 f.open_object_section(i.first.c_str());
297 for (const auto &j : i.second) {
298 f.dump_int(j.first.c_str(), j.second);
299 }
300 f.close_section();
301 }
302 f.close_section();
303 f.open_object_section("all");
304 for (const auto &i : all) {
305 f.dump_int(i.first.c_str(), i.second);
306 }
307 f.close_section();
28e407b8
AA
308 f.open_object_section("pg_stats_sum");
309 pg_map.pg_sum.dump(&f);
310 f.close_section();
7c673cae
FG
311 }
312 );
313 return f.get();
3efd9988 314 } else if (what == "pg_status") {
3efd9988 315 cluster_state.with_pgmap(
11fdf7f2
TL
316 [&f, &tstate](const PGMap &pg_map) {
317 PyEval_RestoreThread(tstate);
3efd9988
FG
318 pg_map.print_summary(&f, nullptr);
319 }
320 );
321 return f.get();
322 } else if (what == "pg_dump") {
11fdf7f2
TL
323 cluster_state.with_pgmap(
324 [&f, &tstate](const PGMap &pg_map) {
325 PyEval_RestoreThread(tstate);
9f95a23c 326 pg_map.dump(&f, false);
11fdf7f2
TL
327 }
328 );
329 return f.get();
330 } else if (what == "devices") {
331 daemon_state.with_devices2(
332 [&tstate, &f]() {
333 PyEval_RestoreThread(tstate);
334 f.open_array_section("devices");
335 },
336 [&f] (const DeviceState& dev) {
337 f.dump_object("device", dev);
338 });
339 f.close_section();
340 return f.get();
341 } else if (what.size() > 7 &&
342 what.substr(0, 7) == "device ") {
343 string devid = what.substr(7);
eafe8130
TL
344 if (!daemon_state.with_device(
345 devid,
346 [&f, &tstate] (const DeviceState& dev) {
347 PyEval_RestoreThread(tstate);
348 f.dump_object("device", dev);
349 })) {
350 // device not found
351 PyEval_RestoreThread(tstate);
352 }
11fdf7f2
TL
353 return f.get();
354 } else if (what == "io_rate") {
355 cluster_state.with_pgmap(
356 [&f, &tstate](const PGMap &pg_map) {
357 PyEval_RestoreThread(tstate);
358 pg_map.dump_delta(&f);
359 }
3efd9988
FG
360 );
361 return f.get();
7c673cae 362 } else if (what == "df") {
11fdf7f2 363 cluster_state.with_osdmap_and_pgmap(
9f95a23c 364 [&f, &tstate](
11fdf7f2
TL
365 const OSDMap& osd_map,
366 const PGMap &pg_map) {
367 PyEval_RestoreThread(tstate);
368 pg_map.dump_cluster_stats(nullptr, &f, true);
31f18b77 369 pg_map.dump_pool_stats_full(osd_map, nullptr, &f, true);
7c673cae 370 });
7c673cae 371 return f.get();
9f95a23c
TL
372 } else if (what == "pg_stats") {
373 cluster_state.with_pgmap(
374 [&f, &tstate](const PGMap &pg_map) {
375 PyEval_RestoreThread(tstate);
376 pg_map.dump_pg_stats(&f, false);
377 });
378 return f.get();
379 } else if (what == "pool_stats") {
380 cluster_state.with_pgmap(
381 [&f, &tstate](const PGMap &pg_map) {
382 PyEval_RestoreThread(tstate);
383 pg_map.dump_pool_stats(&f);
384 });
385 return f.get();
386 } else if (what == "pg_ready") {
387 PyEval_RestoreThread(tstate);
388 server.dump_pg_ready(&f);
389 return f.get();
7c673cae 390 } else if (what == "osd_stats") {
7c673cae 391 cluster_state.with_pgmap(
11fdf7f2
TL
392 [&f, &tstate](const PGMap &pg_map) {
393 PyEval_RestoreThread(tstate);
ded94939 394 pg_map.dump_osd_stats(&f, false);
7c673cae
FG
395 });
396 return f.get();
9f95a23c
TL
397 } else if (what == "osd_ping_times") {
398 cluster_state.with_pgmap(
399 [&f, &tstate](const PGMap &pg_map) {
400 PyEval_RestoreThread(tstate);
401 pg_map.dump_osd_ping_times(&f);
402 });
403 return f.get();
11fdf7f2
TL
404 } else if (what == "osd_pool_stats") {
405 int64_t poolid = -ENOENT;
11fdf7f2
TL
406 cluster_state.with_osdmap_and_pgmap([&](const OSDMap& osdmap,
407 const PGMap& pg_map) {
408 PyEval_RestoreThread(tstate);
409 f.open_array_section("pool_stats");
410 for (auto &p : osdmap.get_pools()) {
411 poolid = p.first;
412 pg_map.dump_pool_stats_and_io_rate(poolid, osdmap, &f, nullptr);
413 }
414 f.close_section();
415 });
416 return f.get();
e306af50
TL
417 } else if (what == "health") {
418 cluster_state.with_health(
419 [&f, &tstate](const ceph::bufferlist &health_json) {
420 PyEval_RestoreThread(tstate);
421 f.dump_string("json", health_json.to_str());
422 });
423 return f.get();
424 } else if (what == "mon_status") {
425 cluster_state.with_mon_status(
426 [&f, &tstate](const ceph::bufferlist &mon_status_json) {
427 PyEval_RestoreThread(tstate);
428 f.dump_string("json", mon_status_json.to_str());
429 });
7c673cae 430 return f.get();
224ce89b 431 } else if (what == "mgr_map") {
11fdf7f2
TL
432 cluster_state.with_mgrmap([&f, &tstate](const MgrMap &mgr_map) {
433 PyEval_RestoreThread(tstate);
224ce89b
WB
434 mgr_map.dump(&f);
435 });
436 return f.get();
7c673cae
FG
437 } else {
438 derr << "Python module requested unknown data '" << what << "'" << dendl;
11fdf7f2 439 PyEval_RestoreThread(tstate);
7c673cae
FG
440 Py_RETURN_NONE;
441 }
442}
443
11fdf7f2 444void ActivePyModules::start_one(PyModuleRef py_module)
7c673cae 445{
11fdf7f2
TL
446 std::lock_guard l(lock);
447
11fdf7f2 448 const auto name = py_module->get_name();
9f95a23c
TL
449 auto em = modules.emplace(name,
450 std::make_shared<ActivePyModule>(py_module, clog));
451 ceph_assert(em.second); // actually inserted
452 auto& active_module = em.first->second;
11fdf7f2
TL
453
454 // Send all python calls down a Finisher to avoid blocking
455 // C++ code, and avoid any potential lock cycles.
9f95a23c 456 finisher.queue(new LambdaContext([this, active_module, name](int) {
11fdf7f2
TL
457 int r = active_module->load(this);
458 if (r != 0) {
459 derr << "Failed to run module in active mode ('" << name << "')"
460 << dendl;
461 std::lock_guard l(lock);
462 modules.erase(name);
463 } else {
464 dout(4) << "Starting thread for " << name << dendl;
465 active_module->thread.create(active_module->get_thread_name());
466 }
467 }));
7c673cae
FG
468}
469
3efd9988 470void ActivePyModules::shutdown()
7c673cae 471{
11fdf7f2 472 std::lock_guard locker(lock);
7c673cae
FG
473
474 // Signal modules to drop out of serve() and/or tear down resources
9f95a23c
TL
475 for (auto& [name, module] : modules) {
476 lock.unlock();
3efd9988 477 dout(10) << "calling module " << name << " shutdown()" << dendl;
7c673cae 478 module->shutdown();
3efd9988 479 dout(10) << "module " << name << " shutdown() returned" << dendl;
9f95a23c 480 lock.lock();
7c673cae
FG
481 }
482
483 // For modules implementing serve(), finish the threads where we
484 // were running that.
9f95a23c
TL
485 for (auto& [name, module] : modules) {
486 lock.unlock();
487 dout(10) << "joining module " << name << dendl;
488 module->thread.join();
489 dout(10) << "joined module " << name << dendl;
490 lock.lock();
7c673cae 491 }
7c673cae 492
81eedcae
TL
493 cmd_finisher.wait_for_empty();
494 cmd_finisher.stop();
495
7c673cae 496 modules.clear();
7c673cae
FG
497}
498
3efd9988 499void ActivePyModules::notify_all(const std::string &notify_type,
7c673cae
FG
500 const std::string &notify_id)
501{
11fdf7f2 502 std::lock_guard l(lock);
7c673cae
FG
503
504 dout(10) << __func__ << ": notify_all " << notify_type << dendl;
9f95a23c 505 for (auto& [name, module] : modules) {
7c673cae
FG
506 // Send all python calls down a Finisher to avoid blocking
507 // C++ code, and avoid any potential lock cycles.
9f95a23c
TL
508 dout(15) << "queuing notify to " << name << dendl;
509 // workaround for https://bugs.llvm.org/show_bug.cgi?id=35984
510 finisher.queue(new LambdaContext([module=module, notify_type, notify_id]
511 (int r){
512 module->notify(notify_type, notify_id);
7c673cae
FG
513 }));
514 }
515}
516
3efd9988 517void ActivePyModules::notify_all(const LogEntry &log_entry)
7c673cae 518{
11fdf7f2 519 std::lock_guard l(lock);
7c673cae
FG
520
521 dout(10) << __func__ << ": notify_all (clog)" << dendl;
9f95a23c 522 for (auto& [name, module] : modules) {
7c673cae
FG
523 // Send all python calls down a Finisher to avoid blocking
524 // C++ code, and avoid any potential lock cycles.
525 //
526 // Note intentional use of non-reference lambda binding on
527 // log_entry: we take a copy because caller's instance is
528 // probably ephemeral.
9f95a23c
TL
529 dout(15) << "queuing notify (clog) to " << name << dendl;
530 // workaround for https://bugs.llvm.org/show_bug.cgi?id=35984
531 finisher.queue(new LambdaContext([module=module, log_entry](int r){
7c673cae
FG
532 module->notify_clog(log_entry);
533 }));
534 }
535}
536
11fdf7f2 537bool ActivePyModules::get_store(const std::string &module_name,
7c673cae
FG
538 const std::string &key, std::string *val) const
539{
11fdf7f2
TL
540 PyThreadState *tstate = PyEval_SaveThread();
541 std::lock_guard l(lock);
542 PyEval_RestoreThread(tstate);
543
544 const std::string global_key = PyModule::config_prefix
3efd9988 545 + module_name + "/" + key;
31f18b77 546
11fdf7f2 547 dout(4) << __func__ << " key: " << global_key << dendl;
7c673cae 548
11fdf7f2
TL
549 auto i = store_cache.find(global_key);
550 if (i != store_cache.end()) {
551 *val = i->second;
552 return true;
553 } else {
554 return false;
555 }
556}
557
558PyObject *ActivePyModules::dispatch_remote(
559 const std::string &other_module,
560 const std::string &method,
561 PyObject *args,
562 PyObject *kwargs,
563 std::string *err)
564{
565 auto mod_iter = modules.find(other_module);
566 ceph_assert(mod_iter != modules.end());
567
568 return mod_iter->second->dispatch_remote(method, args, kwargs, err);
569}
a8e16298 570
11fdf7f2
TL
571bool ActivePyModules::get_config(const std::string &module_name,
572 const std::string &key, std::string *val) const
573{
574 const std::string global_key = PyModule::config_prefix
575 + module_name + "/" + key;
576
9f95a23c 577 dout(20) << " key: " << global_key << dendl;
11fdf7f2
TL
578
579 std::lock_guard lock(module_config.lock);
9f95a23c 580
11fdf7f2
TL
581 auto i = module_config.config.find(global_key);
582 if (i != module_config.config.end()) {
583 *val = i->second;
7c673cae
FG
584 return true;
585 } else {
586 return false;
587 }
588}
589
11fdf7f2
TL
590PyObject *ActivePyModules::get_typed_config(
591 const std::string &module_name,
592 const std::string &key,
593 const std::string &prefix) const
594{
595 PyThreadState *tstate = PyEval_SaveThread();
596 std::string value;
597 std::string final_key;
598 bool found = false;
599 if (prefix.size()) {
600 final_key = prefix + "/" + key;
601 found = get_config(module_name, final_key, &value);
602 }
603 if (!found) {
604 final_key = key;
605 found = get_config(module_name, final_key, &value);
606 }
607 if (found) {
608 PyModuleRef module = py_module_registry.get_module(module_name);
609 PyEval_RestoreThread(tstate);
610 if (!module) {
611 derr << "Module '" << module_name << "' is not available" << dendl;
612 Py_RETURN_NONE;
613 }
614 dout(10) << __func__ << " " << final_key << " found: " << value << dendl;
615 return module->get_typed_option_value(key, value);
616 }
617 PyEval_RestoreThread(tstate);
618 if (prefix.size()) {
9f95a23c 619 dout(10) << " [" << prefix << "/]" << key << " not found "
11fdf7f2
TL
620 << dendl;
621 } else {
9f95a23c 622 dout(10) << " " << key << " not found " << dendl;
11fdf7f2
TL
623 }
624 Py_RETURN_NONE;
625}
626
627PyObject *ActivePyModules::get_store_prefix(const std::string &module_name,
31f18b77
FG
628 const std::string &prefix) const
629{
630 PyThreadState *tstate = PyEval_SaveThread();
11fdf7f2
TL
631 std::lock_guard l(lock);
632 std::lock_guard lock(module_config.lock);
31f18b77
FG
633 PyEval_RestoreThread(tstate);
634
11fdf7f2 635 const std::string base_prefix = PyModule::config_prefix
3efd9988 636 + module_name + "/";
31f18b77 637 const std::string global_prefix = base_prefix + prefix;
11fdf7f2 638 dout(4) << __func__ << " prefix: " << global_prefix << dendl;
31f18b77
FG
639
640 PyFormatter f;
9f95a23c 641
11fdf7f2
TL
642 for (auto p = store_cache.lower_bound(global_prefix);
643 p != store_cache.end() && p->first.find(global_prefix) == 0;
31f18b77
FG
644 ++p) {
645 f.dump_string(p->first.c_str() + base_prefix.size(), p->second);
646 }
647 return f.get();
648}
649
11fdf7f2 650void ActivePyModules::set_store(const std::string &module_name,
d2e6a577 651 const std::string &key, const boost::optional<std::string>& val)
7c673cae 652{
11fdf7f2 653 const std::string global_key = PyModule::config_prefix
3efd9988 654 + module_name + "/" + key;
9f95a23c 655
7c673cae
FG
656 Command set_cmd;
657 {
11fdf7f2 658 std::lock_guard l(lock);
d2e6a577 659 if (val) {
11fdf7f2 660 store_cache[global_key] = *val;
d2e6a577 661 } else {
11fdf7f2 662 store_cache.erase(global_key);
d2e6a577 663 }
7c673cae
FG
664
665 std::ostringstream cmd_json;
7c673cae
FG
666 JSONFormatter jf;
667 jf.open_object_section("cmd");
d2e6a577
FG
668 if (val) {
669 jf.dump_string("prefix", "config-key set");
670 jf.dump_string("key", global_key);
671 jf.dump_string("val", *val);
672 } else {
673 jf.dump_string("prefix", "config-key del");
674 jf.dump_string("key", global_key);
675 }
7c673cae
FG
676 jf.close_section();
677 jf.flush(cmd_json);
7c673cae
FG
678 set_cmd.run(&monc, cmd_json.str());
679 }
680 set_cmd.wait();
681
682 if (set_cmd.r != 0) {
c07f9fc5 683 // config-key set will fail if mgr's auth key has insufficient
7c673cae
FG
684 // permission to set config keys
685 // FIXME: should this somehow raise an exception back into Python land?
c07f9fc5 686 dout(0) << "`config-key set " << global_key << " " << val << "` failed: "
7c673cae
FG
687 << cpp_strerror(set_cmd.r) << dendl;
688 dout(0) << "mon returned " << set_cmd.r << ": " << set_cmd.outs << dendl;
689 }
690}
691
11fdf7f2
TL
692void ActivePyModules::set_config(const std::string &module_name,
693 const std::string &key, const boost::optional<std::string>& val)
c07f9fc5 694{
11fdf7f2 695 module_config.set_config(&monc, module_name, key, val);
c07f9fc5
FG
696}
697
3efd9988 698std::map<std::string, std::string> ActivePyModules::get_services() const
7c673cae 699{
3efd9988 700 std::map<std::string, std::string> result;
11fdf7f2 701 std::lock_guard l(lock);
9f95a23c 702 for (const auto& [name, module] : modules) {
3efd9988
FG
703 std::string svc_str = module->get_uri();
704 if (!svc_str.empty()) {
9f95a23c 705 result[name] = svc_str;
3efd9988
FG
706 }
707 }
7c673cae 708
3efd9988 709 return result;
7c673cae
FG
710}
711
11fdf7f2
TL
712PyObject* ActivePyModules::with_perf_counters(
713 std::function<void(PerfCounterInstance& counter_instance, PerfCounterType& counter_type, PyFormatter& f)> fct,
224ce89b 714 const std::string &svc_name,
7c673cae 715 const std::string &svc_id,
11fdf7f2 716 const std::string &path) const
7c673cae
FG
717{
718 PyThreadState *tstate = PyEval_SaveThread();
11fdf7f2 719 std::lock_guard l(lock);
7c673cae
FG
720 PyEval_RestoreThread(tstate);
721
722 PyFormatter f;
723 f.open_array_section(path.c_str());
724
9f95a23c 725 auto metadata = daemon_state.get(DaemonKey{svc_name, svc_id});
7c673cae 726 if (metadata) {
11fdf7f2 727 std::lock_guard l2(metadata->lock);
7c673cae
FG
728 if (metadata->perf_counters.instances.count(path)) {
729 auto counter_instance = metadata->perf_counters.instances.at(path);
28e407b8 730 auto counter_type = metadata->perf_counters.types.at(path);
11fdf7f2 731 fct(counter_instance, counter_type, f);
7c673cae
FG
732 } else {
733 dout(4) << "Missing counter: '" << path << "' ("
11fdf7f2 734 << svc_name << "." << svc_id << ")" << dendl;
7c673cae
FG
735 dout(20) << "Paths are:" << dendl;
736 for (const auto &i : metadata->perf_counters.instances) {
737 dout(20) << i.first << dendl;
738 }
739 }
740 } else {
741 dout(4) << "No daemon state for "
11fdf7f2 742 << svc_name << "." << svc_id << ")" << dendl;
7c673cae
FG
743 }
744 f.close_section();
745 return f.get();
746}
747
11fdf7f2
TL
748PyObject* ActivePyModules::get_counter_python(
749 const std::string &svc_name,
750 const std::string &svc_id,
751 const std::string &path)
752{
753 auto extract_counters = [](
754 PerfCounterInstance& counter_instance,
755 PerfCounterType& counter_type,
756 PyFormatter& f)
757 {
758 if (counter_type.type & PERFCOUNTER_LONGRUNAVG) {
759 const auto &avg_data = counter_instance.get_data_avg();
760 for (const auto &datapoint : avg_data) {
761 f.open_array_section("datapoint");
9f95a23c 762 f.dump_float("t", datapoint.t);
11fdf7f2
TL
763 f.dump_unsigned("s", datapoint.s);
764 f.dump_unsigned("c", datapoint.c);
765 f.close_section();
766 }
767 } else {
768 const auto &data = counter_instance.get_data();
769 for (const auto &datapoint : data) {
770 f.open_array_section("datapoint");
9f95a23c 771 f.dump_float("t", datapoint.t);
11fdf7f2
TL
772 f.dump_unsigned("v", datapoint.v);
773 f.close_section();
774 }
775 }
776 };
777 return with_perf_counters(extract_counters, svc_name, svc_id, path);
778}
779
780PyObject* ActivePyModules::get_latest_counter_python(
781 const std::string &svc_name,
782 const std::string &svc_id,
783 const std::string &path)
784{
785 auto extract_latest_counters = [](
786 PerfCounterInstance& counter_instance,
787 PerfCounterType& counter_type,
788 PyFormatter& f)
789 {
790 if (counter_type.type & PERFCOUNTER_LONGRUNAVG) {
791 const auto &datapoint = counter_instance.get_latest_data_avg();
9f95a23c 792 f.dump_float("t", datapoint.t);
11fdf7f2
TL
793 f.dump_unsigned("s", datapoint.s);
794 f.dump_unsigned("c", datapoint.c);
795 } else {
796 const auto &datapoint = counter_instance.get_latest_data();
9f95a23c 797 f.dump_float("t", datapoint.t);
11fdf7f2
TL
798 f.dump_unsigned("v", datapoint.v);
799 }
800 };
801 return with_perf_counters(extract_latest_counters, svc_name, svc_id, path);
802}
803
3efd9988 804PyObject* ActivePyModules::get_perf_schema_python(
11fdf7f2 805 const std::string &svc_type,
c07f9fc5
FG
806 const std::string &svc_id)
807{
808 PyThreadState *tstate = PyEval_SaveThread();
11fdf7f2 809 std::lock_guard l(lock);
c07f9fc5
FG
810 PyEval_RestoreThread(tstate);
811
3efd9988 812 DaemonStateCollection daemons;
c07f9fc5
FG
813
814 if (svc_type == "") {
11fdf7f2 815 daemons = daemon_state.get_all();
c07f9fc5 816 } else if (svc_id.empty()) {
11fdf7f2 817 daemons = daemon_state.get_by_service(svc_type);
c07f9fc5 818 } else {
9f95a23c 819 auto key = DaemonKey{svc_type, svc_id};
c07f9fc5 820 // so that the below can be a loop in all cases
3efd9988
FG
821 auto got = daemon_state.get(key);
822 if (got != nullptr) {
823 daemons[key] = got;
c07f9fc5
FG
824 }
825 }
826
827 PyFormatter f;
3efd9988 828 if (!daemons.empty()) {
9f95a23c
TL
829 for (auto& [key, state] : daemons) {
830 f.open_object_section(ceph::to_string(key).c_str());
c07f9fc5 831
11fdf7f2 832 std::lock_guard l(state->lock);
3efd9988
FG
833 for (auto ctr_inst_iter : state->perf_counters.instances) {
834 const auto &counter_name = ctr_inst_iter.first;
835 f.open_object_section(counter_name.c_str());
836 auto type = state->perf_counters.types[counter_name];
c07f9fc5
FG
837 f.dump_string("description", type.description);
838 if (!type.nick.empty()) {
839 f.dump_string("nick", type.nick);
840 }
841 f.dump_unsigned("type", type.type);
3efd9988 842 f.dump_unsigned("priority", type.priority);
1adf2230 843 f.dump_unsigned("units", type.unit);
c07f9fc5
FG
844 f.close_section();
845 }
846 f.close_section();
847 }
848 } else {
849 dout(4) << __func__ << ": No daemon state found for "
850 << svc_type << "." << svc_id << ")" << dendl;
851 }
c07f9fc5
FG
852 return f.get();
853}
854
3efd9988 855PyObject *ActivePyModules::get_context()
7c673cae
FG
856{
857 PyThreadState *tstate = PyEval_SaveThread();
11fdf7f2 858 std::lock_guard l(lock);
7c673cae
FG
859 PyEval_RestoreThread(tstate);
860
861 // Construct a capsule containing ceph context.
862 // Not incrementing/decrementing ref count on the context because
863 // it's the global one and it has process lifetime.
864 auto capsule = PyCapsule_New(g_ceph_context, nullptr, nullptr);
865 return capsule;
866}
867
3efd9988
FG
868/**
869 * Helper for our wrapped types that take a capsule in their constructor.
870 */
871PyObject *construct_with_capsule(
872 const std::string &module_name,
873 const std::string &clsname,
874 void *wrapped)
224ce89b 875{
3efd9988
FG
876 // Look up the OSDMap type which we will construct
877 PyObject *module = PyImport_ImportModule(module_name.c_str());
878 if (!module) {
879 derr << "Failed to import python module:" << dendl;
880 derr << handle_pyerror() << dendl;
224ce89b 881 }
11fdf7f2 882 ceph_assert(module);
3efd9988
FG
883
884 PyObject *wrapper_type = PyObject_GetAttrString(
885 module, (const char*)clsname.c_str());
886 if (!wrapper_type) {
887 derr << "Failed to get python type:" << dendl;
888 derr << handle_pyerror() << dendl;
224ce89b 889 }
11fdf7f2 890 ceph_assert(wrapper_type);
3efd9988
FG
891
892 // Construct a capsule containing an OSDMap.
893 auto wrapped_capsule = PyCapsule_New(wrapped, nullptr, nullptr);
11fdf7f2 894 ceph_assert(wrapped_capsule);
3efd9988
FG
895
896 // Construct the python OSDMap
897 auto pArgs = PyTuple_Pack(1, wrapped_capsule);
898 auto wrapper_instance = PyObject_CallObject(wrapper_type, pArgs);
899 if (wrapper_instance == nullptr) {
900 derr << "Failed to construct python OSDMap:" << dendl;
901 derr << handle_pyerror() << dendl;
902 }
11fdf7f2 903 ceph_assert(wrapper_instance != nullptr);
3efd9988
FG
904 Py_DECREF(pArgs);
905 Py_DECREF(wrapped_capsule);
906
907 Py_DECREF(wrapper_type);
908 Py_DECREF(module);
909
910 return wrapper_instance;
224ce89b
WB
911}
912
3efd9988 913PyObject *ActivePyModules::get_osdmap()
224ce89b 914{
3efd9988
FG
915 OSDMap *newmap = new OSDMap;
916
11fdf7f2
TL
917 PyThreadState *tstate = PyEval_SaveThread();
918 {
919 std::lock_guard l(lock);
920 cluster_state.with_osdmap([&](const OSDMap& o) {
921 newmap->deepish_copy_from(o);
922 });
923 }
924 PyEval_RestoreThread(tstate);
3efd9988
FG
925
926 return construct_with_capsule("mgr_module", "OSDMap", (void*)newmap);
224ce89b 927}
c07f9fc5 928
3efd9988 929void ActivePyModules::set_health_checks(const std::string& module_name,
c07f9fc5
FG
930 health_check_map_t&& checks)
931{
11fdf7f2
TL
932 bool changed = false;
933
9f95a23c 934 lock.lock();
3efd9988 935 auto p = modules.find(module_name);
c07f9fc5 936 if (p != modules.end()) {
11fdf7f2 937 changed = p->second->set_health_checks(std::move(checks));
c07f9fc5 938 }
9f95a23c 939 lock.unlock();
11fdf7f2
TL
940
941 // immediately schedule a report to be sent to the monitors with the new
942 // health checks that have changed. This is done asynchronusly to avoid
943 // blocking python land. ActivePyModules::lock needs to be dropped to make
944 // lockdep happy:
945 //
946 // send_report callers: DaemonServer::lock -> PyModuleRegistery::lock
947 // active_start: PyModuleRegistry::lock -> ActivePyModules::lock
948 //
949 // if we don't release this->lock before calling schedule_tick a cycle is
950 // formed with the addition of ActivePyModules::lock -> DaemonServer::lock.
951 // This is still correct as send_report is run asynchronously under
952 // DaemonServer::lock.
953 if (changed)
954 server.schedule_tick(0);
955}
956
957int ActivePyModules::handle_command(
92f5a8d4
TL
958 const ModuleCommand& module_command,
959 const MgrSession& session,
11fdf7f2
TL
960 const cmdmap_t &cmdmap,
961 const bufferlist &inbuf,
962 std::stringstream *ds,
963 std::stringstream *ss)
964{
92f5a8d4
TL
965 lock.lock();
966 auto mod_iter = modules.find(module_command.module_name);
11fdf7f2 967 if (mod_iter == modules.end()) {
92f5a8d4
TL
968 *ss << "Module '" << module_command.module_name << "' is not available";
969 lock.unlock();
11fdf7f2
TL
970 return -ENOENT;
971 }
972
92f5a8d4
TL
973 lock.unlock();
974 return mod_iter->second->handle_command(module_command, session, cmdmap,
975 inbuf, ds, ss);
c07f9fc5
FG
976}
977
3efd9988 978void ActivePyModules::get_health_checks(health_check_map_t *checks)
c07f9fc5 979{
11fdf7f2 980 std::lock_guard l(lock);
9f95a23c
TL
981 for (auto& [name, module] : modules) {
982 dout(15) << "getting health checks for " << name << dendl;
983 module->get_health_checks(checks);
c07f9fc5
FG
984 }
985}
3efd9988 986
11fdf7f2
TL
987void ActivePyModules::update_progress_event(
988 const std::string& evid,
989 const std::string& desc,
990 float progress)
991{
992 std::lock_guard l(lock);
993 auto& pe = progress_events[evid];
994 pe.message = desc;
995 pe.progress = progress;
996}
997
998void ActivePyModules::complete_progress_event(const std::string& evid)
999{
1000 std::lock_guard l(lock);
1001 progress_events.erase(evid);
1002}
1003
1004void ActivePyModules::clear_all_progress_events()
1005{
1006 std::lock_guard l(lock);
1007 progress_events.clear();
1008}
1009
1010void ActivePyModules::get_progress_events(std::map<std::string,ProgressEvent> *events)
1011{
1012 std::lock_guard l(lock);
1013 *events = progress_events;
1014}
1015
1016void ActivePyModules::config_notify()
1017{
1018 std::lock_guard l(lock);
9f95a23c 1019 for (auto& [name, module] : modules) {
11fdf7f2
TL
1020 // Send all python calls down a Finisher to avoid blocking
1021 // C++ code, and avoid any potential lock cycles.
9f95a23c
TL
1022 dout(15) << "notify (config) " << name << dendl;
1023 // workaround for https://bugs.llvm.org/show_bug.cgi?id=35984
1024 finisher.queue(new LambdaContext([module=module](int r){
1025 module->config_notify();
1026 }));
11fdf7f2
TL
1027 }
1028}
1029
3efd9988
FG
1030void ActivePyModules::set_uri(const std::string& module_name,
1031 const std::string &uri)
1032{
11fdf7f2 1033 std::lock_guard l(lock);
3efd9988
FG
1034
1035 dout(4) << " module " << module_name << " set URI '" << uri << "'" << dendl;
1036
9f95a23c 1037 modules.at(module_name)->set_uri(uri);
3efd9988
FG
1038}
1039
9f95a23c 1040MetricQueryID ActivePyModules::add_osd_perf_query(
11fdf7f2
TL
1041 const OSDPerfMetricQuery &query,
1042 const std::optional<OSDPerfMetricLimit> &limit)
1043{
1044 return server.add_osd_perf_query(query, limit);
1045}
1046
9f95a23c 1047void ActivePyModules::remove_osd_perf_query(MetricQueryID query_id)
11fdf7f2
TL
1048{
1049 int r = server.remove_osd_perf_query(query_id);
1050 if (r < 0) {
1051 dout(0) << "remove_osd_perf_query for query_id=" << query_id << " failed: "
1052 << cpp_strerror(r) << dendl;
1053 }
1054}
1055
9f95a23c 1056PyObject *ActivePyModules::get_osd_perf_counters(MetricQueryID query_id)
11fdf7f2
TL
1057{
1058 std::map<OSDPerfMetricKey, PerformanceCounters> counters;
1059
1060 int r = server.get_osd_perf_counters(query_id, &counters);
1061 if (r < 0) {
1062 dout(0) << "get_osd_perf_counters for query_id=" << query_id << " failed: "
1063 << cpp_strerror(r) << dendl;
1064 Py_RETURN_NONE;
1065 }
1066
1067 PyFormatter f;
1068
1069 f.open_array_section("counters");
1070 for (auto &it : counters) {
1071 auto &key = it.first;
1072 auto &instance_counters = it.second;
1073 f.open_object_section("i");
1074 f.open_array_section("k");
1075 for (auto &sub_key : key) {
1076 f.open_array_section("s");
1077 for (size_t i = 0; i < sub_key.size(); i++) {
1078 f.dump_string(stringify(i).c_str(), sub_key[i]);
1079 }
1080 f.close_section(); // s
1081 }
1082 f.close_section(); // k
1083 f.open_array_section("c");
1084 for (auto &c : instance_counters) {
1085 f.open_array_section("p");
1086 f.dump_unsigned("0", c.first);
1087 f.dump_unsigned("1", c.second);
1088 f.close_section(); // p
1089 }
1090 f.close_section(); // c
1091 f.close_section(); // i
1092 }
1093 f.close_section(); // counters
1094
1095 return f.get();
1096}
1097
1098void ActivePyModules::cluster_log(const std::string &channel, clog_type prio,
1099 const std::string &message)
1100{
1101 std::lock_guard l(lock);
1102
9f95a23c
TL
1103 auto cl = monc.get_log_client()->create_channel(channel);
1104 map<string,string> log_to_monitors;
1105 map<string,string> log_to_syslog;
1106 map<string,string> log_channel;
1107 map<string,string> log_prio;
1108 map<string,string> log_to_graylog;
1109 map<string,string> log_to_graylog_host;
1110 map<string,string> log_to_graylog_port;
1111 uuid_d fsid;
1112 string host;
1113 if (parse_log_client_options(g_ceph_context, log_to_monitors, log_to_syslog,
1114 log_channel, log_prio, log_to_graylog,
1115 log_to_graylog_host, log_to_graylog_port,
1116 fsid, host) == 0)
1117 cl->update_config(log_to_monitors, log_to_syslog,
1118 log_channel, log_prio, log_to_graylog,
1119 log_to_graylog_host, log_to_graylog_port,
1120 fsid, host);
1121 cl->do_log(prio, message);
1122}
1123
1124void ActivePyModules::register_client(std::string_view name, std::string addrs)
1125{
1126 std::lock_guard l(lock);
1127
1128 entity_addrvec_t addrv;
1129 addrv.parse(addrs.data());
1130
1131 dout(7) << "registering msgr client handle " << addrv << dendl;
1132 py_module_registry.register_client(name, std::move(addrv));
1133}
1134
1135void ActivePyModules::unregister_client(std::string_view name, std::string addrs)
1136{
1137 std::lock_guard l(lock);
1138
1139 entity_addrvec_t addrv;
1140 addrv.parse(addrs.data());
1141
1142 dout(7) << "unregistering msgr client handle " << addrv << dendl;
1143 py_module_registry.unregister_client(name, addrv);
11fdf7f2 1144}