]>
Commit | Line | Data |
---|---|---|
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 | 22 | using namespace TOPNSPC::common; |
f67539c2 TL |
23 | |
24 | using namespace std::literals; | |
25 | ||
26 | using std::cerr; | |
27 | using std::cout; | |
28 | using std::dec; | |
29 | using std::hex; | |
30 | using std::list; | |
31 | using std::map; | |
32 | using std::make_pair; | |
33 | using std::ostream; | |
34 | using std::ostringstream; | |
35 | using std::pair; | |
36 | using std::set; | |
37 | using std::setfill; | |
38 | using std::string; | |
39 | using std::stringstream; | |
40 | using std::to_string; | |
41 | using std::vector; | |
42 | using std::unique_ptr; | |
43 | ||
44 | using ceph::bufferlist; | |
45 | using ceph::decode; | |
46 | using ceph::encode; | |
47 | using ceph::Formatter; | |
48 | using ceph::JSONFormatter; | |
49 | using ceph::mono_clock; | |
50 | using ceph::mono_time; | |
51 | using ceph::timespan_str; | |
52 | static 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 | ||
58 | const string KEY_PREFIX("config/"); | |
59 | const string HISTORY_PREFIX("config-history/"); | |
60 | ||
f67539c2 | 61 | ConfigMonitor::ConfigMonitor(Monitor &m, Paxos &p, const string& service_name) |
11fdf7f2 TL |
62 | : PaxosService(m, p, service_name) { |
63 | } | |
64 | ||
65 | void ConfigMonitor::init() | |
66 | { | |
67 | dout(10) << __func__ << dendl; | |
68 | } | |
69 | ||
70 | void ConfigMonitor::create_initial() | |
71 | { | |
72 | dout(10) << __func__ << dendl; | |
73 | version = 0; | |
74 | pending.clear(); | |
75 | } | |
76 | ||
77 | void 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 | ||
88 | void ConfigMonitor::create_pending() | |
89 | { | |
90 | dout(10) << " " << version << dendl; | |
91 | pending.clear(); | |
92 | pending_description.clear(); | |
93 | } | |
94 | ||
95 | void 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 |
103 | void 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 | ||
147 | version_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 | ||
156 | bool 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 |
171 | bool 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 | ||
473 | void 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 | ||
490 | bool 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 | ||
508 | bool 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, §ion, &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 | ||
710 | reply: | |
f67539c2 | 711 | mon.reply_command(op, err, ss.str(), odata, get_last_committed()); |
11fdf7f2 TL |
712 | return false; |
713 | ||
714 | update: | |
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 | ||
747 | void 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 | ||
766 | void ConfigMonitor::on_active() | |
767 | { | |
768 | } | |
769 | ||
770 | void 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, §ion_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 | ||
881 | void 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 | ||
911 | bool 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 | ||
951 | bool 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 | ||
963 | void 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 | ||
970 | void 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 | ||
982 | void 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 | ||
999 | void 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 | } |