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