forked from scylladb/scylladb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig_file.hh
340 lines (299 loc) · 13.1 KB
/
config_file.hh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
/*
* Copyright (C) 2017-present ScyllaDB
*
*/
/*
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
*/
#pragma once
#include <fmt/format.h>
#include <unordered_map>
#include <string_view>
#include <seastar/core/sstring.hh>
#include <seastar/core/future.hh>
#include <seastar/util/log.hh>
#include "utils/updateable_value.hh"
#include "seastarx.hh"
namespace seastar { class file; }
namespace seastar::json { class json_return_type; }
namespace YAML { class Node; }
namespace boost::program_options {
class options_description;
class options_description_easy_init;
}
namespace utils {
namespace bpo = boost::program_options;
class config_type {
std::string_view _name;
std::function<json::json_return_type (const void*)> _to_json;
private:
template <typename NativeType>
std::function<json::json_return_type (const void*)> make_to_json(json::json_return_type (*func)(const NativeType&)) {
return [func] (const void* value) {
return func(*static_cast<const NativeType*>(value));
};
}
public:
template <typename NativeType>
config_type(std::string_view name, json::json_return_type (*to_json)(const NativeType&)) : _name(name), _to_json(make_to_json(to_json)) {}
std::string_view name() const { return _name; }
json::json_return_type to_json(const void* value) const;
};
template <typename T>
extern const config_type& config_type_for();
class config_file {
static thread_local unsigned s_shard_id;
struct any_value {
virtual ~any_value() = default;
virtual std::unique_ptr<any_value> clone() const = 0;
virtual void update_from(const any_value* source) = 0;
};
std::vector<std::vector<std::unique_ptr<any_value>>> _per_shard_values { 1 };
public:
typedef std::unordered_map<sstring, sstring> string_map;
typedef std::vector<sstring> string_list;
enum class value_status {
Used, ///< a valid option which changes scylla's behavior. only the
///< "Used" options are added to the command line options,
///< and can be specified with command line.
Unused, ///< an option inherited or not yet implemented.
///< We want to minimize their "visibility" from user. So
///< despite that they are still accepted in the config file,
///< they are not considered as valid options if specified
///< using the command line anymore.
///< initially, we had loads of these options from Cassandra.
///< they are also used for options supported by the older
///< versions of Scylla.
///< (In the past used to deprecate an option,
///< now there's a separate Deprecated enumerator for that purpose)
Invalid, ///< an option inherited, but we don't intend to
///< implement it. on the contrary, we print a warning message
///< at seeing each of these options if specified when booting
///< up. these options are kept around only to ease the
///< migration process so user can keep using their existing
///< settings.
///< (In the past used to deprecate an option,
///< now there's a separate Deprecated enumerator for that purpose)
Deprecated, ///< an option that was used once, but became deprecated.
///< If specified in configuration file or as a cmd line param,
///< we'll parse it and print a warning saying it's deprecated.
///< You may expect such option will become Unused/Invalid
///< or will be removed altogether some day.
};
enum class liveness {
LiveUpdate,
MustRestart,
};
enum class config_source : uint8_t {
None,
SettingsFile,
CommandLine,
CQL,
Internal,
API,
};
struct config_src {
config_file* _cf;
std::string_view _name, _alias, _desc;
const config_type* _type;
size_t _per_shard_values_offset;
protected:
virtual const void* current_value() const = 0;
public:
config_src(config_file* cf, std::string_view name, const config_type* type, std::string_view desc)
: _cf(cf)
, _name(name)
, _desc(desc)
, _type(type)
{}
config_src(config_file* cf, std::string_view name, std::string_view alias, const config_type* type, std::string_view desc)
: _cf(cf)
, _name(name)
, _alias(alias)
, _desc(desc)
, _type(type)
{}
virtual ~config_src() {}
const std::string_view & name() const {
return _name;
}
std::string_view alias() const {
return _alias;
}
const std::string_view & desc() const {
return _desc;
}
std::string_view type_name() const {
return _type->name();
}
config_file * get_config_file() const {
return _cf;
}
bool matches(std::string_view name) const;
virtual void add_command_line_option(bpo::options_description_easy_init&) = 0;
virtual void set_value(const YAML::Node&) = 0;
virtual bool set_value(sstring, config_source) = 0;
virtual future<> set_value_on_all_shards(const YAML::Node&) = 0;
virtual future<bool> set_value_on_all_shards(sstring, config_source) = 0;
virtual value_status status() const noexcept = 0;
virtual config_source source() const noexcept = 0;
sstring source_name() const noexcept;
json::json_return_type value_as_json() const;
};
template<typename T>
struct named_value : public config_src {
private:
friend class config;
config_source _source = config_source::None;
value_status _value_status;
struct the_value_type final : any_value {
the_value_type(T value) : value(std::move(value)) {}
utils::updateable_value_source<T> value;
virtual std::unique_ptr<any_value> clone() const override {
return std::make_unique<the_value_type>(value());
}
virtual void update_from(const any_value* source) override {
auto typed_source = static_cast<const the_value_type*>(source);
value.set(typed_source->value());
}
};
liveness _liveness;
std::vector<T> _allowed_values;
protected:
updateable_value_source<T>& the_value() {
any_value* av = _cf->_per_shard_values[_cf->s_shard_id][_per_shard_values_offset].get();
return static_cast<the_value_type*>(av)->value;
}
const updateable_value_source<T>& the_value() const {
return const_cast<named_value*>(this)->the_value();
}
virtual const void* current_value() const override {
return &the_value().get();
}
public:
typedef T type;
typedef named_value<T> MyType;
named_value(config_file* file, std::string_view name, std::string_view alias, liveness liveness_, value_status vs, const T& t = T(), std::string_view desc = {},
std::initializer_list<T> allowed_values = {})
: config_src(file, name, alias, &config_type_for<T>(), desc)
, _value_status(vs)
, _liveness(liveness_)
, _allowed_values(std::move(allowed_values)) {
file->add(*this, std::make_unique<the_value_type>(std::move(t)));
}
named_value(config_file* file, std::string_view name, liveness liveness_, value_status vs, const T& t = T(), std::string_view desc = {},
std::initializer_list<T> allowed_values = {})
: named_value(file, name, {}, liveness_, vs, t, desc) {
}
named_value(config_file* file, std::string_view name, std::string_view alias, value_status vs, const T& t = T(), std::string_view desc = {},
std::initializer_list<T> allowed_values = {})
: named_value(file, name, alias, liveness::MustRestart, vs, t, desc, allowed_values) {
}
named_value(config_file* file, std::string_view name, value_status vs, const T& t = T(), std::string_view desc = {},
std::initializer_list<T> allowed_values = {})
: named_value(file, name, {}, liveness::MustRestart, vs, t, desc, allowed_values) {
}
value_status status() const noexcept override {
return _value_status;
}
config_source source() const noexcept override {
return _source;
}
bool is_set() const {
return _source > config_source::None;
}
MyType & operator()(const T& t, config_source src = config_source::Internal) {
if (!_allowed_values.empty() && std::find(_allowed_values.begin(), _allowed_values.end(), t) == _allowed_values.end()) {
throw std::invalid_argument(fmt::format("Invalid value for {}: got {} which is not inside the set of allowed values {}", name(), t, _allowed_values));
}
the_value().set(t);
if (src > config_source::None) {
_source = src;
}
return *this;
}
MyType & operator()(T&& t, config_source src = config_source::Internal) {
if (!_allowed_values.empty() && std::find(_allowed_values.begin(), _allowed_values.end(), t) == _allowed_values.end()) {
throw std::invalid_argument(fmt::format("Invalid value for {}: got {} which is not inside the set of allowed values {}", name(), t, _allowed_values));
}
the_value().set(std::move(t));
if (src > config_source::None) {
_source = src;
}
return *this;
}
void set(T&& t, config_source src = config_source::None) {
operator()(std::move(t), src);
}
const T& operator()() const {
return the_value().get();
}
operator updateable_value<T>() const & {
return updateable_value<T>(the_value());
}
observer<T> observe(std::function<void (const T&)> callback) const {
return the_value().observe(std::move(callback));
}
void add_command_line_option(bpo::options_description_easy_init&) override;
void set_value(const YAML::Node&) override;
bool set_value(sstring, config_source) override;
// For setting a single value on all shards,
// without having to call broadcast_to_all_shards
// that broadcasts all values to all shards.
future<> set_value_on_all_shards(const YAML::Node&) override;
future<bool> set_value_on_all_shards(sstring, config_source) override;
};
typedef std::reference_wrapper<config_src> cfg_ref;
config_file(std::initializer_list<cfg_ref> = {});
config_file(const config_file&) = delete;
virtual ~config_file() = default;
void add(cfg_ref, std::unique_ptr<any_value> value);
void add(std::initializer_list<cfg_ref>);
void add(const std::vector<cfg_ref> &);
boost::program_options::options_description get_options_description();
boost::program_options::options_description get_options_description(boost::program_options::options_description);
boost::program_options::options_description_easy_init&
add_options(boost::program_options::options_description_easy_init&);
boost::program_options::options_description_easy_init&
add_deprecated_options(boost::program_options::options_description_easy_init&);
/**
* Default behaviour for yaml parser is to throw on
* unknown stuff, invalid opts or conversion errors.
*
* Error handling function allows overriding this.
*
* error: <option name>, <message>, <optional value_status>
*
* The last arg, opt value_status will tell you the type of
* error occurred. If not set, the option found does not exist.
* If invalid, it is invalid. Otherwise, a parse error.
*
*/
using error_handler = std::function<void(const sstring&, const sstring&, std::optional<value_status>)>;
void read_from_yaml(const sstring&, error_handler = {});
void read_from_yaml(const char *, error_handler = {});
future<> read_from_file(const sstring&, error_handler = {});
future<> read_from_file(file, error_handler = {});
using configs = std::vector<cfg_ref>;
configs set_values() const;
configs unset_values() const;
const configs& values() const {
return _cfgs;
}
future<> broadcast_to_all_shards();
private:
virtual bool are_live_updatable_config_params_changeable_via_cql() const {
return false;
}
configs
_cfgs;
};
template <typename T>
requires requires (const config_file::named_value<T>& nv) {
{ nv().empty() } -> std::same_as<bool>;
}
const config_file::named_value<T>& operator||(const config_file::named_value<T>& a, const config_file::named_value<T>& b) {
return !a().empty() ? a : b;
}
extern template struct config_file::named_value<seastar::log_level>;
}