summaryrefslogtreecommitdiffstats
path: root/src/mon/ConfigMonitor.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/mon/ConfigMonitor.cc')
-rw-r--r--src/mon/ConfigMonitor.cc957
1 files changed, 957 insertions, 0 deletions
diff --git a/src/mon/ConfigMonitor.cc b/src/mon/ConfigMonitor.cc
new file mode 100644
index 00000000..ae7bda2f
--- /dev/null
+++ b/src/mon/ConfigMonitor.cc
@@ -0,0 +1,957 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include "mon/Monitor.h"
+#include "mon/ConfigMonitor.h"
+#include "mon/MgrMonitor.h"
+#include "mon/OSDMonitor.h"
+#include "messages/MConfig.h"
+#include "messages/MGetConfig.h"
+#include "messages/MMonCommand.h"
+#include "common/Formatter.h"
+#include "common/TextTable.h"
+#include "common/cmdparse.h"
+#include "include/stringify.h"
+
+#define dout_subsys ceph_subsys_mon
+#undef dout_prefix
+#define dout_prefix _prefix(_dout, mon, this)
+static ostream& _prefix(std::ostream *_dout, const Monitor *mon,
+ const ConfigMonitor *hmon) {
+ return *_dout << "mon." << mon->name << "@" << mon->rank
+ << "(" << mon->get_state_name() << ").config ";
+}
+
+const string KEY_PREFIX("config/");
+const string HISTORY_PREFIX("config-history/");
+
+ConfigMonitor::ConfigMonitor(Monitor *m, Paxos *p, const string& service_name)
+ : PaxosService(m, p, service_name) {
+}
+
+void ConfigMonitor::init()
+{
+ dout(10) << __func__ << dendl;
+}
+
+void ConfigMonitor::create_initial()
+{
+ dout(10) << __func__ << dendl;
+ version = 0;
+ pending.clear();
+}
+
+void ConfigMonitor::update_from_paxos(bool *need_bootstrap)
+{
+ if (version == get_last_committed()) {
+ return;
+ }
+ version = get_last_committed();
+ dout(10) << __func__ << " " << version << dendl;
+ load_config();
+ check_all_subs();
+}
+
+void ConfigMonitor::create_pending()
+{
+ dout(10) << " " << version << dendl;
+ pending.clear();
+ pending_description.clear();
+}
+
+void ConfigMonitor::encode_pending(MonitorDBStore::TransactionRef t)
+{
+ dout(10) << " " << (version+1) << dendl;
+ put_last_committed(t, version+1);
+
+ for (auto& [key, value] : pending_cleanup) {
+ if (pending.count(key) == 0) {
+ derr << __func__ << " repair: adjusting config key '" << key << "'"
+ << dendl;
+ pending[key] = value;
+ }
+ }
+ pending_cleanup.clear();
+
+ // TODO: record changed sections (osd, mds.foo, rack:bar, ...)
+
+ string history = HISTORY_PREFIX + stringify(version+1) + "/";
+ {
+ bufferlist metabl;
+ ::encode(ceph_clock_now(), metabl);
+ ::encode(pending_description, metabl);
+ t->put(CONFIG_PREFIX, history, metabl);
+ }
+ for (auto& p : pending) {
+ string key = KEY_PREFIX + p.first;
+ auto q = current.find(p.first);
+ if (q != current.end()) {
+ if (p.second && *p.second == q->second) {
+ continue;
+ }
+ t->put(CONFIG_PREFIX, history + "-" + p.first, q->second);
+ } else if (!p.second) {
+ continue;
+ }
+ if (p.second) {
+ dout(20) << __func__ << " set " << key << dendl;
+ t->put(CONFIG_PREFIX, key, *p.second);
+ t->put(CONFIG_PREFIX, history + "+" + p.first, *p.second);
+ } else {
+ dout(20) << __func__ << " rm " << key << dendl;
+ t->erase(CONFIG_PREFIX, key);
+ }
+ }
+}
+
+version_t ConfigMonitor::get_trim_to() const
+{
+ // we don't actually need *any* old states, but keep a few.
+ if (version > 5) {
+ return version - 5;
+ }
+ return 0;
+}
+
+bool ConfigMonitor::preprocess_query(MonOpRequestRef op)
+{
+ switch (op->get_req()->get_type()) {
+ case MSG_MON_COMMAND:
+ try {
+ return preprocess_command(op);
+ } catch (const bad_cmd_get& e) {
+ bufferlist bl;
+ mon->reply_command(op, -EINVAL, e.what(), bl, get_last_committed());
+ return true;
+ }
+ }
+ return false;
+}
+
+static string indent_who(const string& who)
+{
+ if (who == "global") {
+ return who;
+ }
+ if (who.find('.') == string::npos) {
+ return " " + who;
+ }
+ return " " + who;
+}
+
+bool ConfigMonitor::preprocess_command(MonOpRequestRef op)
+{
+ MMonCommand *m = static_cast<MMonCommand*>(op->get_req());
+ std::stringstream ss;
+ int err = 0;
+
+ cmdmap_t cmdmap;
+ if (!cmdmap_from_json(m->cmd, &cmdmap, ss)) {
+ string rs = ss.str();
+ mon->reply_command(op, -EINVAL, rs, get_last_committed());
+ return true;
+ }
+ string format;
+ cmd_getval(g_ceph_context, cmdmap, "format", format, string("plain"));
+ boost::scoped_ptr<Formatter> f(Formatter::create(format));
+
+ string prefix;
+ cmd_getval(g_ceph_context, cmdmap, "prefix", prefix);
+
+ bufferlist odata;
+ if (prefix == "config help") {
+ stringstream ss;
+ string name;
+ cmd_getval(g_ceph_context, cmdmap, "key", name);
+ const Option *opt = g_conf().find_option(name);
+ if (!opt) {
+ opt = mon->mgrmon()->find_module_option(name);
+ }
+ if (opt) {
+ if (f) {
+ f->dump_object("option", *opt);
+ } else {
+ opt->print(&ss);
+ }
+ } else {
+ ss << "configuration option '" << name << "' not recognized";
+ err = -ENOENT;
+ goto reply;
+ }
+ if (f) {
+ f->flush(odata);
+ } else {
+ odata.append(ss.str());
+ }
+ } else if (prefix == "config ls") {
+ ostringstream ss;
+ if (f) {
+ f->open_array_section("options");
+ }
+ for (auto& i : ceph_options) {
+ if (f) {
+ f->dump_string("option", i.name);
+ } else {
+ ss << i.name << "\n";
+ }
+ }
+ for (auto& i : mon->mgrmon()->get_mgr_module_options()) {
+ if (f) {
+ f->dump_string("option", i.first);
+ } else {
+ ss << i.first << "\n";
+ }
+ }
+ if (f) {
+ f->close_section();
+ f->flush(odata);
+ } else {
+ odata.append(ss.str());
+ }
+ } else if (prefix == "config dump") {
+ list<pair<string,Section*>> sections = {
+ make_pair("global", &config_map.global)
+ };
+ for (string type : { "mon", "mgr", "osd", "mds", "client" }) {
+ auto i = config_map.by_type.find(type);
+ if (i != config_map.by_type.end()) {
+ sections.push_back(make_pair(i->first, &i->second));
+ }
+ auto j = config_map.by_id.lower_bound(type);
+ while (j != config_map.by_id.end() &&
+ j->first.find(type) == 0) {
+ sections.push_back(make_pair(j->first, &j->second));
+ ++j;
+ }
+ }
+ TextTable tbl;
+ if (!f) {
+ tbl.define_column("WHO", TextTable::LEFT, TextTable::LEFT);
+ tbl.define_column("MASK", TextTable::LEFT, TextTable::LEFT);
+ tbl.define_column("LEVEL", TextTable::LEFT, TextTable::LEFT);
+ tbl.define_column("OPTION", TextTable::LEFT, TextTable::LEFT);
+ tbl.define_column("VALUE", TextTable::LEFT, TextTable::LEFT);
+ tbl.define_column("RO", TextTable::LEFT, TextTable::LEFT);
+ } else {
+ f->open_array_section("config");
+ }
+ for (auto s : sections) {
+ for (auto& i : s.second->options) {
+ if (!f) {
+ tbl << indent_who(s.first);
+ tbl << i.second.mask.to_str();
+ tbl << Option::level_to_str(i.second.opt->level);
+ tbl << i.first;
+ tbl << i.second.raw_value;
+ tbl << (i.second.opt->can_update_at_runtime() ? "" : "*");
+ tbl << TextTable::endrow;
+ } else {
+ f->open_object_section("option");
+ f->dump_string("section", s.first);
+ i.second.dump(f.get());
+ f->close_section();
+ }
+ }
+ }
+ if (!f) {
+ odata.append(stringify(tbl));
+ } else {
+ f->close_section();
+ f->flush(odata);
+ }
+ } else if (prefix == "config get") {
+ string who, name;
+ cmd_getval(g_ceph_context, cmdmap, "who", who);
+
+ EntityName entity;
+ if (!entity.from_str(who)) {
+ ss << "unrecognized entity '" << who << "'";
+ err = -EINVAL;
+ goto reply;
+ }
+
+ map<string,string> crush_location;
+ string device_class;
+ if (entity.is_osd()) {
+ mon->osdmon()->osdmap.crush->get_full_location(who, &crush_location);
+ int id = atoi(entity.get_id().c_str());
+ const char *c = mon->osdmon()->osdmap.crush->get_item_class(id);
+ if (c) {
+ device_class = c;
+ }
+ dout(10) << __func__ << " crush_location " << crush_location
+ << " class " << device_class << dendl;
+ }
+
+ std::map<std::string,std::string> config;
+ std::map<std::string,pair<std::string,const MaskedOption*>> src;
+ config_map.generate_entity_map(
+ entity,
+ crush_location,
+ mon->osdmon()->osdmap.crush.get(),
+ device_class,
+ &config, &src);
+
+ if (cmd_getval(g_ceph_context, cmdmap, "key", name)) {
+ const Option *opt = g_conf().find_option(name);
+ if (!opt) {
+ opt = mon->mgrmon()->find_module_option(name);
+ }
+ if (!opt) {
+ err = -ENOENT;
+ goto reply;
+ }
+ if (opt->has_flag(Option::FLAG_NO_MON_UPDATE)) {
+ // handle special options
+ if (name == "fsid") {
+ odata.append(stringify(mon->monmap->get_fsid()));
+ odata.append("\n");
+ goto reply;
+ }
+ err = -EINVAL;
+ ss << name << " is special and cannot be stored by the mon";
+ goto reply;
+ }
+ // get a single value
+ auto p = config.find(name);
+ if (p != config.end()) {
+ odata.append(p->second);
+ odata.append("\n");
+ goto reply;
+ }
+ if (!entity.is_client() &&
+ !boost::get<boost::blank>(&opt->daemon_value)) {
+ odata.append(Option::to_str(opt->daemon_value));
+ } else {
+ odata.append(Option::to_str(opt->value));
+ }
+ odata.append("\n");
+ } else {
+ // dump all (non-default) values for this entity
+ TextTable tbl;
+ if (!f) {
+ tbl.define_column("WHO", TextTable::LEFT, TextTable::LEFT);
+ tbl.define_column("MASK", TextTable::LEFT, TextTable::LEFT);
+ tbl.define_column("LEVEL", TextTable::LEFT, TextTable::LEFT);
+ tbl.define_column("OPTION", TextTable::LEFT, TextTable::LEFT);
+ tbl.define_column("VALUE", TextTable::LEFT, TextTable::LEFT);
+ tbl.define_column("RO", TextTable::LEFT, TextTable::LEFT);
+ } else {
+ f->open_object_section("config");
+ }
+ auto p = config.begin();
+ auto q = src.begin();
+ for (; p != config.end(); ++p, ++q) {
+ if (name.size() && p->first != name) {
+ continue;
+ }
+ if (!f) {
+ tbl << q->second.first;
+ tbl << q->second.second->mask.to_str();
+ tbl << Option::level_to_str(q->second.second->opt->level);
+ tbl << p->first;
+ tbl << p->second;
+ tbl << (q->second.second->opt->can_update_at_runtime() ? "" : "*");
+ tbl << TextTable::endrow;
+ } else {
+ f->open_object_section(p->first.c_str());
+ f->dump_string("value", p->second);
+ f->dump_string("section", q->second.first);
+ f->dump_object("mask", q->second.second->mask);
+ f->dump_bool("can_update_at_runtime",
+ q->second.second->opt->can_update_at_runtime());
+ f->close_section();
+ }
+ }
+ if (!f) {
+ odata.append(stringify(tbl));
+ } else {
+ f->close_section();
+ f->flush(odata);
+ }
+ }
+ } else if (prefix == "config log") {
+ int64_t num = 10;
+ cmd_getval(g_ceph_context, cmdmap, "num", num);
+ ostringstream ds;
+ if (f) {
+ f->open_array_section("changesets");
+ }
+ for (version_t v = version; v > version - std::min(version, (version_t)num); --v) {
+ ConfigChangeSet ch;
+ load_changeset(v, &ch);
+ if (f) {
+ f->dump_object("changeset", ch);
+ } else {
+ ch.print(ds);
+ }
+ }
+ if (f) {
+ f->close_section();
+ f->flush(odata);
+ } else {
+ odata.append(ds.str());
+ }
+ } else if (prefix == "config generate-minimal-conf") {
+ ostringstream conf;
+ conf << "# minimal ceph.conf for " << mon->monmap->get_fsid() << "\n";
+
+ // the basics
+ conf << "[global]\n";
+ conf << "\tfsid = " << mon->monmap->get_fsid() << "\n";
+ conf << "\tmon_host = ";
+ for (auto i = mon->monmap->mon_info.begin();
+ i != mon->monmap->mon_info.end();
+ ++i) {
+ if (i != mon->monmap->mon_info.begin()) {
+ conf << " ";
+ }
+ conf << i->second.public_addrs;
+ }
+ conf << "\n";
+ conf << config_map.global.get_minimal_conf();
+ for (auto m : { &config_map.by_type, &config_map.by_id }) {
+ for (auto& i : *m) {
+ auto s = i.second.get_minimal_conf();
+ if (s.size()) {
+ conf << "\n[" << i.first << "]\n" << s;
+ }
+ }
+ }
+ odata.append(conf.str());
+ err = 0;
+ } else {
+ return false;
+ }
+
+ reply:
+ mon->reply_command(op, err, ss.str(), odata, get_last_committed());
+ return true;
+}
+
+void ConfigMonitor::handle_get_config(MonOpRequestRef op)
+{
+ MGetConfig *m = static_cast<MGetConfig*>(op->get_req());
+ dout(10) << __func__ << " " << m->name << " host " << m->host << dendl;
+
+ const OSDMap& osdmap = mon->osdmon()->osdmap;
+ map<string,string> crush_location;
+ osdmap.crush->get_full_location(m->host, &crush_location);
+ map<string,string> out;
+ config_map.generate_entity_map(
+ m->name,
+ crush_location,
+ osdmap.crush.get(),
+ m->device_class,
+ &out);
+ dout(20) << " config is " << out << dendl;
+ m->get_connection()->send_message(new MConfig(out));
+}
+
+bool ConfigMonitor::prepare_update(MonOpRequestRef op)
+{
+ Message *m = op->get_req();
+ dout(7) << "prepare_update " << *m
+ << " from " << m->get_orig_source_inst() << dendl;
+ switch (m->get_type()) {
+ case MSG_MON_COMMAND:
+ try {
+ return prepare_command(op);
+ } catch (const bad_cmd_get& e) {
+ bufferlist bl;
+ mon->reply_command(op, -EINVAL, e.what(), bl, get_last_committed());
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ConfigMonitor::prepare_command(MonOpRequestRef op)
+{
+ MMonCommand *m = static_cast<MMonCommand*>(op->get_req());
+ std::stringstream ss;
+ int err = -EINVAL;
+
+ cmdmap_t cmdmap;
+ if (!cmdmap_from_json(m->cmd, &cmdmap, ss)) {
+ string rs = ss.str();
+ mon->reply_command(op, -EINVAL, rs, get_last_committed());
+ return true;
+ }
+
+ string prefix;
+ cmd_getval(g_ceph_context, cmdmap, "prefix", prefix);
+ bufferlist odata;
+
+ if (prefix == "config set" ||
+ prefix == "config rm") {
+ string who;
+ string name, value;
+ bool force = false;
+ cmd_getval(g_ceph_context, cmdmap, "who", who);
+ cmd_getval(g_ceph_context, cmdmap, "name", name);
+ cmd_getval(g_ceph_context, cmdmap, "value", value);
+ cmd_getval(g_ceph_context, cmdmap, "force", force);
+
+ if (prefix == "config set" && !force) {
+ const Option *opt = g_conf().find_option(name);
+ if (!opt) {
+ opt = mon->mgrmon()->find_module_option(name);
+ }
+ if (!opt) {
+ ss << "unrecognized config option '" << name << "'";
+ err = -EINVAL;
+ goto reply;
+ }
+
+ Option::value_t real_value;
+ string errstr;
+ err = opt->parse_value(value, &real_value, &errstr, &value);
+ if (err < 0) {
+ ss << "error parsing value: " << errstr;
+ goto reply;
+ }
+
+ if (opt->has_flag(Option::FLAG_NO_MON_UPDATE)) {
+ err = -EINVAL;
+ ss << name << " is special and cannot be stored by the mon";
+ goto reply;
+ }
+ }
+
+ string section;
+ OptionMask mask;
+ if (!ConfigMap::parse_mask(who, &section, &mask)) {
+ ss << "unrecognized config target '" << who << "'";
+ err = -EINVAL;
+ goto reply;
+ }
+
+ string key;
+ if (section.size()) {
+ key += section + "/";
+ } else {
+ key += "global/";
+ }
+ string mask_str = mask.to_str();
+ if (mask_str.size()) {
+ key += mask_str + "/";
+ }
+ key += name;
+
+ if (prefix == "config set") {
+ bufferlist bl;
+ bl.append(value);
+ pending[key] = bl;
+ } else {
+ pending[key] = boost::none;
+ }
+ goto update;
+ } else if (prefix == "config reset") {
+ int64_t num;
+ if (!cmd_getval(g_ceph_context, cmdmap, "num", num)) {
+ err = -EINVAL;
+ ss << "must specify what to revert to";
+ goto reply;
+ }
+ if (num < 0 ||
+ (num > 0 && num > (int64_t)version)) {
+ err = -EINVAL;
+ ss << "must specify a valid version to revert to";
+ goto reply;
+ }
+ if (num == (int64_t)version) {
+ err = 0;
+ goto reply;
+ }
+ ceph_assert(num > 0);
+ ceph_assert((version_t)num < version);
+ for (int64_t v = version; v > num; --v) {
+ ConfigChangeSet ch;
+ load_changeset(v, &ch);
+ for (auto& i : ch.diff) {
+ if (i.second.first) {
+ bufferlist bl;
+ bl.append(*i.second.first);
+ pending[i.first] = bl;
+ } else if (i.second.second) {
+ pending[i.first] = boost::none;
+ }
+ }
+ }
+ pending_description = string("reset to ") + stringify(num);
+ goto update;
+ } else if (prefix == "config assimilate-conf") {
+ ConfFile cf;
+ deque<string> errors;
+ bufferlist bl = m->get_data();
+ err = cf.parse_bufferlist(&bl, &errors, &ss);
+ if (err < 0) {
+ ss << "parse errors: " << errors;
+ goto reply;
+ }
+ bool updated = false;
+ ostringstream newconf;
+ for (auto i = cf.sections_begin(); i != cf.sections_end(); ++i) {
+ string section = i->first;
+ const ConfSection& s = i->second;
+ dout(20) << __func__ << " [" << section << "]" << dendl;
+ bool did_section = false;
+ for (auto& j : s.lines) {
+ Option::value_t real_value;
+ string value;
+ string errstr;
+ if (!j.key.size()) {
+ continue;
+ }
+ // a known and worthy option?
+ const Option *o = g_conf().find_option(j.key);
+ if (!o) {
+ o = mon->mgrmon()->find_module_option(j.key);
+ }
+ if (!o ||
+ o->flags & Option::FLAG_NO_MON_UPDATE) {
+ goto skip;
+ }
+ // normalize
+ err = o->parse_value(j.val, &real_value, &errstr, &value);
+ if (err < 0) {
+ dout(20) << __func__ << " failed to parse " << j.key << " = '"
+ << j.val << "'" << dendl;
+ goto skip;
+ }
+ // does it conflict with an existing value?
+ {
+ const Section *s = config_map.find_section(section);
+ if (s) {
+ auto k = s->options.find(j.key);
+ if (k != s->options.end()) {
+ if (value != k->second.raw_value) {
+ dout(20) << __func__ << " have " << j.key
+ << " = " << k->second.raw_value
+ << " (not " << value << ")" << dendl;
+ goto skip;
+ }
+ dout(20) << __func__ << " already have " << j.key
+ << " = " << k->second.raw_value << dendl;
+ continue;
+ }
+ }
+ }
+ dout(20) << __func__ << " add " << j.key << " = " << value
+ << " (" << j.val << ")" << dendl;
+ {
+ string key = section + "/" + j.key;
+ bufferlist bl;
+ bl.append(value);
+ pending[key] = bl;
+ updated = true;
+ }
+ continue;
+
+ skip:
+ dout(20) << __func__ << " skip " << j.key << " = " << value
+ << " (" << j.val << ")" << dendl;
+ if (!did_section) {
+ newconf << "\n[" << section << "]\n";
+ did_section = true;
+ }
+ newconf << "\t" << j.key << " = " << j.val << "\n";
+ }
+ }
+ odata.append(newconf.str());
+ if (updated) {
+ goto update;
+ }
+ } else {
+ ss << "unknown command " << prefix;
+ err = -EINVAL;
+ }
+
+reply:
+ mon->reply_command(op, err, ss.str(), odata, get_last_committed());
+ return false;
+
+update:
+ // see if there is an actual change
+ auto p = pending.begin();
+ while (p != pending.end()) {
+ auto q = current.find(p->first);
+ if (p->second && q != current.end() && *p->second == q->second) {
+ // set to same value
+ p = pending.erase(p);
+ } else if (!p->second && q == current.end()) {
+ // erasing non-existent value
+ p = pending.erase(p);
+ } else {
+ ++p;
+ }
+ }
+ if (pending.empty()) {
+ err = 0;
+ goto reply;
+ }
+ force_immediate_propose(); // faster response
+ wait_for_finished_proposal(
+ op,
+ new Monitor::C_Command(
+ mon, op, 0, ss.str(), odata,
+ get_last_committed() + 1));
+ return true;
+}
+
+void ConfigMonitor::tick()
+{
+ if (!is_active() || !mon->is_leader()) {
+ return;
+ }
+ dout(10) << __func__ << dendl;
+ bool changed = false;
+ if (!pending_cleanup.empty()) {
+ changed = true;
+ }
+ if (changed) {
+ propose_pending();
+ }
+}
+
+void ConfigMonitor::on_active()
+{
+}
+
+void ConfigMonitor::load_config()
+{
+ unsigned num = 0;
+ KeyValueDB::Iterator it = mon->store->get_iterator(CONFIG_PREFIX);
+ it->lower_bound(KEY_PREFIX);
+ config_map.clear();
+ current.clear();
+ pending_cleanup.clear();
+ while (it->valid() &&
+ it->key().compare(0, KEY_PREFIX.size(), KEY_PREFIX) == 0) {
+ string key = it->key().substr(KEY_PREFIX.size());
+ string value = it->value().to_str();
+
+ current[key] = it->value();
+
+ auto last_slash = key.rfind('/');
+ string name;
+ string who;
+ if (last_slash == std::string::npos) {
+ name = key;
+ } else if (auto mgrpos = key.find("/mgr/"); mgrpos != std::string::npos) {
+ name = key.substr(mgrpos + 1);
+ who = key.substr(0, mgrpos);
+ } else {
+ name = key.substr(last_slash + 1);
+ who = key.substr(0, last_slash);
+ }
+
+ const Option *opt = g_conf().find_option(name);
+ if (!opt) {
+ opt = mon->mgrmon()->find_module_option(name);
+ }
+ if (!opt) {
+ dout(10) << __func__ << " unrecognized option '" << name << "'" << dendl;
+ opt = new Option(name, Option::TYPE_STR, Option::LEVEL_UNKNOWN);
+ // FIXME: this will be leaked!
+ }
+
+ string err;
+ int r = opt->pre_validate(&value, &err);
+ if (r < 0) {
+ dout(10) << __func__ << " pre-validate failed on '" << name << "' = '"
+ << value << "' for " << name << dendl;
+ }
+
+ MaskedOption mopt(opt);
+ mopt.raw_value = value;
+ string section_name;
+ if (who.size() &&
+ !ConfigMap::parse_mask(who, &section_name, &mopt.mask)) {
+ derr << __func__ << " invalid mask for key " << key << dendl;
+ pending_cleanup[key] = boost::none;
+ } else if (opt->has_flag(Option::FLAG_NO_MON_UPDATE)) {
+ dout(10) << __func__ << " NO_MON_UPDATE option '"
+ << name << "' = '" << value << "' for " << name
+ << dendl;
+ pending_cleanup[key] = boost::none;
+ } else {
+ if (section_name.empty()) {
+ // we prefer global/$option instead of just $option
+ derr << __func__ << " adding global/ prefix to key '" << key << "'"
+ << dendl;
+ pending_cleanup[key] = boost::none;
+ pending_cleanup["global/"s + key] = it->value();
+ }
+ Section *section = &config_map.global;;
+ if (section_name.size() && section_name != "global") {
+ if (section_name.find('.') != std::string::npos) {
+ section = &config_map.by_id[section_name];
+ } else {
+ section = &config_map.by_type[section_name];
+ }
+ }
+ section->options.insert(make_pair(name, std::move(mopt)));
+ ++num;
+ }
+ it->next();
+ }
+ dout(10) << __func__ << " got " << num << " keys" << dendl;
+
+ // refresh our own config
+ {
+ const OSDMap& osdmap = mon->osdmon()->osdmap;
+ map<string,string> crush_location;
+ osdmap.crush->get_full_location(g_conf()->host, &crush_location);
+ map<string,string> out;
+ config_map.generate_entity_map(
+ g_conf()->name,
+ crush_location,
+ osdmap.crush.get(),
+ string(), // no device class
+ &out);
+ g_conf().set_mon_vals(g_ceph_context, out, nullptr);
+ }
+}
+
+void ConfigMonitor::load_changeset(version_t v, ConfigChangeSet *ch)
+{
+ ch->version = v;
+ string prefix = HISTORY_PREFIX + stringify(v) + "/";
+ KeyValueDB::Iterator it = mon->store->get_iterator(CONFIG_PREFIX);
+ it->lower_bound(prefix);
+ while (it->valid() && it->key().find(prefix) == 0) {
+ if (it->key() == prefix) {
+ bufferlist bl = it->value();
+ auto p = bl.cbegin();
+ try {
+ decode(ch->stamp, p);
+ decode(ch->name, p);
+ }
+ catch (buffer::error& e) {
+ derr << __func__ << " failure decoding changeset " << v << dendl;
+ }
+ } else {
+ char op = it->key()[prefix.length()];
+ string key = it->key().substr(prefix.length() + 1);
+ if (op == '-') {
+ ch->diff[key].first = it->value().to_str();
+ } else if (op == '+') {
+ ch->diff[key].second = it->value().to_str();
+ }
+ }
+ it->next();
+ }
+}
+
+bool ConfigMonitor::refresh_config(MonSession *s)
+{
+ const OSDMap& osdmap = mon->osdmon()->osdmap;
+ map<string,string> crush_location;
+ if (s->remote_host.size()) {
+ osdmap.crush->get_full_location(s->remote_host, &crush_location);
+ dout(10) << __func__ << " crush_location for remote_host " << s->remote_host
+ << " is " << crush_location << dendl;
+ }
+
+ string device_class;
+ if (s->name.is_osd()) {
+ const char *c = osdmap.crush->get_item_class(s->name.num());
+ if (c) {
+ device_class = c;
+ dout(10) << __func__ << " device_class " << device_class << dendl;
+ }
+ }
+
+ dout(20) << __func__ << " " << s->entity_name << " crush " << crush_location
+ << " device_class " << device_class << dendl;
+ map<string,string> out;
+ config_map.generate_entity_map(
+ s->entity_name,
+ crush_location,
+ osdmap.crush.get(),
+ device_class,
+ &out);
+
+ if (out == s->last_config && s->any_config) {
+ dout(20) << __func__ << " no change, " << out << dendl;
+ return false;
+ }
+ // removing this to hide sensitive data going into logs
+ // leaving this for debugging purposes
+ // dout(20) << __func__ << " " << out << dendl;
+ s->last_config = out;
+ s->any_config = true;
+ return true;
+}
+
+bool ConfigMonitor::maybe_send_config(MonSession *s)
+{
+ bool changed = refresh_config(s);
+ dout(10) << __func__ << " to " << s->name << " "
+ << (changed ? "(changed)" : "(unchanged)")
+ << dendl;
+ if (changed) {
+ send_config(s);
+ }
+ return changed;
+}
+
+void ConfigMonitor::send_config(MonSession *s)
+{
+ dout(10) << __func__ << " to " << s->name << dendl;
+ auto m = new MConfig(s->last_config);
+ s->con->send_message(m);
+}
+
+void ConfigMonitor::check_sub(MonSession *s)
+{
+ if (!s->authenticated) {
+ dout(20) << __func__ << " not authenticated " << s->entity_name << dendl;
+ return;
+ }
+ auto p = s->sub_map.find("config");
+ if (p != s->sub_map.end()) {
+ check_sub(p->second);
+ }
+}
+
+void ConfigMonitor::check_sub(Subscription *sub)
+{
+ dout(10) << __func__
+ << " next " << sub->next
+ << " have " << version << dendl;
+ if (sub->next <= version) {
+ maybe_send_config(sub->session);
+ if (sub->onetime) {
+ mon->with_session_map([sub](MonSessionMap& session_map) {
+ session_map.remove_sub(sub);
+ });
+ } else {
+ sub->next = version + 1;
+ }
+ }
+}
+
+void ConfigMonitor::check_all_subs()
+{
+ dout(10) << __func__ << dendl;
+ auto subs = mon->session_map.subs.find("config");
+ if (subs == mon->session_map.subs.end()) {
+ return;
+ }
+ int updated = 0, total = 0;
+ auto p = subs->second->begin();
+ while (!p.end()) {
+ auto sub = *p;
+ ++p;
+ ++total;
+ if (maybe_send_config(sub->session)) {
+ ++updated;
+ }
+ }
+ dout(10) << __func__ << " updated " << updated << " / " << total << dendl;
+}