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