]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net> | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | * | |
13 | */ | |
14 | ||
f67539c2 | 15 | #include <filesystem> |
7c673cae FG |
16 | #include "common/ceph_argparse.h" |
17 | #include "common/common_init.h" | |
18 | #include "common/config.h" | |
11fdf7f2 | 19 | #include "common/config_obs.h" |
7c673cae | 20 | #include "include/str_list.h" |
7c673cae | 21 | #include "include/stringify.h" |
7c673cae FG |
22 | #include "osd/osd_types.h" |
23 | #include "common/errno.h" | |
24 | #include "common/hostname.h" | |
11fdf7f2 | 25 | #include "common/dout.h" |
7c673cae FG |
26 | |
27 | /* Don't use standard Ceph logging in this file. | |
28 | * We can't use logging until it's initialized, and a lot of the necessary | |
29 | * initialization happens here. | |
30 | */ | |
31 | #undef dout | |
7c673cae FG |
32 | #undef pdout |
33 | #undef derr | |
7c673cae | 34 | #undef generic_dout |
11fdf7f2 TL |
35 | |
36 | // set set_mon_vals() | |
37 | #define dout_subsys ceph_subsys_monc | |
7c673cae | 38 | |
20effc67 TL |
39 | namespace fs = std::filesystem; |
40 | ||
f67539c2 TL |
41 | using std::cerr; |
42 | using std::cout; | |
7c673cae | 43 | using std::map; |
f67539c2 | 44 | using std::less; |
7c673cae | 45 | using std::list; |
f67539c2 | 46 | using std::ostream; |
7c673cae FG |
47 | using std::ostringstream; |
48 | using std::pair; | |
7c673cae | 49 | using std::string; |
f67539c2 TL |
50 | using std::string_view; |
51 | using std::vector; | |
52 | ||
53 | using ceph::bufferlist; | |
54 | using ceph::decode; | |
55 | using ceph::encode; | |
56 | using ceph::Formatter; | |
7c673cae | 57 | |
f67539c2 | 58 | static const char *CEPH_CONF_FILE_DEFAULT = "$data_dir/config,/etc/ceph/$cluster.conf,$home/.ceph/$cluster.conf,$cluster.conf" |
7c673cae | 59 | #if defined(__FreeBSD__) |
f67539c2 TL |
60 | ",/usr/local/etc/ceph/$cluster.conf" |
61 | #elif defined(_WIN32) | |
62 | ",$programdata/ceph/$cluster.conf" | |
7c673cae FG |
63 | #endif |
64 | ; | |
65 | ||
66 | #define _STR(x) #x | |
67 | #define STRINGIFY(x) _STR(x) | |
68 | ||
11fdf7f2 TL |
69 | const char *ceph_conf_level_name(int level) |
70 | { | |
71 | switch (level) { | |
72 | case CONF_DEFAULT: return "default"; // built-in default | |
73 | case CONF_MON: return "mon"; // monitor config database | |
74 | case CONF_ENV: return "env"; // process environment (CEPH_ARGS) | |
75 | case CONF_FILE: return "file"; // ceph.conf file | |
76 | case CONF_CMDLINE: return "cmdline"; // process command line args | |
77 | case CONF_OVERRIDE: return "override"; // injectargs or 'config set' at runtime | |
78 | case CONF_FINAL: return "final"; | |
79 | default: return "???"; | |
80 | } | |
81 | } | |
82 | ||
7c673cae FG |
83 | int ceph_resolve_file_search(const std::string& filename_list, |
84 | std::string& result) | |
85 | { | |
86 | list<string> ls; | |
f67539c2 | 87 | get_str_list(filename_list, ";,", ls); |
7c673cae FG |
88 | |
89 | int ret = -ENOENT; | |
90 | list<string>::iterator iter; | |
91 | for (iter = ls.begin(); iter != ls.end(); ++iter) { | |
91327a77 | 92 | int fd = ::open(iter->c_str(), O_RDONLY|O_CLOEXEC); |
7c673cae FG |
93 | if (fd < 0) { |
94 | ret = -errno; | |
95 | continue; | |
96 | } | |
97 | close(fd); | |
98 | result = *iter; | |
99 | return 0; | |
100 | } | |
101 | ||
102 | return ret; | |
103 | } | |
104 | ||
11fdf7f2 | 105 | static int conf_stringify(const Option::value_t& v, string *out) |
c07f9fc5 | 106 | { |
20effc67 | 107 | if (v == Option::value_t{}) { |
11fdf7f2 TL |
108 | return -ENOENT; |
109 | } | |
110 | *out = Option::to_str(v); | |
111 | return 0; | |
112 | } | |
c07f9fc5 | 113 | |
11fdf7f2 TL |
114 | md_config_t::md_config_t(ConfigValues& values, |
115 | const ConfigTracker& tracker, | |
116 | bool is_daemon) | |
117 | : is_daemon(is_daemon) | |
118 | { | |
c07f9fc5 FG |
119 | // Load the compile-time list of Option into |
120 | // a map so that we can resolve keys quickly. | |
121 | for (const auto &i : ceph_options) { | |
122 | if (schema.count(i.name)) { | |
123 | // We may be instantiated pre-logging so send | |
124 | std::cerr << "Duplicate config key in schema: '" << i.name << "'" | |
125 | << std::endl; | |
11fdf7f2 | 126 | ceph_abort(); |
c07f9fc5 | 127 | } |
9f95a23c | 128 | schema.emplace(i.name, i); |
11fdf7f2 TL |
129 | } |
130 | ||
131 | // Define the debug_* options as well. | |
132 | subsys_options.reserve(values.subsys.get_num()); | |
133 | for (unsigned i = 0; i < values.subsys.get_num(); ++i) { | |
134 | string name = string("debug_") + values.subsys.get_name(i); | |
135 | subsys_options.push_back( | |
136 | Option(name, Option::TYPE_STR, Option::LEVEL_ADVANCED)); | |
137 | Option& opt = subsys_options.back(); | |
138 | opt.set_default(stringify(values.subsys.get_log_level(i)) + "/" + | |
139 | stringify(values.subsys.get_gather_level(i))); | |
140 | string desc = string("Debug level for ") + values.subsys.get_name(i); | |
141 | opt.set_description(desc.c_str()); | |
142 | opt.set_flag(Option::FLAG_RUNTIME); | |
143 | opt.set_long_description("The value takes the form 'N' or 'N/M' where N and M are values between 0 and 99. N is the debug level to log (all values below this are included), and M is the level to gather and buffer in memory. In the event of a crash, the most recent items <= M are dumped to the log file."); | |
144 | opt.set_subsys(i); | |
145 | opt.set_validator([](std::string *value, std::string *error_message) { | |
146 | int m, n; | |
147 | int r = sscanf(value->c_str(), "%d/%d", &m, &n); | |
148 | if (r >= 1) { | |
149 | if (m < 0 || m > 99) { | |
150 | *error_message = "value must be in range [0, 99]"; | |
151 | return -ERANGE; | |
152 | } | |
153 | if (r == 2) { | |
154 | if (n < 0 || n > 99) { | |
155 | *error_message = "value must be in range [0, 99]"; | |
156 | return -ERANGE; | |
157 | } | |
158 | } else { | |
159 | // normalize to M/N | |
160 | n = m; | |
161 | *value = stringify(m) + "/" + stringify(n); | |
162 | } | |
163 | } else { | |
164 | *error_message = "value must take the form N or N/M, where N and M are integers"; | |
165 | return -EINVAL; | |
166 | } | |
167 | return 0; | |
168 | }); | |
169 | } | |
170 | for (auto& opt : subsys_options) { | |
171 | schema.emplace(opt.name, opt); | |
c07f9fc5 FG |
172 | } |
173 | ||
174 | // Populate list of legacy_values according to the OPTION() definitions | |
175 | // Note that this is just setting up our map of name->member ptr. The | |
176 | // default values etc will get loaded in along with new-style data, | |
177 | // as all loads write to both the values map, and the legacy | |
178 | // members if present. | |
179 | legacy_values = { | |
180 | #define OPTION(name, type) \ | |
9f95a23c | 181 | {STRINGIFY(name), &ConfigValues::name}, |
c07f9fc5 | 182 | #define SAFE_OPTION(name, type) OPTION(name, type) |
20effc67 | 183 | #include "options/legacy_config_opts.h" |
7c673cae | 184 | #undef OPTION |
7c673cae | 185 | #undef SAFE_OPTION |
c07f9fc5 | 186 | }; |
7c673cae | 187 | |
c07f9fc5 | 188 | validate_schema(); |
7c673cae | 189 | |
11fdf7f2 | 190 | // Validate default values from the schema |
c07f9fc5 FG |
191 | for (const auto &i : schema) { |
192 | const Option &opt = i.second; | |
c07f9fc5 | 193 | if (opt.type == Option::TYPE_STR) { |
20effc67 | 194 | bool has_daemon_default = (opt.daemon_value != Option::value_t{}); |
11fdf7f2 TL |
195 | Option::value_t default_val; |
196 | if (is_daemon && has_daemon_default) { | |
197 | default_val = opt.daemon_value; | |
198 | } else { | |
199 | default_val = opt.value; | |
200 | } | |
c07f9fc5 FG |
201 | // We call pre_validate as a sanity check, but also to get any |
202 | // side effect (value modification) from the validator. | |
20effc67 | 203 | auto* def_str = std::get_if<std::string>(&default_val); |
11fdf7f2 | 204 | std::string val = *def_str; |
c07f9fc5 | 205 | std::string err; |
11fdf7f2 | 206 | if (opt.pre_validate(&val, &err) != 0) { |
c07f9fc5 FG |
207 | std::cerr << "Default value " << opt.name << "=" << *def_str << " is " |
208 | "invalid: " << err << std::endl; | |
209 | ||
210 | // This is the compiled-in default that is failing its own option's | |
211 | // validation, so this is super-invalid and should never make it | |
212 | // past a pull request: crash out. | |
11fdf7f2 TL |
213 | ceph_abort(); |
214 | } | |
215 | if (val != *def_str) { | |
216 | // if the validator normalizes the string into a different form than | |
217 | // what was compiled in, use that. | |
218 | set_val_default(values, tracker, opt.name, val); | |
c07f9fc5 FG |
219 | } |
220 | } | |
c07f9fc5 | 221 | } |
7c673cae | 222 | |
c07f9fc5 | 223 | // Copy out values (defaults) into any legacy (C struct member) fields |
11fdf7f2 TL |
224 | update_legacy_vals(values); |
225 | } | |
7c673cae | 226 | |
11fdf7f2 TL |
227 | md_config_t::~md_config_t() |
228 | { | |
c07f9fc5 FG |
229 | } |
230 | ||
231 | /** | |
232 | * Sanity check schema. Assert out on failures, to ensure any bad changes | |
233 | * cannot possibly pass any testing and make it into a release. | |
234 | */ | |
235 | void md_config_t::validate_schema() | |
7c673cae | 236 | { |
c07f9fc5 FG |
237 | for (const auto &i : schema) { |
238 | const auto &opt = i.second; | |
239 | for (const auto &see_also_key : opt.see_also) { | |
240 | if (schema.count(see_also_key) == 0) { | |
241 | std::cerr << "Non-existent see-also key '" << see_also_key | |
242 | << "' on option '" << opt.name << "'" << std::endl; | |
11fdf7f2 | 243 | ceph_abort(); |
c07f9fc5 FG |
244 | } |
245 | } | |
246 | } | |
7c673cae | 247 | |
c07f9fc5 FG |
248 | for (const auto &i : legacy_values) { |
249 | if (schema.count(i.first) == 0) { | |
250 | std::cerr << "Schema is missing legacy field '" << i.first << "'" | |
251 | << std::endl; | |
11fdf7f2 | 252 | ceph_abort(); |
c07f9fc5 FG |
253 | } |
254 | } | |
7c673cae FG |
255 | } |
256 | ||
9f95a23c | 257 | const Option *md_config_t::find_option(const std::string_view name) const |
7c673cae | 258 | { |
11fdf7f2 TL |
259 | auto p = schema.find(name); |
260 | if (p != schema.end()) { | |
261 | return &p->second; | |
262 | } | |
263 | return nullptr; | |
7c673cae FG |
264 | } |
265 | ||
11fdf7f2 TL |
266 | void md_config_t::set_val_default(ConfigValues& values, |
267 | const ConfigTracker& tracker, | |
9f95a23c | 268 | const string_view name, const std::string& val) |
7c673cae | 269 | { |
11fdf7f2 TL |
270 | const Option *o = find_option(name); |
271 | ceph_assert(o); | |
272 | string err; | |
273 | int r = _set_val(values, tracker, val, *o, CONF_DEFAULT, &err); | |
274 | ceph_assert(r >= 0); | |
7c673cae FG |
275 | } |
276 | ||
11fdf7f2 TL |
277 | int md_config_t::set_mon_vals(CephContext *cct, |
278 | ConfigValues& values, | |
279 | const ConfigTracker& tracker, | |
9f95a23c | 280 | const map<string,string,less<>>& kv, |
11fdf7f2 | 281 | config_callback config_cb) |
7c673cae | 282 | { |
11fdf7f2 | 283 | ignored_mon_values.clear(); |
f64942e4 | 284 | |
11fdf7f2 TL |
285 | if (!config_cb) { |
286 | ldout(cct, 4) << __func__ << " no callback set" << dendl; | |
287 | } | |
f64942e4 | 288 | |
11fdf7f2 TL |
289 | for (auto& i : kv) { |
290 | if (config_cb) { | |
291 | if (config_cb(i.first, i.second)) { | |
292 | ldout(cct, 4) << __func__ << " callback consumed " << i.first << dendl; | |
293 | continue; | |
294 | } | |
295 | ldout(cct, 4) << __func__ << " callback ignored " << i.first << dendl; | |
7c673cae | 296 | } |
11fdf7f2 TL |
297 | const Option *o = find_option(i.first); |
298 | if (!o) { | |
299 | ldout(cct,10) << __func__ << " " << i.first << " = " << i.second | |
300 | << " (unrecognized option)" << dendl; | |
301 | continue; | |
302 | } | |
303 | if (o->has_flag(Option::FLAG_NO_MON_UPDATE)) { | |
304 | ignored_mon_values.emplace(i); | |
305 | continue; | |
306 | } | |
307 | std::string err; | |
308 | int r = _set_val(values, tracker, i.second, *o, CONF_MON, &err); | |
309 | if (r < 0) { | |
9f95a23c TL |
310 | ldout(cct, 4) << __func__ << " failed to set " << i.first << " = " |
311 | << i.second << ": " << err << dendl; | |
11fdf7f2 TL |
312 | ignored_mon_values.emplace(i); |
313 | } else if (r == ConfigValues::SET_NO_CHANGE || | |
314 | r == ConfigValues::SET_NO_EFFECT) { | |
315 | ldout(cct,20) << __func__ << " " << i.first << " = " << i.second | |
316 | << " (no change)" << dendl; | |
317 | } else if (r == ConfigValues::SET_HAVE_EFFECT) { | |
318 | ldout(cct,10) << __func__ << " " << i.first << " = " << i.second << dendl; | |
319 | } else { | |
320 | ceph_abort(); | |
7c673cae FG |
321 | } |
322 | } | |
11fdf7f2 TL |
323 | values.for_each([&] (auto name, auto configs) { |
324 | auto config = configs.find(CONF_MON); | |
325 | if (config == configs.end()) { | |
326 | return; | |
327 | } | |
328 | if (kv.find(name) != kv.end()) { | |
329 | return; | |
330 | } | |
331 | ldout(cct,10) << __func__ << " " << name | |
332 | << " cleared (was " << Option::to_str(config->second) << ")" | |
333 | << dendl; | |
334 | values.rm_val(name, CONF_MON); | |
92f5a8d4 TL |
335 | // if this is a debug option, it needs to propagate to teh subsys; |
336 | // this isn't covered by update_legacy_vals() below. similarly, | |
337 | // we want to trigger a config notification for these items. | |
338 | const Option *o = find_option(name); | |
339 | _refresh(values, *o); | |
11fdf7f2 TL |
340 | }); |
341 | values_bl.clear(); | |
342 | update_legacy_vals(values); | |
343 | return 0; | |
7c673cae FG |
344 | } |
345 | ||
11fdf7f2 TL |
346 | int md_config_t::parse_config_files(ConfigValues& values, |
347 | const ConfigTracker& tracker, | |
348 | const char *conf_files_str, | |
7c673cae FG |
349 | std::ostream *warnings, |
350 | int flags) | |
351 | { | |
11fdf7f2 | 352 | if (safe_to_start_threads) |
7c673cae FG |
353 | return -ENOSYS; |
354 | ||
f67539c2 TL |
355 | if (values.cluster.empty() && !conf_files_str) { |
356 | values.cluster = get_cluster_name(nullptr); | |
7c673cae | 357 | } |
7c673cae | 358 | // open new conf |
f67539c2 TL |
359 | for (auto& fn : get_conffile_paths(values, conf_files_str, warnings, flags)) { |
360 | bufferlist bl; | |
361 | std::string error; | |
362 | if (bl.read_file(fn.c_str(), &error)) { | |
363 | parse_error = error; | |
364 | continue; | |
365 | } | |
9f95a23c | 366 | ostringstream oss; |
f67539c2 | 367 | int ret = parse_buffer(values, tracker, bl.c_str(), bl.length(), &oss); |
9f95a23c | 368 | if (ret == 0) { |
f67539c2 | 369 | parse_error.clear(); |
b3b6e05e | 370 | conf_path = fn; |
7c673cae | 371 | break; |
9f95a23c | 372 | } |
f67539c2 TL |
373 | parse_error = oss.str(); |
374 | if (ret != -ENOENT) { | |
7c673cae | 375 | return ret; |
f67539c2 | 376 | } |
7c673cae FG |
377 | } |
378 | // it must have been all ENOENTs, that's the only way we got here | |
b3b6e05e | 379 | if (conf_path.empty()) { |
7c673cae | 380 | return -ENOENT; |
7c673cae | 381 | } |
f67539c2 | 382 | if (values.cluster.empty()) { |
b3b6e05e | 383 | values.cluster = get_cluster_name(conf_path.c_str()); |
f67539c2 TL |
384 | } |
385 | update_legacy_vals(values); | |
386 | return 0; | |
387 | } | |
7c673cae | 388 | |
f67539c2 TL |
389 | int |
390 | md_config_t::parse_buffer(ConfigValues& values, | |
391 | const ConfigTracker& tracker, | |
392 | const char* buf, size_t len, | |
393 | std::ostream* warnings) | |
394 | { | |
395 | if (!cf.parse_buffer(string_view{buf, len}, warnings)) { | |
396 | return -EINVAL; | |
397 | } | |
398 | const auto my_sections = get_my_sections(values); | |
c07f9fc5 FG |
399 | for (const auto &i : schema) { |
400 | const auto &opt = i.second; | |
7c673cae | 401 | std::string val; |
f67539c2 TL |
402 | if (_get_val_from_conf_file(my_sections, opt.name, val)) { |
403 | continue; | |
404 | } | |
405 | std::string error_message; | |
406 | if (_set_val(values, tracker, val, opt, CONF_FILE, &error_message) < 0) { | |
407 | if (warnings != nullptr) { | |
408 | *warnings << "parse error setting " << std::quoted(opt.name) | |
409 | << " to " << std::quoted(val); | |
7c673cae FG |
410 | if (!error_message.empty()) { |
411 | *warnings << " (" << error_message << ")"; | |
412 | } | |
f67539c2 | 413 | *warnings << '\n'; |
7c673cae FG |
414 | } |
415 | } | |
416 | } | |
f67539c2 TL |
417 | cf.check_old_style_section_names({"mds", "mon", "osd"}, cerr); |
418 | return 0; | |
419 | } | |
7c673cae | 420 | |
f67539c2 TL |
421 | std::list<std::string> |
422 | md_config_t::get_conffile_paths(const ConfigValues& values, | |
423 | const char *conf_files_str, | |
424 | std::ostream *warnings, | |
425 | int flags) const | |
426 | { | |
427 | if (!conf_files_str) { | |
428 | const char *c = getenv("CEPH_CONF"); | |
429 | if (c) { | |
430 | conf_files_str = c; | |
431 | } else { | |
432 | if (flags & CINIT_FLAG_NO_DEFAULT_CONFIG_FILE) | |
433 | return {}; | |
434 | conf_files_str = CEPH_CONF_FILE_DEFAULT; | |
7c673cae FG |
435 | } |
436 | } | |
f67539c2 TL |
437 | |
438 | std::list<std::string> paths; | |
439 | get_str_list(conf_files_str, ";,", paths); | |
440 | for (auto i = paths.begin(); i != paths.end(); ) { | |
441 | string& path = *i; | |
442 | if (path.find("$data_dir") != path.npos && | |
443 | data_dir_option.empty()) { | |
444 | // useless $data_dir item, skip | |
445 | i = paths.erase(i); | |
446 | } else { | |
447 | early_expand_meta(values, path, warnings); | |
448 | ++i; | |
7c673cae | 449 | } |
7c673cae | 450 | } |
f67539c2 TL |
451 | return paths; |
452 | } | |
11fdf7f2 | 453 | |
f67539c2 TL |
454 | std::string md_config_t::get_cluster_name(const char* conffile) |
455 | { | |
456 | if (conffile) { | |
457 | // If cluster name is not set yet, use the prefix of the | |
458 | // basename of configuration file as cluster name. | |
459 | if (fs::path path{conffile}; path.extension() == ".conf") { | |
460 | return path.stem().string(); | |
461 | } else { | |
462 | // If the configuration file does not follow $cluster.conf | |
463 | // convention, we do the last try and assign the cluster to | |
464 | // 'ceph'. | |
465 | return "ceph"; | |
466 | } | |
467 | } else { | |
468 | // set the cluster name to 'ceph' when configuration file is not specified. | |
469 | return "ceph"; | |
470 | } | |
7c673cae FG |
471 | } |
472 | ||
11fdf7f2 TL |
473 | void md_config_t::parse_env(unsigned entity_type, |
474 | ConfigValues& values, | |
475 | const ConfigTracker& tracker, | |
476 | const char *args_var) | |
7c673cae | 477 | { |
11fdf7f2 | 478 | if (safe_to_start_threads) |
7c673cae | 479 | return; |
11fdf7f2 TL |
480 | if (!args_var) { |
481 | args_var = "CEPH_ARGS"; | |
482 | } | |
81eedcae TL |
483 | if (auto s = getenv("CEPH_KEYRING"); s) { |
484 | string err; | |
485 | _set_val(values, tracker, s, *find_option("keyring"), CONF_ENV, &err); | |
11fdf7f2 | 486 | } |
81eedcae | 487 | if (auto dir = getenv("CEPH_LIB"); dir) { |
11fdf7f2 TL |
488 | for (auto name : { "erasure_code_dir", "plugin_dir", "osd_class_dir" }) { |
489 | std::string err; | |
490 | const Option *o = find_option(name); | |
491 | ceph_assert(o); | |
492 | _set_val(values, tracker, dir, *o, CONF_ENV, &err); | |
493 | } | |
494 | } | |
494da23a TL |
495 | |
496 | // Apply pod memory limits: | |
497 | // | |
498 | // There are two types of resource requests: `limits` and `requests`. | |
499 | // | |
500 | // - Requests: Used by the K8s scheduler to determine on which nodes to | |
501 | // schedule the pods. This helps spread the pods to different nodes. This | |
502 | // value should be conservative in order to make sure all the pods are | |
503 | // schedulable. This corresponds to POD_MEMORY_REQUEST (set by the Rook | |
504 | // CRD) and is the target memory utilization we try to maintain for daemons | |
505 | // that respect it. | |
506 | // | |
507 | // If POD_MEMORY_REQUEST is present, we use it as the target. | |
508 | // | |
509 | // - Limits: At runtime, the container runtime (and Linux) will use the | |
510 | // limits to see if the pod is using too many resources. In that case, the | |
511 | // pod will be killed/restarted automatically if the pod goes over the limit. | |
512 | // This should be higher than what is specified for requests (potentially | |
513 | // much higher). This corresponds to the cgroup memory limit that will | |
514 | // trigger the Linux OOM killer. | |
515 | // | |
516 | // If POD_MEMORY_LIMIT is present, we use it as the /default/ value for | |
517 | // the target, which means it will only apply if the *_memory_target option | |
518 | // isn't set via some other path (e.g., POD_MEMORY_REQUEST, or the cluster | |
519 | // config, or whatever.) | |
520 | // | |
521 | // Here are the documented best practices: | |
522 | // https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/#motivation-for-cpu-requests-and-limits | |
523 | // | |
524 | // When the operator creates the CephCluster CR, it will need to generate the | |
525 | // desired requests and limits. As long as we are conservative in our choice | |
526 | // for requests and generous with the limits we should be in a good place to | |
527 | // get started. | |
528 | // | |
529 | // The support in Rook is already there for applying the limits as seen in | |
530 | // these links. | |
531 | // | |
532 | // Rook docs on the resource requests and limits: | |
533 | // https://rook.io/docs/rook/v1.0/ceph-cluster-crd.html#cluster-wide-resources-configuration-settings | |
534 | // Example CR settings: | |
535 | // https://github.com/rook/rook/blob/6d2ef936698593036185aabcb00d1d74f9c7bfc1/cluster/examples/kubernetes/ceph/cluster.yaml#L90 | |
536 | // | |
537 | uint64_t pod_limit = 0, pod_request = 0; | |
538 | if (auto pod_lim = getenv("POD_MEMORY_LIMIT"); pod_lim) { | |
81eedcae | 539 | string err; |
494da23a | 540 | uint64_t v = atoll(pod_lim); |
11fdf7f2 TL |
541 | if (v) { |
542 | switch (entity_type) { | |
543 | case CEPH_ENTITY_TYPE_OSD: | |
494da23a TL |
544 | { |
545 | double cgroup_ratio = get_val<double>( | |
546 | values, "osd_memory_target_cgroup_limit_ratio"); | |
547 | if (cgroup_ratio > 0.0) { | |
548 | pod_limit = v * cgroup_ratio; | |
549 | // set osd_memory_target *default* based on cgroup limit, so that | |
550 | // it can be overridden by any explicit settings elsewhere. | |
551 | set_val_default(values, tracker, | |
552 | "osd_memory_target", stringify(pod_limit)); | |
553 | } | |
554 | } | |
11fdf7f2 TL |
555 | } |
556 | } | |
557 | } | |
494da23a TL |
558 | if (auto pod_req = getenv("POD_MEMORY_REQUEST"); pod_req) { |
559 | if (uint64_t v = atoll(pod_req); v) { | |
560 | pod_request = v; | |
561 | } | |
562 | } | |
563 | if (pod_request && pod_limit) { | |
564 | // If both LIMIT and REQUEST are set, ensure that we use the | |
565 | // min of request and limit*ratio. This is important | |
566 | // because k8s set set LIMIT == REQUEST if only LIMIT is | |
567 | // specified, and we want to apply the ratio in that case, | |
568 | // even though REQUEST is present. | |
569 | pod_request = std::min<uint64_t>(pod_request, pod_limit); | |
570 | } | |
571 | if (pod_request) { | |
572 | string err; | |
573 | switch (entity_type) { | |
574 | case CEPH_ENTITY_TYPE_OSD: | |
575 | _set_val(values, tracker, stringify(pod_request), | |
576 | *find_option("osd_memory_target"), | |
577 | CONF_ENV, &err); | |
578 | break; | |
579 | } | |
580 | } | |
581 | ||
11fdf7f2 TL |
582 | if (getenv(args_var)) { |
583 | vector<const char *> env_args; | |
584 | env_to_vec(env_args, args_var); | |
585 | parse_argv(values, tracker, env_args, CONF_ENV); | |
7c673cae FG |
586 | } |
587 | } | |
588 | ||
11fdf7f2 TL |
589 | void md_config_t::show_config(const ConfigValues& values, |
590 | std::ostream& out) const | |
7c673cae | 591 | { |
11fdf7f2 | 592 | _show_config(values, &out, nullptr); |
7c673cae FG |
593 | } |
594 | ||
11fdf7f2 TL |
595 | void md_config_t::show_config(const ConfigValues& values, |
596 | Formatter *f) const | |
7c673cae | 597 | { |
11fdf7f2 | 598 | _show_config(values, nullptr, f); |
7c673cae FG |
599 | } |
600 | ||
11fdf7f2 | 601 | void md_config_t::config_options(Formatter *f) const |
1adf2230 | 602 | { |
1adf2230 AA |
603 | f->open_array_section("options"); |
604 | for (const auto& i: schema) { | |
11fdf7f2 | 605 | f->dump_object("option", i.second); |
1adf2230 AA |
606 | } |
607 | f->close_section(); | |
608 | } | |
609 | ||
11fdf7f2 TL |
610 | void md_config_t::_show_config(const ConfigValues& values, |
611 | std::ostream *out, Formatter *f) const | |
7c673cae FG |
612 | { |
613 | if (out) { | |
11fdf7f2 TL |
614 | *out << "name = " << values.name << std::endl; |
615 | *out << "cluster = " << values.cluster << std::endl; | |
7c673cae FG |
616 | } |
617 | if (f) { | |
11fdf7f2 TL |
618 | f->dump_string("name", stringify(values.name)); |
619 | f->dump_string("cluster", values.cluster); | |
7c673cae | 620 | } |
c07f9fc5 FG |
621 | for (const auto& i: schema) { |
622 | const Option &opt = i.second; | |
11fdf7f2 TL |
623 | string val; |
624 | conf_stringify(_get_val(values, opt), &val); | |
625 | if (out) { | |
626 | *out << opt.name << " = " << val << std::endl; | |
627 | } | |
628 | if (f) { | |
629 | f->dump_string(opt.name.c_str(), val); | |
630 | } | |
7c673cae FG |
631 | } |
632 | } | |
633 | ||
11fdf7f2 TL |
634 | int md_config_t::parse_argv(ConfigValues& values, |
635 | const ConfigTracker& tracker, | |
636 | std::vector<const char*>& args, int level) | |
7c673cae | 637 | { |
11fdf7f2 | 638 | if (safe_to_start_threads) { |
7c673cae FG |
639 | return -ENOSYS; |
640 | } | |
641 | ||
7c673cae FG |
642 | // In this function, don't change any parts of the configuration directly. |
643 | // Instead, use set_val to set them. This will allow us to send the proper | |
644 | // observer notifications later. | |
645 | std::string val; | |
646 | for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ) { | |
647 | if (strcmp(*i, "--") == 0) { | |
648 | /* Normally we would use ceph_argparse_double_dash. However, in this | |
649 | * function we *don't* want to remove the double dash, because later | |
650 | * argument parses will still need to see it. */ | |
651 | break; | |
652 | } | |
653 | else if (ceph_argparse_flag(args, i, "--show_conf", (char*)NULL)) { | |
654 | cerr << cf << std::endl; | |
655 | _exit(0); | |
656 | } | |
657 | else if (ceph_argparse_flag(args, i, "--show_config", (char*)NULL)) { | |
11fdf7f2 | 658 | do_show_config = true; |
7c673cae FG |
659 | } |
660 | else if (ceph_argparse_witharg(args, i, &val, "--show_config_value", (char*)NULL)) { | |
11fdf7f2 TL |
661 | do_show_config_value = val; |
662 | } | |
663 | else if (ceph_argparse_flag(args, i, "--no-mon-config", (char*)NULL)) { | |
664 | values.no_mon_config = true; | |
665 | } | |
11fdf7f2 TL |
666 | else if (ceph_argparse_flag(args, i, "--mon-config", (char*)NULL)) { |
667 | values.no_mon_config = false; | |
7c673cae FG |
668 | } |
669 | else if (ceph_argparse_flag(args, i, "--foreground", "-f", (char*)NULL)) { | |
11fdf7f2 | 670 | set_val_or_die(values, tracker, "daemonize", "false"); |
7c673cae FG |
671 | } |
672 | else if (ceph_argparse_flag(args, i, "-d", (char*)NULL)) { | |
e306af50 | 673 | set_val_or_die(values, tracker, "fuse_debug", "true"); |
11fdf7f2 TL |
674 | set_val_or_die(values, tracker, "daemonize", "false"); |
675 | set_val_or_die(values, tracker, "log_file", ""); | |
676 | set_val_or_die(values, tracker, "log_to_stderr", "true"); | |
677 | set_val_or_die(values, tracker, "err_to_stderr", "true"); | |
678 | set_val_or_die(values, tracker, "log_to_syslog", "false"); | |
7c673cae FG |
679 | } |
680 | // Some stuff that we wanted to give universal single-character options for | |
681 | // Careful: you can burn through the alphabet pretty quickly by adding | |
682 | // to this list. | |
683 | else if (ceph_argparse_witharg(args, i, &val, "--monmap", "-M", (char*)NULL)) { | |
11fdf7f2 | 684 | set_val_or_die(values, tracker, "monmap", val.c_str()); |
7c673cae FG |
685 | } |
686 | else if (ceph_argparse_witharg(args, i, &val, "--mon_host", "-m", (char*)NULL)) { | |
11fdf7f2 | 687 | set_val_or_die(values, tracker, "mon_host", val.c_str()); |
7c673cae FG |
688 | } |
689 | else if (ceph_argparse_witharg(args, i, &val, "--bind", (char*)NULL)) { | |
11fdf7f2 | 690 | set_val_or_die(values, tracker, "public_addr", val.c_str()); |
7c673cae FG |
691 | } |
692 | else if (ceph_argparse_witharg(args, i, &val, "--keyfile", "-K", (char*)NULL)) { | |
11fdf7f2 TL |
693 | bufferlist bl; |
694 | string err; | |
695 | int r; | |
696 | if (val == "-") { | |
697 | r = bl.read_fd(STDIN_FILENO, 1024); | |
698 | } else { | |
699 | r = bl.read_file(val.c_str(), &err); | |
700 | } | |
701 | if (r >= 0) { | |
702 | string k(bl.c_str(), bl.length()); | |
703 | set_val_or_die(values, tracker, "key", k.c_str()); | |
704 | } | |
7c673cae FG |
705 | } |
706 | else if (ceph_argparse_witharg(args, i, &val, "--keyring", "-k", (char*)NULL)) { | |
11fdf7f2 | 707 | set_val_or_die(values, tracker, "keyring", val.c_str()); |
7c673cae FG |
708 | } |
709 | else if (ceph_argparse_witharg(args, i, &val, "--client_mountpoint", "-r", (char*)NULL)) { | |
11fdf7f2 | 710 | set_val_or_die(values, tracker, "client_mountpoint", val.c_str()); |
7c673cae FG |
711 | } |
712 | else { | |
11fdf7f2 | 713 | int r = parse_option(values, tracker, args, i, NULL, level); |
3efd9988 FG |
714 | if (r < 0) { |
715 | return r; | |
716 | } | |
7c673cae FG |
717 | } |
718 | } | |
11fdf7f2 TL |
719 | // meta expands could have modified anything. Copy it all out again. |
720 | update_legacy_vals(values); | |
721 | return 0; | |
722 | } | |
723 | ||
724 | void md_config_t::do_argv_commands(const ConfigValues& values) const | |
725 | { | |
7c673cae | 726 | |
11fdf7f2 TL |
727 | if (do_show_config) { |
728 | _show_config(values, &cout, NULL); | |
7c673cae FG |
729 | _exit(0); |
730 | } | |
731 | ||
11fdf7f2 TL |
732 | if (do_show_config_value.size()) { |
733 | string val; | |
734 | int r = conf_stringify(_get_val(values, do_show_config_value, 0, &cerr), | |
735 | &val); | |
7c673cae FG |
736 | if (r < 0) { |
737 | if (r == -ENOENT) | |
11fdf7f2 TL |
738 | std::cerr << "failed to get config option '" |
739 | << do_show_config_value << "': option not found" << std::endl; | |
7c673cae | 740 | else |
11fdf7f2 TL |
741 | std::cerr << "failed to get config option '" |
742 | << do_show_config_value << "': " << cpp_strerror(r) | |
743 | << std::endl; | |
7c673cae FG |
744 | _exit(1); |
745 | } | |
11fdf7f2 | 746 | std::cout << val << std::endl; |
7c673cae FG |
747 | _exit(0); |
748 | } | |
7c673cae FG |
749 | } |
750 | ||
11fdf7f2 TL |
751 | int md_config_t::parse_option(ConfigValues& values, |
752 | const ConfigTracker& tracker, | |
753 | std::vector<const char*>& args, | |
754 | std::vector<const char*>::iterator& i, | |
755 | ostream *oss, | |
756 | int level) | |
7c673cae FG |
757 | { |
758 | int ret = 0; | |
c07f9fc5 | 759 | size_t o = 0; |
7c673cae FG |
760 | std::string val; |
761 | ||
c07f9fc5 | 762 | std::string option_name; |
7c673cae FG |
763 | std::string error_message; |
764 | o = 0; | |
c07f9fc5 FG |
765 | for (const auto& opt_iter: schema) { |
766 | const Option &opt = opt_iter.second; | |
7c673cae | 767 | ostringstream err; |
7c673cae | 768 | std::string as_option("--"); |
c07f9fc5 FG |
769 | as_option += opt.name; |
770 | option_name = opt.name; | |
11fdf7f2 TL |
771 | if (ceph_argparse_witharg( |
772 | args, i, &val, err, | |
773 | string(string("--default-") + opt.name).c_str(), (char*)NULL)) { | |
774 | if (!err.str().empty()) { | |
775 | error_message = err.str(); | |
776 | ret = -EINVAL; | |
777 | break; | |
778 | } | |
779 | ret = _set_val(values, tracker, val, opt, CONF_DEFAULT, &error_message); | |
780 | break; | |
781 | } else if (opt.type == Option::TYPE_BOOL) { | |
7c673cae FG |
782 | int res; |
783 | if (ceph_argparse_binary_flag(args, i, &res, oss, as_option.c_str(), | |
784 | (char*)NULL)) { | |
785 | if (res == 0) | |
11fdf7f2 | 786 | ret = _set_val(values, tracker, "false", opt, level, &error_message); |
7c673cae | 787 | else if (res == 1) |
11fdf7f2 | 788 | ret = _set_val(values, tracker, "true", opt, level, &error_message); |
7c673cae FG |
789 | else |
790 | ret = res; | |
791 | break; | |
792 | } else { | |
793 | std::string no("--no-"); | |
c07f9fc5 | 794 | no += opt.name; |
7c673cae | 795 | if (ceph_argparse_flag(args, i, no.c_str(), (char*)NULL)) { |
11fdf7f2 | 796 | ret = _set_val(values, tracker, "false", opt, level, &error_message); |
7c673cae FG |
797 | break; |
798 | } | |
799 | } | |
800 | } else if (ceph_argparse_witharg(args, i, &val, err, | |
801 | as_option.c_str(), (char*)NULL)) { | |
802 | if (!err.str().empty()) { | |
803 | error_message = err.str(); | |
804 | ret = -EINVAL; | |
805 | break; | |
806 | } | |
11fdf7f2 | 807 | ret = _set_val(values, tracker, val, opt, level, &error_message); |
7c673cae FG |
808 | break; |
809 | } | |
810 | ++o; | |
811 | } | |
812 | ||
11fdf7f2 TL |
813 | if (ret < 0 || !error_message.empty()) { |
814 | ceph_assert(!option_name.empty()); | |
7c673cae FG |
815 | if (oss) { |
816 | *oss << "Parse error setting " << option_name << " to '" | |
817 | << val << "' using injectargs"; | |
818 | if (!error_message.empty()) { | |
819 | *oss << " (" << error_message << ")"; | |
820 | } | |
821 | *oss << ".\n"; | |
822 | } else { | |
823 | cerr << "parse error setting '" << option_name << "' to '" | |
824 | << val << "'"; | |
825 | if (!error_message.empty()) { | |
826 | cerr << " (" << error_message << ")"; | |
827 | } | |
828 | cerr << "\n" << std::endl; | |
829 | } | |
830 | } | |
831 | ||
c07f9fc5 | 832 | if (o == schema.size()) { |
7c673cae FG |
833 | // ignore |
834 | ++i; | |
835 | } | |
11fdf7f2 | 836 | return ret >= 0 ? 0 : ret; |
7c673cae FG |
837 | } |
838 | ||
11fdf7f2 TL |
839 | int md_config_t::parse_injectargs(ConfigValues& values, |
840 | const ConfigTracker& tracker, | |
841 | std::vector<const char*>& args, | |
7c673cae FG |
842 | std::ostream *oss) |
843 | { | |
7c673cae FG |
844 | int ret = 0; |
845 | for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ) { | |
11fdf7f2 | 846 | int r = parse_option(values, tracker, args, i, oss, CONF_OVERRIDE); |
7c673cae FG |
847 | if (r < 0) |
848 | ret = r; | |
849 | } | |
850 | return ret; | |
851 | } | |
852 | ||
11fdf7f2 | 853 | void md_config_t::set_safe_to_start_threads() |
7c673cae | 854 | { |
11fdf7f2 | 855 | safe_to_start_threads = true; |
7c673cae FG |
856 | } |
857 | ||
11fdf7f2 | 858 | void md_config_t::_clear_safe_to_start_threads() |
7c673cae | 859 | { |
11fdf7f2 | 860 | safe_to_start_threads = false; |
7c673cae FG |
861 | } |
862 | ||
11fdf7f2 TL |
863 | int md_config_t::injectargs(ConfigValues& values, |
864 | const ConfigTracker& tracker, | |
865 | const std::string& s, std::ostream *oss) | |
7c673cae FG |
866 | { |
867 | int ret; | |
11fdf7f2 TL |
868 | char b[s.length()+1]; |
869 | strcpy(b, s.c_str()); | |
870 | std::vector<const char*> nargs; | |
871 | char *p = b; | |
872 | while (*p) { | |
873 | nargs.push_back(p); | |
874 | while (*p && *p != ' ') p++; | |
875 | if (!*p) | |
876 | break; | |
877 | *p++ = 0; | |
878 | while (*p && *p == ' ') p++; | |
879 | } | |
880 | ret = parse_injectargs(values, tracker, nargs, oss); | |
881 | if (!nargs.empty()) { | |
882 | *oss << " failed to parse arguments: "; | |
883 | std::string prefix; | |
884 | for (std::vector<const char*>::const_iterator i = nargs.begin(); | |
885 | i != nargs.end(); ++i) { | |
886 | *oss << prefix << *i; | |
887 | prefix = ","; | |
7c673cae | 888 | } |
11fdf7f2 TL |
889 | *oss << "\n"; |
890 | ret = -EINVAL; | |
7c673cae | 891 | } |
11fdf7f2 | 892 | update_legacy_vals(values); |
7c673cae FG |
893 | return ret; |
894 | } | |
895 | ||
11fdf7f2 TL |
896 | void md_config_t::set_val_or_die(ConfigValues& values, |
897 | const ConfigTracker& tracker, | |
9f95a23c | 898 | const std::string_view key, |
11fdf7f2 | 899 | const std::string &val) |
7c673cae | 900 | { |
c07f9fc5 | 901 | std::stringstream err; |
11fdf7f2 | 902 | int ret = set_val(values, tracker, key, val, &err); |
c07f9fc5 FG |
903 | if (ret != 0) { |
904 | std::cerr << "set_val_or_die(" << key << "): " << err.str(); | |
7c673cae | 905 | } |
11fdf7f2 | 906 | ceph_assert(ret == 0); |
7c673cae FG |
907 | } |
908 | ||
11fdf7f2 TL |
909 | int md_config_t::set_val(ConfigValues& values, |
910 | const ConfigTracker& tracker, | |
9f95a23c | 911 | const std::string_view key, const char *val, |
11fdf7f2 | 912 | std::stringstream *err_ss) |
7c673cae | 913 | { |
c07f9fc5 FG |
914 | if (key.empty()) { |
915 | if (err_ss) *err_ss << "No key specified"; | |
7c673cae | 916 | return -EINVAL; |
c07f9fc5 FG |
917 | } |
918 | if (!val) { | |
7c673cae | 919 | return -EINVAL; |
c07f9fc5 | 920 | } |
7c673cae FG |
921 | |
922 | std::string v(val); | |
7c673cae FG |
923 | |
924 | string k(ConfFile::normalize_key_name(key)); | |
925 | ||
c07f9fc5 FG |
926 | const auto &opt_iter = schema.find(k); |
927 | if (opt_iter != schema.end()) { | |
928 | const Option &opt = opt_iter->second; | |
7c673cae | 929 | std::string error_message; |
11fdf7f2 TL |
930 | int r = _set_val(values, tracker, v, opt, CONF_OVERRIDE, &error_message); |
931 | if (r >= 0) { | |
c07f9fc5 | 932 | if (err_ss) *err_ss << "Set " << opt.name << " to " << v; |
11fdf7f2 | 933 | r = 0; |
c07f9fc5 FG |
934 | } else { |
935 | if (err_ss) *err_ss << error_message; | |
936 | } | |
7c673cae FG |
937 | return r; |
938 | } | |
939 | ||
c07f9fc5 | 940 | if (err_ss) *err_ss << "Configuration option not found: '" << key << "'"; |
7c673cae FG |
941 | return -ENOENT; |
942 | } | |
943 | ||
9f95a23c | 944 | int md_config_t::rm_val(ConfigValues& values, const std::string_view key) |
11fdf7f2 TL |
945 | { |
946 | return _rm_val(values, key, CONF_OVERRIDE); | |
947 | } | |
948 | ||
949 | void md_config_t::get_defaults_bl(const ConfigValues& values, | |
950 | bufferlist *bl) | |
951 | { | |
952 | if (defaults_bl.length() == 0) { | |
953 | uint32_t n = 0; | |
954 | bufferlist bl; | |
955 | for (const auto &i : schema) { | |
956 | ++n; | |
957 | encode(i.second.name, bl); | |
958 | auto [value, found] = values.get_value(i.second.name, CONF_DEFAULT); | |
959 | if (found) { | |
960 | encode(Option::to_str(value), bl); | |
961 | } else { | |
962 | string val; | |
963 | conf_stringify(_get_val_default(i.second), &val); | |
964 | encode(val, bl); | |
965 | } | |
966 | } | |
967 | encode(n, defaults_bl); | |
968 | defaults_bl.claim_append(bl); | |
969 | } | |
970 | *bl = defaults_bl; | |
971 | } | |
972 | ||
973 | void md_config_t::get_config_bl( | |
974 | const ConfigValues& values, | |
975 | uint64_t have_version, | |
976 | bufferlist *bl, | |
977 | uint64_t *got_version) | |
978 | { | |
979 | if (values_bl.length() == 0) { | |
980 | uint32_t n = 0; | |
981 | bufferlist bl; | |
982 | values.for_each([&](auto& name, auto& configs) { | |
983 | if (name == "fsid" || | |
984 | name == "host") { | |
985 | return; | |
986 | } | |
987 | ++n; | |
988 | encode(name, bl); | |
989 | encode((uint32_t)configs.size(), bl); | |
990 | for (auto& j : configs) { | |
991 | encode(j.first, bl); | |
992 | encode(Option::to_str(j.second), bl); | |
993 | } | |
994 | }); | |
995 | // make sure overridden items appear, and include the default value | |
996 | for (auto& i : ignored_mon_values) { | |
997 | if (values.contains(i.first)) { | |
998 | continue; | |
999 | } | |
1000 | if (i.first == "fsid" || | |
1001 | i.first == "host") { | |
1002 | continue; | |
1003 | } | |
1004 | const Option *opt = find_option(i.first); | |
1005 | if (!opt) { | |
1006 | continue; | |
1007 | } | |
1008 | ++n; | |
1009 | encode(i.first, bl); | |
1010 | encode((uint32_t)1, bl); | |
1011 | encode((int32_t)CONF_DEFAULT, bl); | |
1012 | string val; | |
1013 | conf_stringify(_get_val_default(*opt), &val); | |
1014 | encode(val, bl); | |
1015 | } | |
1016 | encode(n, values_bl); | |
1017 | values_bl.claim_append(bl); | |
1018 | encode(ignored_mon_values, values_bl); | |
1019 | ++values_bl_version; | |
1020 | } | |
1021 | if (have_version != values_bl_version) { | |
1022 | *bl = values_bl; | |
1023 | *got_version = values_bl_version; | |
1024 | } | |
1025 | } | |
7c673cae | 1026 | |
a4b75251 TL |
1027 | std::optional<std::string> md_config_t::get_val_default(std::string_view key) |
1028 | { | |
1029 | std::string val; | |
1030 | const Option *opt = find_option(key); | |
1031 | if (opt && (conf_stringify(_get_val_default(*opt), &val) == 0)) { | |
1032 | return std::make_optional(std::move(val)); | |
1033 | } | |
1034 | return std::nullopt; | |
1035 | } | |
1036 | ||
11fdf7f2 | 1037 | int md_config_t::get_val(const ConfigValues& values, |
9f95a23c | 1038 | const std::string_view key, char **buf, int len) const |
7c673cae | 1039 | { |
11fdf7f2 TL |
1040 | string k(ConfFile::normalize_key_name(key)); |
1041 | return _get_val_cstr(values, k, buf, len); | |
7c673cae FG |
1042 | } |
1043 | ||
11fdf7f2 TL |
1044 | int md_config_t::get_val( |
1045 | const ConfigValues& values, | |
9f95a23c | 1046 | const std::string_view key, |
11fdf7f2 | 1047 | std::string *val) const |
7c673cae | 1048 | { |
11fdf7f2 | 1049 | return conf_stringify(get_val_generic(values, key), val); |
7c673cae FG |
1050 | } |
1051 | ||
11fdf7f2 TL |
1052 | Option::value_t md_config_t::get_val_generic( |
1053 | const ConfigValues& values, | |
9f95a23c | 1054 | const std::string_view key) const |
7c673cae | 1055 | { |
9f95a23c | 1056 | return _get_val(values, key); |
11fdf7f2 | 1057 | } |
7c673cae | 1058 | |
11fdf7f2 TL |
1059 | Option::value_t md_config_t::_get_val( |
1060 | const ConfigValues& values, | |
9f95a23c | 1061 | const std::string_view key, |
11fdf7f2 TL |
1062 | expand_stack_t *stack, |
1063 | std::ostream *err) const | |
1064 | { | |
c07f9fc5 | 1065 | if (key.empty()) { |
20effc67 | 1066 | return {}; |
c07f9fc5 | 1067 | } |
7c673cae FG |
1068 | |
1069 | // In key names, leading and trailing whitespace are not significant. | |
1070 | string k(ConfFile::normalize_key_name(key)); | |
1071 | ||
9f95a23c | 1072 | const Option *o = find_option(k); |
11fdf7f2 TL |
1073 | if (!o) { |
1074 | // not a valid config option | |
20effc67 | 1075 | return {}; |
7c673cae | 1076 | } |
11fdf7f2 TL |
1077 | |
1078 | return _get_val(values, *o, stack, err); | |
7c673cae FG |
1079 | } |
1080 | ||
11fdf7f2 TL |
1081 | Option::value_t md_config_t::_get_val( |
1082 | const ConfigValues& values, | |
1083 | const Option& o, | |
1084 | expand_stack_t *stack, | |
1085 | std::ostream *err) const | |
1086 | { | |
1087 | expand_stack_t a_stack; | |
1088 | if (!stack) { | |
1089 | stack = &a_stack; | |
1090 | } | |
1091 | return _expand_meta(values, | |
1092 | _get_val_nometa(values, o), | |
1093 | &o, stack, err); | |
1094 | } | |
7c673cae | 1095 | |
11fdf7f2 TL |
1096 | Option::value_t md_config_t::_get_val_nometa(const ConfigValues& values, |
1097 | const Option& o) const | |
1098 | { | |
1099 | if (auto [value, found] = values.get_value(o.name, -1); found) { | |
1100 | return value; | |
1101 | } else { | |
1102 | return _get_val_default(o); | |
1103 | } | |
1104 | } | |
1105 | ||
1106 | const Option::value_t& md_config_t::_get_val_default(const Option& o) const | |
1107 | { | |
20effc67 | 1108 | bool has_daemon_default = (o.daemon_value != Option::value_t{}); |
11fdf7f2 TL |
1109 | if (is_daemon && has_daemon_default) { |
1110 | return o.daemon_value; | |
1111 | } else { | |
1112 | return o.value; | |
1113 | } | |
1114 | } | |
1115 | ||
1116 | void md_config_t::early_expand_meta( | |
1117 | const ConfigValues& values, | |
1118 | std::string &val, | |
1119 | std::ostream *err) const | |
1120 | { | |
1121 | expand_stack_t stack; | |
1122 | Option::value_t v = _expand_meta(values, | |
1123 | Option::value_t(val), | |
1124 | nullptr, &stack, err); | |
1125 | conf_stringify(v, &val); | |
1126 | } | |
1127 | ||
1128 | bool md_config_t::finalize_reexpand_meta(ConfigValues& values, | |
1129 | const ConfigTracker& tracker) | |
1130 | { | |
adb31ebb TL |
1131 | std::vector<std::string> reexpands; |
1132 | reexpands.swap(may_reexpand_meta); | |
1133 | for (auto& name : reexpands) { | |
1134 | // always refresh the options if they are in the may_reexpand_meta | |
1135 | // map, because the options may have already been expanded with old | |
1136 | // meta. | |
1137 | const auto &opt_iter = schema.find(name); | |
1138 | ceph_assert(opt_iter != schema.end()); | |
1139 | const Option &opt = opt_iter->second; | |
1140 | _refresh(values, opt); | |
11fdf7f2 | 1141 | } |
adb31ebb TL |
1142 | |
1143 | return !may_reexpand_meta.empty(); | |
11fdf7f2 TL |
1144 | } |
1145 | ||
1146 | Option::value_t md_config_t::_expand_meta( | |
1147 | const ConfigValues& values, | |
1148 | const Option::value_t& in, | |
1149 | const Option *o, | |
1150 | expand_stack_t *stack, | |
1151 | std::ostream *err) const | |
1152 | { | |
1153 | //cout << __func__ << " in '" << in << "' stack " << stack << std::endl; | |
1154 | if (!stack) { | |
1155 | return in; | |
1156 | } | |
20effc67 | 1157 | const auto str = std::get_if<std::string>(&in); |
11fdf7f2 TL |
1158 | if (!str) { |
1159 | // strings only! | |
1160 | return in; | |
1161 | } | |
1162 | ||
1163 | auto pos = str->find('$'); | |
1164 | if (pos == std::string::npos) { | |
1165 | // no substitutions! | |
1166 | return in; | |
1167 | } | |
1168 | ||
1169 | if (o) { | |
1170 | stack->push_back(make_pair(o, &in)); | |
1171 | } | |
1172 | string out; | |
1173 | decltype(pos) last_pos = 0; | |
1174 | while (pos != std::string::npos) { | |
1175 | ceph_assert((*str)[pos] == '$'); | |
1176 | if (pos > last_pos) { | |
1177 | out += str->substr(last_pos, pos - last_pos); | |
1178 | } | |
1179 | ||
1180 | // try to parse the variable name into var, either \$\{(.+)\} or | |
1181 | // \$[a-z\_]+ | |
1182 | const char *valid_chars = "abcdefghijklmnopqrstuvwxyz_"; | |
1183 | string var; | |
1184 | size_t endpos = 0; | |
1185 | if ((*str)[pos+1] == '{') { | |
1186 | // ...${foo_bar}... | |
1187 | endpos = str->find_first_not_of(valid_chars, pos + 2); | |
1188 | if (endpos != std::string::npos && | |
1189 | (*str)[endpos] == '}') { | |
1190 | var = str->substr(pos + 2, endpos - pos - 2); | |
1191 | endpos++; | |
1192 | } | |
7c673cae | 1193 | } else { |
11fdf7f2 TL |
1194 | // ...$foo... |
1195 | endpos = str->find_first_not_of(valid_chars, pos + 1); | |
1196 | if (endpos != std::string::npos) | |
1197 | var = str->substr(pos + 1, endpos - pos - 1); | |
1198 | else | |
1199 | var = str->substr(pos + 1); | |
7c673cae | 1200 | } |
11fdf7f2 TL |
1201 | last_pos = endpos; |
1202 | ||
1203 | if (!var.size()) { | |
1204 | out += '$'; | |
1205 | } else { | |
1206 | //cout << " found var " << var << std::endl; | |
1207 | // special metavariable? | |
1208 | if (var == "type") { | |
1209 | out += values.name.get_type_name(); | |
1210 | } else if (var == "cluster") { | |
1211 | out += values.cluster; | |
1212 | } else if (var == "name") { | |
1213 | out += values.name.to_cstr(); | |
1214 | } else if (var == "host") { | |
1215 | if (values.host == "") { | |
1216 | out += ceph_get_short_hostname(); | |
1217 | } else { | |
1218 | out += values.host; | |
1219 | } | |
1220 | } else if (var == "num") { | |
1221 | out += values.name.get_id().c_str(); | |
1222 | } else if (var == "id") { | |
1223 | out += values.name.get_id(); | |
1224 | } else if (var == "pid") { | |
f67539c2 TL |
1225 | char *_pid = getenv("PID"); |
1226 | if (_pid) { | |
1227 | out += _pid; | |
1228 | } else { | |
1229 | out += stringify(getpid()); | |
1230 | } | |
11fdf7f2 | 1231 | if (o) { |
adb31ebb | 1232 | may_reexpand_meta.push_back(o->name); |
11fdf7f2 TL |
1233 | } |
1234 | } else if (var == "cctid") { | |
1235 | out += stringify((unsigned long long)this); | |
1236 | } else if (var == "home") { | |
1237 | const char *home = getenv("HOME"); | |
1238 | out = home ? std::string(home) : std::string(); | |
f67539c2 TL |
1239 | } else if (var == "programdata") { |
1240 | const char *home = getenv("ProgramData"); | |
1241 | out = home ? std::string(home) : std::string(); | |
1242 | }else { | |
11fdf7f2 TL |
1243 | if (var == "data_dir") { |
1244 | var = data_dir_option; | |
1245 | } | |
1246 | const Option *o = find_option(var); | |
1247 | if (!o) { | |
1248 | out += str->substr(pos, endpos - pos); | |
1249 | } else { | |
1250 | auto match = std::find_if( | |
1251 | stack->begin(), stack->end(), | |
1252 | [o](pair<const Option *,const Option::value_t*>& item) { | |
1253 | return item.first == o; | |
1254 | }); | |
1255 | if (match != stack->end()) { | |
1256 | // substitution loop; break the cycle | |
1257 | if (err) { | |
1258 | *err << "variable expansion loop at " << var << "=" | |
1259 | << Option::to_str(*match->second) << "\n" | |
1260 | << "expansion stack:\n"; | |
1261 | for (auto i = stack->rbegin(); i != stack->rend(); ++i) { | |
1262 | *err << i->first->name << "=" | |
1263 | << Option::to_str(*i->second) << "\n"; | |
1264 | } | |
1265 | } | |
1266 | return Option::value_t(std::string("$") + o->name); | |
1267 | } else { | |
1268 | // recursively evaluate! | |
1269 | string n; | |
1270 | conf_stringify(_get_val(values, *o, stack, err), &n); | |
1271 | out += n; | |
1272 | } | |
1273 | } | |
1274 | } | |
1275 | } | |
1276 | pos = str->find('$', last_pos); | |
7c673cae | 1277 | } |
11fdf7f2 TL |
1278 | if (last_pos != std::string::npos) { |
1279 | out += str->substr(last_pos); | |
1280 | } | |
1281 | if (o) { | |
1282 | stack->pop_back(); | |
1283 | } | |
1284 | ||
1285 | return Option::value_t(out); | |
7c673cae FG |
1286 | } |
1287 | ||
11fdf7f2 TL |
1288 | int md_config_t::_get_val_cstr( |
1289 | const ConfigValues& values, | |
9f95a23c | 1290 | const std::string& key, char **buf, int len) const |
7c673cae | 1291 | { |
c07f9fc5 | 1292 | if (key.empty()) |
7c673cae FG |
1293 | return -EINVAL; |
1294 | ||
11fdf7f2 TL |
1295 | string val; |
1296 | if (conf_stringify(_get_val(values, key), &val) == 0) { | |
224ce89b | 1297 | int l = val.length() + 1; |
7c673cae FG |
1298 | if (len == -1) { |
1299 | *buf = (char*)malloc(l); | |
1300 | if (!*buf) | |
1301 | return -ENOMEM; | |
224ce89b | 1302 | strncpy(*buf, val.c_str(), l); |
7c673cae FG |
1303 | return 0; |
1304 | } | |
224ce89b | 1305 | snprintf(*buf, len, "%s", val.c_str()); |
7c673cae FG |
1306 | return (l > len) ? -ENAMETOOLONG : 0; |
1307 | } | |
1308 | ||
7c673cae FG |
1309 | // couldn't find a configuration option with key 'k' |
1310 | return -ENOENT; | |
1311 | } | |
1312 | ||
1313 | void md_config_t::get_all_keys(std::vector<std::string> *keys) const { | |
1314 | const std::string negative_flag_prefix("no_"); | |
1315 | ||
1316 | keys->clear(); | |
c07f9fc5 FG |
1317 | keys->reserve(schema.size()); |
1318 | for (const auto &i: schema) { | |
1319 | const Option &opt = i.second; | |
7c673cae | 1320 | keys->push_back(opt.name); |
c07f9fc5 | 1321 | if (opt.type == Option::TYPE_BOOL) { |
7c673cae FG |
1322 | keys->push_back(negative_flag_prefix + opt.name); |
1323 | } | |
1324 | } | |
7c673cae FG |
1325 | } |
1326 | ||
1327 | /* The order of the sections here is important. The first section in the | |
1328 | * vector is the "highest priority" section; if we find it there, we'll stop | |
1329 | * looking. The lowest priority section is the one we look in only if all | |
1330 | * others had nothing. This should always be the global section. | |
1331 | */ | |
f67539c2 TL |
1332 | std::vector <std::string> |
1333 | md_config_t::get_my_sections(const ConfigValues& values) const | |
7c673cae | 1334 | { |
f67539c2 TL |
1335 | return {values.name.to_str(), |
1336 | values.name.get_type_name().data(), | |
1337 | "global"}; | |
7c673cae FG |
1338 | } |
1339 | ||
1340 | // Return a list of all sections | |
1341 | int md_config_t::get_all_sections(std::vector <std::string> §ions) const | |
1342 | { | |
9f95a23c TL |
1343 | for (auto [section_name, section] : cf) { |
1344 | sections.push_back(section_name); | |
1345 | std::ignore = section; | |
7c673cae FG |
1346 | } |
1347 | return 0; | |
1348 | } | |
1349 | ||
11fdf7f2 TL |
1350 | int md_config_t::get_val_from_conf_file( |
1351 | const ConfigValues& values, | |
1352 | const std::vector <std::string> §ions, | |
9f95a23c | 1353 | const std::string_view key, |
11fdf7f2 TL |
1354 | std::string &out, |
1355 | bool emeta) const | |
7c673cae | 1356 | { |
11fdf7f2 TL |
1357 | int r = _get_val_from_conf_file(sections, key, out); |
1358 | if (r < 0) { | |
1359 | return r; | |
1360 | } | |
1361 | if (emeta) { | |
1362 | expand_stack_t stack; | |
1363 | auto v = _expand_meta(values, Option::value_t(out), nullptr, &stack, nullptr); | |
1364 | conf_stringify(v, &out); | |
1365 | } | |
1366 | return 0; | |
7c673cae FG |
1367 | } |
1368 | ||
11fdf7f2 TL |
1369 | int md_config_t::_get_val_from_conf_file( |
1370 | const std::vector <std::string> §ions, | |
9f95a23c | 1371 | const std::string_view key, |
11fdf7f2 | 1372 | std::string &out) const |
7c673cae | 1373 | { |
9f95a23c | 1374 | for (auto &s : sections) { |
f67539c2 | 1375 | int ret = cf.read(s, key, out); |
7c673cae | 1376 | if (ret == 0) { |
7c673cae | 1377 | return 0; |
11fdf7f2 | 1378 | } else if (ret != -ENOENT) { |
7c673cae | 1379 | return ret; |
11fdf7f2 | 1380 | } |
7c673cae FG |
1381 | } |
1382 | return -ENOENT; | |
1383 | } | |
1384 | ||
11fdf7f2 TL |
1385 | int md_config_t::_set_val( |
1386 | ConfigValues& values, | |
1387 | const ConfigTracker& observers, | |
1388 | const std::string &raw_val, | |
1389 | const Option &opt, | |
1390 | int level, | |
1391 | std::string *error_message) | |
7c673cae | 1392 | { |
11fdf7f2 | 1393 | Option::value_t new_value; |
81eedcae | 1394 | ceph_assert(error_message); |
11fdf7f2 TL |
1395 | int r = opt.parse_value(raw_val, &new_value, error_message); |
1396 | if (r < 0) { | |
c07f9fc5 FG |
1397 | return r; |
1398 | } | |
7c673cae | 1399 | |
11fdf7f2 TL |
1400 | // unsafe runtime change? |
1401 | if (!opt.can_update_at_runtime() && | |
1402 | safe_to_start_threads && | |
1403 | !observers.is_tracking(opt.name)) { | |
1404 | // accept value if it is not actually a change | |
1405 | if (new_value != _get_val_nometa(values, opt)) { | |
1406 | *error_message = string("Configuration option '") + opt.name + | |
1407 | "' may not be modified at runtime"; | |
9f95a23c | 1408 | return -EPERM; |
c07f9fc5 | 1409 | } |
7c673cae | 1410 | } |
7c673cae | 1411 | |
c07f9fc5 | 1412 | // Apply the value to its entry in the `values` map |
11fdf7f2 TL |
1413 | auto result = values.set_value(opt.name, std::move(new_value), level); |
1414 | switch (result) { | |
1415 | case ConfigValues::SET_NO_CHANGE: | |
1416 | break; | |
1417 | case ConfigValues::SET_NO_EFFECT: | |
1418 | values_bl.clear(); | |
1419 | break; | |
1420 | case ConfigValues::SET_HAVE_EFFECT: | |
1421 | values_bl.clear(); | |
1422 | _refresh(values, opt); | |
1423 | break; | |
1424 | } | |
1425 | return result; | |
1426 | } | |
7c673cae | 1427 | |
11fdf7f2 TL |
1428 | void md_config_t::_refresh(ConfigValues& values, const Option& opt) |
1429 | { | |
c07f9fc5 FG |
1430 | // Apply the value to its legacy field, if it has one |
1431 | auto legacy_ptr_iter = legacy_values.find(std::string(opt.name)); | |
1432 | if (legacy_ptr_iter != legacy_values.end()) { | |
11fdf7f2 TL |
1433 | update_legacy_val(values, opt, legacy_ptr_iter->second); |
1434 | } | |
1435 | // Was this a debug_* option update? | |
1436 | if (opt.subsys >= 0) { | |
1437 | string actual_val; | |
1438 | conf_stringify(_get_val(values, opt), &actual_val); | |
1439 | values.set_logging(opt.subsys, actual_val.c_str()); | |
1440 | } else { | |
1441 | // normal option, advertise the change. | |
1442 | values.changed.insert(opt.name); | |
7c673cae | 1443 | } |
11fdf7f2 | 1444 | } |
7c673cae | 1445 | |
11fdf7f2 | 1446 | int md_config_t::_rm_val(ConfigValues& values, |
9f95a23c | 1447 | const std::string_view key, |
11fdf7f2 TL |
1448 | int level) |
1449 | { | |
1450 | if (schema.count(key) == 0) { | |
1451 | return -EINVAL; | |
1452 | } | |
9f95a23c | 1453 | auto ret = values.rm_val(std::string{key}, level); |
11fdf7f2 TL |
1454 | if (ret < 0) { |
1455 | return ret; | |
1456 | } | |
1457 | if (ret == ConfigValues::SET_HAVE_EFFECT) { | |
1458 | _refresh(values, *find_option(key)); | |
1459 | } | |
1460 | values_bl.clear(); | |
c07f9fc5 FG |
1461 | return 0; |
1462 | } | |
7c673cae | 1463 | |
11fdf7f2 TL |
1464 | namespace { |
1465 | template<typename Size> | |
20effc67 | 1466 | struct get_size_visitor |
11fdf7f2 TL |
1467 | { |
1468 | get_size_visitor() {} | |
1469 | ||
1470 | template<typename T> | |
1471 | Size operator()(const T&) const { | |
1472 | return -1; | |
1473 | } | |
1474 | Size operator()(const Option::size_t& sz) const { | |
1475 | return static_cast<Size>(sz.value); | |
1476 | } | |
1477 | Size operator()(const Size& v) const { | |
1478 | return v; | |
1479 | } | |
1480 | }; | |
1481 | ||
c07f9fc5 FG |
1482 | /** |
1483 | * Handles assigning from a variant-of-types to a variant-of-pointers-to-types | |
1484 | */ | |
20effc67 | 1485 | class assign_visitor |
c07f9fc5 | 1486 | { |
20effc67 | 1487 | ConfigValues *conf; |
c07f9fc5 FG |
1488 | Option::value_t val; |
1489 | public: | |
7c673cae | 1490 | |
20effc67 | 1491 | assign_visitor(ConfigValues *conf_, Option::value_t val_) |
c07f9fc5 FG |
1492 | : conf(conf_), val(val_) |
1493 | {} | |
1494 | ||
1495 | template <typename T> | |
20effc67 | 1496 | void operator()(T ConfigValues::* ptr) const |
c07f9fc5 | 1497 | { |
20effc67 | 1498 | T *member = const_cast<T *>(&(conf->*(ptr))); |
c07f9fc5 | 1499 | |
20effc67 | 1500 | *member = std::get<T>(val); |
7c673cae | 1501 | } |
20effc67 | 1502 | void operator()(uint64_t ConfigValues::* ptr) const |
11fdf7f2 TL |
1503 | { |
1504 | using T = uint64_t; | |
20effc67 TL |
1505 | auto member = const_cast<T*>(&(conf->*(ptr))); |
1506 | *member = std::visit(get_size_visitor<T>{}, val); | |
11fdf7f2 | 1507 | } |
20effc67 | 1508 | void operator()(int64_t ConfigValues::* ptr) const |
11fdf7f2 TL |
1509 | { |
1510 | using T = int64_t; | |
20effc67 TL |
1511 | auto member = const_cast<T*>(&(conf->*(ptr))); |
1512 | *member = std::visit(get_size_visitor<T>{}, val); | |
c07f9fc5 | 1513 | } |
7c673cae | 1514 | }; |
11fdf7f2 | 1515 | } // anonymous namespace |
7c673cae | 1516 | |
11fdf7f2 | 1517 | void md_config_t::update_legacy_vals(ConfigValues& values) |
7c673cae | 1518 | { |
11fdf7f2 TL |
1519 | for (const auto &i : legacy_values) { |
1520 | const auto &name = i.first; | |
1521 | const auto &option = schema.at(name); | |
1522 | auto ptr = i.second; | |
1523 | update_legacy_val(values, option, ptr); | |
7c673cae | 1524 | } |
7c673cae FG |
1525 | } |
1526 | ||
11fdf7f2 TL |
1527 | void md_config_t::update_legacy_val(ConfigValues& values, |
1528 | const Option &opt, | |
1529 | md_config_t::member_ptr_t member_ptr) | |
7c673cae | 1530 | { |
11fdf7f2 | 1531 | Option::value_t v = _get_val(values, opt); |
20effc67 | 1532 | std::visit(assign_visitor(&values, v), member_ptr); |
7c673cae FG |
1533 | } |
1534 | ||
11fdf7f2 | 1535 | static void dump(Formatter *f, int level, Option::value_t in) |
7c673cae | 1536 | { |
20effc67 | 1537 | if (const auto v = std::get_if<bool>(&in)) { |
11fdf7f2 | 1538 | f->dump_bool(ceph_conf_level_name(level), *v); |
20effc67 | 1539 | } else if (const auto v = std::get_if<int64_t>(&in)) { |
11fdf7f2 | 1540 | f->dump_int(ceph_conf_level_name(level), *v); |
20effc67 | 1541 | } else if (const auto v = std::get_if<uint64_t>(&in)) { |
11fdf7f2 | 1542 | f->dump_unsigned(ceph_conf_level_name(level), *v); |
20effc67 | 1543 | } else if (const auto v = std::get_if<double>(&in)) { |
11fdf7f2 TL |
1544 | f->dump_float(ceph_conf_level_name(level), *v); |
1545 | } else { | |
1546 | f->dump_stream(ceph_conf_level_name(level)) << Option::to_str(in); | |
7c673cae | 1547 | } |
7c673cae FG |
1548 | } |
1549 | ||
1550 | void md_config_t::diff( | |
11fdf7f2 TL |
1551 | const ConfigValues& values, |
1552 | Formatter *f, | |
1553 | string name) const | |
7c673cae | 1554 | { |
11fdf7f2 | 1555 | values.for_each([this, f, &values] (auto& name, auto& configs) { |
494da23a | 1556 | if (configs.empty()) { |
11fdf7f2 | 1557 | return; |
7c673cae | 1558 | } |
9f95a23c | 1559 | f->open_object_section(std::string{name}.c_str()); |
11fdf7f2 | 1560 | const Option *o = find_option(name); |
494da23a TL |
1561 | if (configs.size() && |
1562 | configs.begin()->first != CONF_DEFAULT) { | |
1563 | // show compiled-in default only if an override default wasn't provided | |
1564 | dump(f, CONF_DEFAULT, _get_val_default(*o)); | |
1565 | } | |
11fdf7f2 TL |
1566 | for (auto& j : configs) { |
1567 | dump(f, j.first, j.second); | |
31f18b77 | 1568 | } |
11fdf7f2 TL |
1569 | dump(f, CONF_FINAL, _get_val(values, *o)); |
1570 | f->close_section(); | |
1571 | }); | |
7c673cae FG |
1572 | } |
1573 | ||
9f95a23c | 1574 | void md_config_t::complain_about_parse_error(CephContext *cct) |
7c673cae | 1575 | { |
9f95a23c | 1576 | ::complain_about_parse_error(cct, parse_error); |
7c673cae | 1577 | } |