Coverage Report

Created: 2023-11-12 09:30

/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