]> git.proxmox.com Git - ceph.git/blame - ceph/src/mon/ConfigMonitor.cc
bump version to 18.2.2-pve1
[ceph.git] / ceph / src / mon / ConfigMonitor.cc
CommitLineData
11fdf7f2
TL
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
3
4#include <boost/algorithm/string/predicate.hpp>
5
6#include "mon/Monitor.h"
7#include "mon/ConfigMonitor.h"
f67539c2 8#include "mon/KVMonitor.h"
11fdf7f2
TL
9#include "mon/MgrMonitor.h"
10#include "mon/OSDMonitor.h"
11#include "messages/MConfig.h"
12#include "messages/MGetConfig.h"
13#include "messages/MMonCommand.h"
14#include "common/Formatter.h"
15#include "common/TextTable.h"
16#include "common/cmdparse.h"
17#include "include/stringify.h"
18
19#define dout_subsys ceph_subsys_mon
20#undef dout_prefix
21#define dout_prefix _prefix(_dout, mon, this)
9f95a23c 22using namespace TOPNSPC::common;
f67539c2
TL
23
24using namespace std::literals;
25
26using std::cerr;
27using std::cout;
28using std::dec;
29using std::hex;
30using std::list;
31using std::map;
32using std::make_pair;
33using std::ostream;
34using std::ostringstream;
35using std::pair;
36using std::set;
37using std::setfill;
38using std::string;
39using std::stringstream;
40using std::to_string;
41using std::vector;
42using std::unique_ptr;
43
44using ceph::bufferlist;
45using ceph::decode;
46using ceph::encode;
47using ceph::Formatter;
48using ceph::JSONFormatter;
49using ceph::mono_clock;
50using ceph::mono_time;
51using ceph::timespan_str;
52static ostream& _prefix(std::ostream *_dout, const Monitor &mon,
11fdf7f2 53 const ConfigMonitor *hmon) {
f67539c2
TL
54 return *_dout << "mon." << mon.name << "@" << mon.rank
55 << "(" << mon.get_state_name() << ").config ";
11fdf7f2
TL
56}
57
58const string KEY_PREFIX("config/");
59const string HISTORY_PREFIX("config-history/");
60
f67539c2 61ConfigMonitor::ConfigMonitor(Monitor &m, Paxos &p, const string& service_name)
11fdf7f2
TL
62 : PaxosService(m, p, service_name) {
63}
64
65void ConfigMonitor::init()
66{
67 dout(10) << __func__ << dendl;
68}
69
70void ConfigMonitor::create_initial()
71{
72 dout(10) << __func__ << dendl;
73 version = 0;
74 pending.clear();
75}
76
77void ConfigMonitor::update_from_paxos(bool *need_bootstrap)
78{
79 if (version == get_last_committed()) {
80 return;
81 }
82 version = get_last_committed();
83 dout(10) << __func__ << " " << version << dendl;
84 load_config();
85 check_all_subs();
86}
87
88void ConfigMonitor::create_pending()
89{
90 dout(10) << " " << version << dendl;
91 pending.clear();
92 pending_description.clear();
93}
94
95void ConfigMonitor::encode_pending(MonitorDBStore::TransactionRef t)
96{
97 dout(10) << " " << (version+1) << dendl;
98 put_last_committed(t, version+1);
f67539c2
TL
99 // NOTE: caller should have done encode_pending_to_kvmon() and
100 // kvmon->propose_pending() to commit the actual config changes.
101}
11fdf7f2 102
f67539c2
TL
103void ConfigMonitor::encode_pending_to_kvmon()
104{
105 // we need to pass our data through KVMonitor so that it is properly
106 // versioned and shared with subscribers.
92f5a8d4
TL
107 for (auto& [key, value] : pending_cleanup) {
108 if (pending.count(key) == 0) {
109 derr << __func__ << " repair: adjusting config key '" << key << "'"
110 << dendl;
111 pending[key] = value;
112 }
113 }
114 pending_cleanup.clear();
115
11fdf7f2
TL
116 // TODO: record changed sections (osd, mds.foo, rack:bar, ...)
117
118 string history = HISTORY_PREFIX + stringify(version+1) + "/";
119 {
120 bufferlist metabl;
121 ::encode(ceph_clock_now(), metabl);
122 ::encode(pending_description, metabl);
f67539c2 123 mon.kvmon()->enqueue_set(history, metabl);
11fdf7f2
TL
124 }
125 for (auto& p : pending) {
126 string key = KEY_PREFIX + p.first;
127 auto q = current.find(p.first);
128 if (q != current.end()) {
129 if (p.second && *p.second == q->second) {
130 continue;
131 }
f67539c2 132 mon.kvmon()->enqueue_set(history + "-" + p.first, q->second);
11fdf7f2
TL
133 } else if (!p.second) {
134 continue;
135 }
136 if (p.second) {
137 dout(20) << __func__ << " set " << key << dendl;
f67539c2
TL
138 mon.kvmon()->enqueue_set(key, *p.second);
139 mon.kvmon()->enqueue_set(history + "+" + p.first, *p.second);
140 } else {
11fdf7f2 141 dout(20) << __func__ << " rm " << key << dendl;
f67539c2 142 mon.kvmon()->enqueue_rm(key);
11fdf7f2
TL
143 }
144 }
145}
146
147version_t ConfigMonitor::get_trim_to() const
148{
149 // we don't actually need *any* old states, but keep a few.
150 if (version > 5) {
151 return version - 5;
152 }
153 return 0;
154}
155
156bool ConfigMonitor::preprocess_query(MonOpRequestRef op)
157{
158 switch (op->get_req()->get_type()) {
159 case MSG_MON_COMMAND:
160 try {
161 return preprocess_command(op);
162 } catch (const bad_cmd_get& e) {
163 bufferlist bl;
f67539c2 164 mon.reply_command(op, -EINVAL, e.what(), bl, get_last_committed());
11fdf7f2
TL
165 return true;
166 }
167 }
168 return false;
169}
170
11fdf7f2
TL
171bool ConfigMonitor::preprocess_command(MonOpRequestRef op)
172{
9f95a23c 173 auto m = op->get_req<MMonCommand>();
11fdf7f2
TL
174 std::stringstream ss;
175 int err = 0;
176
177 cmdmap_t cmdmap;
178 if (!cmdmap_from_json(m->cmd, &cmdmap, ss)) {
179 string rs = ss.str();
f67539c2 180 mon.reply_command(op, -EINVAL, rs, get_last_committed());
11fdf7f2
TL
181 return true;
182 }
20effc67 183 string format = cmd_getval_or<string>(cmdmap, "format", "plain");
11fdf7f2
TL
184 boost::scoped_ptr<Formatter> f(Formatter::create(format));
185
186 string prefix;
9f95a23c 187 cmd_getval(cmdmap, "prefix", prefix);
11fdf7f2
TL
188
189 bufferlist odata;
190 if (prefix == "config help") {
191 stringstream ss;
192 string name;
9f95a23c 193 cmd_getval(cmdmap, "key", name);
2a845540 194 name = ConfFile::normalize_key_name(name);
11fdf7f2
TL
195 const Option *opt = g_conf().find_option(name);
196 if (!opt) {
f67539c2 197 opt = mon.mgrmon()->find_module_option(name);
11fdf7f2
TL
198 }
199 if (opt) {
200 if (f) {
201 f->dump_object("option", *opt);
202 } else {
203 opt->print(&ss);
204 }
205 } else {
206 ss << "configuration option '" << name << "' not recognized";
207 err = -ENOENT;
208 goto reply;
209 }
210 if (f) {
211 f->flush(odata);
212 } else {
213 odata.append(ss.str());
214 }
215 } else if (prefix == "config ls") {
216 ostringstream ss;
217 if (f) {
218 f->open_array_section("options");
219 }
220 for (auto& i : ceph_options) {
221 if (f) {
222 f->dump_string("option", i.name);
223 } else {
224 ss << i.name << "\n";
225 }
226 }
f67539c2 227 for (auto& i : mon.mgrmon()->get_mgr_module_options()) {
11fdf7f2
TL
228 if (f) {
229 f->dump_string("option", i.first);
230 } else {
231 ss << i.first << "\n";
232 }
233 }
234 if (f) {
235 f->close_section();
236 f->flush(odata);
237 } else {
238 odata.append(ss.str());
239 }
240 } else if (prefix == "config dump") {
241 list<pair<string,Section*>> sections = {
242 make_pair("global", &config_map.global)
243 };
244 for (string type : { "mon", "mgr", "osd", "mds", "client" }) {
245 auto i = config_map.by_type.find(type);
246 if (i != config_map.by_type.end()) {
247 sections.push_back(make_pair(i->first, &i->second));
248 }
249 auto j = config_map.by_id.lower_bound(type);
250 while (j != config_map.by_id.end() &&
251 j->first.find(type) == 0) {
252 sections.push_back(make_pair(j->first, &j->second));
253 ++j;
254 }
255 }
256 TextTable tbl;
257 if (!f) {
258 tbl.define_column("WHO", TextTable::LEFT, TextTable::LEFT);
259 tbl.define_column("MASK", TextTable::LEFT, TextTable::LEFT);
260 tbl.define_column("LEVEL", TextTable::LEFT, TextTable::LEFT);
261 tbl.define_column("OPTION", TextTable::LEFT, TextTable::LEFT);
262 tbl.define_column("VALUE", TextTable::LEFT, TextTable::LEFT);
263 tbl.define_column("RO", TextTable::LEFT, TextTable::LEFT);
264 } else {
265 f->open_array_section("config");
266 }
267 for (auto s : sections) {
268 for (auto& i : s.second->options) {
269 if (!f) {
20effc67 270 tbl << s.first;
11fdf7f2
TL
271 tbl << i.second.mask.to_str();
272 tbl << Option::level_to_str(i.second.opt->level);
273 tbl << i.first;
274 tbl << i.second.raw_value;
275 tbl << (i.second.opt->can_update_at_runtime() ? "" : "*");
276 tbl << TextTable::endrow;
277 } else {
278 f->open_object_section("option");
279 f->dump_string("section", s.first);
280 i.second.dump(f.get());
281 f->close_section();
282 }
283 }
284 }
285 if (!f) {
286 odata.append(stringify(tbl));
287 } else {
288 f->close_section();
289 f->flush(odata);
290 }
291 } else if (prefix == "config get") {
292 string who, name;
9f95a23c 293 cmd_getval(cmdmap, "who", who);
11fdf7f2
TL
294
295 EntityName entity;
9f95a23c
TL
296 if (!entity.from_str(who) &&
297 !entity.from_str(who + ".")) {
11fdf7f2
TL
298 ss << "unrecognized entity '" << who << "'";
299 err = -EINVAL;
300 goto reply;
301 }
302
303 map<string,string> crush_location;
304 string device_class;
305 if (entity.is_osd()) {
f67539c2 306 mon.osdmon()->osdmap.crush->get_full_location(who, &crush_location);
11fdf7f2 307 int id = atoi(entity.get_id().c_str());
f67539c2 308 const char *c = mon.osdmon()->osdmap.crush->get_item_class(id);
11fdf7f2
TL
309 if (c) {
310 device_class = c;
311 }
312 dout(10) << __func__ << " crush_location " << crush_location
313 << " class " << device_class << dendl;
314 }
315
11fdf7f2 316 std::map<std::string,pair<std::string,const MaskedOption*>> src;
9f95a23c 317 auto config = config_map.generate_entity_map(
11fdf7f2
TL
318 entity,
319 crush_location,
f67539c2 320 mon.osdmon()->osdmap.crush.get(),
11fdf7f2 321 device_class,
9f95a23c 322 &src);
11fdf7f2 323
9f95a23c 324 if (cmd_getval(cmdmap, "key", name)) {
2a845540 325 name = ConfFile::normalize_key_name(name);
11fdf7f2
TL
326 const Option *opt = g_conf().find_option(name);
327 if (!opt) {
f67539c2 328 opt = mon.mgrmon()->find_module_option(name);
11fdf7f2
TL
329 }
330 if (!opt) {
2a845540 331 ss << "unrecognized key '" << name << "'";
11fdf7f2
TL
332 err = -ENOENT;
333 goto reply;
334 }
92f5a8d4
TL
335 if (opt->has_flag(Option::FLAG_NO_MON_UPDATE)) {
336 // handle special options
337 if (name == "fsid") {
f67539c2 338 odata.append(stringify(mon.monmap->get_fsid()));
92f5a8d4
TL
339 odata.append("\n");
340 goto reply;
341 }
342 err = -EINVAL;
343 ss << name << " is special and cannot be stored by the mon";
344 goto reply;
345 }
346 // get a single value
347 auto p = config.find(name);
348 if (p != config.end()) {
349 odata.append(p->second);
350 odata.append("\n");
351 goto reply;
352 }
11fdf7f2 353 if (!entity.is_client() &&
20effc67 354 opt->daemon_value != Option::value_t{}) {
11fdf7f2
TL
355 odata.append(Option::to_str(opt->daemon_value));
356 } else {
357 odata.append(Option::to_str(opt->value));
358 }
359 odata.append("\n");
360 } else {
361 // dump all (non-default) values for this entity
362 TextTable tbl;
363 if (!f) {
364 tbl.define_column("WHO", TextTable::LEFT, TextTable::LEFT);
365 tbl.define_column("MASK", TextTable::LEFT, TextTable::LEFT);
366 tbl.define_column("LEVEL", TextTable::LEFT, TextTable::LEFT);
367 tbl.define_column("OPTION", TextTable::LEFT, TextTable::LEFT);
368 tbl.define_column("VALUE", TextTable::LEFT, TextTable::LEFT);
369 tbl.define_column("RO", TextTable::LEFT, TextTable::LEFT);
370 } else {
371 f->open_object_section("config");
372 }
373 auto p = config.begin();
374 auto q = src.begin();
375 for (; p != config.end(); ++p, ++q) {
376 if (name.size() && p->first != name) {
377 continue;
378 }
379 if (!f) {
380 tbl << q->second.first;
381 tbl << q->second.second->mask.to_str();
382 tbl << Option::level_to_str(q->second.second->opt->level);
383 tbl << p->first;
384 tbl << p->second;
385 tbl << (q->second.second->opt->can_update_at_runtime() ? "" : "*");
386 tbl << TextTable::endrow;
387 } else {
388 f->open_object_section(p->first.c_str());
389 f->dump_string("value", p->second);
390 f->dump_string("section", q->second.first);
391 f->dump_object("mask", q->second.second->mask);
392 f->dump_bool("can_update_at_runtime",
393 q->second.second->opt->can_update_at_runtime());
394 f->close_section();
395 }
396 }
397 if (!f) {
398 odata.append(stringify(tbl));
399 } else {
400 f->close_section();
401 f->flush(odata);
402 }
403 }
404 } else if (prefix == "config log") {
405 int64_t num = 10;
9f95a23c 406 cmd_getval(cmdmap, "num", num);
11fdf7f2
TL
407 ostringstream ds;
408 if (f) {
409 f->open_array_section("changesets");
410 }
411 for (version_t v = version; v > version - std::min(version, (version_t)num); --v) {
412 ConfigChangeSet ch;
413 load_changeset(v, &ch);
414 if (f) {
415 f->dump_object("changeset", ch);
416 } else {
417 ch.print(ds);
418 }
419 }
420 if (f) {
421 f->close_section();
422 f->flush(odata);
423 } else {
424 odata.append(ds.str());
425 }
426 } else if (prefix == "config generate-minimal-conf") {
427 ostringstream conf;
f67539c2 428 conf << "# minimal ceph.conf for " << mon.monmap->get_fsid() << "\n";
11fdf7f2
TL
429
430 // the basics
431 conf << "[global]\n";
f67539c2 432 conf << "\tfsid = " << mon.monmap->get_fsid() << "\n";
11fdf7f2 433 conf << "\tmon_host = ";
f67539c2
TL
434 for (auto i = mon.monmap->mon_info.begin();
435 i != mon.monmap->mon_info.end();
11fdf7f2 436 ++i) {
f67539c2 437 if (i != mon.monmap->mon_info.begin()) {
11fdf7f2
TL
438 conf << " ";
439 }
9f95a23c
TL
440 if (i->second.public_addrs.size() == 1 &&
441 i->second.public_addrs.front().is_legacy() &&
442 i->second.public_addrs.front().get_port() == CEPH_MON_PORT_LEGACY) {
443 // if this is a legacy addr on the legacy default port, then
444 // use the legacy-compatible formatting so that old clients
445 // can use this config. new code will see the :6789 and correctly
446 // interpret this as a v1 address.
447 conf << i->second.public_addrs.get_legacy_str();
448 } else {
449 conf << i->second.public_addrs;
450 }
11fdf7f2
TL
451 }
452 conf << "\n";
453 conf << config_map.global.get_minimal_conf();
454 for (auto m : { &config_map.by_type, &config_map.by_id }) {
455 for (auto& i : *m) {
456 auto s = i.second.get_minimal_conf();
457 if (s.size()) {
458 conf << "\n[" << i.first << "]\n" << s;
459 }
460 }
461 }
462 odata.append(conf.str());
463 err = 0;
464 } else {
465 return false;
466 }
467
468 reply:
f67539c2 469 mon.reply_command(op, err, ss.str(), odata, get_last_committed());
11fdf7f2
TL
470 return true;
471}
472
473void ConfigMonitor::handle_get_config(MonOpRequestRef op)
474{
9f95a23c 475 auto m = op->get_req<MGetConfig>();
11fdf7f2
TL
476 dout(10) << __func__ << " " << m->name << " host " << m->host << dendl;
477
f67539c2 478 const OSDMap& osdmap = mon.osdmon()->osdmap;
11fdf7f2
TL
479 map<string,string> crush_location;
480 osdmap.crush->get_full_location(m->host, &crush_location);
9f95a23c 481 auto out = config_map.generate_entity_map(
11fdf7f2
TL
482 m->name,
483 crush_location,
484 osdmap.crush.get(),
9f95a23c 485 m->device_class);
11fdf7f2 486 dout(20) << " config is " << out << dendl;
9f95a23c 487 m->get_connection()->send_message(new MConfig{std::move(out)});
11fdf7f2
TL
488}
489
490bool ConfigMonitor::prepare_update(MonOpRequestRef op)
491{
492 Message *m = op->get_req();
493 dout(7) << "prepare_update " << *m
494 << " from " << m->get_orig_source_inst() << dendl;
495 switch (m->get_type()) {
496 case MSG_MON_COMMAND:
497 try {
498 return prepare_command(op);
499 } catch (const bad_cmd_get& e) {
500 bufferlist bl;
f67539c2 501 mon.reply_command(op, -EINVAL, e.what(), bl, get_last_committed());
11fdf7f2
TL
502 return true;
503 }
504 }
505 return false;
506}
507
508bool ConfigMonitor::prepare_command(MonOpRequestRef op)
509{
9f95a23c 510 auto m = op->get_req<MMonCommand>();
11fdf7f2
TL
511 std::stringstream ss;
512 int err = -EINVAL;
513
f67539c2
TL
514 // make sure kv is writeable.
515 if (!mon.kvmon()->is_writeable()) {
516 dout(10) << __func__ << " waiting for kv mon to be writeable" << dendl;
517 mon.kvmon()->wait_for_writeable(op, new C_RetryMessage(this, op));
518 return false;
519 }
520
11fdf7f2
TL
521 cmdmap_t cmdmap;
522 if (!cmdmap_from_json(m->cmd, &cmdmap, ss)) {
523 string rs = ss.str();
f67539c2 524 mon.reply_command(op, -EINVAL, rs, get_last_committed());
11fdf7f2
TL
525 return true;
526 }
527
528 string prefix;
9f95a23c 529 cmd_getval(cmdmap, "prefix", prefix);
11fdf7f2
TL
530 bufferlist odata;
531
532 if (prefix == "config set" ||
533 prefix == "config rm") {
534 string who;
535 string name, value;
536 bool force = false;
9f95a23c
TL
537 cmd_getval(cmdmap, "who", who);
538 cmd_getval(cmdmap, "name", name);
539 cmd_getval(cmdmap, "value", value);
540 cmd_getval(cmdmap, "force", force);
2a845540
TL
541 name = ConfFile::normalize_key_name(name);
542
11fdf7f2
TL
543 if (prefix == "config set" && !force) {
544 const Option *opt = g_conf().find_option(name);
545 if (!opt) {
f67539c2 546 opt = mon.mgrmon()->find_module_option(name);
11fdf7f2
TL
547 }
548 if (!opt) {
549 ss << "unrecognized config option '" << name << "'";
550 err = -EINVAL;
551 goto reply;
552 }
553
92f5a8d4
TL
554 Option::value_t real_value;
555 string errstr;
556 err = opt->parse_value(value, &real_value, &errstr, &value);
557 if (err < 0) {
558 ss << "error parsing value: " << errstr;
559 goto reply;
560 }
561
562 if (opt->has_flag(Option::FLAG_NO_MON_UPDATE)) {
563 err = -EINVAL;
564 ss << name << " is special and cannot be stored by the mon";
565 goto reply;
11fdf7f2
TL
566 }
567 }
568
569 string section;
570 OptionMask mask;
571 if (!ConfigMap::parse_mask(who, &section, &mask)) {
572 ss << "unrecognized config target '" << who << "'";
573 err = -EINVAL;
574 goto reply;
575 }
576
577 string key;
578 if (section.size()) {
579 key += section + "/";
92f5a8d4
TL
580 } else {
581 key += "global/";
11fdf7f2
TL
582 }
583 string mask_str = mask.to_str();
584 if (mask_str.size()) {
585 key += mask_str + "/";
586 }
587 key += name;
588
589 if (prefix == "config set") {
590 bufferlist bl;
591 bl.append(value);
592 pending[key] = bl;
593 } else {
20effc67 594 pending[key].reset();
11fdf7f2
TL
595 }
596 goto update;
597 } else if (prefix == "config reset") {
9f95a23c
TL
598 int64_t revert_to = -1;
599 cmd_getval(cmdmap, "num", revert_to);
600 if (revert_to < 0 ||
601 revert_to > (int64_t)version) {
11fdf7f2 602 err = -EINVAL;
9f95a23c
TL
603 ss << "must specify a valid historical version to revert to; "
604 << "see 'ceph config log' for a list of avialable configuration "
605 << "historical versions";
11fdf7f2
TL
606 goto reply;
607 }
9f95a23c 608 if (revert_to == (int64_t)version) {
11fdf7f2
TL
609 err = 0;
610 goto reply;
611 }
9f95a23c 612 for (int64_t v = version; v > revert_to; --v) {
11fdf7f2
TL
613 ConfigChangeSet ch;
614 load_changeset(v, &ch);
615 for (auto& i : ch.diff) {
616 if (i.second.first) {
617 bufferlist bl;
618 bl.append(*i.second.first);
619 pending[i.first] = bl;
620 } else if (i.second.second) {
20effc67 621 pending[i.first].reset();
11fdf7f2
TL
622 }
623 }
624 }
9f95a23c 625 pending_description = string("reset to ") + stringify(revert_to);
11fdf7f2
TL
626 goto update;
627 } else if (prefix == "config assimilate-conf") {
628 ConfFile cf;
11fdf7f2 629 bufferlist bl = m->get_data();
9f95a23c 630 err = cf.parse_bufferlist(&bl, &ss);
11fdf7f2 631 if (err < 0) {
11fdf7f2
TL
632 goto reply;
633 }
634 bool updated = false;
635 ostringstream newconf;
9f95a23c 636 for (auto& [section, s] : cf) {
11fdf7f2
TL
637 dout(20) << __func__ << " [" << section << "]" << dendl;
638 bool did_section = false;
9f95a23c 639 for (auto& [key, val] : s) {
11fdf7f2
TL
640 Option::value_t real_value;
641 string value;
642 string errstr;
9f95a23c 643 if (key.empty()) {
11fdf7f2
TL
644 continue;
645 }
646 // a known and worthy option?
9f95a23c 647 const Option *o = g_conf().find_option(key);
11fdf7f2 648 if (!o) {
f67539c2 649 o = mon.mgrmon()->find_module_option(key);
11fdf7f2
TL
650 }
651 if (!o ||
9f95a23c
TL
652 (o->flags & Option::FLAG_NO_MON_UPDATE) ||
653 (o->flags & Option::FLAG_CLUSTER_CREATE)) {
11fdf7f2
TL
654 goto skip;
655 }
656 // normalize
9f95a23c 657 err = o->parse_value(val, &real_value, &errstr, &value);
11fdf7f2 658 if (err < 0) {
9f95a23c
TL
659 dout(20) << __func__ << " failed to parse " << key << " = '"
660 << val << "'" << dendl;
11fdf7f2
TL
661 goto skip;
662 }
663 // does it conflict with an existing value?
664 {
665 const Section *s = config_map.find_section(section);
666 if (s) {
9f95a23c 667 auto k = s->options.find(key);
11fdf7f2
TL
668 if (k != s->options.end()) {
669 if (value != k->second.raw_value) {
9f95a23c 670 dout(20) << __func__ << " have " << key
11fdf7f2
TL
671 << " = " << k->second.raw_value
672 << " (not " << value << ")" << dendl;
673 goto skip;
674 }
9f95a23c 675 dout(20) << __func__ << " already have " << key
11fdf7f2
TL
676 << " = " << k->second.raw_value << dendl;
677 continue;
678 }
679 }
680 }
9f95a23c
TL
681 dout(20) << __func__ << " add " << key << " = " << value
682 << " (" << val << ")" << dendl;
11fdf7f2 683 {
11fdf7f2
TL
684 bufferlist bl;
685 bl.append(value);
9f95a23c 686 pending[section + "/" + key] = bl;
11fdf7f2
TL
687 updated = true;
688 }
689 continue;
690
691 skip:
9f95a23c
TL
692 dout(20) << __func__ << " skip " << key << " = " << value
693 << " (" << val << ")" << dendl;
11fdf7f2
TL
694 if (!did_section) {
695 newconf << "\n[" << section << "]\n";
696 did_section = true;
697 }
9f95a23c 698 newconf << "\t" << key << " = " << val << "\n";
11fdf7f2
TL
699 }
700 }
701 odata.append(newconf.str());
702 if (updated) {
703 goto update;
704 }
705 } else {
706 ss << "unknown command " << prefix;
707 err = -EINVAL;
708 }
709
710reply:
f67539c2 711 mon.reply_command(op, err, ss.str(), odata, get_last_committed());
11fdf7f2
TL
712 return false;
713
714update:
715 // see if there is an actual change
716 auto p = pending.begin();
717 while (p != pending.end()) {
718 auto q = current.find(p->first);
719 if (p->second && q != current.end() && *p->second == q->second) {
720 // set to same value
721 p = pending.erase(p);
722 } else if (!p->second && q == current.end()) {
723 // erasing non-existent value
724 p = pending.erase(p);
725 } else {
726 ++p;
727 }
728 }
729 if (pending.empty()) {
730 err = 0;
731 goto reply;
732 }
f67539c2
TL
733 // immediately propose *with* KV mon
734 encode_pending_to_kvmon();
735 paxos.plug();
736 mon.kvmon()->propose_pending();
737 paxos.unplug();
738 force_immediate_propose();
11fdf7f2
TL
739 wait_for_finished_proposal(
740 op,
741 new Monitor::C_Command(
742 mon, op, 0, ss.str(), odata,
743 get_last_committed() + 1));
744 return true;
745}
746
747void ConfigMonitor::tick()
748{
f67539c2 749 if (!is_active() || !mon.is_leader()) {
11fdf7f2
TL
750 return;
751 }
752 dout(10) << __func__ << dendl;
753 bool changed = false;
92f5a8d4
TL
754 if (!pending_cleanup.empty()) {
755 changed = true;
756 }
f67539c2
TL
757 if (changed && mon.kvmon()->is_writeable()) {
758 paxos.plug();
759 encode_pending_to_kvmon();
760 mon.kvmon()->propose_pending();
761 paxos.unplug();
11fdf7f2
TL
762 propose_pending();
763 }
764}
765
766void ConfigMonitor::on_active()
767{
768}
769
770void ConfigMonitor::load_config()
771{
f67539c2
TL
772 std::map<std::string,std::string> renamed_pacific = {
773 { "mon_osd_blacklist_default_expire", "mon_osd_blocklist_default_expire" },
774 { "mon_mds_blacklist_interval", "mon_mds_blocklist_interval" },
775 { "mon_mgr_blacklist_interval", "mon_mgr_blocklist_interval" },
776 { "rbd_blacklist_on_break_lock", "rbd_blocklist_on_break_lock" },
777 { "rbd_blacklist_expire_seconds", "rbd_blocklist_expire_seconds" },
778 { "mds_session_blacklist_on_timeout", "mds_session_blocklist_on_timeout" },
779 { "mds_session_blacklist_on_evict", "mds_session_blocklist_on_evict" },
780 };
781
11fdf7f2 782 unsigned num = 0;
f67539c2 783 KeyValueDB::Iterator it = mon.store->get_iterator(KV_PREFIX);
11fdf7f2
TL
784 it->lower_bound(KEY_PREFIX);
785 config_map.clear();
786 current.clear();
92f5a8d4 787 pending_cleanup.clear();
11fdf7f2
TL
788 while (it->valid() &&
789 it->key().compare(0, KEY_PREFIX.size(), KEY_PREFIX) == 0) {
790 string key = it->key().substr(KEY_PREFIX.size());
791 string value = it->value().to_str();
792
793 current[key] = it->value();
794
11fdf7f2
TL
795 string name;
796 string who;
f67539c2
TL
797 config_map.parse_key(key, &name, &who);
798
799 // has this option been renamed?
800 {
801 auto p = renamed_pacific.find(name);
802 if (p != renamed_pacific.end()) {
803 if (mon.monmap->min_mon_release >= ceph_release_t::pacific) {
804 // schedule a cleanup
20effc67 805 pending_cleanup[key].reset();
f67539c2
TL
806 pending_cleanup[who + "/" + p->second] = it->value();
807 }
808 // continue loading under the new name
809 name = p->second;
810 }
11fdf7f2
TL
811 }
812
813 const Option *opt = g_conf().find_option(name);
814 if (!opt) {
f67539c2 815 opt = mon.mgrmon()->find_module_option(name);
11fdf7f2
TL
816 }
817 if (!opt) {
818 dout(10) << __func__ << " unrecognized option '" << name << "'" << dendl;
f67539c2
TL
819 config_map.stray_options.push_back(
820 std::unique_ptr<Option>(
821 new Option(name, Option::TYPE_STR, Option::LEVEL_UNKNOWN)));
822 opt = config_map.stray_options.back().get();
11fdf7f2
TL
823 }
824
825 string err;
826 int r = opt->pre_validate(&value, &err);
827 if (r < 0) {
828 dout(10) << __func__ << " pre-validate failed on '" << name << "' = '"
829 << value << "' for " << name << dendl;
830 }
831
832 MaskedOption mopt(opt);
833 mopt.raw_value = value;
834 string section_name;
835 if (who.size() &&
836 !ConfigMap::parse_mask(who, &section_name, &mopt.mask)) {
92f5a8d4 837 derr << __func__ << " invalid mask for key " << key << dendl;
20effc67 838 pending_cleanup[key].reset();
92f5a8d4
TL
839 } else if (opt->has_flag(Option::FLAG_NO_MON_UPDATE)) {
840 dout(10) << __func__ << " NO_MON_UPDATE option '"
841 << name << "' = '" << value << "' for " << name
842 << dendl;
20effc67 843 pending_cleanup[key].reset();
11fdf7f2 844 } else {
92f5a8d4
TL
845 if (section_name.empty()) {
846 // we prefer global/$option instead of just $option
847 derr << __func__ << " adding global/ prefix to key '" << key << "'"
848 << dendl;
20effc67 849 pending_cleanup[key].reset();
92f5a8d4
TL
850 pending_cleanup["global/"s + key] = it->value();
851 }
11fdf7f2 852 Section *section = &config_map.global;;
92f5a8d4 853 if (section_name.size() && section_name != "global") {
11fdf7f2
TL
854 if (section_name.find('.') != std::string::npos) {
855 section = &config_map.by_id[section_name];
856 } else {
857 section = &config_map.by_type[section_name];
858 }
859 }
860 section->options.insert(make_pair(name, std::move(mopt)));
861 ++num;
862 }
863 it->next();
864 }
865 dout(10) << __func__ << " got " << num << " keys" << dendl;
11fdf7f2
TL
866
867 // refresh our own config
868 {
f67539c2 869 const OSDMap& osdmap = mon.osdmon()->osdmap;
11fdf7f2
TL
870 map<string,string> crush_location;
871 osdmap.crush->get_full_location(g_conf()->host, &crush_location);
9f95a23c 872 auto out = config_map.generate_entity_map(
11fdf7f2
TL
873 g_conf()->name,
874 crush_location,
875 osdmap.crush.get(),
9f95a23c 876 string{}); // no device class
11fdf7f2
TL
877 g_conf().set_mon_vals(g_ceph_context, out, nullptr);
878 }
879}
880
881void ConfigMonitor::load_changeset(version_t v, ConfigChangeSet *ch)
882{
883 ch->version = v;
884 string prefix = HISTORY_PREFIX + stringify(v) + "/";
f67539c2 885 KeyValueDB::Iterator it = mon.store->get_iterator(KV_PREFIX);
11fdf7f2
TL
886 it->lower_bound(prefix);
887 while (it->valid() && it->key().find(prefix) == 0) {
888 if (it->key() == prefix) {
889 bufferlist bl = it->value();
890 auto p = bl.cbegin();
891 try {
892 decode(ch->stamp, p);
893 decode(ch->name, p);
894 }
f67539c2 895 catch (ceph::buffer::error& e) {
11fdf7f2
TL
896 derr << __func__ << " failure decoding changeset " << v << dendl;
897 }
898 } else {
899 char op = it->key()[prefix.length()];
900 string key = it->key().substr(prefix.length() + 1);
901 if (op == '-') {
902 ch->diff[key].first = it->value().to_str();
903 } else if (op == '+') {
904 ch->diff[key].second = it->value().to_str();
905 }
906 }
907 it->next();
908 }
909}
910
911bool ConfigMonitor::refresh_config(MonSession *s)
912{
f67539c2 913 const OSDMap& osdmap = mon.osdmon()->osdmap;
11fdf7f2
TL
914 map<string,string> crush_location;
915 if (s->remote_host.size()) {
916 osdmap.crush->get_full_location(s->remote_host, &crush_location);
917 dout(10) << __func__ << " crush_location for remote_host " << s->remote_host
918 << " is " << crush_location << dendl;
919 }
920
921 string device_class;
922 if (s->name.is_osd()) {
aee94f69 923 osdmap.crush->get_full_location(s->entity_name.to_str(), &crush_location);
11fdf7f2
TL
924 const char *c = osdmap.crush->get_item_class(s->name.num());
925 if (c) {
926 device_class = c;
927 dout(10) << __func__ << " device_class " << device_class << dendl;
928 }
929 }
930
931 dout(20) << __func__ << " " << s->entity_name << " crush " << crush_location
932 << " device_class " << device_class << dendl;
9f95a23c 933 auto out = config_map.generate_entity_map(
11fdf7f2
TL
934 s->entity_name,
935 crush_location,
936 osdmap.crush.get(),
9f95a23c 937 device_class);
11fdf7f2
TL
938
939 if (out == s->last_config && s->any_config) {
940 dout(20) << __func__ << " no change, " << out << dendl;
941 return false;
942 }
adb31ebb
TL
943 // removing this to hide sensitive data going into logs
944 // leaving this for debugging purposes
945 // dout(20) << __func__ << " " << out << dendl;
9f95a23c 946 s->last_config = std::move(out);
11fdf7f2
TL
947 s->any_config = true;
948 return true;
949}
950
951bool ConfigMonitor::maybe_send_config(MonSession *s)
952{
953 bool changed = refresh_config(s);
954 dout(10) << __func__ << " to " << s->name << " "
955 << (changed ? "(changed)" : "(unchanged)")
956 << dendl;
957 if (changed) {
958 send_config(s);
959 }
960 return changed;
961}
962
963void ConfigMonitor::send_config(MonSession *s)
964{
965 dout(10) << __func__ << " to " << s->name << dendl;
966 auto m = new MConfig(s->last_config);
967 s->con->send_message(m);
968}
969
970void ConfigMonitor::check_sub(MonSession *s)
971{
972 if (!s->authenticated) {
973 dout(20) << __func__ << " not authenticated " << s->entity_name << dendl;
974 return;
975 }
976 auto p = s->sub_map.find("config");
977 if (p != s->sub_map.end()) {
978 check_sub(p->second);
979 }
980}
981
982void ConfigMonitor::check_sub(Subscription *sub)
983{
984 dout(10) << __func__
985 << " next " << sub->next
986 << " have " << version << dendl;
987 if (sub->next <= version) {
988 maybe_send_config(sub->session);
989 if (sub->onetime) {
f67539c2 990 mon.with_session_map([sub](MonSessionMap& session_map) {
11fdf7f2
TL
991 session_map.remove_sub(sub);
992 });
993 } else {
994 sub->next = version + 1;
995 }
996 }
997}
998
999void ConfigMonitor::check_all_subs()
1000{
1001 dout(10) << __func__ << dendl;
f67539c2
TL
1002 auto subs = mon.session_map.subs.find("config");
1003 if (subs == mon.session_map.subs.end()) {
11fdf7f2
TL
1004 return;
1005 }
1006 int updated = 0, total = 0;
1007 auto p = subs->second->begin();
1008 while (!p.end()) {
1009 auto sub = *p;
1010 ++p;
1011 ++total;
1012 if (maybe_send_config(sub->session)) {
1013 ++updated;
1014 }
1015 }
1016 dout(10) << __func__ << " updated " << updated << " / " << total << dendl;
1017}