Coverage Report

Created: 2024-09-19 09:45

/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