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
|