Coverage Report

Created: 2023-11-12 09:30

/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