Coverage Report

Created: 2024-09-19 09:45

/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