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