/proc/self/cwd/source/common/config/utility.cc
Line | Count | Source (jump to first uncovered line) |
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 | 5.07k | const LocalInfo::LocalInfo& local_info) { |
57 | 5.07k | if (local_info.clusterName().empty() || local_info.nodeName().empty()) { |
58 | 49 | throwEnvoyExceptionOrPanic( |
59 | 49 | fmt::format("{}: node 'id' and 'cluster' are required. Set it either in 'node' config or " |
60 | 49 | "via --service-node and --service-cluster options.", |
61 | 49 | error_prefix, local_info.node().DebugString())); |
62 | 49 | } |
63 | 5.07k | } |
64 | | |
65 | 2.71k | 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 | 2.71k | if (!api.fileSystem().fileExists(path)) { |
69 | 17 | throwEnvoyExceptionOrPanic(fmt::format( |
70 | 17 | "paths must refer to an existing path in the system: '{}' does not exist", path)); |
71 | 17 | } |
72 | 2.71k | } |
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 | 2.62k | void checkApiConfigSourceNames(const envoy::config::core::v3::ApiConfigSource& api_config_source) { |
82 | 2.62k | const bool is_grpc = |
83 | 2.62k | (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC || |
84 | 2.62k | api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC); |
85 | | |
86 | 2.62k | if (api_config_source.cluster_names().empty() && api_config_source.grpc_services().empty()) { |
87 | 21 | throwEnvoyExceptionOrPanic( |
88 | 21 | fmt::format("API configs must have either a gRPC service or a cluster name defined: {}", |
89 | 21 | api_config_source.DebugString())); |
90 | 21 | } |
91 | | |
92 | 2.59k | if (is_grpc) { |
93 | 2.58k | if (!api_config_source.cluster_names().empty()) { |
94 | 6 | throwEnvoyExceptionOrPanic( |
95 | 6 | fmt::format("{}::(DELTA_)GRPC must not have a cluster name specified: {}", |
96 | 6 | api_config_source.GetTypeName(), api_config_source.DebugString())); |
97 | 6 | } |
98 | 2.57k | if (api_config_source.grpc_services().size() > 1) { |
99 | 10 | throwEnvoyExceptionOrPanic( |
100 | 10 | fmt::format("{}::(DELTA_)GRPC must have a single gRPC service specified: {}", |
101 | 10 | api_config_source.GetTypeName(), api_config_source.DebugString())); |
102 | 10 | } |
103 | 2.57k | } else { |
104 | 17 | if (!api_config_source.grpc_services().empty()) { |
105 | 11 | throwEnvoyExceptionOrPanic( |
106 | 11 | fmt::format("{}, if not a gRPC type, must not have a gRPC service specified: {}", |
107 | 11 | api_config_source.GetTypeName(), api_config_source.DebugString())); |
108 | 11 | } |
109 | 6 | 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 | 6 | } |
115 | 2.59k | } |
116 | | } // namespace |
117 | | |
118 | | void Utility::validateClusterName(const Upstream::ClusterManager::ClusterSet& primary_clusters, |
119 | | const std::string& cluster_name, |
120 | 3 | const std::string& config_source) { |
121 | 3 | const auto& it = primary_clusters.find(cluster_name); |
122 | 3 | if (it == primary_clusters.end()) { |
123 | 3 | throwEnvoyExceptionOrPanic( |
124 | 3 | fmt::format("{} must have a statically defined non-EDS cluster: '{}' does " |
125 | 3 | "not exist, was added via api, or is an EDS cluster", |
126 | 3 | config_source, cluster_name)); |
127 | 3 | } |
128 | 3 | } |
129 | | |
130 | | void Utility::checkApiConfigSourceSubscriptionBackingCluster( |
131 | | const Upstream::ClusterManager::ClusterSet& primary_clusters, |
132 | 1.09k | 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 | 1.09k | if (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC || |
136 | 1.09k | api_config_source.api_type() == |
137 | 1.09k | envoy::config::core::v3::ApiConfigSource::AGGREGATED_DELTA_GRPC) { |
138 | 0 | return; |
139 | 0 | } |
140 | 1.09k | checkApiConfigSourceNames(api_config_source); |
141 | | |
142 | 1.09k | const bool is_grpc = |
143 | 1.09k | (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC); |
144 | | |
145 | 1.09k | 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 | 1 | Utility::validateClusterName(primary_clusters, api_config_source.cluster_names()[0], |
150 | 1 | api_config_source.GetTypeName()); |
151 | 1.08k | } 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 | 638 | if (api_config_source.grpc_services()[0].has_envoy_grpc()) { |
155 | | // If an Envoy gRPC exists, we take its cluster name. |
156 | 2 | Utility::validateClusterName(primary_clusters, |
157 | 2 | api_config_source.grpc_services()[0].envoy_grpc().cluster_name(), |
158 | 2 | api_config_source.GetTypeName()); |
159 | 2 | } |
160 | 638 | } |
161 | | // Otherwise, there is no cluster name to validate. |
162 | 1.09k | } |
163 | | |
164 | | absl::optional<std::string> |
165 | 711 | Utility::getGrpcControlPlane(const envoy::config::core::v3::ApiConfigSource& api_config_source) { |
166 | 711 | 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 | 711 | if (api_config_source.grpc_services(0).has_envoy_grpc()) { |
171 | 5 | return api_config_source.grpc_services(0).envoy_grpc().cluster_name(); |
172 | 5 | } |
173 | 706 | if (api_config_source.grpc_services(0).has_google_grpc()) { |
174 | 706 | return api_config_source.grpc_services(0).google_grpc().target_uri(); |
175 | 706 | } |
176 | 706 | } |
177 | 0 | return absl::nullopt; |
178 | 711 | } |
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 | 1.42k | const envoy::config::core::v3::ConfigSource& config_source) { |
198 | 1.42k | return std::chrono::milliseconds( |
199 | 1.42k | PROTOBUF_GET_MS_OR_DEFAULT(config_source, initial_fetch_timeout, 15000)); |
200 | 1.42k | } |
201 | | |
202 | | RateLimitSettings |
203 | 1.16k | Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& api_config_source) { |
204 | 1.16k | RateLimitSettings rate_limit_settings; |
205 | 1.16k | if (api_config_source.has_rate_limit_settings()) { |
206 | 182 | rate_limit_settings.enabled_ = true; |
207 | 182 | rate_limit_settings.max_tokens_ = |
208 | 182 | PROTOBUF_GET_WRAPPED_OR_DEFAULT(api_config_source.rate_limit_settings(), max_tokens, |
209 | 182 | Envoy::Config::RateLimitSettings::DefaultMaxTokens); |
210 | 182 | rate_limit_settings.fill_rate_ = |
211 | 182 | PROTOBUF_GET_WRAPPED_OR_DEFAULT(api_config_source.rate_limit_settings(), fill_rate, |
212 | 182 | Envoy::Config::RateLimitSettings::DefaultFillRate); |
213 | 182 | } |
214 | 1.16k | return rate_limit_settings; |
215 | 1.16k | } |
216 | | |
217 | | Stats::TagProducerPtr |
218 | | Utility::createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap, |
219 | 11.2k | const Stats::TagVector& cli_tags) { |
220 | 11.2k | return std::make_unique<Stats::TagProducerImpl>(bootstrap.stats_config(), cli_tags); |
221 | 11.2k | } |
222 | | |
223 | | Stats::StatsMatcherPtr |
224 | | Utility::createStatsMatcher(const envoy::config::bootstrap::v3::Bootstrap& bootstrap, |
225 | 5.46k | Stats::SymbolTable& symbol_table) { |
226 | 5.46k | return std::make_unique<Stats::StatsMatcherImpl>(bootstrap.stats_config(), symbol_table); |
227 | 5.46k | } |
228 | | |
229 | | Stats::HistogramSettingsConstPtr |
230 | 5.40k | Utility::createHistogramSettings(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) { |
231 | 5.40k | return std::make_unique<Stats::HistogramSettingsImpl>(bootstrap.stats_config()); |
232 | 5.40k | } |
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 | 1.53k | bool skip_cluster_check) { |
238 | 1.53k | checkApiConfigSourceNames(api_config_source); |
239 | | |
240 | 1.53k | if (api_config_source.api_type() != envoy::config::core::v3::ApiConfigSource::GRPC && |
241 | 1.53k | api_config_source.api_type() != envoy::config::core::v3::ApiConfigSource::DELTA_GRPC) { |
242 | 5 | throwEnvoyExceptionOrPanic(fmt::format("{} type must be gRPC: {}", |
243 | 5 | api_config_source.GetTypeName(), |
244 | 5 | api_config_source.DebugString())); |
245 | 5 | } |
246 | | |
247 | 1.52k | envoy::config::core::v3::GrpcService grpc_service; |
248 | 1.52k | grpc_service.MergeFrom(api_config_source.grpc_services(0)); |
249 | | |
250 | 1.52k | return async_client_manager.factoryForGrpcService(grpc_service, scope, skip_cluster_check); |
251 | 1.53k | } |
252 | | |
253 | | void Utility::translateOpaqueConfig(const ProtobufWkt::Any& typed_config, |
254 | | ProtobufMessage::ValidationVisitor& validation_visitor, |
255 | 163k | Protobuf::Message& out_proto) { |
256 | 163k | static const std::string struct_type = ProtobufWkt::Struct::default_instance().GetTypeName(); |
257 | 163k | static const std::string typed_struct_type = |
258 | 163k | xds::type::v3::TypedStruct::default_instance().GetTypeName(); |
259 | 163k | static const std::string legacy_typed_struct_type = |
260 | 163k | udpa::type::v1::TypedStruct::default_instance().GetTypeName(); |
261 | 163k | 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 | 119k | absl::string_view type = TypeUtil::typeUrlToDescriptorFullName(typed_config.type_url()); |
265 | | |
266 | 119k | if (type == typed_struct_type) { |
267 | 250 | xds::type::v3::TypedStruct typed_struct; |
268 | 250 | MessageUtil::unpackTo(typed_config, typed_struct); |
269 | | // if out_proto is expecting Struct, return directly |
270 | 250 | if (out_proto.GetTypeName() == struct_type) { |
271 | 0 | out_proto.CheckTypeAndMergeFrom(typed_struct.value()); |
272 | 250 | } else { |
273 | | // The typed struct might match out_proto, or some earlier version, let |
274 | | // MessageUtil::jsonConvert sort this out. |
275 | 250 | #ifdef ENVOY_ENABLE_YAML |
276 | 250 | 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 | 250 | } |
281 | 118k | } else if (type == legacy_typed_struct_type) { |
282 | 153 | udpa::type::v1::TypedStruct typed_struct; |
283 | 153 | MessageUtil::unpackTo(typed_config, typed_struct); |
284 | | // if out_proto is expecting Struct, return directly |
285 | 153 | if (out_proto.GetTypeName() == struct_type) { |
286 | 0 | out_proto.CheckTypeAndMergeFrom(typed_struct.value()); |
287 | 153 | } else { |
288 | | // The typed struct might match out_proto, or some earlier version, let |
289 | | // MessageUtil::jsonConvert sort this out. |
290 | 153 | #ifdef ENVOY_ENABLE_YAML |
291 | 153 | 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 | 153 | } |
297 | 153 | } // out_proto is expecting Struct, unpack directly |
298 | 118k | else if (type != struct_type || out_proto.GetTypeName() == struct_type) { |
299 | 118k | MessageUtil::unpackTo(typed_config, out_proto); |
300 | 118k | } 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 | 119k | } |
310 | 163k | } |
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 | 1.18k | absl::optional<const uint32_t> default_max_interval_ms) { |
316 | | // BackoffStrategy config is specified |
317 | 1.18k | if (backoff != absl::nullopt) { |
318 | 30 | uint32_t base_interval_ms = PROTOBUF_GET_MS_REQUIRED(backoff.value(), base_interval); |
319 | 30 | uint32_t max_interval_ms = |
320 | 30 | PROTOBUF_GET_MS_OR_DEFAULT(backoff.value(), max_interval, base_interval_ms * 10); |
321 | | |
322 | 30 | if (max_interval_ms < base_interval_ms) { |
323 | 1 | throwEnvoyExceptionOrPanic("max_interval must be greater than or equal to the base_interval"); |
324 | 1 | } |
325 | 29 | return std::make_unique<JitteredExponentialBackOffStrategy>(base_interval_ms, max_interval_ms, |
326 | 29 | random); |
327 | 30 | } |
328 | | |
329 | | // default_base_interval_ms must be greater than zero |
330 | 1.15k | 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 | 1.15k | if (default_max_interval_ms != absl::nullopt) { |
336 | 1.15k | 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 | 1.15k | return std::make_unique<JitteredExponentialBackOffStrategy>( |
341 | 1.15k | default_base_interval_ms, default_max_interval_ms.value(), random); |
342 | 1.15k | } |
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 | 1.15k | } |
347 | | |
348 | | } // namespace Config |
349 | | } // namespace Envoy |