LCOV - code coverage report
Current view: top level - source/common/config - utility.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 95 219 43.4 %
Date: 2024-01-05 06:35:25 Functions: 12 19 63.2 %

          Line data    Source code
       1             : #include "source/common/config/utility.h"
       2             : 
       3             : #include "envoy/config/bootstrap/v3/bootstrap.pb.h"
       4             : #include "envoy/config/cluster/v3/cluster.pb.h"
       5             : #include "envoy/config/core/v3/address.pb.h"
       6             : #include "envoy/config/core/v3/config_source.pb.h"
       7             : #include "envoy/config/core/v3/grpc_service.pb.h"
       8             : #include "envoy/config/endpoint/v3/endpoint.pb.h"
       9             : #include "envoy/config/endpoint/v3/endpoint_components.pb.h"
      10             : #include "envoy/stats/scope.h"
      11             : 
      12             : #include "source/common/common/assert.h"
      13             : #include "source/common/protobuf/utility.h"
      14             : #include "source/common/stats/histogram_impl.h"
      15             : #include "source/common/stats/stats_matcher_impl.h"
      16             : #include "source/common/stats/tag_producer_impl.h"
      17             : 
      18             : namespace Envoy {
      19             : namespace Config {
      20             : 
      21           0 : std::string Utility::truncateGrpcStatusMessage(absl::string_view error_message) {
      22             :   // GRPC sends error message via trailers, which by default has a 8KB size limit(see
      23             :   // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests). Truncates the
      24             :   // error message if it's too long.
      25           0 :   constexpr uint32_t kProtobufErrMsgLen = 4096;
      26           0 :   return fmt::format("{}{}", error_message.substr(0, kProtobufErrMsgLen),
      27           0 :                      error_message.length() > kProtobufErrMsgLen ? "...(truncated)" : "");
      28           0 : }
      29             : 
      30             : Upstream::ClusterConstOptRef Utility::checkCluster(absl::string_view error_prefix,
      31             :                                                    absl::string_view cluster_name,
      32             :                                                    Upstream::ClusterManager& cm,
      33           0 :                                                    bool allow_added_via_api) {
      34           0 :   const auto cluster = cm.clusters().getCluster(cluster_name);
      35           0 :   if (!cluster.has_value()) {
      36           0 :     throwEnvoyExceptionOrPanic(fmt::format("{}: unknown cluster '{}'", error_prefix, cluster_name));
      37           0 :   }
      38             : 
      39           0 :   if (!allow_added_via_api && cluster->get().info()->addedViaApi()) {
      40           0 :     throwEnvoyExceptionOrPanic(fmt::format(
      41           0 :         "{}: invalid cluster '{}': currently only static (non-CDS) clusters are supported",
      42           0 :         error_prefix, cluster_name));
      43           0 :   }
      44           0 :   return cluster;
      45           0 : }
      46             : 
      47             : Upstream::ClusterConstOptRef
      48             : Utility::checkClusterAndLocalInfo(absl::string_view error_prefix, absl::string_view cluster_name,
      49             :                                   Upstream::ClusterManager& cm,
      50           0 :                                   const LocalInfo::LocalInfo& local_info) {
      51           0 :   checkLocalInfo(error_prefix, local_info);
      52           0 :   return checkCluster(error_prefix, cluster_name, cm);
      53           0 : }
      54             : 
      55             : void Utility::checkLocalInfo(absl::string_view error_prefix,
      56         230 :                              const LocalInfo::LocalInfo& local_info) {
      57         230 :   if (local_info.clusterName().empty() || local_info.nodeName().empty()) {
      58           0 :     throwEnvoyExceptionOrPanic(
      59           0 :         fmt::format("{}: node 'id' and 'cluster' are required. Set it either in 'node' config or "
      60           0 :                     "via --service-node and --service-cluster options.",
      61           0 :                     error_prefix, local_info.node().DebugString()));
      62           0 :   }
      63         230 : }
      64             : 
      65          70 : void Utility::checkFilesystemSubscriptionBackingPath(const std::string& path, Api::Api& api) {
      66             :   // TODO(junr03): the file might be deleted between this check and the
      67             :   // watch addition.
      68          70 :   if (!api.fileSystem().fileExists(path)) {
      69           0 :     throwEnvoyExceptionOrPanic(fmt::format(
      70           0 :         "paths must refer to an existing path in the system: '{}' does not exist", path));
      71           0 :   }
      72          70 : }
      73             : 
      74             : namespace {
      75             : /**
      76             :  * Check the grpc_services and cluster_names for API config sanity. Throws on error.
      77             :  * @param api_config_source the config source to validate.
      78             :  * @throws EnvoyException when an API config has the wrong number of gRPC
      79             :  * services or cluster names, depending on expectations set by its API type.
      80             :  */
      81          34 : void checkApiConfigSourceNames(const envoy::config::core::v3::ApiConfigSource& api_config_source) {
      82          34 :   const bool is_grpc =
      83          34 :       (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC ||
      84          34 :        api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC);
      85             : 
      86          34 :   if (api_config_source.cluster_names().empty() && api_config_source.grpc_services().empty()) {
      87           0 :     throwEnvoyExceptionOrPanic(
      88           0 :         fmt::format("API configs must have either a gRPC service or a cluster name defined: {}",
      89           0 :                     api_config_source.DebugString()));
      90           0 :   }
      91             : 
      92          34 :   if (is_grpc) {
      93          31 :     if (!api_config_source.cluster_names().empty()) {
      94           0 :       throwEnvoyExceptionOrPanic(
      95           0 :           fmt::format("{}::(DELTA_)GRPC must not have a cluster name specified: {}",
      96           0 :                       api_config_source.GetTypeName(), api_config_source.DebugString()));
      97           0 :     }
      98          31 :     if (api_config_source.grpc_services().size() > 1) {
      99           0 :       throwEnvoyExceptionOrPanic(
     100           0 :           fmt::format("{}::(DELTA_)GRPC must have a single gRPC service specified: {}",
     101           0 :                       api_config_source.GetTypeName(), api_config_source.DebugString()));
     102           0 :     }
     103          31 :   } else {
     104           3 :     if (!api_config_source.grpc_services().empty()) {
     105           2 :       throwEnvoyExceptionOrPanic(
     106           2 :           fmt::format("{}, if not a gRPC type, must not have a gRPC service specified: {}",
     107           2 :                       api_config_source.GetTypeName(), api_config_source.DebugString()));
     108           2 :     }
     109           1 :     if (api_config_source.cluster_names().size() != 1) {
     110           0 :       throwEnvoyExceptionOrPanic(fmt::format("{} must have a singleton cluster name specified: {}",
     111           0 :                                              api_config_source.GetTypeName(),
     112           0 :                                              api_config_source.DebugString()));
     113           0 :     }
     114           1 :   }
     115          34 : }
     116             : } // namespace
     117             : 
     118             : void Utility::validateClusterName(const Upstream::ClusterManager::ClusterSet& primary_clusters,
     119             :                                   const std::string& cluster_name,
     120           0 :                                   const std::string& config_source) {
     121           0 :   const auto& it = primary_clusters.find(cluster_name);
     122           0 :   if (it == primary_clusters.end()) {
     123           0 :     throwEnvoyExceptionOrPanic(
     124           0 :         fmt::format("{} must have a statically defined non-EDS cluster: '{}' does "
     125           0 :                     "not exist, was added via api, or is an EDS cluster",
     126           0 :                     config_source, cluster_name));
     127           0 :   }
     128           0 : }
     129             : 
     130             : void Utility::checkApiConfigSourceSubscriptionBackingCluster(
     131             :     const Upstream::ClusterManager::ClusterSet& primary_clusters,
     132           0 :     const envoy::config::core::v3::ApiConfigSource& api_config_source) {
     133             :   // We don't need to check backing sources for ADS sources, the backing cluster must be verified in
     134             :   // the ads_config.
     135           0 :   if (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC ||
     136           0 :       api_config_source.api_type() ==
     137           0 :           envoy::config::core::v3::ApiConfigSource::AGGREGATED_DELTA_GRPC) {
     138           0 :     return;
     139           0 :   }
     140           0 :   checkApiConfigSourceNames(api_config_source);
     141             : 
     142           0 :   const bool is_grpc =
     143           0 :       (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC);
     144             : 
     145           0 :   if (!api_config_source.cluster_names().empty()) {
     146             :     // All API configs of type REST and UNSUPPORTED_REST_LEGACY should have cluster names.
     147             :     // Additionally, some gRPC API configs might have a cluster name set instead
     148             :     // of an envoy gRPC.
     149           0 :     Utility::validateClusterName(primary_clusters, api_config_source.cluster_names()[0],
     150           0 :                                  api_config_source.GetTypeName());
     151           0 :   } else if (is_grpc) {
     152             :     // Some ApiConfigSources of type GRPC won't have a cluster name, such as if
     153             :     // they've been configured with google_grpc.
     154           0 :     if (api_config_source.grpc_services()[0].has_envoy_grpc()) {
     155             :       // If an Envoy gRPC exists, we take its cluster name.
     156           0 :       Utility::validateClusterName(primary_clusters,
     157           0 :                                    api_config_source.grpc_services()[0].envoy_grpc().cluster_name(),
     158           0 :                                    api_config_source.GetTypeName());
     159           0 :     }
     160           0 :   }
     161             :   // Otherwise, there is no cluster name to validate.
     162           0 : }
     163             : 
     164             : absl::optional<std::string>
     165          11 : Utility::getGrpcControlPlane(const envoy::config::core::v3::ApiConfigSource& api_config_source) {
     166          11 :   if (api_config_source.grpc_services_size() > 0) {
     167             :     // Only checking for the first entry in grpc_services, because Envoy's xDS implementation
     168             :     // currently only considers the first gRPC endpoint and ignores any other xDS management servers
     169             :     // specified in an ApiConfigSource.
     170          11 :     if (api_config_source.grpc_services(0).has_envoy_grpc()) {
     171          10 :       return api_config_source.grpc_services(0).envoy_grpc().cluster_name();
     172          10 :     }
     173           1 :     if (api_config_source.grpc_services(0).has_google_grpc()) {
     174           1 :       return api_config_source.grpc_services(0).google_grpc().target_uri();
     175           1 :     }
     176           1 :   }
     177           0 :   return absl::nullopt;
     178          11 : }
     179             : 
     180             : std::chrono::milliseconds Utility::apiConfigSourceRefreshDelay(
     181           0 :     const envoy::config::core::v3::ApiConfigSource& api_config_source) {
     182           0 :   if (!api_config_source.has_refresh_delay()) {
     183           0 :     throwEnvoyExceptionOrPanic("refresh_delay is required for REST API configuration sources");
     184           0 :   }
     185             : 
     186           0 :   return std::chrono::milliseconds(
     187           0 :       DurationUtil::durationToMilliseconds(api_config_source.refresh_delay()));
     188           0 : }
     189             : 
     190             : std::chrono::milliseconds Utility::apiConfigSourceRequestTimeout(
     191           0 :     const envoy::config::core::v3::ApiConfigSource& api_config_source) {
     192           0 :   return std::chrono::milliseconds(
     193           0 :       PROTOBUF_GET_MS_OR_DEFAULT(api_config_source, request_timeout, 1000));
     194           0 : }
     195             : 
     196             : std::chrono::milliseconds Utility::configSourceInitialFetchTimeout(
     197         135 :     const envoy::config::core::v3::ConfigSource& config_source) {
     198         135 :   return std::chrono::milliseconds(
     199         135 :       PROTOBUF_GET_MS_OR_DEFAULT(config_source, initial_fetch_timeout, 15000));
     200         135 : }
     201             : 
     202             : RateLimitSettings
     203          29 : Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& api_config_source) {
     204          29 :   RateLimitSettings rate_limit_settings;
     205          29 :   if (api_config_source.has_rate_limit_settings()) {
     206           1 :     rate_limit_settings.enabled_ = true;
     207           1 :     rate_limit_settings.max_tokens_ =
     208           1 :         PROTOBUF_GET_WRAPPED_OR_DEFAULT(api_config_source.rate_limit_settings(), max_tokens,
     209           1 :                                         Envoy::Config::RateLimitSettings::DefaultMaxTokens);
     210           1 :     rate_limit_settings.fill_rate_ =
     211           1 :         PROTOBUF_GET_WRAPPED_OR_DEFAULT(api_config_source.rate_limit_settings(), fill_rate,
     212           1 :                                         Envoy::Config::RateLimitSettings::DefaultFillRate);
     213           1 :   }
     214          29 :   return rate_limit_settings;
     215          29 : }
     216             : 
     217             : Stats::TagProducerPtr
     218             : Utility::createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap,
     219         248 :                            const Stats::TagVector& cli_tags) {
     220         248 :   return std::make_unique<Stats::TagProducerImpl>(bootstrap.stats_config(), cli_tags);
     221         248 : }
     222             : 
     223             : Stats::StatsMatcherPtr
     224             : Utility::createStatsMatcher(const envoy::config::bootstrap::v3::Bootstrap& bootstrap,
     225         134 :                             Stats::SymbolTable& symbol_table) {
     226         134 :   return std::make_unique<Stats::StatsMatcherImpl>(bootstrap.stats_config(), symbol_table);
     227         134 : }
     228             : 
     229             : Stats::HistogramSettingsConstPtr
     230         134 : Utility::createHistogramSettings(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
     231         134 :   return std::make_unique<Stats::HistogramSettingsImpl>(bootstrap.stats_config());
     232         134 : }
     233             : 
     234             : Grpc::AsyncClientFactoryPtr Utility::factoryForGrpcApiConfigSource(
     235             :     Grpc::AsyncClientManager& async_client_manager,
     236             :     const envoy::config::core::v3::ApiConfigSource& api_config_source, Stats::Scope& scope,
     237          34 :     bool skip_cluster_check) {
     238          34 :   checkApiConfigSourceNames(api_config_source);
     239             : 
     240          34 :   if (api_config_source.api_type() != envoy::config::core::v3::ApiConfigSource::GRPC &&
     241          34 :       api_config_source.api_type() != envoy::config::core::v3::ApiConfigSource::DELTA_GRPC) {
     242           1 :     throwEnvoyExceptionOrPanic(fmt::format("{} type must be gRPC: {}",
     243           1 :                                            api_config_source.GetTypeName(),
     244           1 :                                            api_config_source.DebugString()));
     245           1 :   }
     246             : 
     247          33 :   envoy::config::core::v3::GrpcService grpc_service;
     248          33 :   grpc_service.MergeFrom(api_config_source.grpc_services(0));
     249             : 
     250          33 :   return async_client_manager.factoryForGrpcService(grpc_service, scope, skip_cluster_check);
     251          34 : }
     252             : 
     253             : void Utility::translateOpaqueConfig(const ProtobufWkt::Any& typed_config,
     254             :                                     ProtobufMessage::ValidationVisitor& validation_visitor,
     255        1945 :                                     Protobuf::Message& out_proto) {
     256        1945 :   static const std::string struct_type = ProtobufWkt::Struct::default_instance().GetTypeName();
     257        1945 :   static const std::string typed_struct_type =
     258        1945 :       xds::type::v3::TypedStruct::default_instance().GetTypeName();
     259        1945 :   static const std::string legacy_typed_struct_type =
     260        1945 :       udpa::type::v1::TypedStruct::default_instance().GetTypeName();
     261        1945 :   if (!typed_config.value().empty()) {
     262             :     // Unpack methods will only use the fully qualified type name after the last '/'.
     263             :     // https://github.com/protocolbuffers/protobuf/blob/3.6.x/src/google/protobuf/any.proto#L87
     264         918 :     absl::string_view type = TypeUtil::typeUrlToDescriptorFullName(typed_config.type_url());
     265             : 
     266         918 :     if (type == typed_struct_type) {
     267           0 :       xds::type::v3::TypedStruct typed_struct;
     268           0 :       MessageUtil::unpackTo(typed_config, typed_struct);
     269             :       // if out_proto is expecting Struct, return directly
     270           0 :       if (out_proto.GetTypeName() == struct_type) {
     271           0 :         out_proto.CheckTypeAndMergeFrom(typed_struct.value());
     272           0 :       } else {
     273             :         // The typed struct might match out_proto, or some earlier version, let
     274             :         // MessageUtil::jsonConvert sort this out.
     275           0 : #ifdef ENVOY_ENABLE_YAML
     276           0 :         MessageUtil::jsonConvert(typed_struct.value(), validation_visitor, out_proto);
     277             : #else
     278             :         IS_ENVOY_BUG("Attempting to use JSON typed structs with JSON compiled out");
     279             : #endif
     280           0 :       }
     281         918 :     } else if (type == legacy_typed_struct_type) {
     282           0 :       udpa::type::v1::TypedStruct typed_struct;
     283           0 :       MessageUtil::unpackTo(typed_config, typed_struct);
     284             :       // if out_proto is expecting Struct, return directly
     285           0 :       if (out_proto.GetTypeName() == struct_type) {
     286           0 :         out_proto.CheckTypeAndMergeFrom(typed_struct.value());
     287           0 :       } else {
     288             :         // The typed struct might match out_proto, or some earlier version, let
     289             :         // MessageUtil::jsonConvert sort this out.
     290           0 : #ifdef ENVOY_ENABLE_YAML
     291           0 :         MessageUtil::jsonConvert(typed_struct.value(), validation_visitor, out_proto);
     292             : #else
     293             :         UNREFERENCED_PARAMETER(validation_visitor);
     294             :         IS_ENVOY_BUG("Attempting to use legacy JSON structs with JSON compiled out");
     295             : #endif
     296           0 :       }
     297           0 :     } // out_proto is expecting Struct, unpack directly
     298         918 :     else if (type != struct_type || out_proto.GetTypeName() == struct_type) {
     299         918 :       MessageUtil::unpackTo(typed_config, out_proto);
     300         918 :     } else {
     301           0 : #ifdef ENVOY_ENABLE_YAML
     302           0 :       ProtobufWkt::Struct struct_config;
     303           0 :       MessageUtil::unpackTo(typed_config, struct_config);
     304           0 :       MessageUtil::jsonConvert(struct_config, validation_visitor, out_proto);
     305             : #else
     306             :       IS_ENVOY_BUG("Attempting to use JSON structs with JSON compiled out");
     307             : #endif
     308           0 :     }
     309         918 :   }
     310        1945 : }
     311             : 
     312             : JitteredExponentialBackOffStrategyPtr Utility::buildJitteredExponentialBackOffStrategy(
     313             :     absl::optional<const envoy::config::core::v3::BackoffStrategy> backoff,
     314             :     Random::RandomGenerator& random, const uint32_t default_base_interval_ms,
     315          31 :     absl::optional<const uint32_t> default_max_interval_ms) {
     316             :   // BackoffStrategy config is specified
     317          31 :   if (backoff != absl::nullopt) {
     318           0 :     uint32_t base_interval_ms = PROTOBUF_GET_MS_REQUIRED(backoff.value(), base_interval);
     319           0 :     uint32_t max_interval_ms =
     320           0 :         PROTOBUF_GET_MS_OR_DEFAULT(backoff.value(), max_interval, base_interval_ms * 10);
     321             : 
     322           0 :     if (max_interval_ms < base_interval_ms) {
     323           0 :       throwEnvoyExceptionOrPanic("max_interval must be greater than or equal to the base_interval");
     324           0 :     }
     325           0 :     return std::make_unique<JitteredExponentialBackOffStrategy>(base_interval_ms, max_interval_ms,
     326           0 :                                                                 random);
     327           0 :   }
     328             : 
     329             :   // default_base_interval_ms must be greater than zero
     330          31 :   if (default_base_interval_ms == 0) {
     331           0 :     throwEnvoyExceptionOrPanic("default_base_interval_ms must be greater than zero");
     332           0 :   }
     333             : 
     334             :   // default maximum interval is specified
     335          31 :   if (default_max_interval_ms != absl::nullopt) {
     336          31 :     if (default_max_interval_ms.value() < default_base_interval_ms) {
     337           0 :       throwEnvoyExceptionOrPanic(
     338           0 :           "default_max_interval_ms must be greater than or equal to the default_base_interval_ms");
     339           0 :     }
     340          31 :     return std::make_unique<JitteredExponentialBackOffStrategy>(
     341          31 :         default_base_interval_ms, default_max_interval_ms.value(), random);
     342          31 :   }
     343             :   // use default base interval
     344           0 :   return std::make_unique<JitteredExponentialBackOffStrategy>(
     345           0 :       default_base_interval_ms, default_base_interval_ms * 10, random);
     346          31 : }
     347             : 
     348             : } // namespace Config
     349             : } // namespace Envoy

Generated by: LCOV version 1.15