/proc/self/cwd/source/common/runtime/runtime_impl.cc
Line | Count | Source (jump to first uncovered line) |
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 | 110k | void refreshReloadableFlags(const Snapshot::EntryMap& flag_map) { |
47 | 110k | absl::flat_hash_map<std::string, bool> quiche_flags_override; |
48 | 110k | for (const auto& it : flag_map) { |
49 | 71.8k | #ifdef ENVOY_ENABLE_QUIC |
50 | 71.8k | if (absl::StartsWith(it.first, quiche::EnvoyQuicheReloadableFlagPrefix) && |
51 | 71.8k | 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 | 71.8k | #endif |
56 | 71.8k | if (it.second.bool_value_.has_value() && isRuntimeFeature(it.first)) { |
57 | 49.1k | maybeSetRuntimeGuard(it.first, it.second.bool_value_.value()); |
58 | 49.1k | } |
59 | 71.8k | } |
60 | 110k | #ifdef ENVOY_ENABLE_QUIC |
61 | 110k | quiche::FlagRegistry::getInstance().updateReloadableFlags(quiche_flags_override); |
62 | 110k | #endif |
63 | | // Make sure ints are parsed after the flag allowing deprecated ints is parsed. |
64 | 110k | for (const auto& it : flag_map) { |
65 | 71.8k | if (it.second.uint_value_.has_value()) { |
66 | 6.27k | maybeSetDeprecatedInts(it.first, it.second.uint_value_.value()); |
67 | 6.27k | } |
68 | 71.8k | } |
69 | 110k | markRuntimeInitialized(); |
70 | 110k | } |
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 | 6.37k | bool SnapshotImpl::featureEnabled(absl::string_view key, uint64_t default_value) const { |
109 | | // Avoid PRNG if we know we don't need it. |
110 | 6.37k | uint64_t cutoff = std::min(getInteger(key, default_value), static_cast<uint64_t>(100)); |
111 | 6.37k | if (cutoff == 0) { |
112 | 5.10k | return false; |
113 | 5.10k | } else if (cutoff == 100) { |
114 | 1.27k | return true; |
115 | 1.27k | } else { |
116 | 0 | return generator_.random() % 100 < cutoff; |
117 | 0 | } |
118 | 6.37k | } |
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 | 8.06k | Snapshot::ConstStringOptRef SnapshotImpl::get(absl::string_view key) const { |
126 | 8.06k | ASSERT(!isRuntimeFeature(key)); // Make sure runtime guarding is only used for getBoolean |
127 | 8.06k | auto entry = key.empty() ? values_.end() : values_.find(key); |
128 | 8.06k | if (entry == values_.end()) { |
129 | 8.06k | return absl::nullopt; |
130 | 8.06k | } else { |
131 | 0 | return entry->second.raw_string_value_; |
132 | 0 | } |
133 | 8.06k | } |
134 | | |
135 | | bool SnapshotImpl::featureEnabled(absl::string_view key, |
136 | 2.71k | const envoy::type::v3::FractionalPercent& default_value) const { |
137 | 2.71k | return featureEnabled(key, default_value, generator_.random()); |
138 | 2.71k | } |
139 | | |
140 | | bool SnapshotImpl::featureEnabled(absl::string_view key, |
141 | | const envoy::type::v3::FractionalPercent& default_value, |
142 | 2.71k | uint64_t random_value) const { |
143 | 2.71k | const auto& entry = key.empty() ? values_.end() : values_.find(key); |
144 | 2.71k | envoy::type::v3::FractionalPercent percent; |
145 | 2.71k | if (entry != values_.end() && entry->second.fractional_percent_value_.has_value()) { |
146 | 0 | percent = entry->second.fractional_percent_value_.value(); |
147 | 2.71k | } 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 | 2.71k | } else { |
161 | 2.71k | percent = default_value; |
162 | 2.71k | } |
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 | 2.71k | uint64_t denominator_value = |
168 | 2.71k | ProtobufPercentHelper::fractionalPercentDenominatorToInt(percent.denominator()); |
169 | 2.71k | 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 | 2.71k | return ProtobufPercentHelper::evaluateFractionalPercent(percent, random_value); |
177 | 2.71k | } |
178 | | |
179 | 114k | uint64_t SnapshotImpl::getInteger(absl::string_view key, uint64_t default_value) const { |
180 | 114k | ASSERT(!isRuntimeFeature(key)); |
181 | 114k | const auto& entry = key.empty() ? values_.end() : values_.find(key); |
182 | 114k | if (entry == values_.end() || !entry->second.uint_value_) { |
183 | 114k | return default_value; |
184 | 114k | } else { |
185 | 0 | return entry->second.uint_value_.value(); |
186 | 0 | } |
187 | 114k | } |
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 | 5.27k | bool SnapshotImpl::getBoolean(absl::string_view key, bool default_value) const { |
200 | 5.27k | const auto& entry = key.empty() ? values_.end() : values_.find(key); |
201 | 5.27k | if (entry == values_.end() || !entry->second.bool_value_.has_value()) { |
202 | 5.27k | return default_value; |
203 | 5.27k | } else { |
204 | 0 | return entry->second.bool_value_.value(); |
205 | 0 | } |
206 | 5.27k | } |
207 | | |
208 | 0 | const std::vector<Snapshot::OverrideLayerConstPtr>& SnapshotImpl::getLayers() const { |
209 | 0 | return layers_; |
210 | 0 | } |
211 | | |
212 | 110k | const Snapshot::EntryMap& SnapshotImpl::values() const { return values_; } |
213 | | |
214 | | SnapshotImpl::SnapshotImpl(Random::RandomGenerator& generator, RuntimeStats& stats, |
215 | | std::vector<OverrideLayerConstPtr>&& layers) |
216 | 110k | : layers_{std::move(layers)}, generator_{generator}, stats_{stats} { |
217 | 113k | for (const auto& layer : layers_) { |
218 | 113k | for (const auto& kv : layer->values()) { |
219 | 72.4k | values_.erase(kv.first); |
220 | 72.4k | values_.emplace(kv.first, kv.second); |
221 | 72.4k | } |
222 | 113k | } |
223 | 110k | stats.num_keys_.set(values_.size()); |
224 | 110k | } |
225 | | |
226 | 1.63k | void parseFractionValue(SnapshotImpl::Entry& entry, const ProtobufWkt::Struct& value) { |
227 | 1.63k | envoy::type::v3::FractionalPercent percent; |
228 | 1.63k | static_assert(envoy::type::v3::FractionalPercent::MILLION == |
229 | 1.63k | envoy::type::v3::FractionalPercent::DenominatorType_MAX); |
230 | 1.63k | percent.set_denominator(envoy::type::v3::FractionalPercent::HUNDRED); |
231 | 1.63k | 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 | 1.63k | entry.fractional_percent_value_ = percent; |
252 | 1.63k | } |
253 | | |
254 | 6.89k | void setNumberValue(Envoy::Runtime::Snapshot::Entry& entry, double value) { |
255 | 6.89k | entry.double_value_ = value; |
256 | 6.89k | if (value < std::numeric_limits<int>::max() && value == static_cast<int>(value)) { |
257 | 5.69k | entry.bool_value_ = value != 0; |
258 | 5.69k | } |
259 | 6.89k | 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 | 6.59k | entry.uint_value_ = entry.double_value_; |
264 | 6.59k | } |
265 | 6.89k | } |
266 | | |
267 | | // Handle corner cases in parsing: negatives and decimals aren't always parsed as doubles. |
268 | 8.08k | bool parseEntryDoubleValue(Envoy::Runtime::Snapshot::Entry& entry) { |
269 | 8.08k | double converted_double; |
270 | 8.08k | if (absl::SimpleAtod(entry.raw_string_value_, &converted_double)) { |
271 | 522 | setNumberValue(entry, converted_double); |
272 | 522 | return true; |
273 | 522 | } |
274 | 7.56k | return false; |
275 | 8.08k | } |
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 | 8.08k | bool parseEntryFractionalPercentValue(Envoy::Runtime::Snapshot::Entry& entry) { |
282 | 8.08k | if (!absl::StrContains(entry.raw_string_value_, "numerator")) { |
283 | 8.08k | return false; |
284 | 8.08k | } |
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 | 8.08k | bool parseEntryBooleanValue(Envoy::Runtime::Snapshot::Entry& entry) { |
316 | 8.08k | absl::string_view stripped = entry.raw_string_value_; |
317 | 8.08k | stripped = absl::StripAsciiWhitespace(stripped); |
318 | | |
319 | 8.08k | if (absl::EqualsIgnoreCase(stripped, "true")) { |
320 | 0 | entry.bool_value_ = true; |
321 | 0 | return true; |
322 | 8.08k | } else if (absl::EqualsIgnoreCase(stripped, "false")) { |
323 | 0 | entry.bool_value_ = false; |
324 | 0 | return true; |
325 | 0 | } |
326 | 8.08k | return false; |
327 | 8.08k | } |
328 | | |
329 | | void SnapshotImpl::addEntry(Snapshot::EntryMap& values, const std::string& key, |
330 | 72.8k | const ProtobufWkt::Value& value, absl::string_view raw_string) { |
331 | 72.8k | const char* error_message = nullptr; |
332 | 72.8k | values.emplace(key, SnapshotImpl::createEntry(value, raw_string, error_message)); |
333 | 72.8k | if (error_message != nullptr) { |
334 | 0 | IS_ENVOY_BUG( |
335 | 0 | absl::StrCat(error_message, "\n[ key:", key, ", value: ", value.DebugString(), "]")); |
336 | 0 | } |
337 | 72.8k | } |
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 | 72.8k | const char*& error_message) { |
353 | 72.8k | Entry entry; |
354 | 72.8k | entry.raw_string_value_ = value.string_value(); |
355 | 72.8k | if (!raw_string.empty()) { |
356 | 50.7k | entry.raw_string_value_ = raw_string; |
357 | 50.7k | } |
358 | 72.8k | switch (value.kind_case()) { |
359 | 6.37k | case ProtobufWkt::Value::kNumberValue: |
360 | 6.37k | setNumberValue(entry, value.number_value()); |
361 | 6.37k | if (entry.raw_string_value_.empty()) { |
362 | 6.37k | entry.raw_string_value_ = absl::StrCat(value.number_value()); |
363 | 6.37k | } |
364 | 6.37k | break; |
365 | 56.8k | case ProtobufWkt::Value::kBoolValue: |
366 | 56.8k | entry.bool_value_ = value.bool_value(); |
367 | 56.8k | if (entry.raw_string_value_.empty()) { |
368 | 6.07k | entry.raw_string_value_ = absl::StrCat(value.bool_value()); |
369 | 6.07k | } |
370 | 56.8k | break; |
371 | 1.63k | case ProtobufWkt::Value::kStructValue: |
372 | 1.63k | if (entry.raw_string_value_.empty()) { |
373 | 1.63k | entry.raw_string_value_ = value.struct_value().DebugString(); |
374 | 1.63k | } |
375 | 1.63k | parseFractionValue(entry, value.struct_value()); |
376 | 1.63k | break; |
377 | 8.08k | case ProtobufWkt::Value::kStringValue: |
378 | 8.08k | parseEntryDoubleValue(entry); |
379 | 8.08k | if (parseEntryBooleanValue(entry)) { |
380 | 0 | error_message = kBoolError; |
381 | 0 | } |
382 | 8.08k | if (parseEntryFractionalPercentValue(entry)) { |
383 | 0 | error_message = kFractionError; |
384 | 0 | } |
385 | 8.08k | default: |
386 | 8.08k | break; |
387 | 72.8k | } |
388 | | |
389 | 72.8k | return entry; |
390 | 72.8k | } |
391 | | |
392 | 48.6k | void AdminLayer::mergeValues(const absl::node_hash_map<std::string, std::string>& values) { |
393 | 48.6k | #ifdef ENVOY_ENABLE_YAML |
394 | 50.7k | for (const auto& kv : values) { |
395 | 50.7k | values_.erase(kv.first); |
396 | 50.7k | if (!kv.second.empty()) { |
397 | 50.7k | SnapshotImpl::addEntry(values_, kv.first, ValueUtil::loadFromYaml(kv.second), kv.second); |
398 | 50.7k | } |
399 | 50.7k | } |
400 | 48.6k | 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 | 48.6k | } |
407 | | |
408 | | DiskLayer::DiskLayer(absl::string_view name, const std::string& path, Api::Api& api) |
409 | 253 | : OverrideLayerImpl{name} { |
410 | 253 | walkDirectory(path, "", 1, api); |
411 | 253 | } |
412 | | |
413 | | void DiskLayer::walkDirectory(const std::string& path, const std::string& prefix, uint32_t depth, |
414 | 4.19k | Api::Api& api) { |
415 | | // Maximum recursion depth for walkDirectory(). |
416 | 4.19k | static constexpr uint32_t MaxWalkDepth = 16; |
417 | | |
418 | 4.19k | ENVOY_LOG(debug, "walking directory: {}", path); |
419 | 4.19k | if (depth > MaxWalkDepth) { |
420 | 246 | throwEnvoyExceptionOrPanic(absl::StrCat("Walk recursion depth exceeded ", MaxWalkDepth)); |
421 | 246 | } |
422 | | // Check if this is an obviously bad path. |
423 | 3.94k | if (api.fileSystem().illegalPath(path)) { |
424 | 0 | throwEnvoyExceptionOrPanic(absl::StrCat("Invalid path: ", path)); |
425 | 0 | } |
426 | | |
427 | 3.94k | Filesystem::Directory directory(path); |
428 | 4.35k | for (const Filesystem::DirectoryEntry& entry : directory) { |
429 | 4.35k | std::string full_path = path + "/" + entry.name_; |
430 | 4.35k | std::string full_prefix; |
431 | 4.35k | if (prefix.empty()) { |
432 | 296 | full_prefix = entry.name_; |
433 | 4.05k | } else { |
434 | 4.05k | full_prefix = prefix + "." + entry.name_; |
435 | 4.05k | } |
436 | | |
437 | 4.35k | if (entry.type_ == Filesystem::FileType::Directory && entry.name_ != "." && |
438 | 4.35k | entry.name_ != "..") { |
439 | 3.93k | walkDirectory(full_path, full_prefix, depth + 1, api); |
440 | 3.93k | } else if (entry.type_ == Filesystem::FileType::Regular) { |
441 | | // Suck the file into a string. This is not very efficient but it should be good enough |
442 | | // for small files. Also, as noted elsewhere, none of this is non-blocking which could |
443 | | // theoretically lead to issues. |
444 | 8 | ENVOY_LOG(debug, "reading file: {}", full_path); |
445 | 8 | std::string value; |
446 | | |
447 | | // Read the file and remove any comments. A comment is a line starting with a '#' character. |
448 | | // Comments are useful for placeholder files with no value. |
449 | 8 | auto file_or_error = api.fileSystem().fileReadToEnd(full_path); |
450 | 8 | THROW_IF_STATUS_NOT_OK(file_or_error, throw); |
451 | 8 | const std::string text_file{file_or_error.value()}; |
452 | | |
453 | 8 | const auto lines = StringUtil::splitToken(text_file, "\n"); |
454 | 19.5k | for (const auto& line : lines) { |
455 | 19.5k | if (!line.empty() && line.front() == '#') { |
456 | 359 | continue; |
457 | 359 | } |
458 | 19.1k | if (line == lines.back()) { |
459 | 604 | const absl::string_view trimmed = StringUtil::rtrim(line); |
460 | 604 | value.append(trimmed.data(), trimmed.size()); |
461 | 18.5k | } else { |
462 | 18.5k | value.append(std::string{line} + "\n"); |
463 | 18.5k | } |
464 | 19.1k | } |
465 | | // Separate erase/insert calls required due to the value type being constant; this prevents |
466 | | // the use of the [] operator. Can leverage insert_or_assign in C++17 in the future. |
467 | 8 | values_.erase(full_prefix); |
468 | 8 | #ifdef ENVOY_ENABLE_YAML |
469 | 8 | SnapshotImpl::addEntry(values_, full_prefix, ValueUtil::loadFromYaml(value), value); |
470 | | #else |
471 | | IS_ENVOY_BUG("Runtime admin reload requires YAML support"); |
472 | | UNREFERENCED_PARAMETER(value); |
473 | | return; |
474 | | #endif |
475 | 8 | } |
476 | 4.35k | } |
477 | 3.94k | } |
478 | | |
479 | | ProtoLayer::ProtoLayer(absl::string_view name, const ProtobufWkt::Struct& proto) |
480 | 5.28k | : OverrideLayerImpl{name} { |
481 | 5.28k | for (const auto& f : proto.fields()) { |
482 | 4.87k | walkProtoValue(f.second, f.first); |
483 | 4.87k | } |
484 | 5.28k | } |
485 | | |
486 | 27.6k | void ProtoLayer::walkProtoValue(const ProtobufWkt::Value& v, const std::string& prefix) { |
487 | 27.6k | switch (v.kind_case()) { |
488 | 27 | case ProtobufWkt::Value::KIND_NOT_SET: |
489 | 28 | case ProtobufWkt::Value::kListValue: |
490 | 28 | case ProtobufWkt::Value::kNullValue: |
491 | 28 | throwEnvoyExceptionOrPanic(absl::StrCat("Invalid runtime entry value for ", prefix)); |
492 | 0 | break; |
493 | 8.08k | case ProtobufWkt::Value::kStringValue: |
494 | 8.08k | SnapshotImpl::addEntry(values_, prefix, v, ""); |
495 | 8.08k | break; |
496 | 6.37k | case ProtobufWkt::Value::kNumberValue: |
497 | 12.4k | case ProtobufWkt::Value::kBoolValue: |
498 | 12.4k | if (hasRuntimePrefix(prefix) && !isRuntimeFeature(prefix)) { |
499 | 0 | IS_ENVOY_BUG(absl::StrCat( |
500 | 0 | "Using a removed guard ", prefix, |
501 | 0 | ". In future version of Envoy this will be treated as invalid configuration")); |
502 | 0 | } |
503 | 12.4k | SnapshotImpl::addEntry(values_, prefix, v, ""); |
504 | 12.4k | break; |
505 | 7.12k | case ProtobufWkt::Value::kStructValue: { |
506 | 7.12k | const ProtobufWkt::Struct& s = v.struct_value(); |
507 | 7.12k | if (s.fields().empty() || s.fields().find("numerator") != s.fields().end() || |
508 | 7.12k | s.fields().find("denominator") != s.fields().end()) { |
509 | 1.63k | SnapshotImpl::addEntry(values_, prefix, v, ""); |
510 | 1.63k | break; |
511 | 1.63k | } |
512 | 22.8k | for (const auto& f : s.fields()) { |
513 | 22.8k | walkProtoValue(f.second, prefix + "." + f.first); |
514 | 22.8k | } |
515 | 5.48k | break; |
516 | 7.12k | } |
517 | 27.6k | } |
518 | 27.6k | } |
519 | | |
520 | | LoaderImpl::LoaderImpl(Event::Dispatcher& dispatcher, ThreadLocal::SlotAllocator& tls, |
521 | | const envoy::config::bootstrap::v3::LayeredRuntime& config, |
522 | | const LocalInfo::LocalInfo& local_info, Stats::Store& store, |
523 | | Random::RandomGenerator& generator, |
524 | | ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api) |
525 | | : generator_(generator), stats_(generateStats(store)), tls_(tls.allocateSlot()), |
526 | | config_(config), service_cluster_(local_info.clusterName()), api_(api), |
527 | 62.3k | init_watcher_("RTDS", [this]() { onRtdsReady(); }), store_(store) { |
528 | 62.3k | absl::node_hash_set<std::string> layer_names; |
529 | 65.8k | for (const auto& layer : config_.layers()) { |
530 | 65.8k | auto ret = layer_names.insert(layer.name()); |
531 | 65.8k | if (!ret.second) { |
532 | 9 | throwEnvoyExceptionOrPanic(absl::StrCat("Duplicate layer name: ", layer.name())); |
533 | 9 | } |
534 | 65.8k | switch (layer.layer_specifier_case()) { |
535 | 3.70k | case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kStaticLayer: |
536 | | // Nothing needs to be done here. |
537 | 3.70k | break; |
538 | 59.9k | case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kAdminLayer: |
539 | 59.9k | if (admin_layer_ != nullptr) { |
540 | 5 | throwEnvoyExceptionOrPanic( |
541 | 5 | "Too many admin layers specified in LayeredRuntime, at most one may be specified"); |
542 | 5 | } |
543 | 59.9k | admin_layer_ = std::make_unique<AdminLayer>(layer.name(), stats_); |
544 | 59.9k | break; |
545 | 336 | case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kDiskLayer: |
546 | 336 | if (watcher_ == nullptr) { |
547 | 76 | watcher_ = dispatcher.createFilesystemWatcher(); |
548 | 76 | } |
549 | 336 | watcher_->addWatch(layer.disk_layer().symlink_root(), Filesystem::Watcher::Events::MovedTo, |
550 | 336 | [this](uint32_t) -> void { loadNewSnapshot(); }); |
551 | 336 | break; |
552 | 1.87k | case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kRtdsLayer: |
553 | 1.87k | subscriptions_.emplace_back( |
554 | 1.87k | std::make_unique<RtdsSubscription>(*this, layer.rtds_layer(), store, validation_visitor)); |
555 | 1.87k | init_manager_.add(subscriptions_.back()->init_target_); |
556 | 1.87k | break; |
557 | 0 | case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::LAYER_SPECIFIER_NOT_SET: |
558 | 0 | throwEnvoyExceptionOrPanic("layer specifier not set"); |
559 | 65.8k | } |
560 | 65.8k | } |
561 | | |
562 | 62.3k | loadNewSnapshot(); |
563 | 62.3k | } |
564 | | |
565 | 4.06k | void LoaderImpl::initialize(Upstream::ClusterManager& cm) { |
566 | 4.06k | cm_ = &cm; |
567 | | |
568 | 4.06k | for (const auto& s : subscriptions_) { |
569 | 1.16k | s->createSubscription(); |
570 | 1.16k | } |
571 | 4.06k | } |
572 | | |
573 | 3.83k | void LoaderImpl::startRtdsSubscriptions(ReadyCallback on_done) { |
574 | 3.83k | on_rtds_initialized_ = on_done; |
575 | 3.83k | init_manager_.initialize(init_watcher_); |
576 | 3.83k | } |
577 | | |
578 | 3.72k | void LoaderImpl::onRtdsReady() { |
579 | 3.72k | ENVOY_LOG(info, "RTDS has finished initialization"); |
580 | 3.72k | on_rtds_initialized_(); |
581 | 3.72k | } |
582 | | |
583 | | RtdsSubscription::RtdsSubscription( |
584 | | LoaderImpl& parent, const envoy::config::bootstrap::v3::RuntimeLayer::RtdsLayer& rtds_layer, |
585 | | Stats::Store& store, ProtobufMessage::ValidationVisitor& validation_visitor) |
586 | | : Envoy::Config::SubscriptionBase<envoy::service::runtime::v3::Runtime>(validation_visitor, |
587 | | "name"), |
588 | | parent_(parent), config_source_(rtds_layer.rtds_config()), store_(store), |
589 | | stats_scope_(store_.createScope("runtime")), resource_name_(rtds_layer.name()), |
590 | 1.87k | init_target_("RTDS " + resource_name_, [this]() { start(); }) {} |
591 | | |
592 | 1.16k | void RtdsSubscription::createSubscription() { |
593 | 1.16k | const auto resource_name = getResourceName(); |
594 | 1.16k | subscription_ = parent_.cm_->subscriptionFactory().subscriptionFromConfigSource( |
595 | 1.16k | config_source_, Grpc::Common::typeUrl(resource_name), *stats_scope_, *this, resource_decoder_, |
596 | 1.16k | {}); |
597 | 1.16k | } |
598 | | |
599 | | absl::Status |
600 | | RtdsSubscription::onConfigUpdate(const std::vector<Config::DecodedResourceRef>& resources, |
601 | 0 | const std::string&) { |
602 | 0 | absl::Status valid = validateUpdateSize(resources.size(), 0); |
603 | 0 | if (!valid.ok()) { |
604 | 0 | return valid; |
605 | 0 | } |
606 | 0 | const auto& runtime = |
607 | 0 | dynamic_cast<const envoy::service::runtime::v3::Runtime&>(resources[0].get().resource()); |
608 | 0 | if (runtime.name() != resource_name_) { |
609 | 0 | return absl::InvalidArgumentError( |
610 | 0 | fmt::format("Unexpected RTDS runtime (expecting {}): {}", resource_name_, runtime.name())); |
611 | 0 | } |
612 | 0 | ENVOY_LOG(debug, "Reloading RTDS snapshot for onConfigUpdate"); |
613 | 0 | proto_.CopyFrom(runtime.layer()); |
614 | 0 | parent_.loadNewSnapshot(); |
615 | 0 | init_target_.ready(); |
616 | 0 | return absl::OkStatus(); |
617 | 0 | } |
618 | | |
619 | | absl::Status |
620 | | RtdsSubscription::onConfigUpdate(const std::vector<Config::DecodedResourceRef>& added_resources, |
621 | | const Protobuf::RepeatedPtrField<std::string>& removed_resources, |
622 | 0 | const std::string&) { |
623 | 0 | absl::Status valid = validateUpdateSize(added_resources.size(), removed_resources.size()); |
624 | 0 | if (!valid.ok()) { |
625 | 0 | return valid; |
626 | 0 | } |
627 | | |
628 | | // This is a singleton subscription, so we can only have the subscribed resource added or removed, |
629 | | // but not both. |
630 | 0 | if (!added_resources.empty()) { |
631 | 0 | return onConfigUpdate(added_resources, added_resources[0].get().version()); |
632 | 0 | } else { |
633 | 0 | return onConfigRemoved(removed_resources); |
634 | 0 | } |
635 | 0 | } |
636 | | |
637 | | void RtdsSubscription::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, |
638 | 48 | const EnvoyException*) { |
639 | 48 | ASSERT(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure != reason); |
640 | | // We need to allow server startup to continue, even if we have a bad |
641 | | // config. |
642 | 48 | init_target_.ready(); |
643 | 48 | } |
644 | | |
645 | 367 | void RtdsSubscription::start() { subscription_->start({resource_name_}); } |
646 | | |
647 | | absl::Status RtdsSubscription::validateUpdateSize(uint32_t added_resources_num, |
648 | 0 | uint32_t removed_resources_num) { |
649 | 0 | if (added_resources_num + removed_resources_num != 1) { |
650 | 0 | init_target_.ready(); |
651 | 0 | return absl::InvalidArgumentError( |
652 | 0 | fmt::format("Unexpected RTDS resource length, number of added recources " |
653 | 0 | "{}, number of removed recources {}", |
654 | 0 | added_resources_num, removed_resources_num)); |
655 | 0 | } |
656 | 0 | return absl::OkStatus(); |
657 | 0 | } |
658 | | |
659 | | absl::Status RtdsSubscription::onConfigRemoved( |
660 | 0 | const Protobuf::RepeatedPtrField<std::string>& removed_resources) { |
661 | 0 | if (removed_resources[0] != resource_name_) { |
662 | 0 | return absl::InvalidArgumentError( |
663 | 0 | fmt::format("Unexpected removal of unknown RTDS runtime layer {}, expected {}", |
664 | 0 | removed_resources[0], resource_name_)); |
665 | 0 | } |
666 | 0 | ENVOY_LOG(debug, "Clear RTDS snapshot for onConfigUpdate"); |
667 | 0 | proto_.Clear(); |
668 | 0 | parent_.loadNewSnapshot(); |
669 | 0 | init_target_.ready(); |
670 | 0 | return absl::OkStatus(); |
671 | 0 | } |
672 | | |
673 | 110k | void LoaderImpl::loadNewSnapshot() { |
674 | 110k | std::shared_ptr<SnapshotImpl> ptr = createNewSnapshot(); |
675 | 113k | tls_->set([ptr](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { |
676 | 113k | return std::static_pointer_cast<ThreadLocal::ThreadLocalObject>(ptr); |
677 | 113k | }); |
678 | | |
679 | 110k | refreshReloadableFlags(ptr->values()); |
680 | | |
681 | 110k | { |
682 | 110k | absl::MutexLock lock(&snapshot_mutex_); |
683 | 110k | thread_safe_snapshot_ = ptr; |
684 | 110k | } |
685 | 110k | } |
686 | | |
687 | 124k | const Snapshot& LoaderImpl::snapshot() { |
688 | 124k | ASSERT(tls_->currentThreadRegistered(), |
689 | 124k | "snapshot can only be called from a worker thread or after the main thread is registered"); |
690 | 124k | return tls_->getTyped<Snapshot>(); |
691 | 124k | } |
692 | | |
693 | 6.27k | SnapshotConstSharedPtr LoaderImpl::threadsafeSnapshot() { |
694 | 6.27k | if (tls_->currentThreadRegistered()) { |
695 | 6.27k | return std::dynamic_pointer_cast<const Snapshot>(tls_->get()); |
696 | 6.27k | } |
697 | | |
698 | 0 | { |
699 | 0 | absl::ReaderMutexLock lock(&snapshot_mutex_); |
700 | 0 | return thread_safe_snapshot_; |
701 | 6.27k | } |
702 | 6.27k | } |
703 | | |
704 | 48.6k | void LoaderImpl::mergeValues(const absl::node_hash_map<std::string, std::string>& values) { |
705 | 48.6k | if (admin_layer_ == nullptr) { |
706 | 0 | throwEnvoyExceptionOrPanic("No admin layer specified"); |
707 | 0 | } |
708 | 48.6k | admin_layer_->mergeValues(values); |
709 | 48.6k | loadNewSnapshot(); |
710 | 48.6k | } |
711 | | |
712 | 0 | Stats::Scope& LoaderImpl::getRootScope() { return *store_.rootScope(); } |
713 | | |
714 | 0 | void LoaderImpl::countDeprecatedFeatureUse() const { countDeprecatedFeatureUseInternal(stats_); } |
715 | | |
716 | 62.3k | RuntimeStats LoaderImpl::generateStats(Stats::Store& store) { |
717 | 62.3k | std::string prefix = "runtime."; |
718 | 62.3k | RuntimeStats stats{ |
719 | 62.3k | ALL_RUNTIME_STATS(POOL_COUNTER_PREFIX(store, prefix), POOL_GAUGE_PREFIX(store, prefix))}; |
720 | 62.3k | return stats; |
721 | 62.3k | } |
722 | | |
723 | 110k | SnapshotImplPtr LoaderImpl::createNewSnapshot() { |
724 | 110k | std::vector<Snapshot::OverrideLayerConstPtr> layers; |
725 | 110k | uint32_t disk_layers = 0; |
726 | 110k | uint32_t error_layers = 0; |
727 | 110k | uint32_t rtds_layer = 0; |
728 | 114k | for (const auto& layer : config_.layers()) { |
729 | 114k | switch (layer.layer_specifier_case()) { |
730 | 3.61k | case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kStaticLayer: |
731 | 3.61k | layers.emplace_back(std::make_unique<const ProtoLayer>(layer.name(), layer.static_layer())); |
732 | 3.61k | break; |
733 | 268 | case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kDiskLayer: { |
734 | 268 | std::string path = |
735 | 268 | layer.disk_layer().symlink_root() + "/" + layer.disk_layer().subdirectory(); |
736 | 268 | if (layer.disk_layer().append_service_cluster()) { |
737 | 66 | absl::StrAppend(&path, "/", service_cluster_); |
738 | 66 | } |
739 | 268 | if (api_.fileSystem().directoryExists(path)) { |
740 | 253 | TRY_ASSERT_MAIN_THREAD { |
741 | 253 | layers.emplace_back(std::make_unique<DiskLayer>(layer.name(), path, api_)); |
742 | 253 | ++disk_layers; |
743 | 253 | } |
744 | 253 | END_TRY |
745 | 253 | CATCH(EnvoyException & e, { |
746 | | // TODO(htuch): Consider latching here, rather than ignoring the |
747 | | // layer. This would be consistent with filesystem RTDS. |
748 | 253 | ++error_layers; |
749 | 253 | ENVOY_LOG(debug, "error loading runtime values for layer {} from disk: {}", |
750 | 253 | layer.DebugString(), e.what()); |
751 | 253 | }); |
752 | 253 | } |
753 | 268 | break; |
754 | 268 | } |
755 | 108k | case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kAdminLayer: |
756 | 108k | layers.push_back(std::make_unique<AdminLayer>(*admin_layer_)); |
757 | 108k | break; |
758 | 1.67k | case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kRtdsLayer: { |
759 | 1.67k | auto* subscription = subscriptions_[rtds_layer++].get(); |
760 | 1.67k | layers.emplace_back(std::make_unique<const ProtoLayer>(layer.name(), subscription->proto_)); |
761 | 1.67k | break; |
762 | 268 | } |
763 | 0 | case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::LAYER_SPECIFIER_NOT_SET: |
764 | 0 | PANIC_DUE_TO_PROTO_UNSET; |
765 | 114k | } |
766 | 114k | } |
767 | 110k | stats_.num_layers_.set(layers.size()); |
768 | 110k | if (error_layers == 0) { |
769 | 110k | stats_.load_success_.inc(); |
770 | 110k | } else { |
771 | 54 | stats_.load_error_.inc(); |
772 | 54 | } |
773 | 110k | if (disk_layers > 1) { |
774 | 0 | stats_.override_dir_exists_.inc(); |
775 | 110k | } else { |
776 | 110k | stats_.override_dir_not_exists_.inc(); |
777 | 110k | } |
778 | 110k | return std::make_unique<SnapshotImpl>(generator_, stats_, std::move(layers)); |
779 | 110k | } |
780 | | |
781 | | } // namespace Runtime |
782 | | } // namespace Envoy |