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