LCOV - code coverage report
Current view: top level - source/common/runtime - runtime_impl.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 222 566 39.2 %
Date: 2024-01-05 06:35:25 Functions: 30 52 57.7 %

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

Generated by: LCOV version 1.15