Line data Source code
1 : #include "source/common/runtime/runtime_impl.h"
2 :
3 : #include <cstdint>
4 : #include <string>
5 :
6 : #include "envoy/config/bootstrap/v3/bootstrap.pb.h"
7 : #include "envoy/config/core/v3/config_source.pb.h"
8 : #include "envoy/event/dispatcher.h"
9 : #include "envoy/service/discovery/v3/discovery.pb.h"
10 : #include "envoy/thread_local/thread_local.h"
11 : #include "envoy/type/v3/percent.pb.h"
12 : #include "envoy/type/v3/percent.pb.validate.h"
13 :
14 : #include "source/common/common/assert.h"
15 : #include "source/common/common/fmt.h"
16 : #include "source/common/common/utility.h"
17 : #include "source/common/config/api_version.h"
18 : #include "source/common/filesystem/directory.h"
19 : #include "source/common/grpc/common.h"
20 : #include "source/common/protobuf/message_validator_impl.h"
21 : #include "source/common/protobuf/utility.h"
22 : #include "source/common/runtime/runtime_features.h"
23 :
24 : #include "absl/container/node_hash_map.h"
25 : #include "absl/container/node_hash_set.h"
26 : #include "absl/flags/flag.h"
27 : #include "absl/strings/match.h"
28 : #include "absl/strings/numbers.h"
29 : #include "re2/re2.h"
30 :
31 : #ifdef ENVOY_ENABLE_QUIC
32 : #include "quiche_platform_impl/quiche_flags_impl.h"
33 : #endif
34 :
35 : namespace Envoy {
36 : namespace Runtime {
37 :
38 : namespace {
39 :
40 0 : void countDeprecatedFeatureUseInternal(const RuntimeStats& stats) {
41 0 : stats.deprecated_feature_use_.inc();
42 : // Similar to the above, but a gauge that isn't imported during a hot restart.
43 0 : stats.deprecated_feature_seen_since_process_start_.inc();
44 0 : }
45 :
46 1701 : void refreshReloadableFlags(const Snapshot::EntryMap& flag_map) {
47 1701 : absl::flat_hash_map<std::string, bool> quiche_flags_override;
48 1729 : for (const auto& it : flag_map) {
49 792 : #ifdef ENVOY_ENABLE_QUIC
50 792 : if (absl::StartsWith(it.first, quiche::EnvoyQuicheReloadableFlagPrefix) &&
51 792 : it.second.bool_value_.has_value()) {
52 0 : quiche_flags_override[it.first.substr(quiche::EnvoyFeaturePrefix.length())] =
53 0 : it.second.bool_value_.value();
54 0 : }
55 792 : #endif
56 792 : if (it.second.bool_value_.has_value() && isRuntimeFeature(it.first)) {
57 572 : maybeSetRuntimeGuard(it.first, it.second.bool_value_.value());
58 572 : }
59 792 : }
60 1701 : #ifdef ENVOY_ENABLE_QUIC
61 1701 : quiche::FlagRegistry::getInstance().updateReloadableFlags(quiche_flags_override);
62 1701 : #endif
63 : // Make sure ints are parsed after the flag allowing deprecated ints is parsed.
64 1729 : for (const auto& it : flag_map) {
65 792 : if (it.second.uint_value_.has_value()) {
66 0 : maybeSetDeprecatedInts(it.first, it.second.uint_value_.value());
67 0 : }
68 792 : }
69 1701 : markRuntimeInitialized();
70 1701 : }
71 :
72 : } // namespace
73 :
74 0 : bool SnapshotImpl::deprecatedFeatureEnabled(absl::string_view key, bool default_value) const {
75 : // A deprecated feature is enabled if at least one of the following conditions holds:
76 : // 1. A boolean runtime entry <key> doesn't exist, and default_value is true.
77 : // 2. A boolean runtime entry <key> exists, with a value of "true".
78 : // 3. A boolean runtime entry "envoy.features.enable_all_deprecated_features" with a value of
79 : // "true" exists, and there isn't a boolean runtime entry <key> with a value of "false".
80 :
81 0 : if (!getBoolean(key,
82 0 : getBoolean("envoy.features.enable_all_deprecated_features", default_value))) {
83 0 : return false;
84 0 : }
85 :
86 : // The feature is allowed. It is assumed this check is called when the feature
87 : // is about to be used, so increment the feature use stat.
88 0 : countDeprecatedFeatureUseInternal(stats_);
89 :
90 : #ifdef ENVOY_DISABLE_DEPRECATED_FEATURES
91 : return false;
92 : #endif
93 :
94 0 : return true;
95 0 : }
96 :
97 0 : bool SnapshotImpl::runtimeFeatureEnabled(absl::string_view key) const {
98 : // If the value is not explicitly set as a runtime boolean, the default value is based on
99 : // the underlying value.
100 0 : return getBoolean(key, Runtime::runtimeFeatureEnabled(key));
101 0 : }
102 :
103 : bool SnapshotImpl::featureEnabled(absl::string_view key, uint64_t default_value,
104 0 : uint64_t random_value, uint64_t num_buckets) const {
105 0 : return random_value % num_buckets < std::min(getInteger(key, default_value), num_buckets);
106 0 : }
107 :
108 492 : bool SnapshotImpl::featureEnabled(absl::string_view key, uint64_t default_value) const {
109 : // Avoid PRNG if we know we don't need it.
110 492 : uint64_t cutoff = std::min(getInteger(key, default_value), static_cast<uint64_t>(100));
111 492 : if (cutoff == 0) {
112 387 : return false;
113 387 : } else if (cutoff == 100) {
114 105 : return true;
115 105 : } else {
116 0 : return generator_.random() % 100 < cutoff;
117 0 : }
118 492 : }
119 :
120 : bool SnapshotImpl::featureEnabled(absl::string_view key, uint64_t default_value,
121 0 : uint64_t random_value) const {
122 0 : return featureEnabled(key, default_value, random_value, 100);
123 0 : }
124 :
125 232 : Snapshot::ConstStringOptRef SnapshotImpl::get(absl::string_view key) const {
126 232 : ASSERT(!isRuntimeFeature(key)); // Make sure runtime guarding is only used for getBoolean
127 232 : auto entry = key.empty() ? values_.end() : values_.find(key);
128 232 : if (entry == values_.end()) {
129 232 : return absl::nullopt;
130 232 : } else {
131 0 : return entry->second.raw_string_value_;
132 0 : }
133 232 : }
134 :
135 : bool SnapshotImpl::featureEnabled(absl::string_view key,
136 136 : const envoy::type::v3::FractionalPercent& default_value) const {
137 136 : return featureEnabled(key, default_value, generator_.random());
138 136 : }
139 :
140 : bool SnapshotImpl::featureEnabled(absl::string_view key,
141 : const envoy::type::v3::FractionalPercent& default_value,
142 136 : uint64_t random_value) const {
143 136 : const auto& entry = key.empty() ? values_.end() : values_.find(key);
144 136 : envoy::type::v3::FractionalPercent percent;
145 136 : if (entry != values_.end() && entry->second.fractional_percent_value_.has_value()) {
146 0 : percent = entry->second.fractional_percent_value_.value();
147 136 : } else if (entry != values_.end() && entry->second.uint_value_.has_value()) {
148 : // Check for > 100 because the runtime value is assumed to be specified as
149 : // an integer, and it also ensures that truncating the uint64_t runtime
150 : // value into a uint32_t percent numerator later is safe
151 0 : if (entry->second.uint_value_.value() > 100) {
152 0 : return true;
153 0 : }
154 :
155 : // The runtime value was specified as an integer rather than a fractional
156 : // percent proto. To preserve legacy semantics, we treat it as a percentage
157 : // (i.e. denominator of 100).
158 0 : percent.set_numerator(entry->second.uint_value_.value());
159 0 : percent.set_denominator(envoy::type::v3::FractionalPercent::HUNDRED);
160 136 : } else {
161 136 : percent = default_value;
162 136 : }
163 :
164 : // When numerator > denominator condition is always evaluates to TRUE
165 : // It becomes hard to debug why configuration does not work in case of wrong numerator.
166 : // Log debug message that numerator is invalid.
167 136 : uint64_t denominator_value =
168 136 : ProtobufPercentHelper::fractionalPercentDenominatorToInt(percent.denominator());
169 136 : if (percent.numerator() > denominator_value) {
170 0 : ENVOY_LOG(debug,
171 0 : "WARNING runtime key '{}': numerator ({}) > denominator ({}), condition always "
172 0 : "evaluates to true",
173 0 : key, percent.numerator(), denominator_value);
174 0 : }
175 :
176 136 : return ProtobufPercentHelper::evaluateFractionalPercent(percent, random_value);
177 136 : }
178 :
179 11917 : uint64_t SnapshotImpl::getInteger(absl::string_view key, uint64_t default_value) const {
180 11917 : ASSERT(!isRuntimeFeature(key));
181 11917 : const auto& entry = key.empty() ? values_.end() : values_.find(key);
182 11917 : if (entry == values_.end() || !entry->second.uint_value_) {
183 11917 : return default_value;
184 11917 : } else {
185 0 : return entry->second.uint_value_.value();
186 0 : }
187 11917 : }
188 :
189 0 : double SnapshotImpl::getDouble(absl::string_view key, double default_value) const {
190 0 : ASSERT(!isRuntimeFeature(key)); // Make sure runtime guarding is only used for getBoolean
191 0 : const auto& entry = key.empty() ? values_.end() : values_.find(key);
192 0 : if (entry == values_.end() || !entry->second.double_value_) {
193 0 : return default_value;
194 0 : } else {
195 0 : return entry->second.double_value_.value();
196 0 : }
197 0 : }
198 :
199 1308 : bool SnapshotImpl::getBoolean(absl::string_view key, bool default_value) const {
200 1308 : const auto& entry = key.empty() ? values_.end() : values_.find(key);
201 1308 : if (entry == values_.end() || !entry->second.bool_value_.has_value()) {
202 1308 : return default_value;
203 1308 : } else {
204 0 : return entry->second.bool_value_.value();
205 0 : }
206 1308 : }
207 :
208 0 : const std::vector<Snapshot::OverrideLayerConstPtr>& SnapshotImpl::getLayers() const {
209 0 : return layers_;
210 0 : }
211 :
212 1701 : const Snapshot::EntryMap& SnapshotImpl::values() const { return values_; }
213 :
214 : SnapshotImpl::SnapshotImpl(Random::RandomGenerator& generator, RuntimeStats& stats,
215 : std::vector<OverrideLayerConstPtr>&& layers)
216 1701 : : layers_{std::move(layers)}, generator_{generator}, stats_{stats} {
217 1799 : for (const auto& layer : layers_) {
218 1767 : for (const auto& kv : layer->values()) {
219 792 : values_.erase(kv.first);
220 792 : values_.emplace(kv.first, kv.second);
221 792 : }
222 1767 : }
223 1701 : stats.num_keys_.set(values_.size());
224 1701 : }
225 :
226 0 : void parseFractionValue(SnapshotImpl::Entry& entry, const ProtobufWkt::Struct& value) {
227 0 : envoy::type::v3::FractionalPercent percent;
228 0 : static_assert(envoy::type::v3::FractionalPercent::MILLION ==
229 0 : envoy::type::v3::FractionalPercent::DenominatorType_MAX);
230 0 : percent.set_denominator(envoy::type::v3::FractionalPercent::HUNDRED);
231 0 : for (const auto& f : value.fields()) {
232 0 : if (f.first == "numerator") {
233 0 : if (f.second.has_number_value()) {
234 0 : percent.set_numerator(f.second.number_value());
235 0 : }
236 0 : } else if (f.first == "denominator" && f.second.has_string_value()) {
237 0 : if (f.second.string_value() == "HUNDRED") {
238 0 : percent.set_denominator(envoy::type::v3::FractionalPercent::HUNDRED);
239 0 : } else if (f.second.string_value() == "TEN_THOUSAND") {
240 0 : percent.set_denominator(envoy::type::v3::FractionalPercent::TEN_THOUSAND);
241 0 : } else if (f.second.string_value() == "MILLION") {
242 0 : percent.set_denominator(envoy::type::v3::FractionalPercent::MILLION);
243 0 : } else {
244 0 : return;
245 0 : }
246 0 : } else {
247 0 : return;
248 0 : }
249 0 : }
250 :
251 0 : entry.fractional_percent_value_ = percent;
252 0 : }
253 :
254 0 : void setNumberValue(Envoy::Runtime::Snapshot::Entry& entry, double value) {
255 0 : entry.double_value_ = value;
256 0 : if (value < std::numeric_limits<int>::max() && value == static_cast<int>(value)) {
257 0 : entry.bool_value_ = value != 0;
258 0 : }
259 0 : if (entry.double_value_ >= 0 && entry.double_value_ <= std::numeric_limits<uint64_t>::max()) {
260 : // Valid uint values will always be parseable as doubles, so we assign the value to both the
261 : // uint and double fields. In cases where the value is something like "3.1", we will floor the
262 : // number by casting it to a uint and assigning the uint value.
263 0 : entry.uint_value_ = entry.double_value_;
264 0 : }
265 0 : }
266 :
267 : // Handle corner cases in parsing: negatives and decimals aren't always parsed as doubles.
268 0 : bool parseEntryDoubleValue(Envoy::Runtime::Snapshot::Entry& entry) {
269 0 : double converted_double;
270 0 : if (absl::SimpleAtod(entry.raw_string_value_, &converted_double)) {
271 0 : setNumberValue(entry, converted_double);
272 0 : return true;
273 0 : }
274 0 : return false;
275 0 : }
276 :
277 : // Handle an awful corner case where we explicitly shove a yaml percent in a proto string
278 : // value. Basically due to prior parsing logic we have to handle any combination
279 : // of numerator: #### [denominator Y] with quotes braces etc that could possibly be valid json.
280 : // E.g. "final_value": "{\"numerator\": 10000, \"denominator\": \"TEN_THOUSAND\"}",
281 0 : bool parseEntryFractionalPercentValue(Envoy::Runtime::Snapshot::Entry& entry) {
282 0 : if (!absl::StrContains(entry.raw_string_value_, "numerator")) {
283 0 : return false;
284 0 : }
285 :
286 0 : const re2::RE2 numerator_re(".*numerator[^\\d]+(\\d+)[^\\d]*");
287 :
288 0 : std::string match_string;
289 0 : if (!re2::RE2::FullMatch(entry.raw_string_value_.c_str(), numerator_re, &match_string)) {
290 0 : return false;
291 0 : }
292 :
293 0 : uint32_t numerator;
294 0 : if (!absl::SimpleAtoi(match_string, &numerator)) {
295 0 : return false;
296 0 : }
297 0 : envoy::type::v3::FractionalPercent converted_fractional_percent;
298 0 : converted_fractional_percent.set_numerator(numerator);
299 0 : entry.fractional_percent_value_ = converted_fractional_percent;
300 :
301 0 : if (!absl::StrContains(entry.raw_string_value_, "denominator")) {
302 0 : return true;
303 0 : }
304 0 : if (absl::StrContains(entry.raw_string_value_, "TEN_THOUSAND")) {
305 0 : entry.fractional_percent_value_->set_denominator(
306 0 : envoy::type::v3::FractionalPercent::TEN_THOUSAND);
307 0 : }
308 0 : if (absl::StrContains(entry.raw_string_value_, "MILLION")) {
309 0 : entry.fractional_percent_value_->set_denominator(envoy::type::v3::FractionalPercent::MILLION);
310 0 : }
311 0 : return true;
312 0 : }
313 :
314 : // Handle corner cases in non-yaml parsing: mixed case strings aren't parsed as booleans.
315 0 : bool parseEntryBooleanValue(Envoy::Runtime::Snapshot::Entry& entry) {
316 0 : absl::string_view stripped = entry.raw_string_value_;
317 0 : stripped = absl::StripAsciiWhitespace(stripped);
318 :
319 0 : if (absl::EqualsIgnoreCase(stripped, "true")) {
320 0 : entry.bool_value_ = true;
321 0 : return true;
322 0 : } else if (absl::EqualsIgnoreCase(stripped, "false")) {
323 0 : entry.bool_value_ = false;
324 0 : return true;
325 0 : }
326 0 : return false;
327 0 : }
328 :
329 : void SnapshotImpl::addEntry(Snapshot::EntryMap& values, const std::string& key,
330 792 : const ProtobufWkt::Value& value, absl::string_view raw_string) {
331 792 : const char* error_message = nullptr;
332 792 : values.emplace(key, SnapshotImpl::createEntry(value, raw_string, error_message));
333 792 : if (error_message != nullptr) {
334 0 : IS_ENVOY_BUG(
335 0 : absl::StrCat(error_message, "\n[ key:", key, ", value: ", value.DebugString(), "]"));
336 0 : }
337 792 : }
338 :
339 : static const char* kBoolError =
340 : "Runtime YAML appears to be setting booleans as strings. Support for this is planned "
341 : "to removed in an upcoming release. If you can not fix your YAML and need this to continue "
342 : "working "
343 : "please ping on https://github.com/envoyproxy/envoy/issues/27434";
344 : static const char* kFractionError =
345 : "Runtime YAML appears to be setting fractions as strings. Support for this is planned "
346 : "to removed in an upcoming release. If you can not fix your YAML and need this to continue "
347 : "working "
348 : "please ping on https://github.com/envoyproxy/envoy/issues/27434";
349 :
350 : SnapshotImpl::Entry SnapshotImpl::createEntry(const ProtobufWkt::Value& value,
351 : absl::string_view raw_string,
352 792 : const char*& error_message) {
353 792 : Entry entry;
354 792 : entry.raw_string_value_ = value.string_value();
355 792 : if (!raw_string.empty()) {
356 666 : entry.raw_string_value_ = raw_string;
357 666 : }
358 792 : switch (value.kind_case()) {
359 0 : case ProtobufWkt::Value::kNumberValue:
360 0 : setNumberValue(entry, value.number_value());
361 0 : if (entry.raw_string_value_.empty()) {
362 0 : entry.raw_string_value_ = absl::StrCat(value.number_value());
363 0 : }
364 0 : break;
365 792 : case ProtobufWkt::Value::kBoolValue:
366 792 : entry.bool_value_ = value.bool_value();
367 792 : if (entry.raw_string_value_.empty()) {
368 126 : entry.raw_string_value_ = absl::StrCat(value.bool_value());
369 126 : }
370 792 : break;
371 0 : case ProtobufWkt::Value::kStructValue:
372 0 : if (entry.raw_string_value_.empty()) {
373 0 : entry.raw_string_value_ = value.struct_value().DebugString();
374 0 : }
375 0 : parseFractionValue(entry, value.struct_value());
376 0 : break;
377 0 : case ProtobufWkt::Value::kStringValue:
378 0 : parseEntryDoubleValue(entry);
379 0 : if (parseEntryBooleanValue(entry)) {
380 0 : error_message = kBoolError;
381 0 : }
382 0 : if (parseEntryFractionalPercentValue(entry)) {
383 0 : error_message = kFractionError;
384 0 : }
385 0 : default:
386 0 : break;
387 792 : }
388 :
389 792 : return entry;
390 792 : }
391 :
392 556 : void AdminLayer::mergeValues(const absl::node_hash_map<std::string, std::string>& values) {
393 556 : #ifdef ENVOY_ENABLE_YAML
394 666 : for (const auto& kv : values) {
395 666 : values_.erase(kv.first);
396 666 : if (!kv.second.empty()) {
397 666 : SnapshotImpl::addEntry(values_, kv.first, ValueUtil::loadFromYaml(kv.second), kv.second);
398 666 : }
399 666 : }
400 556 : stats_.admin_overrides_active_.set(values_.empty() ? 0 : 1);
401 : #else
402 : IS_ENVOY_BUG("Runtime admin reload requires YAML support");
403 : UNREFERENCED_PARAMETER(values);
404 : return;
405 : #endif
406 556 : }
407 :
408 : DiskLayer::DiskLayer(absl::string_view name, const std::string& path, Api::Api& api)
409 0 : : OverrideLayerImpl{name} {
410 0 : walkDirectory(path, "", 1, api);
411 0 : }
412 :
413 : void DiskLayer::walkDirectory(const std::string& path, const std::string& prefix, uint32_t depth,
414 0 : Api::Api& api) {
415 : // Maximum recursion depth for walkDirectory().
416 0 : static constexpr uint32_t MaxWalkDepth = 16;
417 :
418 0 : ENVOY_LOG(debug, "walking directory: {}", path);
419 0 : if (depth > MaxWalkDepth) {
420 0 : throwEnvoyExceptionOrPanic(absl::StrCat("Walk recursion depth exceeded ", MaxWalkDepth));
421 0 : }
422 : // Check if this is an obviously bad path.
423 0 : if (api.fileSystem().illegalPath(path)) {
424 0 : throwEnvoyExceptionOrPanic(absl::StrCat("Invalid path: ", path));
425 0 : }
426 :
427 0 : Filesystem::Directory directory(path);
428 0 : Filesystem::DirectoryIteratorImpl it = directory.begin();
429 0 : THROW_IF_NOT_OK_REF(it.status());
430 0 : for (; it != directory.end(); ++it) {
431 0 : THROW_IF_NOT_OK_REF(it.status());
432 0 : Filesystem::DirectoryEntry entry = *it;
433 0 : std::string full_path = path + "/" + entry.name_;
434 0 : std::string full_prefix;
435 0 : if (prefix.empty()) {
436 0 : full_prefix = entry.name_;
437 0 : } else {
438 0 : full_prefix = prefix + "." + entry.name_;
439 0 : }
440 :
441 0 : if (entry.type_ == Filesystem::FileType::Directory && entry.name_ != "." &&
442 0 : entry.name_ != "..") {
443 0 : walkDirectory(full_path, full_prefix, depth + 1, api);
444 0 : } else if (entry.type_ == Filesystem::FileType::Regular) {
445 : // Suck the file into a string. This is not very efficient but it should be good enough
446 : // for small files. Also, as noted elsewhere, none of this is non-blocking which could
447 : // theoretically lead to issues.
448 0 : ENVOY_LOG(debug, "reading file: {}", full_path);
449 0 : std::string value;
450 :
451 : // Read the file and remove any comments. A comment is a line starting with a '#' character.
452 : // Comments are useful for placeholder files with no value.
453 0 : auto file_or_error = api.fileSystem().fileReadToEnd(full_path);
454 0 : THROW_IF_STATUS_NOT_OK(file_or_error, throw);
455 0 : const std::string text_file{file_or_error.value()};
456 :
457 0 : const auto lines = StringUtil::splitToken(text_file, "\n");
458 0 : for (const auto& line : lines) {
459 0 : if (!line.empty() && line.front() == '#') {
460 0 : continue;
461 0 : }
462 0 : if (line == lines.back()) {
463 0 : const absl::string_view trimmed = StringUtil::rtrim(line);
464 0 : value.append(trimmed.data(), trimmed.size());
465 0 : } else {
466 0 : value.append(std::string{line} + "\n");
467 0 : }
468 0 : }
469 : // Separate erase/insert calls required due to the value type being constant; this prevents
470 : // the use of the [] operator. Can leverage insert_or_assign in C++17 in the future.
471 0 : values_.erase(full_prefix);
472 0 : #ifdef ENVOY_ENABLE_YAML
473 0 : SnapshotImpl::addEntry(values_, full_prefix, ValueUtil::loadFromYaml(value), value);
474 : #else
475 : IS_ENVOY_BUG("Runtime admin reload requires YAML support");
476 : UNREFERENCED_PARAMETER(value);
477 : return;
478 : #endif
479 0 : }
480 0 : }
481 0 : THROW_IF_NOT_OK_REF(it.status());
482 0 : }
483 :
484 : ProtoLayer::ProtoLayer(absl::string_view name, const ProtobufWkt::Struct& proto)
485 99 : : OverrideLayerImpl{name} {
486 127 : for (const auto& f : proto.fields()) {
487 126 : walkProtoValue(f.second, f.first);
488 126 : }
489 99 : }
490 :
491 126 : void ProtoLayer::walkProtoValue(const ProtobufWkt::Value& v, const std::string& prefix) {
492 126 : switch (v.kind_case()) {
493 0 : case ProtobufWkt::Value::KIND_NOT_SET:
494 0 : case ProtobufWkt::Value::kListValue:
495 0 : case ProtobufWkt::Value::kNullValue:
496 0 : throwEnvoyExceptionOrPanic(absl::StrCat("Invalid runtime entry value for ", prefix));
497 0 : break;
498 0 : case ProtobufWkt::Value::kStringValue:
499 0 : SnapshotImpl::addEntry(values_, prefix, v, "");
500 0 : break;
501 0 : case ProtobufWkt::Value::kNumberValue:
502 126 : case ProtobufWkt::Value::kBoolValue:
503 126 : if (hasRuntimePrefix(prefix) && !isRuntimeFeature(prefix)) {
504 0 : IS_ENVOY_BUG(absl::StrCat(
505 0 : "Using a removed guard ", prefix,
506 0 : ". In future version of Envoy this will be treated as invalid configuration"));
507 0 : }
508 126 : SnapshotImpl::addEntry(values_, prefix, v, "");
509 126 : break;
510 0 : case ProtobufWkt::Value::kStructValue: {
511 0 : const ProtobufWkt::Struct& s = v.struct_value();
512 0 : if (s.fields().empty() || s.fields().find("numerator") != s.fields().end() ||
513 0 : s.fields().find("denominator") != s.fields().end()) {
514 0 : SnapshotImpl::addEntry(values_, prefix, v, "");
515 0 : break;
516 0 : }
517 0 : for (const auto& f : s.fields()) {
518 0 : walkProtoValue(f.second, prefix + "." + f.first);
519 0 : }
520 0 : break;
521 0 : }
522 126 : }
523 126 : }
524 :
525 : LoaderImpl::LoaderImpl(Event::Dispatcher& dispatcher, ThreadLocal::SlotAllocator& tls,
526 : const envoy::config::bootstrap::v3::LayeredRuntime& config,
527 : const LocalInfo::LocalInfo& local_info, Stats::Store& store,
528 : Random::RandomGenerator& generator,
529 : ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api)
530 : : generator_(generator), stats_(generateStats(store)), tls_(tls.allocateSlot()),
531 : config_(config), service_cluster_(local_info.clusterName()), api_(api),
532 1145 : init_watcher_("RTDS", [this]() { onRtdsReady(); }), store_(store) {
533 1145 : absl::node_hash_set<std::string> layer_names;
534 1243 : for (const auto& layer : config_.layers()) {
535 1211 : auto ret = layer_names.insert(layer.name());
536 1211 : if (!ret.second) {
537 0 : throwEnvoyExceptionOrPanic(absl::StrCat("Duplicate layer name: ", layer.name()));
538 0 : }
539 1211 : switch (layer.layer_specifier_case()) {
540 98 : case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kStaticLayer:
541 : // Nothing needs to be done here.
542 98 : break;
543 1112 : case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kAdminLayer:
544 1112 : if (admin_layer_ != nullptr) {
545 0 : throwEnvoyExceptionOrPanic(
546 0 : "Too many admin layers specified in LayeredRuntime, at most one may be specified");
547 0 : }
548 1112 : admin_layer_ = std::make_unique<AdminLayer>(layer.name(), stats_);
549 1112 : break;
550 0 : case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kDiskLayer:
551 0 : if (watcher_ == nullptr) {
552 0 : watcher_ = dispatcher.createFilesystemWatcher();
553 0 : }
554 0 : watcher_->addWatch(layer.disk_layer().symlink_root(), Filesystem::Watcher::Events::MovedTo,
555 0 : [this](uint32_t) -> void { loadNewSnapshot(); });
556 0 : break;
557 1 : case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kRtdsLayer:
558 1 : subscriptions_.emplace_back(
559 1 : std::make_unique<RtdsSubscription>(*this, layer.rtds_layer(), store, validation_visitor));
560 1 : init_manager_.add(subscriptions_.back()->init_target_);
561 1 : break;
562 0 : case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::LAYER_SPECIFIER_NOT_SET:
563 0 : throwEnvoyExceptionOrPanic("layer specifier not set");
564 1211 : }
565 1211 : }
566 :
567 1145 : loadNewSnapshot();
568 1145 : }
569 :
570 111 : void LoaderImpl::initialize(Upstream::ClusterManager& cm) {
571 111 : cm_ = &cm;
572 :
573 111 : for (const auto& s : subscriptions_) {
574 1 : s->createSubscription();
575 1 : }
576 111 : }
577 :
578 111 : void LoaderImpl::startRtdsSubscriptions(ReadyCallback on_done) {
579 111 : on_rtds_initialized_ = on_done;
580 111 : init_manager_.initialize(init_watcher_);
581 111 : }
582 :
583 110 : void LoaderImpl::onRtdsReady() {
584 110 : ENVOY_LOG(info, "RTDS has finished initialization");
585 110 : on_rtds_initialized_();
586 110 : }
587 :
588 : RtdsSubscription::RtdsSubscription(
589 : LoaderImpl& parent, const envoy::config::bootstrap::v3::RuntimeLayer::RtdsLayer& rtds_layer,
590 : Stats::Store& store, ProtobufMessage::ValidationVisitor& validation_visitor)
591 : : Envoy::Config::SubscriptionBase<envoy::service::runtime::v3::Runtime>(validation_visitor,
592 : "name"),
593 : parent_(parent), config_source_(rtds_layer.rtds_config()), store_(store),
594 : stats_scope_(store_.createScope("runtime")), resource_name_(rtds_layer.name()),
595 1 : init_target_("RTDS " + resource_name_, [this]() { start(); }) {}
596 :
597 1 : void RtdsSubscription::createSubscription() {
598 1 : const auto resource_name = getResourceName();
599 1 : subscription_ = parent_.cm_->subscriptionFactory().subscriptionFromConfigSource(
600 1 : config_source_, Grpc::Common::typeUrl(resource_name), *stats_scope_, *this, resource_decoder_,
601 1 : {});
602 1 : }
603 :
604 : absl::Status
605 : RtdsSubscription::onConfigUpdate(const std::vector<Config::DecodedResourceRef>& resources,
606 0 : const std::string&) {
607 0 : absl::Status valid = validateUpdateSize(resources.size(), 0);
608 0 : if (!valid.ok()) {
609 0 : return valid;
610 0 : }
611 0 : const auto& runtime =
612 0 : dynamic_cast<const envoy::service::runtime::v3::Runtime&>(resources[0].get().resource());
613 0 : if (runtime.name() != resource_name_) {
614 0 : return absl::InvalidArgumentError(
615 0 : fmt::format("Unexpected RTDS runtime (expecting {}): {}", resource_name_, runtime.name()));
616 0 : }
617 0 : ENVOY_LOG(debug, "Reloading RTDS snapshot for onConfigUpdate");
618 0 : proto_.CopyFrom(runtime.layer());
619 0 : parent_.loadNewSnapshot();
620 0 : init_target_.ready();
621 0 : return absl::OkStatus();
622 0 : }
623 :
624 : absl::Status
625 : RtdsSubscription::onConfigUpdate(const std::vector<Config::DecodedResourceRef>& added_resources,
626 : const Protobuf::RepeatedPtrField<std::string>& removed_resources,
627 0 : const std::string&) {
628 0 : absl::Status valid = validateUpdateSize(added_resources.size(), removed_resources.size());
629 0 : if (!valid.ok()) {
630 0 : return valid;
631 0 : }
632 :
633 : // This is a singleton subscription, so we can only have the subscribed resource added or removed,
634 : // but not both.
635 0 : if (!added_resources.empty()) {
636 0 : return onConfigUpdate(added_resources, added_resources[0].get().version());
637 0 : } else {
638 0 : return onConfigRemoved(removed_resources);
639 0 : }
640 0 : }
641 :
642 : void RtdsSubscription::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason,
643 0 : const EnvoyException*) {
644 0 : ASSERT(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure != reason);
645 : // We need to allow server startup to continue, even if we have a bad
646 : // config.
647 0 : init_target_.ready();
648 0 : }
649 :
650 1 : void RtdsSubscription::start() { subscription_->start({resource_name_}); }
651 :
652 : absl::Status RtdsSubscription::validateUpdateSize(uint32_t added_resources_num,
653 0 : uint32_t removed_resources_num) {
654 0 : if (added_resources_num + removed_resources_num != 1) {
655 0 : init_target_.ready();
656 0 : return absl::InvalidArgumentError(
657 0 : fmt::format("Unexpected RTDS resource length, number of added recources "
658 0 : "{}, number of removed recources {}",
659 0 : added_resources_num, removed_resources_num));
660 0 : }
661 0 : return absl::OkStatus();
662 0 : }
663 :
664 : absl::Status RtdsSubscription::onConfigRemoved(
665 0 : const Protobuf::RepeatedPtrField<std::string>& removed_resources) {
666 0 : if (removed_resources[0] != resource_name_) {
667 0 : return absl::InvalidArgumentError(
668 0 : fmt::format("Unexpected removal of unknown RTDS runtime layer {}, expected {}",
669 0 : removed_resources[0], resource_name_));
670 0 : }
671 0 : ENVOY_LOG(debug, "Clear RTDS snapshot for onConfigUpdate");
672 0 : proto_.Clear();
673 0 : parent_.loadNewSnapshot();
674 0 : init_target_.ready();
675 0 : return absl::OkStatus();
676 0 : }
677 :
678 1701 : void LoaderImpl::loadNewSnapshot() {
679 1701 : std::shared_ptr<SnapshotImpl> ptr = createNewSnapshot();
680 1795 : tls_->set([ptr](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr {
681 1795 : return std::static_pointer_cast<ThreadLocal::ThreadLocalObject>(ptr);
682 1795 : });
683 :
684 1701 : refreshReloadableFlags(ptr->values());
685 :
686 1701 : {
687 1701 : absl::MutexLock lock(&snapshot_mutex_);
688 1701 : thread_safe_snapshot_ = ptr;
689 1701 : }
690 1701 : }
691 :
692 12711 : const Snapshot& LoaderImpl::snapshot() {
693 12711 : ASSERT(tls_->currentThreadRegistered(),
694 12711 : "snapshot can only be called from a worker thread or after the main thread is registered");
695 12711 : return tls_->getTyped<Snapshot>();
696 12711 : }
697 :
698 882 : SnapshotConstSharedPtr LoaderImpl::threadsafeSnapshot() {
699 882 : if (tls_->currentThreadRegistered()) {
700 882 : return std::dynamic_pointer_cast<const Snapshot>(tls_->get());
701 882 : }
702 :
703 0 : {
704 0 : absl::ReaderMutexLock lock(&snapshot_mutex_);
705 0 : return thread_safe_snapshot_;
706 882 : }
707 882 : }
708 :
709 556 : void LoaderImpl::mergeValues(const absl::node_hash_map<std::string, std::string>& values) {
710 556 : if (admin_layer_ == nullptr) {
711 0 : throwEnvoyExceptionOrPanic("No admin layer specified");
712 0 : }
713 556 : admin_layer_->mergeValues(values);
714 556 : loadNewSnapshot();
715 556 : }
716 :
717 0 : Stats::Scope& LoaderImpl::getRootScope() { return *store_.rootScope(); }
718 :
719 0 : void LoaderImpl::countDeprecatedFeatureUse() const { countDeprecatedFeatureUseInternal(stats_); }
720 :
721 1145 : RuntimeStats LoaderImpl::generateStats(Stats::Store& store) {
722 1145 : std::string prefix = "runtime.";
723 1145 : RuntimeStats stats{
724 1145 : ALL_RUNTIME_STATS(POOL_COUNTER_PREFIX(store, prefix), POOL_GAUGE_PREFIX(store, prefix))};
725 1145 : return stats;
726 1145 : }
727 :
728 1701 : SnapshotImplPtr LoaderImpl::createNewSnapshot() {
729 1701 : std::vector<Snapshot::OverrideLayerConstPtr> layers;
730 1701 : uint32_t disk_layers = 0;
731 1701 : uint32_t error_layers = 0;
732 1701 : uint32_t rtds_layer = 0;
733 1799 : for (const auto& layer : config_.layers()) {
734 1767 : switch (layer.layer_specifier_case()) {
735 98 : case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kStaticLayer:
736 98 : layers.emplace_back(std::make_unique<const ProtoLayer>(layer.name(), layer.static_layer()));
737 98 : break;
738 0 : case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kDiskLayer: {
739 0 : std::string path =
740 0 : layer.disk_layer().symlink_root() + "/" + layer.disk_layer().subdirectory();
741 0 : if (layer.disk_layer().append_service_cluster()) {
742 0 : absl::StrAppend(&path, "/", service_cluster_);
743 0 : }
744 0 : if (api_.fileSystem().directoryExists(path)) {
745 0 : TRY_ASSERT_MAIN_THREAD {
746 0 : layers.emplace_back(std::make_unique<DiskLayer>(layer.name(), path, api_));
747 0 : ++disk_layers;
748 0 : }
749 0 : END_TRY
750 0 : CATCH(EnvoyException & e, {
751 : // TODO(htuch): Consider latching here, rather than ignoring the
752 : // layer. This would be consistent with filesystem RTDS.
753 0 : ++error_layers;
754 0 : ENVOY_LOG(debug, "error loading runtime values for layer {} from disk: {}",
755 0 : layer.DebugString(), e.what());
756 0 : });
757 0 : }
758 0 : break;
759 0 : }
760 1668 : case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kAdminLayer:
761 1668 : layers.push_back(std::make_unique<AdminLayer>(*admin_layer_));
762 1668 : break;
763 1 : case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kRtdsLayer: {
764 1 : auto* subscription = subscriptions_[rtds_layer++].get();
765 1 : layers.emplace_back(std::make_unique<const ProtoLayer>(layer.name(), subscription->proto_));
766 1 : break;
767 0 : }
768 0 : case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::LAYER_SPECIFIER_NOT_SET:
769 0 : PANIC_DUE_TO_PROTO_UNSET;
770 1767 : }
771 1767 : }
772 1701 : stats_.num_layers_.set(layers.size());
773 1701 : if (error_layers == 0) {
774 1701 : stats_.load_success_.inc();
775 1701 : } else {
776 0 : stats_.load_error_.inc();
777 0 : }
778 1701 : if (disk_layers > 1) {
779 0 : stats_.override_dir_exists_.inc();
780 1701 : } else {
781 1701 : stats_.override_dir_not_exists_.inc();
782 1701 : }
783 1701 : return std::make_unique<SnapshotImpl>(generator_, stats_, std::move(layers));
784 1701 : }
785 :
786 : } // namespace Runtime
787 : } // namespace Envoy
|