From b2b3978afb25c5d2c3ca6b344071b285e8820da1 Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Fri, 30 Oct 2020 16:27:49 -0400 Subject: [PATCH 001/117] xds: add support for TTLs (#13201) Adds support for per resource TTL for both Delta and SOTW xDS. This allows the server to direct Envoy to remove the resources in the case of control plane unavailability. Signed-off-by: Snow Pettersen Co-authored-by: Bill Gallagher --- .../service/discovery/v3/discovery.proto | 18 +- .../service/discovery/v4alpha/discovery.proto | 18 +- api/xds_protocol.rst | 36 +++ docs/root/api/client_features.rst | 4 + docs/root/configuration/overview/xds_api.rst | 22 ++ .../operations/dynamic_configuration.rst | 6 + docs/root/version_history/current.rst | 4 + .../service/discovery/v3/discovery.proto | 18 +- .../service/discovery/v4alpha/discovery.proto | 18 +- include/envoy/config/subscription.h | 2 + source/common/config/BUILD | 15 ++ source/common/config/decoded_resource_impl.h | 46 +++- .../common/config/delta_subscription_state.cc | 85 +++++-- .../common/config/delta_subscription_state.h | 32 ++- source/common/config/grpc_mux_impl.cc | 110 ++++++-- source/common/config/grpc_mux_impl.h | 22 +- source/common/config/new_grpc_mux_impl.cc | 5 +- source/common/config/new_grpc_mux_impl.h | 6 +- source/common/config/ttl.cc | 73 ++++++ source/common/config/ttl.h | 69 +++++ source/common/config/watch_map.cc | 2 +- source/common/runtime/runtime_features.cc | 1 + source/common/upstream/eds.cc | 6 +- test/common/config/BUILD | 12 + .../config/config_provider_impl_test.cc | 3 +- .../config/decoded_resource_impl_test.cc | 13 +- .../config/delta_subscription_state_test.cc | 147 ++++++++++- .../config/delta_subscription_test_harness.h | 2 +- test/common/config/grpc_mux_impl_test.cc | 235 +++++++++++++----- .../config/grpc_subscription_impl_test.cc | 4 +- .../config/grpc_subscription_test_harness.h | 19 +- .../config/subscription_factory_impl_test.cc | 2 +- test/common/config/subscription_impl_test.cc | 5 + test/common/config/ttl_test.cc | 84 +++++++ test/common/upstream/eds_speed_test.cc | 2 + .../aggregate/cluster_integration_test.cc | 4 +- test/integration/cds_integration_test.cc | 4 +- tools/spelling/spelling_dictionary.txt | 1 + 38 files changed, 992 insertions(+), 163 deletions(-) create mode 100644 source/common/config/ttl.cc create mode 100644 source/common/config/ttl.h create mode 100644 test/common/config/ttl_test.cc diff --git a/api/envoy/service/discovery/v3/discovery.proto b/api/envoy/service/discovery/v3/discovery.proto index 40479539213c..860ad5ab6d0f 100644 --- a/api/envoy/service/discovery/v3/discovery.proto +++ b/api/envoy/service/discovery/v3/discovery.proto @@ -5,6 +5,7 @@ package envoy.service.discovery.v3; import "envoy/config/core/v3/base.proto"; import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; import "google/rpc/status.proto"; import "udpa/core/v1/resource_locator.proto"; @@ -252,7 +253,7 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 6] +// [#next-free-field: 7] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Resource"; @@ -272,4 +273,19 @@ message Resource { // The resource being tracked. google.protobuf.Any resource = 2; + + // Time-to-live value for the resource. For each resource, a timer is started. The timer is + // reset each time the resource is received with a new TTL. If the resource is received with + // no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + // configuration for the resource will be removed. + // + // The TTL can be refreshed or changed by sending a response that doesn't change the resource + // version. In this case the resource field does not need to be populated, which allows for + // light-weight "heartbeat" updates to keep a resource with a TTL alive. + // + // The TTL feature is meant to support configurations that should be removed in the event of + // a management server // failure. For example, the feature may be used for fault injection + // testing where the fault injection should be terminated in the event that Envoy loses contact + // with the management server. + google.protobuf.Duration ttl = 6; } diff --git a/api/envoy/service/discovery/v4alpha/discovery.proto b/api/envoy/service/discovery/v4alpha/discovery.proto index 3f5dca95cbb8..2abf261bcd16 100644 --- a/api/envoy/service/discovery/v4alpha/discovery.proto +++ b/api/envoy/service/discovery/v4alpha/discovery.proto @@ -5,6 +5,7 @@ package envoy.service.discovery.v4alpha; import "envoy/config/core/v4alpha/base.proto"; import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; import "google/rpc/status.proto"; import "udpa/core/v1/resource_locator.proto"; @@ -254,7 +255,7 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 6] +// [#next-free-field: 7] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.service.discovery.v3.Resource"; @@ -276,4 +277,19 @@ message Resource { // The resource being tracked. google.protobuf.Any resource = 2; + + // Time-to-live value for the resource. For each resource, a timer is started. The timer is + // reset each time the resource is received with a new TTL. If the resource is received with + // no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + // configuration for the resource will be removed. + // + // The TTL can be refreshed or changed by sending a response that doesn't change the resource + // version. In this case the resource field does not need to be populated, which allows for + // light-weight "heartbeat" updates to keep a resource with a TTL alive. + // + // The TTL feature is meant to support configurations that should be removed in the event of + // a management server // failure. For example, the feature may be used for fault injection + // testing where the fault injection should be terminated in the event that Envoy loses contact + // with the management server. + google.protobuf.Duration ttl = 6; } diff --git a/api/xds_protocol.rst b/api/xds_protocol.rst index 6a96001238de..182a71ddd1c8 100644 --- a/api/xds_protocol.rst +++ b/api/xds_protocol.rst @@ -656,6 +656,42 @@ adding/removing/updating clusters. On the other hand, routes are not warmed, i.e., the management plane must ensure that clusters referenced by a route are in place, before pushing the updates for a route. +.. _xds_protocol_TTL: + +TTL +~~~ + +In the event that the management server becomes unreachable, the last known configuration received +by Envoy will persist until the connection is reestablished. For some services, this may not be +desirable. For example, in the case of a fault injection service, a management server crash at the +wrong time may leave Envoy in an undesirable state. The TTL setting allows Envoy to remove a set of +resources after a specified period of time if contact with the management server is lost. This can +be used, for example, to terminate a fault injection test when the management server can no longer +be reached. + +For clients that support the *xds.config.supports-resource-ttl* client feature, A TTL field may +be specified on each :ref:`Resource `. Each resource will have its own TTL +expiry time, at which point the resource will be expired. Each xDS type may have different ways of +handling such an expiry. + +To update the TTL associated with a *Resource*, the management server resends the resource with a +new TTL. To remove the TTL, the management server resends the resource with the TTL field unset. + +To allow for lightweight TTL updates ("heartbeats"), a response can be sent that provides a +:ref:`Resource ` with the :ref:`resource ` +unset and version matching the most recently sent version can be used to update the TTL. These +resources will not be treated as resource updates, but only as TTL updates. + +SotW TTL +^^^^^^^^ + +In order to use TTL with SotW xDS, the relevant resources must be wrapped in a +:ref:`Resource `. This allows setting the same TTL field that is used for +Delta xDS with SotW, without changing the SotW API. Heartbeats are supported for SotW as well: +any resource within the response that look like a heartbeat resource will only be used to update the TTL. + +This feature is gated by the *xds.config.supports-resource-in-sotw* client feature. + .. _xds_protocol_ads: Aggregated Discovery Service diff --git a/docs/root/api/client_features.rst b/docs/root/api/client_features.rst index 4cd6594f0bdc..07f0bd259a53 100644 --- a/docs/root/api/client_features.rst +++ b/docs/root/api/client_features.rst @@ -23,3 +23,7 @@ Currently Defined Client Features - **envoy.lrs.supports_send_all_clusters**: This feature indicates that the client supports the *envoy_api_field_service.load_stats.v2.LoadStatsResponse.send_all_clusters* field in the LRS response. +- **xds.config.supports-resource-ttl**: This feature indicates that xDS client supports + per-resource or per SotW :ref:`TTL `. +- **xds.config.resource-in-sotw**: This feature indicates that the xDS client is able to unwrap + *Resource* wrappers within the SotW DiscoveryResponse. diff --git a/docs/root/configuration/overview/xds_api.rst b/docs/root/configuration/overview/xds_api.rst index 428575afda8f..3217c000b209 100644 --- a/docs/root/configuration/overview/xds_api.rst +++ b/docs/root/configuration/overview/xds_api.rst @@ -348,3 +348,25 @@ To use delta, simply set the api_type field of your That works for both xDS and ADS; for ADS, it's the api_type field of :ref:`DynamicResources.ads_config `, as described in the previous section. + + +.. _config_overview_ttl: + +xDS TTL +^^^^^^^ + +When using xDS, users might find themself wanting to temporarily update certain xDS resources. In order to do +safely, xDS TTLs can be used to make sure that if the control plane becomes unavailable and is unable to revert +the xDS change, Envoy will remove the resource after a TTL specified by the server. See the +:ref:`protocol documentation ` for more information. + +Currently the behavior when a TTL expires is that the resource is *removed* (as opposed to reverted to the +previous version). As such, this feature should primarily be used for use cases where the absence of the resource +is preferred instead of the temporary version, e.g. when using RTDS to apply a temporary runtime override. + +The TTL is specified on the :ref:`Resource ` proto: for Delta xDS this is specified directly +within the response, while for SotW xDS the server may wrap individual resources listed in the response within a +:ref:`Resource ` in order to specify a TTL value. + +The server can refresh or modify the TTL by issuing another response for the same version. In this case the resource +itself does not have to be included. \ No newline at end of file diff --git a/docs/root/intro/arch_overview/operations/dynamic_configuration.rst b/docs/root/intro/arch_overview/operations/dynamic_configuration.rst index e5d026c88f50..4b0fe43f5140 100644 --- a/docs/root/intro/arch_overview/operations/dynamic_configuration.rst +++ b/docs/root/intro/arch_overview/operations/dynamic_configuration.rst @@ -138,3 +138,9 @@ a resource from an update implying that the resource is gone. Envoy supports a " xDS (including ADS), where updates only contain resources added/changed/removed. Delta xDS is a new protocol, with request/response APIs different from SotW. :ref:`More details about delta `. + +xDS TTL +------- + +Certain xDS updates might want to set a TTL to guard against control plane unavailability, read more +:ref:`here `. \ No newline at end of file diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index a33ae97a46b0..0f331b036a46 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -15,6 +15,8 @@ Minor Behavior Changes * ext_authz filter: the deprecated field :ref:`use_alpha ` is no longer supported and cannot be set anymore. * grpc_web filter: if a `grpc-accept-encoding` header is present it's passed as-is to the upstream and if it isn't `grpc-accept-encoding:identity` is sent instead. The header was always overwriten with `grpc-accept-encoding:identity,deflate,gzip` before. * watchdog: the watchdog action :ref:`abort_action ` is now the default action to terminate the process if watchdog kill / multikill is enabled. +* xds: to support TTLs, heartbeating has been added to xDS. As a result, responses that contain empty resources without updating the version will no longer be propagated to the + subscribers. To undo this for VHDS (which is the only subscriber that wants empty resources), the `envoy.reloadable_features.vhds_heartbeats` can be set to "false". Bug Fixes --------- @@ -48,6 +50,8 @@ New Features * ratelimit: added support for use of various :ref:`metadata ` as a ratelimit action. * ratelimit: added :ref:`disable_x_envoy_ratelimited_header ` option to disable `X-Envoy-RateLimited` header. * tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections ` action to reject incoming TCP connections. +* xds: added support for resource TTLs. A TTL is specified on the :ref:`Resource `. For SotW, a :ref:`Resource ` can be embedded + in the list of resources to specify the TTL. Deprecated ---------- diff --git a/generated_api_shadow/envoy/service/discovery/v3/discovery.proto b/generated_api_shadow/envoy/service/discovery/v3/discovery.proto index 40479539213c..860ad5ab6d0f 100644 --- a/generated_api_shadow/envoy/service/discovery/v3/discovery.proto +++ b/generated_api_shadow/envoy/service/discovery/v3/discovery.proto @@ -5,6 +5,7 @@ package envoy.service.discovery.v3; import "envoy/config/core/v3/base.proto"; import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; import "google/rpc/status.proto"; import "udpa/core/v1/resource_locator.proto"; @@ -252,7 +253,7 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 6] +// [#next-free-field: 7] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Resource"; @@ -272,4 +273,19 @@ message Resource { // The resource being tracked. google.protobuf.Any resource = 2; + + // Time-to-live value for the resource. For each resource, a timer is started. The timer is + // reset each time the resource is received with a new TTL. If the resource is received with + // no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + // configuration for the resource will be removed. + // + // The TTL can be refreshed or changed by sending a response that doesn't change the resource + // version. In this case the resource field does not need to be populated, which allows for + // light-weight "heartbeat" updates to keep a resource with a TTL alive. + // + // The TTL feature is meant to support configurations that should be removed in the event of + // a management server // failure. For example, the feature may be used for fault injection + // testing where the fault injection should be terminated in the event that Envoy loses contact + // with the management server. + google.protobuf.Duration ttl = 6; } diff --git a/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto b/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto index 3f5dca95cbb8..2abf261bcd16 100644 --- a/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto +++ b/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto @@ -5,6 +5,7 @@ package envoy.service.discovery.v4alpha; import "envoy/config/core/v4alpha/base.proto"; import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; import "google/rpc/status.proto"; import "udpa/core/v1/resource_locator.proto"; @@ -254,7 +255,7 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 6] +// [#next-free-field: 7] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.service.discovery.v3.Resource"; @@ -276,4 +277,19 @@ message Resource { // The resource being tracked. google.protobuf.Any resource = 2; + + // Time-to-live value for the resource. For each resource, a timer is started. The timer is + // reset each time the resource is received with a new TTL. If the resource is received with + // no TTL set, the timer is removed for the resource. Upon expiration of the timer, the + // configuration for the resource will be removed. + // + // The TTL can be refreshed or changed by sending a response that doesn't change the resource + // version. In this case the resource field does not need to be populated, which allows for + // light-weight "heartbeat" updates to keep a resource with a TTL alive. + // + // The TTL feature is meant to support configurations that should be removed in the event of + // a management server // failure. For example, the feature may be used for fault injection + // testing where the fault injection should be terminated in the event that Envoy loses contact + // with the management server. + google.protobuf.Duration ttl = 6; } diff --git a/include/envoy/config/subscription.h b/include/envoy/config/subscription.h index 0506ed46dcb7..e768482f2ee5 100644 --- a/include/envoy/config/subscription.h +++ b/include/envoy/config/subscription.h @@ -53,6 +53,8 @@ class DecodedResource { */ virtual const Protobuf::Message& resource() const PURE; + virtual absl::optional ttl() const PURE; + /** * @return bool does the xDS discovery response have a set resource payload? */ diff --git a/source/common/config/BUILD b/source/common/config/BUILD index 61d0bf4b4445..cf1ac31142ff 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -68,6 +68,19 @@ envoy_cc_library( "//include/envoy/config:subscription_interface", "//source/common/protobuf:utility_lib", "@com_github_cncf_udpa//udpa/core/v1:pkg_cc_proto", + "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "ttl_lib", + srcs = ["ttl.cc"], + hdrs = ["ttl.h"], + deps = [ + "//include/envoy/config:subscription_interface", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/event:timer_interface", + "@com_google_absl//absl/container:btree", ], ) @@ -78,6 +91,7 @@ envoy_cc_library( deps = [ ":api_version_lib", ":pausable_ack_queue_lib", + ":ttl_lib", ":utility_lib", ":watch_map_lib", "//include/envoy/config:subscription_interface", @@ -137,6 +151,7 @@ envoy_cc_library( ":api_version_lib", ":decoded_resource_lib", ":grpc_stream_lib", + ":ttl_lib", ":utility_lib", "//include/envoy/config:grpc_mux_interface", "//include/envoy/config:subscription_interface", diff --git a/source/common/config/decoded_resource_impl.h b/source/common/config/decoded_resource_impl.h index 559eef874c5b..983cca19a606 100644 --- a/source/common/config/decoded_resource_impl.h +++ b/source/common/config/decoded_resource_impl.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/config/subscription.h" +#include "envoy/service/discovery/v3/discovery.pb.h" #include "common/protobuf/utility.h" @@ -20,25 +21,45 @@ repeatedPtrFieldToVector(const Protobuf::RepeatedPtrField& xs) { } // namespace +class DecodedResourceImpl; +using DecodedResourceImplPtr = std::unique_ptr; + class DecodedResourceImpl : public DecodedResource { public: - DecodedResourceImpl(OpaqueResourceDecoder& resource_decoder, const ProtobufWkt::Any& resource, - const std::string& version) - : DecodedResourceImpl(resource_decoder, {}, Protobuf::RepeatedPtrField(), - resource, true, version) {} + static DecodedResourceImplPtr fromResource(OpaqueResourceDecoder& resource_decoder, + const ProtobufWkt::Any& resource, + const std::string& version) { + if (resource.Is()) { + envoy::service::discovery::v3::Resource r; + MessageUtil::unpackTo(resource, r); + + r.set_version(version); + + return std::make_unique(resource_decoder, r); + } + + return std::unique_ptr(new DecodedResourceImpl( + resource_decoder, absl::nullopt, Protobuf::RepeatedPtrField(), resource, true, + version, absl::nullopt)); + } + DecodedResourceImpl(OpaqueResourceDecoder& resource_decoder, const envoy::service::discovery::v3::Resource& resource) : DecodedResourceImpl(resource_decoder, resource.name(), resource.aliases(), - resource.resource(), resource.has_resource(), resource.version()) {} + resource.resource(), resource.has_resource(), resource.version(), + resource.has_ttl() + ? absl::make_optional(std::chrono::milliseconds( + DurationUtil::durationToMilliseconds(resource.ttl()))) + : absl::nullopt) {} DecodedResourceImpl(OpaqueResourceDecoder& resource_decoder, const udpa::core::v1::CollectionEntry::InlineEntry& inline_entry) : DecodedResourceImpl(resource_decoder, inline_entry.name(), Protobuf::RepeatedPtrField(), inline_entry.resource(), - true, inline_entry.version()) {} + true, inline_entry.version(), absl::nullopt) {} DecodedResourceImpl(ProtobufTypes::MessagePtr resource, const std::string& name, const std::vector& aliases, const std::string& version) : resource_(std::move(resource)), has_resource_(true), name_(name), aliases_(aliases), - version_(version) {} + version_(version), ttl_(absl::nullopt) {} // Config::DecodedResource const std::string& name() const override { return name_; } @@ -46,32 +67,33 @@ class DecodedResourceImpl : public DecodedResource { const std::string& version() const override { return version_; }; const Protobuf::Message& resource() const override { return *resource_; }; bool hasResource() const override { return has_resource_; } + absl::optional ttl() const override { return ttl_; } private: DecodedResourceImpl(OpaqueResourceDecoder& resource_decoder, absl::optional name, const Protobuf::RepeatedPtrField& aliases, const ProtobufWkt::Any& resource, bool has_resource, - const std::string& version) + const std::string& version, absl::optional ttl) : resource_(resource_decoder.decodeResource(resource)), has_resource_(has_resource), name_(name ? *name : resource_decoder.resourceName(*resource_)), - aliases_(repeatedPtrFieldToVector(aliases)), version_(version) {} + aliases_(repeatedPtrFieldToVector(aliases)), version_(version), ttl_(ttl) {} const ProtobufTypes::MessagePtr resource_; const bool has_resource_; const std::string name_; const std::vector aliases_; const std::string version_; + // Per resource TTL. + const absl::optional ttl_; }; -using DecodedResourceImplPtr = std::unique_ptr; - struct DecodedResourcesWrapper { DecodedResourcesWrapper() = default; DecodedResourcesWrapper(OpaqueResourceDecoder& resource_decoder, const Protobuf::RepeatedPtrField& resources, const std::string& version) { for (const auto& resource : resources) { - pushBack(std::make_unique(resource_decoder, resource, version)); + pushBack((DecodedResourceImpl::fromResource(resource_decoder, resource, version))); } } diff --git a/source/common/config/delta_subscription_state.cc b/source/common/config/delta_subscription_state.cc index c0a6a5502cb0..7702081602a1 100644 --- a/source/common/config/delta_subscription_state.cc +++ b/source/common/config/delta_subscription_state.cc @@ -1,18 +1,36 @@ #include "common/config/delta_subscription_state.h" +#include "envoy/event/dispatcher.h" #include "envoy/service/discovery/v3/discovery.pb.h" #include "common/common/assert.h" #include "common/common/hash.h" #include "common/config/utility.h" +#include "common/runtime/runtime_features.h" namespace Envoy { namespace Config { DeltaSubscriptionState::DeltaSubscriptionState(std::string type_url, UntypedConfigUpdateCallbacks& watch_map, - const LocalInfo::LocalInfo& local_info) - : type_url_(std::move(type_url)), watch_map_(watch_map), local_info_(local_info) {} + const LocalInfo::LocalInfo& local_info, + Event::Dispatcher& dispatcher) + // TODO(snowp): Hard coding VHDS here is temporary until we can move it away from relying on + // empty resources as updates. + : supports_heartbeats_(type_url != "envoy.config.route.v3.VirtualHost"), + ttl_( + [this](const auto& expired) { + Protobuf::RepeatedPtrField removed_resources; + for (const auto& resource : expired) { + setResourceWaitingForServer(resource); + removed_resources.Add(std::string(resource)); + } + + watch_map_.onConfigUpdate({}, removed_resources, ""); + }, + dispatcher, dispatcher.timeSource()), + type_url_(std::move(type_url)), watch_map_(watch_map), local_info_(local_info), + dispatcher_(dispatcher) {} void DeltaSubscriptionState::updateSubscriptionInterest(const std::set& cur_added, const std::set& cur_removed) { @@ -25,7 +43,7 @@ void DeltaSubscriptionState::updateSubscriptionInterest(const std::setsecond.waitingForServer() && + resource.version() == itr->second.version(); +} + void DeltaSubscriptionState::handleGoodResponse( const envoy::service::discovery::v3::DeltaDiscoveryResponse& message) { absl::flat_hash_set names_added_removed; + Protobuf::RepeatedPtrField non_heartbeat_resources; for (const auto& resource : message.resources()) { if (!names_added_removed.insert(resource.name()).second) { throw EnvoyException( fmt::format("duplicate name {} found among added/updated resources", resource.name())); } + if (isHeartbeatResponse(resource)) { + continue; + } + non_heartbeat_resources.Add()->CopyFrom(resource); // DeltaDiscoveryResponses for unresolved aliases don't contain an actual resource if (!resource.has_resource() && resource.aliases_size() > 0) { continue; @@ -81,11 +119,17 @@ void DeltaSubscriptionState::handleGoodResponse( fmt::format("duplicate name {} found in the union of added+removed resources", name)); } } - watch_map_.onConfigUpdate(message.resources(), message.removed_resources(), - message.system_version_info()); - for (const auto& resource : message.resources()) { - setResourceVersion(resource.name(), resource.version()); + + { + const auto scoped_update = ttl_.scopedTtlUpdate(); + for (const auto& resource : message.resources()) { + addResourceState(resource); + } } + + watch_map_.onConfigUpdate(non_heartbeat_resources, message.removed_resources(), + message.system_version_info()); + // If a resource is gone, there is no longer a meaningful version for it that makes sense to // provide to the server upon stream reconnect: either it will continue to not exist, in which // case saying nothing is fine, or the server will bring back something new, which we should @@ -124,12 +168,12 @@ DeltaSubscriptionState::getNextRequestAckless() { // initial_resource_versions "must be populated for first request in a stream". // Also, since this might be a new server, we must explicitly state *all* of our subscription // interest. - for (auto const& [resource_name, resource_version] : resource_versions_) { + for (auto const& [resource_name, resource_state] : resource_state_) { // Populate initial_resource_versions with the resource versions we currently have. // Resources we are interested in, but are still waiting to get any version of from the // server, do not belong in initial_resource_versions. (But do belong in new subscriptions!) - if (!resource_version.waitingForServer()) { - (*request.mutable_initial_resource_versions())[resource_name] = resource_version.version(); + if (!resource_state.waitingForServer()) { + (*request.mutable_initial_resource_versions())[resource_name] = resource_state.version(); } // As mentioned above, fill resource_names_subscribe with everything, including names we // have yet to receive any resource for. @@ -160,19 +204,26 @@ DeltaSubscriptionState::getNextRequestWithAck(const UpdateAck& ack) { return request; } -void DeltaSubscriptionState::setResourceVersion(const std::string& resource_name, - const std::string& resource_version) { - resource_versions_[resource_name] = ResourceVersion(resource_version); - resource_names_.insert(resource_name); +void DeltaSubscriptionState::addResourceState( + const envoy::service::discovery::v3::Resource& resource) { + if (resource.has_ttl()) { + ttl_.add(std::chrono::milliseconds(DurationUtil::durationToMilliseconds(resource.ttl())), + resource.name()); + } else { + ttl_.clear(resource.name()); + } + + resource_state_[resource.name()] = ResourceState(resource); + resource_names_.insert(resource.name()); } void DeltaSubscriptionState::setResourceWaitingForServer(const std::string& resource_name) { - resource_versions_[resource_name] = ResourceVersion(); + resource_state_[resource_name] = ResourceState(); resource_names_.insert(resource_name); } -void DeltaSubscriptionState::setLostInterestInResource(const std::string& resource_name) { - resource_versions_.erase(resource_name); +void DeltaSubscriptionState::removeResourceState(const std::string& resource_name) { + resource_state_.erase(resource_name); resource_names_.erase(resource_name); } diff --git a/source/common/config/delta_subscription_state.h b/source/common/config/delta_subscription_state.h index 1e21ba3a8efd..089b632c9a01 100644 --- a/source/common/config/delta_subscription_state.h +++ b/source/common/config/delta_subscription_state.h @@ -11,6 +11,7 @@ #include "common/common/logger.h" #include "common/config/api_version.h" #include "common/config/pausable_ack_queue.h" +#include "common/config/ttl.h" #include "common/config/watch_map.h" #include "absl/container/node_hash_map.h" @@ -25,7 +26,7 @@ namespace Config { class DeltaSubscriptionState : public Logger::Loggable { public: DeltaSubscriptionState(std::string type_url, UntypedConfigUpdateCallbacks& watch_map, - const LocalInfo::LocalInfo& local_info); + const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher); // Update which resources we're interested in subscribing to. void updateSubscriptionInterest(const std::set& cur_added, @@ -44,6 +45,7 @@ class DeltaSubscriptionState : public Logger::Loggable { // Returns the next gRPC request proto to be sent off to the server, based on this object's // understanding of the current protocol state, and new resources that Envoy wants to request. envoy::service::discovery::v3::DeltaDiscoveryRequest getNextRequestAckless(); + // The WithAck version first calls the Ack-less version, then adds in the passed-in ack. envoy::service::discovery::v3::DeltaDiscoveryRequest getNextRequestWithAck(const UpdateAck& ack); @@ -51,18 +53,22 @@ class DeltaSubscriptionState : public Logger::Loggable { DeltaSubscriptionState& operator=(const DeltaSubscriptionState&) = delete; private: + bool isHeartbeatResponse(const envoy::service::discovery::v3::Resource& resource) const; void handleGoodResponse(const envoy::service::discovery::v3::DeltaDiscoveryResponse& message); void handleBadResponse(const EnvoyException& e, UpdateAck& ack); - class ResourceVersion { + class ResourceState { public: - explicit ResourceVersion(absl::string_view version) : version_(version) {} - // Builds a ResourceVersion in the waitingForServer state. - ResourceVersion() = default; + ResourceState(const envoy::service::discovery::v3::Resource& resource) + : version_(resource.version()) {} + + // Builds a ResourceState in the waitingForServer state. + ResourceState() = default; // If true, we currently have no version of this resource - we are waiting for the server to // provide us with one. bool waitingForServer() const { return version_ == absl::nullopt; } + // Must not be called if waitingForServer() == true. std::string version() const { ASSERT(version_.has_value()); @@ -73,17 +79,24 @@ class DeltaSubscriptionState : public Logger::Loggable { absl::optional version_; }; - // Use these helpers to ensure resource_versions_ and resource_names_ get updated together. - void setResourceVersion(const std::string& resource_name, const std::string& resource_version); + // Use these helpers to ensure resource_state_ and resource_names_ get updated together. + void addResourceState(const envoy::service::discovery::v3::Resource& resource); void setResourceWaitingForServer(const std::string& resource_name); - void setLostInterestInResource(const std::string& resource_name); + void removeResourceState(const std::string& resource_name); + void populateDiscoveryRequest(envoy::service::discovery::v3::DeltaDiscoveryResponse& request); // A map from resource name to per-resource version. The keys of this map are exactly the resource // names we are currently interested in. Those in the waitingForServer state currently don't have // any version for that resource: we need to inform the server if we lose interest in them, but we // also need to *not* include them in the initial_resource_versions map upon a reconnect. - absl::node_hash_map resource_versions_; + absl::node_hash_map resource_state_; + + // Not all xDS resources supports heartbeats due to there being specific information encoded in + // an empty response, which is indistinguishable from a heartbeat in some cases. For now we just + // disable heartbeats for these resources (currently only VHDS). + const bool supports_heartbeats_; + TtlManager ttl_; // The keys of resource_versions_. Only tracked separately because std::map does not provide an // iterator into just its keys, e.g. for use in std::set_difference. std::set resource_names_; @@ -91,6 +104,7 @@ class DeltaSubscriptionState : public Logger::Loggable { const std::string type_url_; UntypedConfigUpdateCallbacks& watch_map_; const LocalInfo::LocalInfo& local_info_; + Event::Dispatcher& dispatcher_; std::chrono::milliseconds init_fetch_timeout_; bool any_request_sent_yet_in_current_stream_{}; diff --git a/source/common/config/grpc_mux_impl.cc b/source/common/config/grpc_mux_impl.cc index b415776d6b8e..c45aab8d0f56 100644 --- a/source/common/config/grpc_mux_impl.cc +++ b/source/common/config/grpc_mux_impl.cc @@ -24,6 +24,7 @@ GrpcMuxImpl::GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, rate_limit_settings), local_info_(local_info), skip_subsequent_node_(skip_subsequent_node), first_stream_request_(true), transport_api_version_(transport_api_version), + dispatcher_(dispatcher), enable_type_url_downgrade_and_upgrade_(Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.enable_type_url_downgrade_and_upgrade")) { Config::Utility::checkLocalInfo("ads", local_info); @@ -32,7 +33,7 @@ GrpcMuxImpl::GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, void GrpcMuxImpl::start() { grpc_stream_.establishNewStream(); } void GrpcMuxImpl::sendDiscoveryRequest(const std::string& type_url) { - ApiState& api_state = api_state_[type_url]; + ApiState& api_state = apiStateFor(type_url); auto& request = api_state.request_; request.mutable_resource_names()->Clear(); @@ -56,8 +57,8 @@ void GrpcMuxImpl::sendDiscoveryRequest(const std::string& type_url) { first_stream_request_ = false; // clear error_detail after the request is sent if it exists. - if (api_state_[type_url].request_.has_error_detail()) { - api_state_[type_url].request_.clear_error_detail(); + if (apiStateFor(type_url).request_.has_error_detail()) { + apiStateFor(type_url).request_.clear_error_detail(); } } @@ -73,10 +74,10 @@ GrpcMuxWatchPtr GrpcMuxImpl::addWatch(const std::string& type_url, // convenient side-effect that we order messages on the channel based on // Envoy's internal dependency ordering. // TODO(gsagula): move TokenBucketImpl params to a config. - if (!api_state_[type_url].subscribed_) { - api_state_[type_url].request_.set_type_url(type_url); - api_state_[type_url].request_.mutable_node()->MergeFrom(local_info_.node()); - api_state_[type_url].subscribed_ = true; + if (!apiStateFor(type_url).subscribed_) { + apiStateFor(type_url).request_.set_type_url(type_url); + apiStateFor(type_url).request_.mutable_node()->MergeFrom(local_info_.node()); + apiStateFor(type_url).subscribed_ = true; subscriptions_.emplace_back(type_url); if (enable_type_url_downgrade_and_upgrade_) { registerVersionedTypeUrl(type_url); @@ -98,14 +99,14 @@ ScopedResume GrpcMuxImpl::pause(const std::string& type_url) { ScopedResume GrpcMuxImpl::pause(const std::vector type_urls) { for (const auto& type_url : type_urls) { - ApiState& api_state = api_state_[type_url]; + ApiState& api_state = apiStateFor(type_url); ENVOY_LOG(debug, "Pausing discovery requests for {} (previous count {})", type_url, api_state.pauses_); ++api_state.pauses_; } return std::make_unique([this, type_urls]() { for (const auto& type_url : type_urls) { - ApiState& api_state = api_state_[type_url]; + ApiState& api_state = apiStateFor(type_url); ENVOY_LOG(debug, "Resuming discovery requests for {} (previous count {})", type_url, api_state.pauses_); ASSERT(api_state.paused()); @@ -155,16 +156,17 @@ void GrpcMuxImpl::onDiscoveryResponse( type_url); return; } - if (api_state_[type_url].watches_.empty()) { + + if (apiStateFor(type_url).watches_.empty()) { // update the nonce as we are processing this response. - api_state_[type_url].request_.set_response_nonce(message->nonce()); + apiStateFor(type_url).request_.set_response_nonce(message->nonce()); if (message->resources().empty()) { // No watches and no resources. This can happen when envoy unregisters from a // resource that's removed from the server as well. For example, a deleted cluster // triggers un-watching the ClusterLoadAssignment watch, and at the same time the // xDS server sends an empty list of ClusterLoadAssignment resources. we'll accept // this update. no need to send a discovery request, as we don't watch for anything. - api_state_[type_url].request_.set_version_info(message->version_info()); + apiStateFor(type_url).request_.set_version_info(message->version_info()); } else { // No watches and we have resources - this should not happen. send a NACK (by not // updating the version). @@ -189,19 +191,36 @@ void GrpcMuxImpl::onDiscoveryResponse( absl::btree_map resource_ref_map; std::vector all_resource_refs; OpaqueResourceDecoder& resource_decoder = - api_state_[type_url].watches_.front()->resource_decoder_; + apiStateFor(type_url).watches_.front()->resource_decoder_; + + const auto scoped_ttl_update = apiStateFor(type_url).ttl_.scopedTtlUpdate(); + for (const auto& resource : message->resources()) { - if (message->type_url() != resource.type_url()) { + // TODO(snowp): Check the underlying type when the resource is a Resource. + if (!resource.Is() && + message->type_url() != resource.type_url()) { throw EnvoyException( fmt::format("{} does not match the message-wide type URL {} in DiscoveryResponse {}", resource.type_url(), message->type_url(), message->DebugString())); } - resources.emplace_back( - new DecodedResourceImpl(resource_decoder, resource, message->version_info())); - all_resource_refs.emplace_back(*resources.back()); - resource_ref_map.emplace(resources.back()->name(), *resources.back()); + + auto decoded_resource = + DecodedResourceImpl::fromResource(resource_decoder, resource, message->version_info()); + + if (decoded_resource->ttl()) { + apiStateFor(type_url).ttl_.add(*decoded_resource->ttl(), decoded_resource->name()); + } else { + apiStateFor(type_url).ttl_.clear(decoded_resource->name()); + } + + if (!isHeartbeatResource(type_url, *decoded_resource)) { + resources.emplace_back(std::move(decoded_resource)); + all_resource_refs.emplace_back(*resources.back()); + resource_ref_map.emplace(resources.back()->name(), *resources.back()); + } } - for (auto watch : api_state_[type_url].watches_) { + + for (auto watch : apiStateFor(type_url).watches_) { // onConfigUpdate should be called in all cases for single watch xDS (Cluster and // Listener) even if the message does not have resources so that update_empty stat // is properly incremented and state-of-the-world semantics are maintained. @@ -216,6 +235,7 @@ void GrpcMuxImpl::onDiscoveryResponse( found_resources.emplace_back(it->second); } } + // onConfigUpdate should be called only on watches(clusters/routes) that have // updates in the message for EDS/RDS. if (!found_resources.empty()) { @@ -224,19 +244,19 @@ void GrpcMuxImpl::onDiscoveryResponse( } // TODO(mattklein123): In the future if we start tracking per-resource versions, we // would do that tracking here. - api_state_[type_url].request_.set_version_info(message->version_info()); + apiStateFor(type_url).request_.set_version_info(message->version_info()); Memory::Utils::tryShrinkHeap(); } catch (const EnvoyException& e) { - for (auto watch : api_state_[type_url].watches_) { + for (auto watch : apiStateFor(type_url).watches_) { watch->callbacks_.onConfigUpdateFailed( Envoy::Config::ConfigUpdateFailureReason::UpdateRejected, &e); } - ::google::rpc::Status* error_detail = api_state_[type_url].request_.mutable_error_detail(); + ::google::rpc::Status* error_detail = apiStateFor(type_url).request_.mutable_error_detail(); error_detail->set_code(Grpc::Status::WellKnownGrpcStatus::Internal); error_detail->set_message(Config::Utility::truncateGrpcStatusMessage(e.what())); } - api_state_[type_url].request_.set_response_nonce(message->nonce()); - ASSERT(api_state_[type_url].paused()); + apiStateFor(type_url).request_.set_response_nonce(message->nonce()); + ASSERT(apiStateFor(type_url).paused()); queueDiscoveryRequest(type_url); } @@ -253,7 +273,7 @@ void GrpcMuxImpl::onStreamEstablished() { void GrpcMuxImpl::onEstablishmentFailure() { for (const auto& api_state : api_state_) { - for (auto watch : api_state.second.watches_) { + for (auto watch : api_state.second->watches_) { watch->callbacks_.onConfigUpdateFailed( Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, nullptr); } @@ -265,7 +285,7 @@ void GrpcMuxImpl::queueDiscoveryRequest(const std::string& queue_item) { ENVOY_LOG(debug, "No stream available to queueDiscoveryRequest for {}", queue_item); return; // Drop this request; the reconnect will enqueue a new one. } - ApiState& api_state = api_state_[queue_item]; + ApiState& api_state = apiStateFor(queue_item); if (api_state.paused()) { ENVOY_LOG(trace, "API {} paused during queueDiscoveryRequest(), setting pending.", queue_item); api_state.pending_ = true; @@ -275,6 +295,44 @@ void GrpcMuxImpl::queueDiscoveryRequest(const std::string& queue_item) { drainRequests(); } +void GrpcMuxImpl::expiryCallback(const std::string& type_url, + const std::vector& expired) { + // The TtlManager triggers a callback with a list of all the expired elements, which we need + // to compare against the various watched resources to return the subset that each watch is + // subscribed to. + + // We convert the incoming list into a set in order to more efficiently perform this + // comparison when there are a lot of watches. + absl::flat_hash_set all_expired; + all_expired.insert(expired.begin(), expired.end()); + + // Note: We can blindly dereference the lookup here since the only time we call this is in a + // callback that is created at the same time as we insert the ApiState for this type. + for (auto watch : api_state_.find(type_url)->second->watches_) { + Protobuf::RepeatedPtrField found_resources_for_watch; + + for (const auto& resource : expired) { + if (all_expired.find(resource) != all_expired.end()) { + found_resources_for_watch.Add(std::string(resource)); + } + } + + watch->callbacks_.onConfigUpdate({}, found_resources_for_watch, ""); + } +} + +GrpcMuxImpl::ApiState& GrpcMuxImpl::apiStateFor(const std::string& type_url) { + auto itr = api_state_.find(type_url); + if (itr == api_state_.end()) { + api_state_.emplace( + type_url, std::make_unique(dispatcher_, [this, type_url](const auto& expired) { + expiryCallback(type_url, expired); + })); + } + + return *api_state_.find(type_url)->second; +} + void GrpcMuxImpl::drainRequests() { while (!request_queue_->empty() && grpc_stream_.checkRateLimitAllowsDrain()) { // Process the request, if rate limiting is not enabled at all or if it is under rate limit. diff --git a/source/common/config/grpc_mux_impl.h b/source/common/config/grpc_mux_impl.h index 1006b165c5ba..bd554891461d 100644 --- a/source/common/config/grpc_mux_impl.h +++ b/source/common/config/grpc_mux_impl.h @@ -19,6 +19,7 @@ #include "common/common/utility.h" #include "common/config/api_version.h" #include "common/config/grpc_stream.h" +#include "common/config/ttl.h" #include "common/config/utility.h" #include "common/runtime/runtime_features.h" @@ -83,7 +84,7 @@ class GrpcMuxImpl : public GrpcMux, OpaqueResourceDecoder& resource_decoder, const std::string& type_url, GrpcMuxImpl& parent) : resources_(resources), callbacks_(callbacks), resource_decoder_(resource_decoder), - type_url_(type_url), parent_(parent), watches_(parent.api_state_[type_url].watches_) { + type_url_(type_url), parent_(parent), watches_(parent.apiStateFor(type_url).watches_) { watches_.emplace(watches_.begin(), this); } @@ -117,6 +118,10 @@ class GrpcMuxImpl : public GrpcMux, // Per muxed API state. struct ApiState { + ApiState(Event::Dispatcher& dispatcher, + std::function&)> callback) + : ttl_(callback, dispatcher, dispatcher.timeSource()) {} + bool paused() const { return pauses_ > 0; } // Watches on the returned resources for the API; @@ -129,8 +134,14 @@ class GrpcMuxImpl : public GrpcMux, bool pending_{}; // Has this API been tracked in subscriptions_? bool subscribed_{}; + TtlManager ttl_; }; + bool isHeartbeatResource(const std::string& type_url, const DecodedResource& resource) { + return !resource.hasResource() && + resource.version() == apiStateFor(type_url).request_.version_info(); + } + void expiryCallback(const std::string& type_url, const std::vector& expired); // Request queue management logic. void queueDiscoveryRequest(const std::string& queue_item); @@ -140,7 +151,12 @@ class GrpcMuxImpl : public GrpcMux, const LocalInfo::LocalInfo& local_info_; const bool skip_subsequent_node_; bool first_stream_request_; - absl::node_hash_map api_state_; + + // Helper function for looking up and potentially allocating a new ApiState. + ApiState& apiStateFor(const std::string& type_url); + + absl::node_hash_map> api_state_; + // Envoy's dependency ordering. std::list subscriptions_; @@ -149,6 +165,8 @@ class GrpcMuxImpl : public GrpcMux, // This string is a type URL. std::unique_ptr> request_queue_; const envoy::config::core::v3::ApiVersion transport_api_version_; + + Event::Dispatcher& dispatcher_; bool enable_type_url_downgrade_and_upgrade_; }; diff --git a/source/common/config/new_grpc_mux_impl.cc b/source/common/config/new_grpc_mux_impl.cc index 0015a2689971..e850c8504caa 100644 --- a/source/common/config/new_grpc_mux_impl.cc +++ b/source/common/config/new_grpc_mux_impl.cc @@ -24,6 +24,7 @@ NewGrpcMuxImpl::NewGrpcMuxImpl(Grpc::RawAsyncClientPtr&& async_client, : grpc_stream_(this, std::move(async_client), service_method, random, dispatcher, scope, rate_limit_settings), local_info_(local_info), transport_api_version_(transport_api_version), + dispatcher_(dispatcher), enable_type_url_downgrade_and_upgrade_(Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.enable_type_url_downgrade_and_upgrade")) {} @@ -192,8 +193,8 @@ void NewGrpcMuxImpl::removeWatch(const std::string& type_url, Watch* watch) { void NewGrpcMuxImpl::addSubscription(const std::string& type_url, const bool use_namespace_matching) { - subscriptions_.emplace( - type_url, std::make_unique(type_url, local_info_, use_namespace_matching)); + subscriptions_.emplace(type_url, std::make_unique( + type_url, local_info_, use_namespace_matching, dispatcher_)); subscription_ordering_.emplace_back(type_url); } diff --git a/source/common/config/new_grpc_mux_impl.h b/source/common/config/new_grpc_mux_impl.h index 8e64f3e4399f..f39308748df6 100644 --- a/source/common/config/new_grpc_mux_impl.h +++ b/source/common/config/new_grpc_mux_impl.h @@ -68,8 +68,9 @@ class NewGrpcMuxImpl struct SubscriptionStuff { SubscriptionStuff(const std::string& type_url, const LocalInfo::LocalInfo& local_info, - const bool use_namespace_matching) - : watch_map_(use_namespace_matching), sub_state_(type_url, watch_map_, local_info) {} + const bool use_namespace_matching, Event::Dispatcher& dispatcher) + : watch_map_(use_namespace_matching), + sub_state_(type_url, watch_map_, local_info, dispatcher) {} WatchMap watch_map_; DeltaSubscriptionState sub_state_; @@ -154,6 +155,7 @@ class NewGrpcMuxImpl const LocalInfo::LocalInfo& local_info_; const envoy::config::core::v3::ApiVersion transport_api_version_; + Event::Dispatcher& dispatcher_; const bool enable_type_url_downgrade_and_upgrade_; }; diff --git a/source/common/config/ttl.cc b/source/common/config/ttl.cc new file mode 100644 index 000000000000..3b54a788ae28 --- /dev/null +++ b/source/common/config/ttl.cc @@ -0,0 +1,73 @@ +#include "common/config/ttl.h" + +namespace Envoy { +namespace Config { + +TtlManager::TtlManager(std::function&)> callback, + Event::Dispatcher& dispatcher, TimeSource& time_source) + : callback_(callback), dispatcher_(dispatcher), time_source_(time_source) { + timer_ = dispatcher_.createTimer([this]() { + ScopedTtlUpdate scoped_update(*this); + + std::vector expired; + last_scheduled_time_ = absl::nullopt; + + const auto now = time_source_.monotonicTime(); + auto itr = ttls_.begin(); + while (itr != ttls_.end() && itr->first <= now) { + expired.push_back(itr->second); + ttl_lookup_.erase(itr->second); + itr++; + } + + if (itr != ttls_.begin()) { + ttls_.erase(ttls_.begin(), itr); + } + + if (!expired.empty()) { + callback_(expired); + } + }); +} +void TtlManager::add(std::chrono::milliseconds ttl, const std::string& name) { + ScopedTtlUpdate scoped_update(*this); + + clear(name); + + auto itr_and_inserted = ttls_.insert({time_source_.monotonicTime() + ttl, name}); + ttl_lookup_[name] = itr_and_inserted.first; +} + +void TtlManager::clear(const std::string& name) { + ScopedTtlUpdate scoped_update(*this); + + auto lookup_itr = ttl_lookup_.find(name); + if (lookup_itr != ttl_lookup_.end()) { + ttls_.erase(lookup_itr->second); + ttl_lookup_.erase(lookup_itr); + } +} + +void TtlManager::refreshTimer() { + // No TTLs, so just disable the timer. + if (ttls_.empty()) { + timer_->disableTimer(); + return; + } + + auto next_ttl_expiry = ttls_.begin()->first; + + // The currently scheduled timer execution matches the next TTL expiry, so do nothing. + if (timer_->enabled() && last_scheduled_time_ == next_ttl_expiry) { + return; + } + + auto timer_duration = std::chrono::duration_cast( + next_ttl_expiry - time_source_.monotonicTime()); + + // The time until the next TTL changed, so reset the timer to match the new value. + last_scheduled_time_ = next_ttl_expiry; + timer_->enableTimer(timer_duration, nullptr); +} +} // namespace Config +} // namespace Envoy \ No newline at end of file diff --git a/source/common/config/ttl.h b/source/common/config/ttl.h new file mode 100644 index 000000000000..aa7091c63765 --- /dev/null +++ b/source/common/config/ttl.h @@ -0,0 +1,69 @@ +#pragma once + +#include + +#include "envoy/event/dispatcher.h" +#include "envoy/event/timer.h" + +namespace Envoy { +namespace Config { + +/** + * Class for managing TTL expiration of xDS resources. TTLs are managed with a single timer that + * will always be set to the time until the next TTL, ensuring that we have a constant amount of + * timers per xDS resource type. + * + * We use a combination of two data structures here: a std::set that ensures that we can iterate + * over the pending TTLs in sorted over, and a map from resource name to the set iterator which + * allows us to efficiently clear or update TTLs. As iterator stability is required to track the + * iterators, we use std::set over something like ``absl::btree_set``. + * + * As a result of these two data structures, all lookups and modifications can be performed in + * O(log (number of TTL entries)). This comes at the cost of a higher memory overhead versus just + * using a single data structure. + */ +class TtlManager { +public: + TtlManager(std::function&)> callback, + Event::Dispatcher& dispatcher, TimeSource& time_source); + + // RAII tracker to simplify managing when we should be running the update callbacks. + class ScopedTtlUpdate { + public: + ~ScopedTtlUpdate() { + if (--parent_.scoped_update_counter_ == 0) { + parent_.refreshTimer(); + } + } + + private: + ScopedTtlUpdate(TtlManager& parent) : parent_(parent) { parent_.scoped_update_counter_++; } + + friend TtlManager; + + TtlManager& parent_; + }; + + ScopedTtlUpdate scopedTtlUpdate() { return ScopedTtlUpdate(*this); } + + void add(std::chrono::milliseconds ttl, const std::string& name); + + void clear(const std::string& name); + +private: + void refreshTimer(); + + using TtlSet = std::set>; + TtlSet ttls_; + absl::flat_hash_map ttl_lookup_; + + Event::TimerPtr timer_; + absl::optional last_scheduled_time_; + uint8_t scoped_update_counter_{}; + + std::function&)> callback_; + Event::Dispatcher& dispatcher_; + TimeSource& time_source_; +}; +} // namespace Config +} // namespace Envoy \ No newline at end of file diff --git a/source/common/config/watch_map.cc b/source/common/config/watch_map.cc index 5859313ded60..38040d1bb633 100644 --- a/source/common/config/watch_map.cc +++ b/source/common/config/watch_map.cc @@ -101,7 +101,7 @@ void WatchMap::onConfigUpdate(const Protobuf::RepeatedPtrField absl::flat_hash_map> per_watch_updates; for (const auto& r : resources) { decoded_resources.emplace_back( - new DecodedResourceImpl((*watches_.begin())->resource_decoder_, r, version_info)); + DecodedResourceImpl::fromResource((*watches_.begin())->resource_decoder_, r, version_info)); const absl::flat_hash_set& interested_in_r = watchesInterestedIn(decoded_resources.back()->name()); for (const auto& interested_watch : interested_in_r) { diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 55f9f79de526..e1dbe6eebcd0 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -87,6 +87,7 @@ constexpr const char* runtime_features[] = { "envoy.reloadable_features.stop_faking_paths", "envoy.reloadable_features.strict_1xx_and_204_response_headers", "envoy.reloadable_features.tls_use_io_handle_bio", + "envoy.reloadable_features.vhds_heartbeats", "envoy.reloadable_features.unify_grpc_handling", "envoy.restart_features.use_apple_api_for_dns_lookups", }; diff --git a/source/common/upstream/eds.cc b/source/common/upstream/eds.cc index d8dc0d21c242..124ea3c5489d 100644 --- a/source/common/upstream/eds.cc +++ b/source/common/upstream/eds.cc @@ -173,12 +173,14 @@ void EdsClusterImpl::onAssignmentTimeout() { // TODO(vishalpowar) This is not going to work for incremental updates, and we // need to instead change the health status to indicate the assignments are // stale. + // TODO(snowp): This should probably just use xDS TTLs? envoy::config::endpoint::v3::ClusterLoadAssignment resource; resource.set_cluster_name(cluster_name_); ProtobufWkt::Any any_resource; any_resource.PackFrom(resource); - Config::DecodedResourceImpl decoded_resource(resource_decoder_, any_resource, ""); - std::vector resource_refs = {decoded_resource}; + auto decoded_resource = + Config::DecodedResourceImpl::fromResource(resource_decoder_, any_resource, ""); + std::vector resource_refs = {*decoded_resource}; onConfigUpdate(resource_refs, ""); // Stat to track how often we end up with stale assignments. info_->stats().assignment_stale_.inc(); diff --git a/test/common/config/BUILD b/test/common/config/BUILD index f53ca9aacb69..52c508569ec6 100644 --- a/test/common/config/BUILD +++ b/test/common/config/BUILD @@ -36,6 +36,17 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "ttl_test", + srcs = ["ttl_test.cc"], + deps = [ + "//source/common/config:ttl_lib", + "//test/mocks/event:event_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + envoy_cc_test( name = "delta_subscription_impl_test", srcs = ["delta_subscription_impl_test.cc"], @@ -74,6 +85,7 @@ envoy_cc_test( "//test/mocks/local_info:local_info_mocks", "//test/mocks/runtime:runtime_mocks", "//test/test_common:logging_lib", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], diff --git a/test/common/config/config_provider_impl_test.cc b/test/common/config/config_provider_impl_test.cc index c8308e42e500..b9d507062384 100644 --- a/test/common/config/config_provider_impl_test.cc +++ b/test/common/config/config_provider_impl_test.cc @@ -3,6 +3,7 @@ #include "envoy/config/core/v3/config_source.pb.h" #include "envoy/service/discovery/v3/discovery.pb.h" +#include "common/common/assert.h" #include "common/config/config_provider_impl.h" #include "common/protobuf/utility.h" @@ -89,12 +90,12 @@ class DummyConfigSubscription : public ConfigSubscriptionInstance, ConfigSubscriptionCommonBase::onConfigUpdate(); } + // Envoy::Config::SubscriptionCallbacks void onConfigUpdate(const std::vector&, const Protobuf::RepeatedPtrField&, const std::string&) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - // Envoy::Config::SubscriptionCallbacks void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason, const EnvoyException*) override {} diff --git a/test/common/config/decoded_resource_impl_test.cc b/test/common/config/decoded_resource_impl_test.cc index 938d29611d33..2bb1020869c7 100644 --- a/test/common/config/decoded_resource_impl_test.cc +++ b/test/common/config/decoded_resource_impl_test.cc @@ -23,12 +23,13 @@ TEST(DecodedResourceImplTest, All) { []() -> ProtobufTypes::MessagePtr { return std::make_unique(); })); EXPECT_CALL(resource_decoder, resourceName(ProtoEq(ProtobufWkt::Empty()))) .WillOnce(Return("some_name")); - DecodedResourceImpl decoded_resource(resource_decoder, some_opaque_resource, "foo"); - EXPECT_EQ("some_name", decoded_resource.name()); - EXPECT_TRUE(decoded_resource.aliases().empty()); - EXPECT_EQ("foo", decoded_resource.version()); - EXPECT_THAT(decoded_resource.resource(), ProtoEq(ProtobufWkt::Empty())); - EXPECT_TRUE(decoded_resource.hasResource()); + auto decoded_resource = + DecodedResourceImpl::fromResource(resource_decoder, some_opaque_resource, "foo"); + EXPECT_EQ("some_name", decoded_resource->name()); + EXPECT_TRUE(decoded_resource->aliases().empty()); + EXPECT_EQ("foo", decoded_resource->version()); + EXPECT_THAT(decoded_resource->resource(), ProtoEq(ProtobufWkt::Empty())); + EXPECT_TRUE(decoded_resource->hasResource()); } { diff --git a/test/common/config/delta_subscription_state_test.cc b/test/common/config/delta_subscription_state_test.cc index 554ebe3884df..92193cbe143c 100644 --- a/test/common/config/delta_subscription_state_test.cc +++ b/test/common/config/delta_subscription_state_test.cc @@ -1,3 +1,5 @@ +#include + #include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/service/discovery/v3/discovery.pb.h" @@ -8,6 +10,8 @@ #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/local_info/mocks.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/test_runtime.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -22,9 +26,11 @@ namespace { const char TypeUrl[] = "type.googleapis.com/envoy.api.v2.Cluster"; -class DeltaSubscriptionStateTest : public testing::Test { +class DeltaSubscriptionStateTestBase : public testing::Test { protected: - DeltaSubscriptionStateTest() : state_(TypeUrl, callbacks_, local_info_) { + DeltaSubscriptionStateTestBase(const std::string& type_url) + : timer_(new Event::MockTimer(&dispatcher_)), + state_(type_url, callbacks_, local_info_, dispatcher_) { state_.updateSubscriptionInterest({"name1", "name2", "name3"}, {}); envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = state_.getNextRequestAckless(); @@ -36,7 +42,7 @@ class DeltaSubscriptionStateTest : public testing::Test { const Protobuf::RepeatedPtrField& added_resources, const Protobuf::RepeatedPtrField& removed_resources, const std::string& version_info, absl::optional nonce = absl::nullopt, - bool expect_config_update_call = true) { + bool expect_config_update_call = true, absl::optional updated_resources = {}) { envoy::service::discovery::v3::DeltaDiscoveryResponse message; *message.mutable_resources() = added_resources; *message.mutable_removed_resources() = removed_resources; @@ -44,7 +50,13 @@ class DeltaSubscriptionStateTest : public testing::Test { if (nonce.has_value()) { message.set_nonce(nonce.value()); } - EXPECT_CALL(callbacks_, onConfigUpdate(_, _, _)).Times(expect_config_update_call ? 1 : 0); + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, _)) + .Times(expect_config_update_call ? 1 : 0) + .WillRepeatedly(Invoke([updated_resources](const auto& added, const auto&, const auto&) { + if (updated_resources) { + EXPECT_EQ(added.size(), *updated_resources); + } + })); return state_.handleResponse(message); } @@ -64,6 +76,7 @@ class DeltaSubscriptionStateTest : public testing::Test { NiceMock callbacks_; NiceMock local_info_; NiceMock dispatcher_; + Event::MockTimer* timer_; // We start out interested in three resources: name1, name2, and name3. DeltaSubscriptionState state_; }; @@ -79,6 +92,11 @@ populateRepeatedResource(std::vector> items) return add_to; } +class DeltaSubscriptionStateTest : public DeltaSubscriptionStateTestBase { +public: + DeltaSubscriptionStateTest() : DeltaSubscriptionStateTestBase(TypeUrl) {} +}; + // Basic gaining/losing interest in resources should lead to subscription updates. TEST_F(DeltaSubscriptionStateTest, SubscribeAndUnsubscribe) { { @@ -177,6 +195,7 @@ TEST_F(DeltaSubscriptionStateTest, AckGenerated) { { Protobuf::RepeatedPtrField added_resources = populateRepeatedResource({{"name1", "version1A"}, {"name2", "version2A"}}); + EXPECT_CALL(*timer_, disableTimer()); UpdateAck ack = deliverDiscoveryResponse(added_resources, {}, "debug1", "nonce1"); EXPECT_EQ("nonce1", ack.nonce_); EXPECT_EQ(Grpc::Status::WellKnownGrpcStatus::Ok, ack.error_detail_.code()); @@ -186,6 +205,7 @@ TEST_F(DeltaSubscriptionStateTest, AckGenerated) { Protobuf::RepeatedPtrField added_resources = populateRepeatedResource( {{"name1", "version1B"}, {"name2", "version2B"}, {"name3", "version3A"}}); + EXPECT_CALL(*timer_, disableTimer()); UpdateAck ack = deliverDiscoveryResponse(added_resources, {}, "debug2", "nonce2"); EXPECT_EQ("nonce2", ack.nonce_); EXPECT_EQ(Grpc::Status::WellKnownGrpcStatus::Ok, ack.error_detail_.code()); @@ -195,6 +215,7 @@ TEST_F(DeltaSubscriptionStateTest, AckGenerated) { Protobuf::RepeatedPtrField added_resources = populateRepeatedResource( {{"name1", "version1C"}, {"name2", "version2C"}, {"name3", "version3B"}}); + EXPECT_CALL(*timer_, disableTimer()); UpdateAck ack = deliverBadDiscoveryResponse(added_resources, {}, "debug3", "nonce3", "oh no"); EXPECT_EQ("nonce3", ack.nonce_); EXPECT_NE(Grpc::Status::WellKnownGrpcStatus::Ok, ack.error_detail_.code()); @@ -204,6 +225,7 @@ TEST_F(DeltaSubscriptionStateTest, AckGenerated) { Protobuf::RepeatedPtrField added_resources = populateRepeatedResource( {{"name1", "version1D"}, {"name2", "version2D"}, {"name3", "version3C"}}); + EXPECT_CALL(*timer_, disableTimer()); UpdateAck ack = deliverDiscoveryResponse(added_resources, {}, "debug4", "nonce4"); EXPECT_EQ("nonce4", ack.nonce_); EXPECT_EQ(Grpc::Status::WellKnownGrpcStatus::Ok, ack.error_detail_.code()); @@ -214,6 +236,7 @@ TEST_F(DeltaSubscriptionStateTest, AckGenerated) { Protobuf::RepeatedPtrField added_resources = populateRepeatedResource( {{"name1", "version1D"}, {"name2", "version2D"}, {"name3", "version3D"}}); + EXPECT_CALL(*timer_, disableTimer()); UpdateAck ack = deliverBadDiscoveryResponse(added_resources, {}, "debug5", "nonce5", very_large_error_message); EXPECT_EQ("nonce5", ack.nonce_); @@ -233,6 +256,7 @@ TEST_F(DeltaSubscriptionStateTest, ResourceGoneLeadsToBlankInitialVersion) { // The xDS server's first update includes items for name1 and 2, but not 3. Protobuf::RepeatedPtrField add1_2 = populateRepeatedResource({{"name1", "version1A"}, {"name2", "version2A"}}); + EXPECT_CALL(*timer_, disableTimer()); deliverDiscoveryResponse(add1_2, {}, "debugversion1"); state_.markStreamFresh(); // simulate a stream reconnection envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = @@ -249,6 +273,7 @@ TEST_F(DeltaSubscriptionStateTest, ResourceGoneLeadsToBlankInitialVersion) { populateRepeatedResource({{"name1", "version1B"}, {"name3", "version3A"}}); Protobuf::RepeatedPtrField remove2; *remove2.Add() = "name2"; + EXPECT_CALL(*timer_, disableTimer()).Times(2); deliverDiscoveryResponse(add1_3, remove2, "debugversion2"); state_.markStreamFresh(); // simulate a stream reconnection envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = @@ -293,6 +318,7 @@ TEST_F(DeltaSubscriptionStateTest, ResourceGoneLeadsToBlankInitialVersion) { TEST_F(DeltaSubscriptionStateTest, SubscribeAndUnsubscribeAfterReconnect) { Protobuf::RepeatedPtrField add1_2 = populateRepeatedResource({{"name1", "version1A"}, {"name2", "version2A"}}); + EXPECT_CALL(*timer_, disableTimer()); deliverDiscoveryResponse(add1_2, {}, "debugversion1"); state_.updateSubscriptionInterest({"name4"}, {"name1"}); @@ -318,6 +344,7 @@ TEST_F(DeltaSubscriptionStateTest, InitialVersionMapFirstMessageOnly) { Protobuf::RepeatedPtrField add_all = populateRepeatedResource( {{"name1", "version1A"}, {"name2", "version2A"}, {"name3", "version3A"}}); + EXPECT_CALL(*timer_, disableTimer()); deliverDiscoveryResponse(add_all, {}, "debugversion1"); state_.markStreamFresh(); // simulate a stream reconnection envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = @@ -336,6 +363,7 @@ TEST_F(DeltaSubscriptionStateTest, InitialVersionMapFirstMessageOnly) { {"name2", "version2B"}, {"name3", "version3B"}, {"name4", "version4A"}}); + EXPECT_CALL(*timer_, disableTimer()); deliverDiscoveryResponse(add_all, {}, "debugversion2"); envoy::service::discovery::v3::DeltaDiscoveryRequest cur_request = state_.getNextRequestAckless(); @@ -388,6 +416,117 @@ TEST_F(DeltaSubscriptionStateTest, AddedAndRemoved) { ack.error_detail_.message()); } +TEST_F(DeltaSubscriptionStateTest, ResourceTTL) { + Event::SimulatedTimeSystem time_system; + time_system.setSystemTime(std::chrono::milliseconds(0)); + + auto create_resource_with_ttl = [](absl::optional ttl_s, + bool include_resource) { + Protobuf::RepeatedPtrField added_resources; + auto* resource = added_resources.Add(); + resource->set_name("name1"); + resource->set_version("version1A"); + + if (include_resource) { + resource->mutable_resource(); + } + + if (ttl_s) { + ProtobufWkt::Duration ttl; + ttl.set_seconds(ttl_s->count()); + resource->mutable_ttl()->CopyFrom(ttl); + } + + return added_resources; + }; + + { + EXPECT_CALL(*timer_, enabled()); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000), _)); + deliverDiscoveryResponse(create_resource_with_ttl(std::chrono::seconds(1), true), {}, "debug1", + "nonce1"); + } + + { + // Increase the TTL. + EXPECT_CALL(*timer_, enabled()); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(2000), _)); + deliverDiscoveryResponse(create_resource_with_ttl(std::chrono::seconds(2), true), {}, "debug1", + "nonce1", true, 1); + } + + { + // Refresh the TTL with a heartbeat. The resource should not be passed to the update callbacks. + EXPECT_CALL(*timer_, enabled()); + deliverDiscoveryResponse(create_resource_with_ttl(std::chrono::seconds(2), false), {}, "debug1", + "nonce1", true, 0); + } + + // Remove the TTL. + EXPECT_CALL(*timer_, disableTimer()); + deliverDiscoveryResponse(create_resource_with_ttl(absl::nullopt, true), {}, "debug1", "nonce1", + true, 1); + + // Add back the TTL. + EXPECT_CALL(*timer_, enabled()); + EXPECT_CALL(*timer_, enableTimer(_, _)); + deliverDiscoveryResponse(create_resource_with_ttl(std::chrono::seconds(2), true), {}, "debug1", + "nonce1"); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, _)); + EXPECT_CALL(*timer_, disableTimer()); + time_system.setSystemTime(std::chrono::seconds(2)); + + // Invoke the TTL. + timer_->invokeCallback(); +} + +class VhdsDeltaSubscriptionStateTest : public DeltaSubscriptionStateTestBase { +public: + VhdsDeltaSubscriptionStateTest() + : DeltaSubscriptionStateTestBase("envoy.config.route.v3.VirtualHost") {} +}; + +TEST_F(VhdsDeltaSubscriptionStateTest, ResourceTTL) { + Event::SimulatedTimeSystem time_system; + time_system.setSystemTime(std::chrono::milliseconds(0)); + + TestScopedRuntime scoped_runtime; + + auto create_resource_with_ttl = [](bool include_resource) { + Protobuf::RepeatedPtrField added_resources; + auto* resource = added_resources.Add(); + resource->set_name("name1"); + resource->set_version("version1A"); + + if (include_resource) { + resource->mutable_resource(); + } + + ProtobufWkt::Duration ttl; + ttl.set_seconds(1); + resource->mutable_ttl()->CopyFrom(ttl); + + return added_resources; + }; + + EXPECT_CALL(*timer_, enabled()); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000), _)); + deliverDiscoveryResponse(create_resource_with_ttl(true), {}, "debug1", "nonce1", true, 1); + + // Heartbeat update should not be propagated to the subscription callback. + EXPECT_CALL(*timer_, enabled()); + deliverDiscoveryResponse(create_resource_with_ttl(false), {}, "debug1", "nonce1", true, 0); + + // When runtime flag is disabled, maintain old behavior where we do propagate + // the update to the subscription callback. + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.vhds_heartbeats", "false"}}); + + EXPECT_CALL(*timer_, enabled()); + deliverDiscoveryResponse(create_resource_with_ttl(false), {}, "debug1", "nonce1", true, 1); +} + } // namespace } // namespace Config } // namespace Envoy diff --git a/test/common/config/delta_subscription_test_harness.h b/test/common/config/delta_subscription_test_harness.h index 04fca753ab05..ac2c18b74f43 100644 --- a/test/common/config/delta_subscription_test_harness.h +++ b/test/common/config/delta_subscription_test_harness.h @@ -40,7 +40,7 @@ class DeltaSubscriptionTestHarness : public SubscriptionTestHarness { async_client_(new Grpc::MockAsyncClient()) { node_.set_id("fo0"); EXPECT_CALL(local_info_, node()).WillRepeatedly(testing::ReturnRef(node_)); - EXPECT_CALL(dispatcher_, createTimer_(_)); + EXPECT_CALL(dispatcher_, createTimer_(_)).Times(2); xds_context_ = std::make_shared( std::unique_ptr(async_client_), dispatcher_, *method_descriptor_, envoy::config::core::v3::ApiVersion::AUTO, random_, stats_store_, rate_limit_settings_, diff --git a/test/common/config/grpc_mux_impl_test.cc b/test/common/config/grpc_mux_impl_test.cc index 8c869aa44b1f..fbf7230cea51 100644 --- a/test/common/config/grpc_mux_impl_test.cc +++ b/test/common/config/grpc_mux_impl_test.cc @@ -143,14 +143,11 @@ TEST_F(GrpcMuxImplTest, MultipleTypeUrlStreams) { TEST_F(GrpcMuxImplTest, ResetStream) { InSequence s; - Event::MockTimer* timer = nullptr; - Event::TimerCb timer_cb; - EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([&timer, &timer_cb](Event::TimerCb cb) { - timer_cb = cb; - EXPECT_EQ(nullptr, timer); - timer = new Event::MockTimer(); - return timer; - })); + auto* timer = new Event::MockTimer(&dispatcher_); + // TTL timers. + new Event::MockTimer(&dispatcher_); + new Event::MockTimer(&dispatcher_); + new Event::MockTimer(&dispatcher_); setup(); auto foo_sub = grpc_mux_->addWatch("foo", {"x", "y"}, callbacks_, resource_decoder_); @@ -166,7 +163,6 @@ TEST_F(GrpcMuxImplTest, ResetStream) { onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _)) .Times(3); EXPECT_CALL(random_, random()); - ASSERT_TRUE(timer != nullptr); // initialized from dispatcher mock. EXPECT_CALL(*timer, enableTimer(_, _)); grpc_mux_->grpcStreamForTest().onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Canceled, ""); EXPECT_EQ(0, control_plane_connected_state_.value()); @@ -175,7 +171,7 @@ TEST_F(GrpcMuxImplTest, ResetStream) { expectSendMessage("foo", {"x", "y"}, "", true); expectSendMessage("bar", {}, ""); expectSendMessage("baz", {"z"}, ""); - timer_cb(); + timer->invokeCallback(); expectSendMessage("baz", {}, ""); expectSendMessage("foo", {}, ""); @@ -230,11 +226,13 @@ TEST_F(GrpcMuxImplTest, TypeUrlMismatch) { { auto response = std::make_unique(); response->set_type_url("bar"); + response->set_version_info("bar-version"); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); } { invalid_response->set_type_url("foo"); + invalid_response->set_version_info("foo-version"); invalid_response->mutable_resources()->Add()->set_type_url("bar"); EXPECT_CALL(callbacks_, onConfigUpdateFailed(_, _)) .WillOnce(Invoke([](Envoy::Config::ConfigUpdateFailureReason, const EnvoyException* e) { @@ -265,6 +263,7 @@ TEST_F(GrpcMuxImplTest, RpcErrorMessageTruncated) { { // Large error message sent back to management server is truncated. const std::string very_large_type_url(1 << 20, 'A'); invalid_response->set_type_url("foo"); + invalid_response->set_version_info("invalid"); invalid_response->mutable_resources()->Add()->set_type_url(very_large_type_url); EXPECT_CALL(callbacks_, onConfigUpdateFailed(_, _)) .WillOnce(Invoke([&very_large_type_url](Envoy::Config::ConfigUpdateFailureReason, @@ -282,6 +281,152 @@ TEST_F(GrpcMuxImplTest, RpcErrorMessageTruncated) { expectSendMessage("foo", {}, ""); } +envoy::service::discovery::v3::Resource heartbeatResource(std::chrono::milliseconds ttl, + const std::string& name) { + envoy::service::discovery::v3::Resource resource; + + resource.mutable_ttl()->CopyFrom(Protobuf::util::TimeUtil::MillisecondsToDuration(ttl.count())); + resource.set_name(name); + + return resource; +} + +envoy::service::discovery::v3::Resource +resourceWithTtl(std::chrono::milliseconds ttl, + envoy::config::endpoint::v3::ClusterLoadAssignment& cla) { + envoy::service::discovery::v3::Resource resource; + resource.mutable_resource()->PackFrom(cla); + resource.mutable_ttl()->CopyFrom(Protobuf::util::TimeUtil::MillisecondsToDuration(ttl.count())); + + resource.set_name(cla.cluster_name()); + + return resource; +} +// Validates the behavior when the TTL timer expires. +TEST_F(GrpcMuxImplTest, ResourceTTL) { + setup(); + + time_system_.setSystemTime(std::chrono::seconds(0)); + + TestUtility::TestOpaqueResourceDecoderImpl + resource_decoder("cluster_name"); + const std::string& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + InSequence s; + auto* ttl_timer = new Event::MockTimer(&dispatcher_); + auto eds_sub = grpc_mux_->addWatch(type_url, {"x"}, callbacks_, resource_decoder); + + EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); + expectSendMessage(type_url, {"x"}, "", true); + grpc_mux_->start(); + + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + + auto wrapped_resource = resourceWithTtl(std::chrono::milliseconds(1000), load_assignment); + response->add_resources()->PackFrom(wrapped_resource); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(1000), _)); + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Increase the TTL. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + auto wrapped_resource = resourceWithTtl(std::chrono::milliseconds(10000), load_assignment); + response->add_resources()->PackFrom(wrapped_resource); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(10000), _)); + // No update, just a change in TTL. + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Refresh the TTL with a heartbeat response. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + auto wrapped_resource = heartbeatResource(std::chrono::milliseconds(10000), "x"); + response->add_resources()->PackFrom(wrapped_resource); + + EXPECT_CALL(*ttl_timer, enabled()); + + // No update, just a change in TTL. + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Remove the TTL. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment)); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*ttl_timer, disableTimer()); + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + // Put the TTL back. + { + auto response = std::make_unique(); + response->set_type_url(type_url); + response->set_version_info("1"); + envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; + load_assignment.set_cluster_name("x"); + auto wrapped_resource = resourceWithTtl(std::chrono::milliseconds(10000), load_assignment); + response->add_resources()->PackFrom(wrapped_resource); + + EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) + .WillOnce(Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + })); + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(10000), _)); + // No update, just a change in TTL. + expectSendMessage(type_url, {"x"}, "1"); + grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); + } + + time_system_.setSystemTime(std::chrono::seconds(11)); + EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "")) + .WillOnce(Invoke([](auto, const auto& removed, auto) { + EXPECT_EQ(1, removed.size()); + EXPECT_EQ("x", removed.Get(0)); + })); + // Fire the TTL timer. + EXPECT_CALL(*ttl_timer, disableTimer()); + ttl_timer->invokeCallback(); + + expectSendMessage(type_url, {}, "1"); +} + // Validate behavior when watches has an unknown resource name. TEST_F(GrpcMuxImplTest, WildcardWatch) { setup(); @@ -455,15 +600,10 @@ class GrpcMuxImplTestWithMockTimeSystem : public GrpcMuxImplTestBase { // Verifies that rate limiting is not enforced with defaults. TEST_F(GrpcMuxImplTestWithMockTimeSystem, TooManyRequestsWithDefaultSettings) { - // Validate that only connection retry timer is enabled. - Event::MockTimer* timer = nullptr; - Event::TimerCb timer_cb; - EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([&timer, &timer_cb](Event::TimerCb cb) { - timer_cb = cb; - EXPECT_EQ(nullptr, timer); - timer = new Event::MockTimer(); - return timer; - })); + + auto ttl_timer = new Event::MockTimer(&dispatcher_); + // Retry timer, + new Event::MockTimer(&dispatcher_); // Validate that rate limiter is not created. EXPECT_CALL(*mock_time_system_, monotonicTime()).Times(0); @@ -479,6 +619,7 @@ TEST_F(GrpcMuxImplTestWithMockTimeSystem, TooManyRequestsWithDefaultSettings) { response->set_version_info("baz"); response->set_nonce("bar"); response->set_type_url("foo"); + EXPECT_CALL(*ttl_timer, disableTimer()); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); } }; @@ -498,24 +639,10 @@ TEST_F(GrpcMuxImplTestWithMockTimeSystem, TooManyRequestsWithDefaultSettings) { // Verifies that default rate limiting is enforced with empty RateLimitSettings. TEST_F(GrpcMuxImplTest, TooManyRequestsWithEmptyRateLimitSettings) { // Validate that request drain timer is created. - Event::MockTimer* timer = nullptr; - Event::MockTimer* drain_request_timer = nullptr; - - Event::TimerCb timer_cb; - Event::TimerCb drain_timer_cb; - EXPECT_CALL(dispatcher_, createTimer_(_)) - .WillOnce(Invoke([&timer, &timer_cb](Event::TimerCb cb) { - timer_cb = cb; - EXPECT_EQ(nullptr, timer); - timer = new Event::MockTimer(); - return timer; - })) - .WillOnce(Invoke([&drain_request_timer, &drain_timer_cb](Event::TimerCb cb) { - drain_timer_cb = cb; - EXPECT_EQ(nullptr, drain_request_timer); - drain_request_timer = new Event::MockTimer(); - return drain_request_timer; - })); + + auto ttl_timer = new Event::MockTimer(&dispatcher_); + Event::MockTimer* drain_request_timer = new Event::MockTimer(&dispatcher_); + Event::MockTimer* retry_timer = new Event::MockTimer(&dispatcher_); RateLimitSettings custom_rate_limit_settings; custom_rate_limit_settings.enabled_ = true; @@ -531,6 +658,7 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithEmptyRateLimitSettings) { response->set_version_info("baz"); response->set_nonce("bar"); response->set_type_url("foo"); + EXPECT_CALL(*ttl_timer, disableTimer()); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); } }; @@ -552,15 +680,14 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithEmptyRateLimitSettings) { EXPECT_CALL(callbacks_, onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _)); EXPECT_CALL(random_, random()); - ASSERT_TRUE(timer != nullptr); // initialized from dispatcher mock. - EXPECT_CALL(*timer, enableTimer(_, _)); + EXPECT_CALL(*retry_timer, enableTimer(_, _)); grpc_mux_->grpcStreamForTest().onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Canceled, ""); EXPECT_EQ(11, control_plane_pending_requests_.value()); EXPECT_EQ(0, control_plane_connected_state_.value()); EXPECT_CALL(async_stream_, sendMessageRaw_(_, false)); EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); time_system_.setMonotonicTime(std::chrono::seconds(30)); - timer_cb(); + retry_timer->invokeCallback(); EXPECT_EQ(0, control_plane_pending_requests_.value()); // One more message on the way out when the watch is destroyed. EXPECT_CALL(async_stream_, sendMessageRaw_(_, false)); @@ -569,25 +696,12 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithEmptyRateLimitSettings) { // Verifies that rate limiting is enforced with custom RateLimitSettings. TEST_F(GrpcMuxImplTest, TooManyRequestsWithCustomRateLimitSettings) { // Validate that request drain timer is created. - Event::MockTimer* timer = nullptr; - Event::MockTimer* drain_request_timer = nullptr; - - Event::TimerCb timer_cb; - Event::TimerCb drain_timer_cb; - - EXPECT_CALL(dispatcher_, createTimer_(_)) - .WillOnce(Invoke([&timer, &timer_cb](Event::TimerCb cb) { - timer_cb = cb; - EXPECT_EQ(nullptr, timer); - timer = new Event::MockTimer(); - return timer; - })) - .WillOnce(Invoke([&drain_request_timer, &drain_timer_cb](Event::TimerCb cb) { - drain_timer_cb = cb; - EXPECT_EQ(nullptr, drain_request_timer); - drain_request_timer = new Event::MockTimer(); - return drain_request_timer; - })); + + // TTL timer. + auto ttl_timer = new Event::MockTimer(&dispatcher_); + Event::MockTimer* drain_request_timer = new Event::MockTimer(&dispatcher_); + // Retry timer. + new Event::MockTimer(&dispatcher_); RateLimitSettings custom_rate_limit_settings; custom_rate_limit_settings.enabled_ = true; @@ -604,6 +718,7 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithCustomRateLimitSettings) { response->set_version_info("baz"); response->set_nonce("bar"); response->set_type_url("foo"); + EXPECT_CALL(*ttl_timer, disableTimer()); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); } }; @@ -625,7 +740,7 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithCustomRateLimitSettings) { // Validate that drain requests call when there are multiple requests in queue. time_system_.setMonotonicTime(std::chrono::seconds(10)); - drain_timer_cb(); + drain_request_timer->invokeCallback(); // Check that the pending_requests stat is updated with the queue drain. EXPECT_EQ(0, control_plane_pending_requests_.value()); diff --git a/test/common/config/grpc_subscription_impl_test.cc b/test/common/config/grpc_subscription_impl_test.cc index eb51a0d051d7..74860c19e859 100644 --- a/test/common/config/grpc_subscription_impl_test.cc +++ b/test/common/config/grpc_subscription_impl_test.cc @@ -31,7 +31,7 @@ TEST_F(GrpcSubscriptionImplTest, StreamCreationFailure) { EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); expectSendMessage({"cluster2"}, "", true); - timer_cb_(); + timer_->invokeCallback(); EXPECT_TRUE(statsAre(3, 0, 0, 1, 0, 0, 0, "")); verifyControlPlaneStats(1); } @@ -53,7 +53,7 @@ TEST_F(GrpcSubscriptionImplTest, RemoteStreamClose) { // Retry and succeed. EXPECT_CALL(*async_client_, startRaw(_, _, _, _)).WillOnce(Return(&async_stream_)); expectSendMessage({"cluster0", "cluster1"}, "", true); - timer_cb_(); + timer_->invokeCallback(); EXPECT_TRUE(statsAre(2, 0, 0, 1, 0, 0, 0, "")); } diff --git a/test/common/config/grpc_subscription_test_harness.h b/test/common/config/grpc_subscription_test_harness.h index 9f00b2622afa..4cb6e737daf3 100644 --- a/test/common/config/grpc_subscription_test_harness.h +++ b/test/common/config/grpc_subscription_test_harness.h @@ -42,13 +42,12 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { GrpcSubscriptionTestHarness(std::chrono::milliseconds init_fetch_timeout) : method_descriptor_(Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.api.v2.EndpointDiscoveryService.StreamEndpoints")), - async_client_(new NiceMock()), timer_(new Event::MockTimer()) { + async_client_(new NiceMock()) { node_.set_id("fo0"); EXPECT_CALL(local_info_, node()).WillOnce(testing::ReturnRef(node_)); - EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([this](Event::TimerCb timer_cb) { - timer_cb_ = timer_cb; - return timer_; - })); + ttl_timer_ = new NiceMock(&dispatcher_); + + timer_ = new Event::MockTimer(&dispatcher_); mux_ = std::make_shared( local_info_, std::unique_ptr(async_client_), dispatcher_, @@ -59,7 +58,11 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { dispatcher_, init_fetch_timeout, false); } - ~GrpcSubscriptionTestHarness() override { EXPECT_CALL(async_stream_, sendMessageRaw_(_, false)); } + ~GrpcSubscriptionTestHarness() override { + EXPECT_CALL(async_stream_, sendMessageRaw_(_, false)); + EXPECT_CALL(dispatcher_, clearDeferredDeleteList()); + dispatcher_.clearDeferredDeleteList(); + } void expectSendMessage(const std::set& cluster_names, const std::string& version, bool expect_node = false) override { @@ -119,8 +122,10 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { const auto decoded_resources = TestUtility::decodeResources( *response, "cluster_name"); + EXPECT_CALL(callbacks_, onConfigUpdate(DecodedResourcesEq(decoded_resources.refvec_), version)) .WillOnce(ThrowOnRejectedConfig(accept)); + if (accept) { expectSendMessage(last_cluster_names_, version, false); version_ = version; @@ -180,7 +185,7 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { Event::MockDispatcher dispatcher_; Random::MockRandomGenerator random_; Event::MockTimer* timer_; - Event::TimerCb timer_cb_; + Event::MockTimer* ttl_timer_; envoy::config::core::v3::Node node_; NiceMock callbacks_; TestUtility::TestOpaqueResourceDecoderImpl diff --git a/test/common/config/subscription_factory_impl_test.cc b/test/common/config/subscription_factory_impl_test.cc index 65a7e1b7bd12..f7a27a0da449 100644 --- a/test/common/config/subscription_factory_impl_test.cc +++ b/test/common/config/subscription_factory_impl_test.cc @@ -311,7 +311,7 @@ TEST_F(SubscriptionFactoryTest, GrpcSubscription) { return async_client_factory; })); EXPECT_CALL(random_, random()); - EXPECT_CALL(dispatcher_, createTimer_(_)).Times(2); + EXPECT_CALL(dispatcher_, createTimer_(_)).Times(3); // onConfigUpdateFailed() should not be called for gRPC stream connection failure EXPECT_CALL(callbacks_, onConfigUpdateFailed(_, _)).Times(0); subscriptionFromConfigSource(config)->start({"static_cluster"}); diff --git a/test/common/config/subscription_impl_test.cc b/test/common/config/subscription_impl_test.cc index d8e48bcb820c..84bc88f6cea6 100644 --- a/test/common/config/subscription_impl_test.cc +++ b/test/common/config/subscription_impl_test.cc @@ -1,3 +1,4 @@ +#include #include #include "test/common/config/delta_subscription_test_harness.h" @@ -23,6 +24,10 @@ class SubscriptionImplTest : public testing::TestWithParam { public: SubscriptionImplTest() : SubscriptionImplTest(std::chrono::milliseconds(0)) {} SubscriptionImplTest(std::chrono::milliseconds init_fetch_timeout) { + initialize(init_fetch_timeout); + } + + void initialize(std::chrono::milliseconds init_fetch_timeout = std::chrono::milliseconds(0)) { switch (GetParam()) { case SubscriptionType::Grpc: test_harness_ = std::make_unique(init_fetch_timeout); diff --git a/test/common/config/ttl_test.cc b/test/common/config/ttl_test.cc new file mode 100644 index 000000000000..094c800ccba3 --- /dev/null +++ b/test/common/config/ttl_test.cc @@ -0,0 +1,84 @@ +#include + +#include "common/config/ttl.h" + +#include "test/mocks/event/mocks.h" +#include "test/test_common/simulated_time_system.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Config { +namespace { + +class TtlManagerTest : public testing::Test { +public: + TtlManagerTest() { test_time_.setSystemTime(std::chrono::milliseconds(0)); } + Event::MockDispatcher dispatcher_; + Event::SimulatedTimeSystem test_time_; +}; + +TEST_F(TtlManagerTest, BasicUsage) { + absl::optional> maybe_expired; + auto cb = [&](const auto& expired) { maybe_expired = expired; }; + auto ttl_timer = new Event::MockTimer(&dispatcher_); + TtlManager ttl(cb, dispatcher_, dispatcher_.timeSource()); + + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(1), _)); + EXPECT_CALL(*ttl_timer, enabled()); + ttl.add(std::chrono::milliseconds(1), "hello"); + + // Adding another expiration after the first one should not update the timer. + EXPECT_CALL(*ttl_timer, enabled()); + ttl.add(std::chrono::milliseconds(5), "not hello"); + + // Advance time by 3 ms, this should only trigger the first TTL. + test_time_.setSystemTime(std::chrono::milliseconds(3)); + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(2), _)); + ttl_timer->invokeCallback(); + EXPECT_EQ(maybe_expired.value(), std::vector({"hello"})); + + // Add in a TTL entry that comes before the scheduled timer run, this should update the timer. + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(1), _)); + ttl.add(std::chrono::milliseconds(1), "hello"); + + // Clearing the first TTL entry should reset the timer to match the next in line. + EXPECT_CALL(*ttl_timer, enabled()); + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(2), _)); + ttl.clear("hello"); + + // Removing all the TTLs should disable the timer. + EXPECT_CALL(*ttl_timer, disableTimer()); + ttl.clear("not hello"); +} + +TEST_F(TtlManagerTest, ScopedUpdate) { + absl::optional> maybe_expired; + auto cb = [&](const auto& expired) { maybe_expired = expired; }; + auto ttl_timer = new Event::MockTimer(&dispatcher_); + TtlManager ttl(cb, dispatcher_, dispatcher_.timeSource()); + + { + const auto scoped = ttl.scopedTtlUpdate(); + ttl.add(std::chrono::milliseconds(1), "hello"); + ttl.add(std::chrono::milliseconds(5), "not hello"); + + // There should only be a single update due to the scoping. + EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(1), _)); + EXPECT_CALL(*ttl_timer, enabled()); + } + + { + const auto scoped = ttl.scopedTtlUpdate(); + ttl.clear("hello"); + ttl.clear("not hello"); + + // There should only be a single update due to the scoping. + EXPECT_CALL(*ttl_timer, disableTimer()); + } +} +} // namespace +} // namespace Config +} // namespace Envoy \ No newline at end of file diff --git a/test/common/upstream/eds_speed_test.cc b/test/common/upstream/eds_speed_test.cc index 41b56317653a..79de684a015b 100644 --- a/test/common/upstream/eds_speed_test.cc +++ b/test/common/upstream/eds_speed_test.cc @@ -124,6 +124,7 @@ class EdsSpeedTest { auto response = std::make_unique(); response->set_type_url(type_url_); + response->set_version_info(fmt::format("version-{}", version_++)); auto* resource = response->mutable_resources()->Add(); resource->PackFrom(cluster_load_assignment); if (v2_config_) { @@ -141,6 +142,7 @@ class EdsSpeedTest { State& state_; const bool v2_config_; const std::string type_url_; + uint64_t version_{}; bool initialized_{}; Stats::IsolatedStoreImpl stats_; Config::SubscriptionStats subscription_stats_; diff --git a/test/extensions/clusters/aggregate/cluster_integration_test.cc b/test/extensions/clusters/aggregate/cluster_integration_test.cc index 566392702462..b99d0a3974d5 100644 --- a/test/extensions/clusters/aggregate/cluster_integration_test.cc +++ b/test/extensions/clusters/aggregate/cluster_integration_test.cc @@ -227,7 +227,7 @@ TEST_P(AggregateIntegrationTest, TwoClusters) { // Tell Envoy that cluster_1 is gone. EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "42", {}, {}, {})); sendDiscoveryResponse( - Config::TypeUrl::get().Cluster, {cluster2_}, {}, {FirstClusterName}, "42"); + Config::TypeUrl::get().Cluster, {cluster2_}, {}, {FirstClusterName}, "43"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); @@ -237,7 +237,7 @@ TEST_P(AggregateIntegrationTest, TwoClusters) { ASSERT_TRUE(codec_client_->waitForDisconnect()); // Tell Envoy that cluster_1 is back. - EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "42", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "43", {}, {}, {})); sendDiscoveryResponse( Config::TypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster1_}, {}, "413"); diff --git a/test/integration/cds_integration_test.cc b/test/integration/cds_integration_test.cc index 2fd76b992274..08e32d2a392c 100644 --- a/test/integration/cds_integration_test.cc +++ b/test/integration/cds_integration_test.cc @@ -196,7 +196,7 @@ TEST_P(CdsIntegrationTest, TwoClusters) { // Tell Envoy that cluster_1 is gone. EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "42", {}, {}, {})); sendDiscoveryResponse(Config::TypeUrl::get().Cluster, - {cluster2_}, {}, {ClusterName1}, "42"); + {cluster2_}, {}, {ClusterName1}, "43"); // We can continue the test once we're sure that Envoy's ClusterManager has made use of // the DiscoveryResponse that says cluster_1 is gone. test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); @@ -207,7 +207,7 @@ TEST_P(CdsIntegrationTest, TwoClusters) { ASSERT_TRUE(codec_client_->waitForDisconnect()); // Tell Envoy that cluster_1 is back. - EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "42", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "43", {}, {}, {})); sendDiscoveryResponse( Config::TypeUrl::get().Cluster, {cluster1_, cluster2_}, {cluster1_}, {}, "413"); diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 5eaaceff978f..1a0529d16319 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -392,6 +392,7 @@ alignas alignof alloc alloca +allocatable allocator allowlist allowlisted From 0b450f45554c08ca3a1131b0e7deec484250977c Mon Sep 17 00:00:00 2001 From: Sunjay Bhatia <5337253+sunjayBhatia@users.noreply.github.com> Date: Fri, 30 Oct 2020 17:37:42 -0400 Subject: [PATCH 002/117] Windows: listener_manager_impl_quic_only_test compiles (#13804) Accomodate abstraction of UDP GSO batch writer Signed-off-by: Sunjay Bhatia Co-authored-by: William A Rowe Jr --- .../quic_listeners/quiche/udp_gso_batch_writer_config.cc | 4 +--- test/server/BUILD | 7 +------ test/server/listener_manager_impl_quic_only_test.cc | 8 ++++++-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/source/extensions/quic_listeners/quiche/udp_gso_batch_writer_config.cc b/source/extensions/quic_listeners/quiche/udp_gso_batch_writer_config.cc index 95aac4447c1d..8b0b5461aeca 100644 --- a/source/extensions/quic_listeners/quiche/udp_gso_batch_writer_config.cc +++ b/source/extensions/quic_listeners/quiche/udp_gso_batch_writer_config.cc @@ -4,9 +4,7 @@ #include "common/api/os_sys_calls_impl.h" -#if defined(__linux__) #include "extensions/quic_listeners/quiche/udp_gso_batch_writer.h" -#endif namespace Envoy { namespace Quic { @@ -22,7 +20,7 @@ UdpGsoBatchWriterConfigFactory::createUdpPacketWriterFactory(const Protobuf::Mes "for UDP GSO. Reset udp_writer_config to default writer"); } -#if defined(__linux__) +#if UDP_GSO_BATCH_WRITER_COMPILETIME_SUPPORT return std::make_unique(); #else // On non-linux, `supportsUdpGso()` always returns false. diff --git a/test/server/BUILD b/test/server/BUILD index 1f459ab6f9ac..39f3d0df60e0 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -269,12 +269,7 @@ envoy_cc_test( envoy_cc_test( name = "listener_manager_impl_quic_only_test", srcs = ["listener_manager_impl_quic_only_test.cc"], - tags = [ - "nofips", - # Skipping as quiche quic_gso_batch_writer.h does not exist on Windows - # required by quic_stream_send_buffer.cc - "skip_on_windows", - ], + tags = ["nofips"], deps = [ ":listener_manager_impl_test_lib", ":utility_lib", diff --git a/test/server/listener_manager_impl_quic_only_test.cc b/test/server/listener_manager_impl_quic_only_test.cc index 891949861423..78d6600a529f 100644 --- a/test/server/listener_manager_impl_quic_only_test.cc +++ b/test/server/listener_manager_impl_quic_only_test.cc @@ -20,6 +20,7 @@ class ListenerManagerImplQuicOnlyTest : public ListenerManagerImplTest { public: NiceMock udp_gso_syscall_; TestThreadsafeSingletonInjector os_calls{&udp_gso_syscall_}; + Api::OsSysCallsImpl os_sys_calls_actual_; }; TEST_F(ListenerManagerImplQuicOnlyTest, QuicListenerFactoryAndSslContext) { @@ -61,7 +62,8 @@ reuse_port: true Network::Address::IpVersion::v4); envoy::config::listener::v3::Listener listener_proto = parseListenerFromV3Yaml(yaml); - ON_CALL(udp_gso_syscall_, supportsUdpGso()).WillByDefault(Return(true)); + ON_CALL(udp_gso_syscall_, supportsUdpGso()) + .WillByDefault(Return(os_sys_calls_actual_.supportsUdpGso())); EXPECT_CALL(server_.api_.random_, uuid()); expectCreateListenSocket(envoy::config::core::v3::SocketOption::STATE_PREBIND, #ifdef SO_RXQ_OVFL // SO_REUSEPORT is on as configured @@ -87,12 +89,14 @@ reuse_port: true /* expected_sockopt_name */ SO_REUSEPORT, /* expected_value */ 1, /* expected_num_calls */ 1); +#ifdef UDP_GRO if (Api::OsSysCallsSingleton::get().supportsUdpGro()) { expectSetsockopt(/* expected_sockopt_level */ SOL_UDP, /* expected_sockopt_name */ UDP_GRO, /* expected_value */ 1, /* expected_num_calls */ 1); } +#endif manager_->addOrUpdateListener(listener_proto, "", true); EXPECT_EQ(1u, manager_->listeners().size()); @@ -103,7 +107,7 @@ reuse_port: true Network::UdpPacketWriterPtr udp_packet_writer = manager_->listeners().front().get().udpPacketWriterFactory()->get().createUdpPacketWriter( listen_socket->ioHandle(), manager_->listeners()[0].get().listenerScope()); - EXPECT_TRUE(udp_packet_writer->isBatchMode()); + EXPECT_EQ(udp_packet_writer->isBatchMode(), Api::OsSysCallsSingleton::get().supportsUdpGso()); // No filter chain found with non-matching transport protocol. EXPECT_EQ(nullptr, findFilterChain(1234, "127.0.0.1", "", "tls", {}, "8.8.8.8", 111)); From 2a1ec68d9fe9179e67e20694fe9dd5a660d515cf Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Fri, 30 Oct 2020 19:20:40 -0400 Subject: [PATCH 003/117] tls: Use ThreadLocal::TypedSlot rather than ThreadLocal::Slot for every use of runOnAllThreads (#13819) Signed-off-by: Joshua Marantz --- include/envoy/thread_local/thread_local.h | 24 ++-- .../http/filter_config_discovery_impl.cc | 17 +-- .../http/filter_config_discovery_impl.h | 2 +- source/common/router/rds_impl.cc | 15 +-- source/common/router/rds_impl.h | 2 +- .../common/thread_local/thread_local_impl.cc | 13 +- .../common/thread_local/thread_local_impl.h | 6 +- .../common/upstream/cluster_manager_impl.cc | 69 ++++------- source/common/upstream/cluster_manager_impl.h | 2 +- .../extensions/clusters/aggregate/cluster.cc | 9 +- .../extensions/clusters/aggregate/cluster.h | 2 +- .../dynamic_forward_proxy/dns_cache_impl.cc | 13 +- .../dynamic_forward_proxy/dns_cache_impl.h | 2 +- source/extensions/filters/common/lua/lua.cc | 14 +-- source/extensions/filters/common/lua/lua.h | 15 +-- source/server/overload_manager_impl.cc | 112 +++++++++--------- source/server/overload_manager_impl.h | 4 +- .../thread_local/thread_local_impl_test.cc | 109 ++++++++++------- test/mocks/thread_local/mocks.h | 6 +- test/per_file_coverage.sh | 2 + 20 files changed, 219 insertions(+), 219 deletions(-) diff --git a/include/envoy/thread_local/thread_local.h b/include/envoy/thread_local/thread_local.h index b4828d6e6550..0ee6bab7be87 100644 --- a/include/envoy/thread_local/thread_local.h +++ b/include/envoy/thread_local/thread_local.h @@ -69,7 +69,7 @@ class Slot { * a shared_ptr. Thus, this is a flexible mechanism that can be used to share * the same data across all threads or to share different data on each thread. * - * NOTE: The initialize callback is not supposed to capture the Slot, or its owner. As the owner + * NOTE: The initialize callback is not supposed to capture the Slot, or its owner, as the owner * may be destructed in main thread before the update_cb gets called in a worker thread. */ using InitializeCb = std::function; @@ -85,12 +85,14 @@ class Slot { * the current value. * * NOTE: The update callback is not supposed to capture the Slot, or its - * owner. As the owner may be destructed in main thread before the update_cb + * owner, as the owner may be destructed in main thread before the update_cb * gets called in a worker thread. **/ using UpdateCb = std::function; virtual void runOnAllThreads(const UpdateCb& update_cb) PURE; - virtual void runOnAllThreads(const UpdateCb& update_cb, Event::PostCb complete_cb) PURE; + virtual void runOnAllThreads(const UpdateCb& update_cb, const Event::PostCb& complete_cb) PURE; + virtual void runOnAllThreads(const Event::PostCb& cb) PURE; + virtual void runOnAllThreads(const Event::PostCb& cb, const Event::PostCb& complete_cb) PURE; }; using SlotPtr = std::unique_ptr; @@ -108,11 +110,13 @@ class SlotAllocator { virtual SlotPtr allocateSlot() PURE; }; -// Provides a typesafe API for slots. +// Provides a typesafe API for slots. The slot data must be derived from +// ThreadLocalObject. If there is no slot data, you can instantiated TypedSlot +// with the default type param: TypedSlot<> tls_; // // TODO(jmarantz): Rename the Slot class to something like RawSlot, where the // only reference is from TypedSlot, which we can then rename to Slot. -template class TypedSlot { +template class TypedSlot { public: /** * Helper method to create a unique_ptr for a typed slot. This helper @@ -144,7 +148,7 @@ template class TypedSlot { * a shared_ptr. Thus, this is a flexible mechanism that can be used to share * the same data across all threads or to share different data on each thread. * - * NOTE: The initialize callback is not supposed to capture the Slot, or its owner. As the owner + * NOTE: The initialize callback is not supposed to capture the Slot, or its owner, as the owner * may be destructed in main thread before the update_cb gets called in a worker thread. */ using InitializeCb = std::function(Event::Dispatcher& dispatcher)>; @@ -163,14 +167,18 @@ template class TypedSlot { /** * UpdateCb is passed a mutable reference to the current stored data. * - * NOTE: The update callback is not supposed to capture the TypedSlot, or its owner. As the owner + * NOTE: The update callback is not supposed to capture the TypedSlot, or its owner, as the owner * may be destructed in main thread before the update_cb gets called in a worker thread. */ using UpdateCb = std::function; void runOnAllThreads(const UpdateCb& cb) { slot_->runOnAllThreads(makeSlotUpdateCb(cb)); } - void runOnAllThreads(const UpdateCb& cb, Event::PostCb complete_cb) { + void runOnAllThreads(const UpdateCb& cb, const Event::PostCb& complete_cb) { slot_->runOnAllThreads(makeSlotUpdateCb(cb), complete_cb); } + void runOnAllThreads(const Event::PostCb& cb) { slot_->runOnAllThreads(cb); } + void runOnAllThreads(const Event::PostCb& cb, const Event::PostCb& complete_cb) { + slot_->runOnAllThreads(cb, complete_cb); + } private: Slot::UpdateCb makeSlotUpdateCb(UpdateCb cb) { diff --git a/source/common/filter/http/filter_config_discovery_impl.cc b/source/common/filter/http/filter_config_discovery_impl.cc index 32b7e3e8b038..d6c7e1b6bb62 100644 --- a/source/common/filter/http/filter_config_discovery_impl.cc +++ b/source/common/filter/http/filter_config_discovery_impl.cc @@ -18,7 +18,7 @@ DynamicFilterConfigProviderImpl::DynamicFilterConfigProviderImpl( const std::set& require_type_urls, Server::Configuration::FactoryContext& factory_context) : subscription_(std::move(subscription)), require_type_urls_(require_type_urls), - tls_(factory_context.threadLocal().allocateSlot()), + tls_(factory_context.threadLocal()), init_target_("DynamicFilterConfigProviderImpl", [this]() { subscription_->start(); // This init target is used to activate the subscription but not wait @@ -27,9 +27,7 @@ DynamicFilterConfigProviderImpl::DynamicFilterConfigProviderImpl( init_target_.ready(); }) { subscription_->filter_config_providers_.insert(this); - tls_->set([](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(); - }); + tls_.set([](Event::Dispatcher&) { return std::make_shared(); }); } DynamicFilterConfigProviderImpl::~DynamicFilterConfigProviderImpl() { @@ -39,7 +37,7 @@ DynamicFilterConfigProviderImpl::~DynamicFilterConfigProviderImpl() { const std::string& DynamicFilterConfigProviderImpl::name() { return subscription_->name(); } absl::optional DynamicFilterConfigProviderImpl::config() { - return tls_->getTyped().config_; + return tls_.get().config_; } void DynamicFilterConfigProviderImpl::validateConfig( @@ -54,15 +52,12 @@ void DynamicFilterConfigProviderImpl::validateConfig( void DynamicFilterConfigProviderImpl::onConfigUpdate(Envoy::Http::FilterFactoryCb config, const std::string&, Config::ConfigAppliedCb cb) { - tls_->runOnAllThreads( - [config, cb](ThreadLocal::ThreadLocalObjectSharedPtr previous) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - auto prev_config = std::dynamic_pointer_cast(previous); - prev_config->config_ = config; + tls_.runOnAllThreads( + [config, cb](ThreadLocalConfig& tls) { + tls.config_ = config; if (cb) { cb(); } - return previous; }, [this, config]() { // This happens after all workers have discarded the previous config so it can be safely diff --git a/source/common/filter/http/filter_config_discovery_impl.h b/source/common/filter/http/filter_config_discovery_impl.h index 43a75542d138..4f79b244fba9 100644 --- a/source/common/filter/http/filter_config_discovery_impl.h +++ b/source/common/filter/http/filter_config_discovery_impl.h @@ -55,7 +55,7 @@ class DynamicFilterConfigProviderImpl : public FilterConfigProvider { // Currently applied configuration to ensure that the main thread deletes the last reference to // it. absl::optional current_config_{absl::nullopt}; - ThreadLocal::SlotPtr tls_; + ThreadLocal::TypedSlot tls_; // Local initialization target to ensure that the subscription starts in // case no warming is requested by any other filter config provider. diff --git a/source/common/router/rds_impl.cc b/source/common/router/rds_impl.cc index cf48e3551b62..62844e63888a 100644 --- a/source/common/router/rds_impl.cc +++ b/source/common/router/rds_impl.cc @@ -228,7 +228,7 @@ RdsRouteConfigProviderImpl::RdsRouteConfigProviderImpl( : subscription_(std::move(subscription)), config_update_info_(subscription_->routeConfigUpdate()), factory_context_(factory_context), validator_(factory_context.messageValidationContext().dynamicValidationVisitor()), - tls_(factory_context.threadLocal().allocateSlot()) { + tls_(factory_context.threadLocal()) { ConfigConstSharedPtr initial_config; if (config_update_info_->configInfo().has_value()) { initial_config = std::make_shared(config_update_info_->routeConfiguration(), @@ -236,7 +236,7 @@ RdsRouteConfigProviderImpl::RdsRouteConfigProviderImpl( } else { initial_config = std::make_shared(); } - tls_->set([initial_config](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.set([initial_config](Event::Dispatcher&) { return std::make_shared(initial_config); }); // It should be 1:1 mapping due to shared rds config. @@ -250,19 +250,12 @@ RdsRouteConfigProviderImpl::~RdsRouteConfigProviderImpl() { ASSERT(subscription_->routeConfigProviders().empty()); } -Router::ConfigConstSharedPtr RdsRouteConfigProviderImpl::config() { - return tls_->getTyped().config_; -} +Router::ConfigConstSharedPtr RdsRouteConfigProviderImpl::config() { return tls_->config_; } void RdsRouteConfigProviderImpl::onConfigUpdate() { ConfigConstSharedPtr new_config(new ConfigImpl(config_update_info_->routeConfiguration(), factory_context_, validator_, false)); - tls_->runOnAllThreads([new_config](ThreadLocal::ThreadLocalObjectSharedPtr previous) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - auto prev_config = std::dynamic_pointer_cast(previous); - prev_config->config_ = new_config; - return previous; - }); + tls_.runOnAllThreads([new_config](ThreadLocalConfig& tls) { tls.config_ = new_config; }); const auto aliases = config_update_info_->resourceIdsInLastVhdsUpdate(); // Regular (non-VHDS) RDS updates don't populate aliases fields in resources. diff --git a/source/common/router/rds_impl.h b/source/common/router/rds_impl.h index ca9e3c562741..10717c8bce3c 100644 --- a/source/common/router/rds_impl.h +++ b/source/common/router/rds_impl.h @@ -224,7 +224,7 @@ class RdsRouteConfigProviderImpl : public RouteConfigProvider, RouteConfigUpdatePtr& config_update_info_; Server::Configuration::ServerFactoryContext& factory_context_; ProtobufMessage::ValidationVisitor& validator_; - ThreadLocal::SlotPtr tls_; + ThreadLocal::TypedSlot tls_; std::list config_update_callbacks_; // A flag used to determine if this instance of RdsRouteConfigProviderImpl hasn't been // deallocated. Please also see a comment in requestVirtualHostsUpdate() method implementation. diff --git a/source/common/thread_local/thread_local_impl.cc b/source/common/thread_local/thread_local_impl.cc index a6c924ba9f9f..d07f41e26c60 100644 --- a/source/common/thread_local/thread_local_impl.cc +++ b/source/common/thread_local/thread_local_impl.cc @@ -41,7 +41,7 @@ SlotPtr InstanceImpl::allocateSlot() { InstanceImpl::SlotImpl::SlotImpl(InstanceImpl& parent, uint32_t index) : parent_(parent), index_(index), still_alive_guard_(std::make_shared(true)) {} -Event::PostCb InstanceImpl::SlotImpl::wrapCallback(Event::PostCb&& cb) { +Event::PostCb InstanceImpl::SlotImpl::wrapCallback(const Event::PostCb& cb) { // See the header file comments for still_alive_guard_ for the purpose of this capture and the // expired check below. // @@ -92,7 +92,7 @@ Event::PostCb InstanceImpl::SlotImpl::dataCallback(const UpdateCb& cb) { }; } -void InstanceImpl::SlotImpl::runOnAllThreads(const UpdateCb& cb, Event::PostCb complete_cb) { +void InstanceImpl::SlotImpl::runOnAllThreads(const UpdateCb& cb, const Event::PostCb& complete_cb) { parent_.runOnAllThreads(dataCallback(cb), complete_cb); } @@ -100,6 +100,15 @@ void InstanceImpl::SlotImpl::runOnAllThreads(const UpdateCb& cb) { parent_.runOnAllThreads(dataCallback(cb)); } +void InstanceImpl::SlotImpl::runOnAllThreads(const Event::PostCb& cb, + const Event::PostCb& complete_cb) { + parent_.runOnAllThreads(wrapCallback(cb), complete_cb); +} + +void InstanceImpl::SlotImpl::runOnAllThreads(const Event::PostCb& cb) { + parent_.runOnAllThreads(wrapCallback(cb)); +} + void InstanceImpl::SlotImpl::set(InitializeCb cb) { ASSERT(std::this_thread::get_id() == parent_.main_thread_id_); ASSERT(!parent_.shutdown_); diff --git a/source/common/thread_local/thread_local_impl.h b/source/common/thread_local/thread_local_impl.h index 888657ad5e81..4f5bb1b88125 100644 --- a/source/common/thread_local/thread_local_impl.h +++ b/source/common/thread_local/thread_local_impl.h @@ -36,7 +36,7 @@ class InstanceImpl : Logger::Loggable, public NonCopyable, pub struct SlotImpl : public Slot { SlotImpl(InstanceImpl& parent, uint32_t index); ~SlotImpl() override { parent_.removeSlot(index_); } - Event::PostCb wrapCallback(Event::PostCb&& cb); + Event::PostCb wrapCallback(const Event::PostCb& cb); Event::PostCb dataCallback(const UpdateCb& cb); static bool currentThreadRegisteredWorker(uint32_t index); static ThreadLocalObjectSharedPtr getWorker(uint32_t index); @@ -44,7 +44,9 @@ class InstanceImpl : Logger::Loggable, public NonCopyable, pub // ThreadLocal::Slot ThreadLocalObjectSharedPtr get() override; void runOnAllThreads(const UpdateCb& cb) override; - void runOnAllThreads(const UpdateCb& cb, Event::PostCb complete_cb) override; + void runOnAllThreads(const UpdateCb& cb, const Event::PostCb& complete_cb) override; + void runOnAllThreads(const Event::PostCb& cb) override; + void runOnAllThreads(const Event::PostCb& cb, const Event::PostCb& complete_cb) override; bool currentThreadRegistered() override; void set(InitializeCb cb) override; diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 924c0a6feb07..edb4c5b070d0 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -238,7 +238,7 @@ ClusterManagerImpl::ClusterManagerImpl( Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, ProtobufMessage::ValidationContext& validation_context, Api::Api& api, Http::Context& http_context, Grpc::Context& grpc_context) - : factory_(factory), runtime_(runtime), stats_(stats), tls_(tls.allocateSlot()), + : factory_(factory), runtime_(runtime), stats_(stats), tls_(tls), random_(api.randomGenerator()), bind_config_(bootstrap.cluster_manager().upstream_bind_config()), local_info_(local_info), cm_stats_(generateStats(stats)), @@ -362,8 +362,7 @@ ClusterManagerImpl::ClusterManagerImpl( // Once the initial set of static bootstrap clusters are created (including the local cluster), // we can instantiate the thread local cluster manager. - tls_->set([this, local_cluster_name = local_cluster_name_]( - Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.set([this, local_cluster_name = local_cluster_name_](Event::Dispatcher& dispatcher) { return std::make_shared(*this, dispatcher, local_cluster_name); }); @@ -661,13 +660,9 @@ void ClusterManagerImpl::clusterWarmingToActive(const std::string& cluster_name) } void ClusterManagerImpl::createOrUpdateThreadLocalCluster(ClusterData& cluster) { - tls_->runOnAllThreads([new_cluster = cluster.cluster_->info(), - thread_aware_lb_factory = cluster.loadBalancerFactory()]( - ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - ThreadLocalClusterManagerImpl& cluster_manager = - object->asType(); - + tls_.runOnAllThreads([new_cluster = cluster.cluster_->info(), + thread_aware_lb_factory = cluster.loadBalancerFactory()]( + ThreadLocalClusterManagerImpl& cluster_manager) { if (cluster_manager.thread_local_clusters_.count(new_cluster->name()) > 0) { ENVOY_LOG(debug, "updating TLS cluster {}", new_cluster->name()); } else { @@ -680,8 +675,6 @@ void ClusterManagerImpl::createOrUpdateThreadLocalCluster(ClusterData& cluster) for (auto& cb : cluster_manager.update_callbacks_) { cb->onClusterAddOrUpdate(*thread_local_cluster); } - - return object; }); } @@ -695,18 +688,13 @@ bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { active_clusters_.erase(existing_active_cluster); ENVOY_LOG(info, "removing cluster {}", cluster_name); - tls_->runOnAllThreads([cluster_name](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - ThreadLocalClusterManagerImpl& cluster_manager = - object->asType(); - + tls_.runOnAllThreads([cluster_name](ThreadLocalClusterManagerImpl& cluster_manager) { ASSERT(cluster_manager.thread_local_clusters_.count(cluster_name) == 1); ENVOY_LOG(debug, "removing TLS cluster {}", cluster_name); for (auto& cb : cluster_manager.update_callbacks_) { cb->onClusterRemoval(cluster_name); } cluster_manager.thread_local_clusters_.erase(cluster_name); - return object; }); } @@ -834,7 +822,7 @@ void ClusterManagerImpl::updateClusterCounts() { } ThreadLocalCluster* ClusterManagerImpl::get(absl::string_view cluster) { - auto& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry != cluster_manager.thread_local_clusters_.end()) { @@ -873,7 +861,7 @@ Http::ConnectionPool::Instance* ClusterManagerImpl::httpConnPoolForCluster(const std::string& cluster, ResourcePriority priority, absl::optional protocol, LoadBalancerContext* context) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry == cluster_manager.thread_local_clusters_.end()) { @@ -899,7 +887,7 @@ ClusterManagerImpl::httpConnPoolForCluster(const std::string& cluster, ResourceP Tcp::ConnectionPool::Instance* ClusterManagerImpl::tcpConnPoolForCluster(const std::string& cluster, ResourcePriority priority, LoadBalancerContext* context) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry == cluster_manager.thread_local_clusters_.end()) { @@ -924,12 +912,10 @@ ClusterManagerImpl::tcpConnPoolForCluster(const std::string& cluster, ResourcePr void ClusterManagerImpl::postThreadLocalDrainConnections(const Cluster& cluster, const HostVector& hosts_removed) { - tls_->runOnAllThreads( - [name = cluster.info()->name(), hosts_removed](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().removeHosts(name, hosts_removed); - return object; - }); + tls_.runOnAllThreads([name = cluster.info()->name(), + hosts_removed](ThreadLocalClusterManagerImpl& cluster_manager) { + cluster_manager.removeHosts(name, hosts_removed); + }); } void ClusterManagerImpl::postThreadLocalClusterUpdate(const Cluster& cluster, uint32_t priority, @@ -937,30 +923,25 @@ void ClusterManagerImpl::postThreadLocalClusterUpdate(const Cluster& cluster, ui const HostVector& hosts_removed) { const auto& host_set = cluster.prioritySet().hostSetsPerPriority()[priority]; - tls_->runOnAllThreads([name = cluster.info()->name(), priority, - update_params = HostSetImpl::updateHostsParams(*host_set), - locality_weights = host_set->localityWeights(), hosts_added, hosts_removed, - overprovisioning_factor = host_set->overprovisioningFactor()]( - ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().updateClusterMembership( - name, priority, update_params, locality_weights, hosts_added, hosts_removed, - overprovisioning_factor); - return object; + tls_.runOnAllThreads([name = cluster.info()->name(), priority, + update_params = HostSetImpl::updateHostsParams(*host_set), + locality_weights = host_set->localityWeights(), hosts_added, hosts_removed, + overprovisioning_factor = host_set->overprovisioningFactor()]( + ThreadLocalClusterManagerImpl& cluster_manager) { + cluster_manager.updateClusterMembership(name, priority, update_params, locality_weights, + hosts_added, hosts_removed, overprovisioning_factor); }); } void ClusterManagerImpl::postThreadLocalHealthFailure(const HostSharedPtr& host) { - tls_->runOnAllThreads([host](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().onHostHealthFailure(host); - return object; + tls_.runOnAllThreads([host](ThreadLocalClusterManagerImpl& cluster_manager) { + cluster_manager.onHostHealthFailure(host); }); } Host::CreateConnectionData ClusterManagerImpl::tcpConnForCluster(const std::string& cluster, LoadBalancerContext* context) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry == cluster_manager.thread_local_clusters_.end()) { @@ -988,7 +969,7 @@ Host::CreateConnectionData ClusterManagerImpl::tcpConnForCluster(const std::stri } Http::AsyncClient& ClusterManagerImpl::httpAsyncClientForCluster(const std::string& cluster) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry != cluster_manager.thread_local_clusters_.end()) { return entry->second->http_async_client_; @@ -999,7 +980,7 @@ Http::AsyncClient& ClusterManagerImpl::httpAsyncClientForCluster(const std::stri ClusterUpdateCallbacksHandlePtr ClusterManagerImpl::addThreadLocalClusterUpdateCallbacks(ClusterUpdateCallbacks& cb) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_->getTyped(); + ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); return std::make_unique(cb, cluster_manager.update_callbacks_); } diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 1aa14c4be78c..147bbdd4c35c 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -489,7 +489,7 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable tls_; Random::RandomGenerator& random_; protected: diff --git a/source/extensions/clusters/aggregate/cluster.cc b/source/extensions/clusters/aggregate/cluster.cc index 958c678d0202..52d99b036f9a 100644 --- a/source/extensions/clusters/aggregate/cluster.cc +++ b/source/extensions/clusters/aggregate/cluster.cc @@ -19,8 +19,8 @@ Cluster::Cluster(const envoy::config::cluster::v3::Cluster& cluster, Stats::ScopePtr&& stats_scope, ThreadLocal::SlotAllocator& tls, bool added_via_api) : Upstream::ClusterImplBase(cluster, runtime, factory_context, std::move(stats_scope), added_via_api), - cluster_manager_(cluster_manager), runtime_(runtime), random_(random), - tls_(tls.allocateSlot()), clusters_(config.clusters().begin(), config.clusters().end()) {} + cluster_manager_(cluster_manager), runtime_(runtime), random_(random), tls_(tls), + clusters_(config.clusters().begin(), config.clusters().end()) {} PriorityContextPtr Cluster::linearizePrioritySet(const std::function& skip_predicate) { @@ -91,15 +91,12 @@ void Cluster::startPreInit() { void Cluster::refresh(const std::function& skip_predicate) { // Post the priority set to worker threads. // TODO(mattklein123): Remove "this" capture. - tls_->runOnAllThreads([this, skip_predicate, cluster_name = this->info()->name()]( - ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.runOnAllThreads([this, skip_predicate, cluster_name = this->info()->name()]() { PriorityContextPtr priority_context = linearizePrioritySet(skip_predicate); Upstream::ThreadLocalCluster* cluster = cluster_manager_.get(cluster_name); ASSERT(cluster != nullptr); dynamic_cast(cluster->loadBalancer()) .refresh(std::move(priority_context)); - return object; }); } diff --git a/source/extensions/clusters/aggregate/cluster.h b/source/extensions/clusters/aggregate/cluster.h index 92adfe68f187..e5beeb46ef5a 100644 --- a/source/extensions/clusters/aggregate/cluster.h +++ b/source/extensions/clusters/aggregate/cluster.h @@ -54,7 +54,7 @@ class Cluster : public Upstream::ClusterImplBase, Upstream::ClusterUpdateCallbac Upstream::ClusterManager& cluster_manager_; Runtime::Loader& runtime_; Random::RandomGenerator& random_; - ThreadLocal::SlotPtr tls_; + ThreadLocal::TypedSlot<> tls_; const std::vector clusters_; private: diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc index d378534b7cfc..5d5e39e4b68b 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc @@ -21,8 +21,7 @@ DnsCacheImpl::DnsCacheImpl( : main_thread_dispatcher_(main_thread_dispatcher), dns_lookup_family_(Upstream::getDnsLookupFamilyFromEnum(config.dns_lookup_family())), resolver_(main_thread_dispatcher.createDnsResolver({}, config.use_tcp_for_dns_lookups())), - tls_slot_(tls.allocateSlot()), - scope_(root_scope.createScope(fmt::format("dns_cache.{}.", config.name()))), + tls_slot_(tls), scope_(root_scope.createScope(fmt::format("dns_cache.{}.", config.name()))), stats_(generateDnsCacheStats(*scope_)), resource_manager_(*scope_, loader, config.name(), config.dns_cache_circuit_breaker()), refresh_interval_(PROTOBUF_GET_MS_OR_DEFAULT(config, dns_refresh_rate, 60000)), @@ -32,7 +31,7 @@ DnsCacheImpl::DnsCacheImpl( config, refresh_interval_.count(), random)), host_ttl_(PROTOBUF_GET_MS_OR_DEFAULT(config, host_ttl, 300000)), max_hosts_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_hosts, 1024)) { - tls_slot_->set([](Event::Dispatcher&) { return std::make_shared(); }); + tls_slot_.set([](Event::Dispatcher&) { return std::make_shared(); }); updateTlsHostsMap(); } @@ -56,7 +55,7 @@ DnsCacheImpl::LoadDnsCacheEntryResult DnsCacheImpl::loadDnsCacheEntry(absl::string_view host, uint16_t default_port, LoadDnsCacheEntryCallbacks& callbacks) { ENVOY_LOG(debug, "thread local lookup for host '{}'", host); - auto& tls_host_info = tls_slot_->getTyped(); + ThreadLocalHostInfo& tls_host_info = tls_slot_.get(); auto tls_host = tls_host_info.host_map_->find(host); if (tls_host != tls_host_info.host_map_->end()) { ENVOY_LOG(debug, "thread local hit for host '{}'", host); @@ -276,10 +275,8 @@ void DnsCacheImpl::updateTlsHostsMap() { } } - tls_slot_->runOnAllThreads([new_host_map](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().updateHostMap(new_host_map); - return object; + tls_slot_.runOnAllThreads([new_host_map](ThreadLocalHostInfo& local_host_info) { + local_host_info.updateHostMap(new_host_map); }); } diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h index b0f3444fdac6..bf5106374702 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h @@ -140,7 +140,7 @@ class DnsCacheImpl : public DnsCache, Logger::Loggable tls_slot_; Stats::ScopePtr scope_; DnsCacheStats stats_; std::list update_callbacks_; diff --git a/source/extensions/filters/common/lua/lua.cc b/source/extensions/filters/common/lua/lua.cc index b5be8771cdac..e1fea1f96d38 100644 --- a/source/extensions/filters/common/lua/lua.cc +++ b/source/extensions/filters/common/lua/lua.cc @@ -49,7 +49,7 @@ void Coroutine::resume(int num_args, const std::function& yield_callback } ThreadLocalState::ThreadLocalState(const std::string& code, ThreadLocal::SlotAllocator& tls) - : tls_slot_(tls.allocateSlot()) { + : tls_slot_(ThreadLocal::TypedSlot::makeUnique(tls)) { // First verify that the supplied code can be parsed. CSmartPtr state(lua_open()); @@ -61,20 +61,17 @@ ThreadLocalState::ThreadLocalState(const std::string& code, ThreadLocal::SlotAll } // Now initialize on all threads. - tls_slot_->set([code](Event::Dispatcher&) { - return ThreadLocal::ThreadLocalObjectSharedPtr{new LuaThreadLocal(code)}; - }); + tls_slot_->set([code](Event::Dispatcher&) { return std::make_shared(code); }); } int ThreadLocalState::getGlobalRef(uint64_t slot) { - LuaThreadLocal& tls = tls_slot_->getTyped(); + LuaThreadLocal& tls = tls_slot_->get(); ASSERT(tls.global_slots_.size() > slot); return tls.global_slots_[slot]; } uint64_t ThreadLocalState::registerGlobal(const std::string& global) { - tls_slot_->runOnAllThreads([global](ThreadLocal::ThreadLocalObjectSharedPtr previous) { - LuaThreadLocal& tls = *std::dynamic_pointer_cast(previous); + tls_slot_->runOnAllThreads([global](LuaThreadLocal& tls) { lua_getglobal(tls.state_.get(), global.c_str()); if (lua_isfunction(tls.state_.get(), -1)) { tls.global_slots_.push_back(luaL_ref(tls.state_.get(), LUA_REGISTRYINDEX)); @@ -83,14 +80,13 @@ uint64_t ThreadLocalState::registerGlobal(const std::string& global) { lua_pop(tls.state_.get(), 1); tls.global_slots_.push_back(LUA_REFNIL); } - return previous; }); return current_global_slot_++; } CoroutinePtr ThreadLocalState::createCoroutine() { - lua_State* state = tls_slot_->getTyped().state_.get(); + lua_State* state = tls_slot_->get().state_.get(); return std::make_unique(std::make_pair(lua_newthread(state), state)); } diff --git a/source/extensions/filters/common/lua/lua.h b/source/extensions/filters/common/lua/lua.h index 726b6c149e16..ac84ac14af7e 100644 --- a/source/extensions/filters/common/lua/lua.h +++ b/source/extensions/filters/common/lua/lua.h @@ -386,27 +386,22 @@ class ThreadLocalState : Logger::Loggable { * all threaded workers. */ template void registerType() { - tls_slot_->runOnAllThreads([](ThreadLocal::ThreadLocalObjectSharedPtr previous) { - LuaThreadLocal& tls = *std::dynamic_pointer_cast(previous); - T::registerType(tls.state_.get()); - return previous; - }); + tls_slot_->runOnAllThreads([](LuaThreadLocal& tls) { T::registerType(tls.state_.get()); }); } /** * Return the number of bytes used by the runtime. */ uint64_t runtimeBytesUsed() { - uint64_t bytes_used = - lua_gc(tls_slot_->getTyped().state_.get(), LUA_GCCOUNT, 0) * 1024; - bytes_used += lua_gc(tls_slot_->getTyped().state_.get(), LUA_GCCOUNTB, 0); + uint64_t bytes_used = lua_gc(tls_slot_->get().state_.get(), LUA_GCCOUNT, 0) * 1024; + bytes_used += lua_gc(tls_slot_->get().state_.get(), LUA_GCCOUNTB, 0); return bytes_used; } /** * Force a full runtime GC. */ - void runtimeGC() { lua_gc(tls_slot_->getTyped().state_.get(), LUA_GCCOLLECT, 0); } + void runtimeGC() { lua_gc(tls_slot_->get().state_.get(), LUA_GCCOLLECT, 0); } private: struct LuaThreadLocal : public ThreadLocal::ThreadLocalObject { @@ -416,7 +411,7 @@ class ThreadLocalState : Logger::Loggable { std::vector global_slots_; }; - ThreadLocal::SlotPtr tls_slot_; + ThreadLocal::TypedSlotPtr tls_slot_; uint64_t current_global_slot_{}; }; diff --git a/source/server/overload_manager_impl.cc b/source/server/overload_manager_impl.cc index fe6a6746361b..c128caf41d12 100644 --- a/source/server/overload_manager_impl.cc +++ b/source/server/overload_manager_impl.cc @@ -22,6 +22,54 @@ namespace Envoy { namespace Server { +/** + * Thread-local copy of the state of each configured overload action. + */ +class ThreadLocalOverloadStateImpl : public ThreadLocalOverloadState { +public: + ThreadLocalOverloadStateImpl( + Event::ScaledRangeTimerManagerPtr scaled_timer_manager, + const NamedOverloadActionSymbolTable& action_symbol_table, + const absl::flat_hash_map& timer_minimums) + : action_symbol_table_(action_symbol_table), timer_minimums_(timer_minimums), + actions_(action_symbol_table.size(), OverloadActionState(0)), + scaled_timer_action_(action_symbol_table.lookup(OverloadActionNames::get().ReduceTimeouts)), + scaled_timer_manager_(std::move(scaled_timer_manager)) {} + + const OverloadActionState& getState(const std::string& action) override { + if (const auto symbol = action_symbol_table_.lookup(action); symbol != absl::nullopt) { + return actions_[symbol->index()]; + } + return always_inactive_; + } + + Event::TimerPtr createScaledTimer(OverloadTimerType timer_type, + Event::TimerCb callback) override { + auto minimum_it = timer_minimums_.find(timer_type); + const Event::ScaledTimerMinimum minimum = + minimum_it != timer_minimums_.end() ? minimum_it->second + : Event::ScaledTimerMinimum(Event::ScaledMinimum(1.0)); + return scaled_timer_manager_->createTimer(minimum, std::move(callback)); + } + + void setState(NamedOverloadActionSymbolTable::Symbol action, OverloadActionState state) { + actions_[action.index()] = state; + if (scaled_timer_action_.has_value() && scaled_timer_action_.value() == action) { + scaled_timer_manager_->setScaleFactor(1 - state.value()); + } + } + +private: + static const OverloadActionState always_inactive_; + const NamedOverloadActionSymbolTable& action_symbol_table_; + const absl::flat_hash_map& timer_minimums_; + std::vector actions_; + absl::optional scaled_timer_action_; + const Event::ScaledRangeTimerManagerPtr scaled_timer_manager_; +}; + +const OverloadActionState ThreadLocalOverloadStateImpl::always_inactive_{0.0}; + namespace { class ThresholdTriggerImpl final : public OverloadAction::Trigger { @@ -75,54 +123,6 @@ class ScaledTriggerImpl final : public OverloadAction::Trigger { OverloadActionState state_; }; -/** - * Thread-local copy of the state of each configured overload action. - */ -class ThreadLocalOverloadStateImpl : public ThreadLocalOverloadState { -public: - ThreadLocalOverloadStateImpl( - Event::ScaledRangeTimerManagerPtr scaled_timer_manager, - const NamedOverloadActionSymbolTable& action_symbol_table, - const absl::flat_hash_map& timer_minimums) - : action_symbol_table_(action_symbol_table), timer_minimums_(timer_minimums), - actions_(action_symbol_table.size(), OverloadActionState(0)), - scaled_timer_action_(action_symbol_table.lookup(OverloadActionNames::get().ReduceTimeouts)), - scaled_timer_manager_(std::move(scaled_timer_manager)) {} - - const OverloadActionState& getState(const std::string& action) override { - if (const auto symbol = action_symbol_table_.lookup(action); symbol != absl::nullopt) { - return actions_[symbol->index()]; - } - return always_inactive_; - } - - Event::TimerPtr createScaledTimer(OverloadTimerType timer_type, - Event::TimerCb callback) override { - auto minimum_it = timer_minimums_.find(timer_type); - const Event::ScaledTimerMinimum minimum = - minimum_it != timer_minimums_.end() ? minimum_it->second - : Event::ScaledTimerMinimum(Event::ScaledMinimum(1.0)); - return scaled_timer_manager_->createTimer(minimum, std::move(callback)); - } - - void setState(NamedOverloadActionSymbolTable::Symbol action, OverloadActionState state) { - actions_[action.index()] = state; - if (scaled_timer_action_.has_value() && scaled_timer_action_.value() == action) { - scaled_timer_manager_->setScaleFactor(1 - state.value()); - } - } - -private: - static const OverloadActionState always_inactive_; - const NamedOverloadActionSymbolTable& action_symbol_table_; - const absl::flat_hash_map& timer_minimums_; - std::vector actions_; - absl::optional scaled_timer_action_; - const Event::ScaledRangeTimerManagerPtr scaled_timer_manager_; -}; - -const OverloadActionState ThreadLocalOverloadStateImpl::always_inactive_{0.0}; - Stats::Counter& makeCounter(Stats::Scope& scope, absl::string_view a, absl::string_view b) { Stats::StatNameManagedStorage stat_name(absl::StrCat("overload.", a, ".", b), scope.symbolTable()); @@ -275,7 +275,7 @@ OverloadManagerImpl::OverloadManagerImpl(Event::Dispatcher& dispatcher, Stats::S const envoy::config::overload::v3::OverloadManager& config, ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api) - : started_(false), dispatcher_(dispatcher), tls_(slot_allocator.allocateSlot()), + : started_(false), dispatcher_(dispatcher), tls_(slot_allocator), refresh_interval_( std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(config, refresh_interval, 1000))) { Configuration::ResourceMonitorFactoryContextImpl context(dispatcher, api, validation_visitor); @@ -331,7 +331,7 @@ void OverloadManagerImpl::start() { ASSERT(!started_); started_ = true; - tls_->set([this](Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.set([this](Event::Dispatcher& dispatcher) { return std::make_shared(createScaledRangeTimerManager(dispatcher), action_symbol_table_, timer_minimums_); }); @@ -384,9 +384,7 @@ bool OverloadManagerImpl::registerForAction(const std::string& action, return true; } -ThreadLocalOverloadState& OverloadManagerImpl::getThreadLocalOverloadState() { - return tls_->getTyped(); -} +ThreadLocalOverloadState& OverloadManagerImpl::getThreadLocalOverloadState() { return tls_.get(); } Event::ScaledRangeTimerManagerPtr OverloadManagerImpl::createScaledRangeTimerManager(Event::Dispatcher& dispatcher) const { @@ -443,13 +441,11 @@ void OverloadManagerImpl::flushResourceUpdates() { absl::flat_hash_map>(); std::swap(*shared_updates, state_updates_to_flush_); - tls_->runOnAllThreads( - [updates = std::move(shared_updates)](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.runOnAllThreads( + [updates = std::move(shared_updates)](ThreadLocalOverloadStateImpl& overload_state) { for (const auto& [action, state] : *updates) { - object->asType().setState(action, state); + overload_state.setState(action, state); } - return object; }); } diff --git a/source/server/overload_manager_impl.h b/source/server/overload_manager_impl.h index 59902bc08e75..55f0ee7517b0 100644 --- a/source/server/overload_manager_impl.h +++ b/source/server/overload_manager_impl.h @@ -100,6 +100,8 @@ class NamedOverloadActionSymbolTable { std::vector names_; }; +class ThreadLocalOverloadStateImpl; + class OverloadManagerImpl : Logger::Loggable, public OverloadManager { public: OverloadManagerImpl(Event::Dispatcher& dispatcher, Stats::Scope& stats_scope, @@ -161,7 +163,7 @@ class OverloadManagerImpl : Logger::Loggable, public OverloadM bool started_; Event::Dispatcher& dispatcher_; - ThreadLocal::SlotPtr tls_; + ThreadLocal::TypedSlot tls_; NamedOverloadActionSymbolTable action_symbol_table_; const std::chrono::milliseconds refresh_interval_; Event::TimerPtr timer_; diff --git a/test/common/thread_local/thread_local_impl_test.cc b/test/common/thread_local/thread_local_impl_test.cc index 0e203e77a94f..6963a02e586b 100644 --- a/test/common/thread_local/thread_local_impl_test.cc +++ b/test/common/thread_local/thread_local_impl_test.cc @@ -95,58 +95,81 @@ struct ThreadStatus { bool all_threads_complete_ = false; }; -TEST_F(ThreadLocalInstanceImplTest, CallbackNotInvokedAfterDeletion) { - InSequence s; +// Test helper class for running two similar tests, covering 4 variants of +// runOnAllThreads: with/without completion callback, and with/without the slot +// data as an argument. +class CallbackNotInvokedAfterDeletionTest : public ThreadLocalInstanceImplTest { +protected: + CallbackNotInvokedAfterDeletionTest() : slot_(tls_.allocateSlot()) { + EXPECT_CALL(thread_dispatcher_, post(_)).Times(4).WillRepeatedly(Invoke([&](Event::PostCb cb) { + // Holds the posted callback. + holder_.push_back(cb); + })); + + slot_->set([this](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { + // Callbacks happen on the main thread but not the workers, so track the total. + total_callbacks_++; + return nullptr; + }); + } + + ~CallbackNotInvokedAfterDeletionTest() override { + EXPECT_FALSE(thread_status_.all_threads_complete_); + EXPECT_EQ(2, total_callbacks_); + slot_.reset(); + EXPECT_EQ(freeSlotIndexesListSize(), 1); + + EXPECT_CALL(main_dispatcher_, post(_)); + while (!holder_.empty()) { + holder_.front()(); + holder_.pop_front(); + } + EXPECT_EQ(2, total_callbacks_); + EXPECT_TRUE(thread_status_.all_threads_complete_); + + tls_.shutdownGlobalThreading(); + } // Allocate a slot and invoke all callback variants. Hold all callbacks and destroy the slot. // Make sure that recycling happens appropriately. - SlotPtr slot = tls_.allocateSlot(); - - std::list holder; - EXPECT_CALL(thread_dispatcher_, post(_)).Times(4).WillRepeatedly(Invoke([&](Event::PostCb cb) { - // Holds the posted callback. - holder.push_back(cb); - })); + SlotPtr slot_; + std::list holder_; + uint32_t total_callbacks_{0}; + ThreadStatus thread_status_; +}; - uint32_t total_callbacks = 0; - slot->set([&total_callbacks](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - // Callbacks happen on the main thread but not the workers, so track the total. - total_callbacks++; - return nullptr; - }); - slot->runOnAllThreads([&total_callbacks](ThreadLocal::ThreadLocalObjectSharedPtr) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - // Callbacks happen on the main thread but not the workers, so track the total. - total_callbacks++; - return nullptr; - }); - ThreadStatus thread_status; - slot->runOnAllThreads( - [&thread_status]( - ThreadLocal::ThreadLocalObjectSharedPtr) -> ThreadLocal::ThreadLocalObjectSharedPtr { - ++thread_status.thread_local_calls_; +TEST_F(CallbackNotInvokedAfterDeletionTest, WithArg) { + InSequence s; + slot_->runOnAllThreads( + [this](ThreadLocal::ThreadLocalObjectSharedPtr) -> ThreadLocal::ThreadLocalObjectSharedPtr { + // Callbacks happen on the main thread but not the workers, so track the total. + total_callbacks_++; + return nullptr; + }); + slot_->runOnAllThreads( + [this](ThreadLocal::ThreadLocalObjectSharedPtr) -> ThreadLocal::ThreadLocalObjectSharedPtr { + ++thread_status_.thread_local_calls_; return nullptr; }, - [&thread_status]() -> void { + [this]() -> void { // Callbacks happen on the main thread but not the workers. - EXPECT_EQ(thread_status.thread_local_calls_, 1); - thread_status.all_threads_complete_ = true; + EXPECT_EQ(thread_status_.thread_local_calls_, 1); + thread_status_.all_threads_complete_ = true; }); - EXPECT_FALSE(thread_status.all_threads_complete_); - - EXPECT_EQ(2, total_callbacks); - slot.reset(); - EXPECT_EQ(freeSlotIndexesListSize(), 1); - - EXPECT_CALL(main_dispatcher_, post(_)); - while (!holder.empty()) { - holder.front()(); - holder.pop_front(); - } - EXPECT_EQ(2, total_callbacks); - EXPECT_TRUE(thread_status.all_threads_complete_); +} - tls_.shutdownGlobalThreading(); +TEST_F(CallbackNotInvokedAfterDeletionTest, WithoutArg) { + InSequence s; + slot_->runOnAllThreads([this]() { + // Callbacks happen on the main thread but not the workers, so track the total. + total_callbacks_++; + }); + slot_->runOnAllThreads([this]() { ++thread_status_.thread_local_calls_; }, + [this]() -> void { + // Callbacks happen on the main thread but not the workers. + EXPECT_EQ(thread_status_.thread_local_calls_, 1); + thread_status_.all_threads_complete_ = true; + }); } // Test that the update callback is called as expected, for the worker and main threads. diff --git a/test/mocks/thread_local/mocks.h b/test/mocks/thread_local/mocks.h index dc6518c5068a..bd8699fe24c5 100644 --- a/test/mocks/thread_local/mocks.h +++ b/test/mocks/thread_local/mocks.h @@ -63,10 +63,14 @@ class MockInstance : public Instance { void runOnAllThreads(const UpdateCb& cb) override { parent_.runOnAllThreads([cb, this]() { parent_.data_[index_] = cb(parent_.data_[index_]); }); } - void runOnAllThreads(const UpdateCb& cb, Event::PostCb main_callback) override { + void runOnAllThreads(const UpdateCb& cb, const Event::PostCb& main_callback) override { parent_.runOnAllThreads([cb, this]() { parent_.data_[index_] = cb(parent_.data_[index_]); }, main_callback); } + void runOnAllThreads(const Event::PostCb& cb) override { parent_.runOnAllThreads(cb); } + void runOnAllThreads(const Event::PostCb& cb, const Event::PostCb& main_callback) override { + parent_.runOnAllThreads(cb, main_callback); + } void set(InitializeCb cb) override { if (parent_.defer_data) { diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index 9fd20dc2dec5..13526f0eebb2 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -11,6 +11,8 @@ declare -a KNOWN_LOW_COVERAGE=( "source/common/singleton:95.1" "source/common/api:72.9" "source/common/api/posix:71.8" +"source/common/filter:96.3" +"source/common/filter/http:96.3" "source/common/init:96.2" "source/common/json:90.6" "source/common/thread:0.0" # Death tests don't report LCOV From b51a36346b8b0e71a80d2b9e239f82b6c657103c Mon Sep 17 00:00:00 2001 From: asraa Date: Fri, 30 Oct 2020 20:33:54 -0400 Subject: [PATCH 004/117] [test] Add upstream flood tests and track inbound header frames (#13784) Signed-off-by: Asra Ali --- source/common/http/http2/codec_impl.cc | 2 +- .../http2_flood_integration_test.cc | 104 ++++++++++++++---- 2 files changed, 85 insertions(+), 21 deletions(-) diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index b24fb48cd678..c3be00073dba 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -1408,6 +1408,7 @@ Status ClientConnectionImpl::onBeginHeaders(const nghttp2_frame* frame) { RELEASE_ASSERT(frame->headers.cat == NGHTTP2_HCAT_RESPONSE || frame->headers.cat == NGHTTP2_HCAT_HEADERS, ""); + RETURN_IF_ERROR(trackInboundFrames(&frame->hd, frame->headers.padlen)); if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) { StreamImpl* stream = getStream(frame->hd.stream_id); stream->allocTrailers(); @@ -1479,7 +1480,6 @@ ServerConnectionImpl::ServerConnectionImpl( Status ServerConnectionImpl::onBeginHeaders(const nghttp2_frame* frame) { // For a server connection, we should never get push promise frames. ASSERT(frame->hd.type == NGHTTP2_HEADERS); - RETURN_IF_ERROR(trackInboundFrames(&frame->hd, frame->headers.padlen)); if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { diff --git a/test/integration/http2_flood_integration_test.cc b/test/integration/http2_flood_integration_test.cc index f54735c5f45e..d43643e122c1 100644 --- a/test/integration/http2_flood_integration_test.cc +++ b/test/integration/http2_flood_integration_test.cc @@ -100,11 +100,13 @@ class Http2FloodMitigationTest : public SocketInterfaceSwap, } protected: + bool initializeUpstreamFloodTest(); std::vector serializeFrames(const Http2Frame& frame, uint32_t num_frames); void floodServer(const Http2Frame& frame, const std::string& flood_stat, uint32_t num_frames); void floodServer(absl::string_view host, absl::string_view path, Http2Frame::ResponseStatus expected_http_status, const std::string& flood_stat, uint32_t num_frames); + void floodClient(const Http2Frame& frame, uint32_t num_frames, const std::string& flood_stat); void setNetworkConnectionBufferSize(); void beginSession() override; @@ -116,6 +118,21 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, Http2FloodMitigationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); +bool Http2FloodMitigationTest::initializeUpstreamFloodTest() { + if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { + // Upstream flood checks are not implemented in the old codec based on exceptions + return false; + } + config_helper_.addRuntimeOverride("envoy.reloadable_features.upstream_http2_flood_checks", + "true"); + setDownstreamProtocol(Http::CodecClient::Type::HTTP2); + setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); + // set lower upstream outbound frame limits to make tests run faster + config_helper_.setUpstreamOutboundFramesLimits(AllFrameFloodLimit, ControlFrameFloodLimit); + initialize(); + return true; +} + void Http2FloodMitigationTest::setNetworkConnectionBufferSize() { // nghttp2 library has its own internal mitigation for outbound control frames (see // NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM). The default nghttp2 mitigation threshold of 1K is modified @@ -176,6 +193,34 @@ void Http2FloodMitigationTest::floodServer(const Http2Frame& frame, const std::s test_server_->waitForCounterGe("http.config_test.downstream_cx_delayed_close_timeout", 1); } +// Send header only request, flood client, and verify that the upstream is disconnected and client +// receives 503. +void Http2FloodMitigationTest::floodClient(const Http2Frame& frame, uint32_t num_frames, + const std::string& flood_stat) { + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + waitForNextUpstreamRequest(); + + // Make Envoy's writes into the upstream connection to return EAGAIN + writev_matcher_->setSourcePort( + fake_upstream_connection_->connection().remoteAddress()->ip()->port()); + + auto buf = serializeFrames(frame, num_frames); + + writev_matcher_->setWritevReturnsEgain(); + auto* upstream = fake_upstreams_.front().get(); + ASSERT_TRUE(upstream->rawWriteConnection(0, std::string(buf.begin(), buf.end()))); + + // Upstream connection should be disconnected + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + // Downstream client should receive 503 since upstream did not send response headers yet + response->waitForEndStream(); + EXPECT_EQ("503", response->headers().getStatusValue()); + if (!flood_stat.empty()) { + EXPECT_EQ(1, test_server_->counter(flood_stat)->value()); + } +} + // Verify that the server detects the flood using specified request parameters. void Http2FloodMitigationTest::floodServer(absl::string_view host, absl::string_view path, Http2Frame::ResponseStatus expected_http_status, @@ -1064,38 +1109,57 @@ TEST_P(Http2FloodMitigationTest, ZerolenHeaderAllowed) { } TEST_P(Http2FloodMitigationTest, UpstreamPingFlood) { - if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { - // Upstream flood checks are not implemented in the old codec based on exceptions + if (!initializeUpstreamFloodTest()) { + return; + } + + floodClient(Http2Frame::makePingFrame(), ControlFrameFloodLimit + 1, + "cluster.cluster_0.http2.outbound_control_flood"); +} + +TEST_P(Http2FloodMitigationTest, UpstreamSettings) { + if (!initializeUpstreamFloodTest()) { + return; + } + + floodClient(Http2Frame::makeEmptySettingsFrame(), ControlFrameFloodLimit + 1, + "cluster.cluster_0.http2.outbound_control_flood"); +} + +TEST_P(Http2FloodMitigationTest, UpstreamWindowUpdate) { + if (!initializeUpstreamFloodTest()) { + return; + } + + floodClient(Http2Frame::makeWindowUpdateFrame(0, 1), 4, + "cluster.cluster_0.http2.inbound_window_update_frames_flood"); +} + +TEST_P(Http2FloodMitigationTest, UpstreamEmptyHeaders) { + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, ""); + auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(0); + cluster->mutable_http2_protocol_options() + ->mutable_max_consecutive_inbound_frames_with_empty_payload() + ->set_value(0); + }); + if (!initializeUpstreamFloodTest()) { return; } - config_helper_.addRuntimeOverride("envoy.reloadable_features.upstream_http2_flood_checks", - "true"); - setDownstreamProtocol(Http::CodecClient::Type::HTTP2); - setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); - // set lower upstream outbound frame limits to make tests run faster - config_helper_.setUpstreamOutboundFramesLimits(AllFrameFloodLimit, ControlFrameFloodLimit); - initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); waitForNextUpstreamRequest(); auto* upstream = fake_upstreams_.front().get(); - // Make Envoy's writes into the upstream connection to return EAGAIN - writev_matcher_->setSourcePort( - fake_upstream_connection_->connection().remoteAddress()->ip()->port()); - - auto buf = serializeFrames(Http2Frame::makePingFrame(), ControlFrameFloodLimit + 1); - - writev_matcher_->setWritevReturnsEgain(); + auto buf = Http2Frame::makeEmptyHeadersFrame(Http2Frame::makeClientStreamId(0), + Http2Frame::HeadersFlags::None); ASSERT_TRUE(upstream->rawWriteConnection(0, std::string(buf.begin(), buf.end()))); - // Upstream connection should be disconnected - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - // Downstream client should receive 503 since upstream did not send response headers yet response->waitForEndStream(); EXPECT_EQ("503", response->headers().getStatusValue()); - EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.http2.outbound_control_flood")->value()); + EXPECT_EQ(1, + test_server_->counter("cluster.cluster_0.http2.inbound_empty_frames_flood")->value()); } } // namespace Envoy From 0226d0e084d832ce24ee6303f5cb2fc01ec4970b Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Sun, 1 Nov 2020 11:07:27 -0500 Subject: [PATCH 005/117] docs: remove spurious comment marker (#13848) Signed-off-by: Snow Pettersen --- api/envoy/service/discovery/v3/discovery.proto | 2 +- api/envoy/service/discovery/v4alpha/discovery.proto | 2 +- generated_api_shadow/envoy/service/discovery/v3/discovery.proto | 2 +- .../envoy/service/discovery/v4alpha/discovery.proto | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/envoy/service/discovery/v3/discovery.proto b/api/envoy/service/discovery/v3/discovery.proto index 860ad5ab6d0f..c63d9e74355f 100644 --- a/api/envoy/service/discovery/v3/discovery.proto +++ b/api/envoy/service/discovery/v3/discovery.proto @@ -284,7 +284,7 @@ message Resource { // light-weight "heartbeat" updates to keep a resource with a TTL alive. // // The TTL feature is meant to support configurations that should be removed in the event of - // a management server // failure. For example, the feature may be used for fault injection + // a management server failure. For example, the feature may be used for fault injection // testing where the fault injection should be terminated in the event that Envoy loses contact // with the management server. google.protobuf.Duration ttl = 6; diff --git a/api/envoy/service/discovery/v4alpha/discovery.proto b/api/envoy/service/discovery/v4alpha/discovery.proto index 2abf261bcd16..45f9a524db04 100644 --- a/api/envoy/service/discovery/v4alpha/discovery.proto +++ b/api/envoy/service/discovery/v4alpha/discovery.proto @@ -288,7 +288,7 @@ message Resource { // light-weight "heartbeat" updates to keep a resource with a TTL alive. // // The TTL feature is meant to support configurations that should be removed in the event of - // a management server // failure. For example, the feature may be used for fault injection + // a management server failure. For example, the feature may be used for fault injection // testing where the fault injection should be terminated in the event that Envoy loses contact // with the management server. google.protobuf.Duration ttl = 6; diff --git a/generated_api_shadow/envoy/service/discovery/v3/discovery.proto b/generated_api_shadow/envoy/service/discovery/v3/discovery.proto index 860ad5ab6d0f..c63d9e74355f 100644 --- a/generated_api_shadow/envoy/service/discovery/v3/discovery.proto +++ b/generated_api_shadow/envoy/service/discovery/v3/discovery.proto @@ -284,7 +284,7 @@ message Resource { // light-weight "heartbeat" updates to keep a resource with a TTL alive. // // The TTL feature is meant to support configurations that should be removed in the event of - // a management server // failure. For example, the feature may be used for fault injection + // a management server failure. For example, the feature may be used for fault injection // testing where the fault injection should be terminated in the event that Envoy loses contact // with the management server. google.protobuf.Duration ttl = 6; diff --git a/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto b/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto index 2abf261bcd16..45f9a524db04 100644 --- a/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto +++ b/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto @@ -288,7 +288,7 @@ message Resource { // light-weight "heartbeat" updates to keep a resource with a TTL alive. // // The TTL feature is meant to support configurations that should be removed in the event of - // a management server // failure. For example, the feature may be used for fault injection + // a management server failure. For example, the feature may be used for fault injection // testing where the fault injection should be terminated in the event that Envoy loses contact // with the management server. google.protobuf.Duration ttl = 6; From d249ee44735ccde83fdf67701721e305050833bd Mon Sep 17 00:00:00 2001 From: Zach Reyes <39203661+zasweq@users.noreply.github.com> Date: Mon, 2 Nov 2020 09:49:27 -0500 Subject: [PATCH 006/117] [fuzz] Added Round Robin load balancer fuzz test (#13722) * Added Round Robin load balancer fuzz test Signed-off-by: Zach --- test/common/upstream/BUILD | 40 +++++++++ .../upstream/load_balancer_fuzz_base.cc | 5 +- .../common/upstream/load_balancer_fuzz_base.h | 16 ++-- .../round_robin-high-number-of-hosts | 62 ++++++++++++++ ...d_robin-with-locality-high-number-of-hosts | 62 ++++++++++++++ .../round_robin_local_priority | 48 +++++++++++ .../round_robin_local_priority_update_hosts | 48 +++++++++++ .../round_robin_no_hosts | 28 +++++++ .../round_robin_normal | 45 ++++++++++ .../round_robin_load_balancer_fuzz.proto | 12 +++ .../round_robin_load_balancer_fuzz_test.cc | 45 ++++++++++ .../zone_aware_load_balancer_fuzz.proto | 16 ++++ .../zone_aware_load_balancer_fuzz_base.cc | 84 +++++++++++++++++++ .../zone_aware_load_balancer_fuzz_base.h | 54 ++++++++++++ 14 files changed, 557 insertions(+), 8 deletions(-) create mode 100644 test/common/upstream/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts create mode 100644 test/common/upstream/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts create mode 100644 test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority create mode 100644 test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts create mode 100644 test/common/upstream/round_robin_load_balancer_corpus/round_robin_no_hosts create mode 100644 test/common/upstream/round_robin_load_balancer_corpus/round_robin_normal create mode 100644 test/common/upstream/round_robin_load_balancer_fuzz.proto create mode 100644 test/common/upstream/round_robin_load_balancer_fuzz_test.cc create mode 100644 test/common/upstream/zone_aware_load_balancer_fuzz.proto create mode 100644 test/common/upstream/zone_aware_load_balancer_fuzz_base.cc create mode 100644 test/common/upstream/zone_aware_load_balancer_fuzz_base.h diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index e1561389bacb..849159b5fa72 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -737,3 +737,43 @@ envoy_cc_fuzz_test( "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) + +envoy_proto_library( + name = "zone_aware_load_balancer_fuzz_proto", + srcs = ["zone_aware_load_balancer_fuzz.proto"], + deps = [ + "//test/common/upstream:load_balancer_fuzz_proto", + ], +) + +envoy_cc_test_library( + name = "zone_aware_load_balancer_fuzz_lib", + srcs = ["zone_aware_load_balancer_fuzz_base.cc"], + hdrs = ["zone_aware_load_balancer_fuzz_base.h"], + deps = [ + ":load_balancer_fuzz_lib", + ":zone_aware_load_balancer_fuzz_proto_cc_proto", + "//test/mocks/upstream:host_set_mocks", + "//test/mocks/upstream:priority_set_mocks", + ], +) + +envoy_proto_library( + name = "round_robin_load_balancer_fuzz_proto", + srcs = ["round_robin_load_balancer_fuzz.proto"], + deps = [ + "//test/common/upstream:zone_aware_load_balancer_fuzz_proto", + ], +) + +envoy_cc_fuzz_test( + name = "round_robin_load_balancer_fuzz_test", + srcs = ["round_robin_load_balancer_fuzz_test.cc"], + corpus = "//test/common/upstream:round_robin_load_balancer_corpus", + deps = [ + ":round_robin_load_balancer_fuzz_proto_cc_proto", + ":utility_lib", + ":zone_aware_load_balancer_fuzz_lib", + "//test/fuzz:utility_lib", + ], +) diff --git a/test/common/upstream/load_balancer_fuzz_base.cc b/test/common/upstream/load_balancer_fuzz_base.cc index 1bf578b214df..9ad2d538fbc4 100644 --- a/test/common/upstream/load_balancer_fuzz_base.cc +++ b/test/common/upstream/load_balancer_fuzz_base.cc @@ -40,6 +40,8 @@ void LoadBalancerFuzzBase::initializeASingleHostSet( // all priority levels. while (hosts_made < std::min(num_hosts_in_priority_level, MaxNumHostsPerPriorityLevel) && port < 60000) { + // Make sure no health flags persisted from previous fuzz iterations + ASSERT(initialized_hosts_[port]->health() == Host::Health::Healthy); host_set.hosts_.push_back(initialized_hosts_[port]); ++port; ++hosts_made; @@ -246,9 +248,10 @@ void LoadBalancerFuzzBase::replay( break; } } + clearStaticHostsState(); } -void LoadBalancerFuzzBase::clearStaticHostsHealthFlags() { +void LoadBalancerFuzzBase::clearStaticHostsState() { // The only outstanding health flags set are those that are set from hosts being placed in // degraded and excluded. Thus, use the priority set pointer to know which flags to clear. for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); diff --git a/test/common/upstream/load_balancer_fuzz_base.h b/test/common/upstream/load_balancer_fuzz_base.h index fc9abcffbf0f..4e2fc3f30b52 100644 --- a/test/common/upstream/load_balancer_fuzz_base.h +++ b/test/common/upstream/load_balancer_fuzz_base.h @@ -22,8 +22,8 @@ class LoadBalancerFuzzBase { // Initializes load balancer components shared amongst every load balancer, random_, and // priority_set_ - void initializeLbComponents(const test::common::upstream::LoadBalancerTestCase& input); - void + virtual void initializeLbComponents(const test::common::upstream::LoadBalancerTestCase& input); + virtual void updateHealthFlagsForAHostSet(const uint64_t host_priority, const uint32_t num_healthy_hosts, const uint32_t num_degraded_hosts, const uint32_t num_excluded_hosts, const Protobuf::RepeatedField& random_bytestring); @@ -33,10 +33,9 @@ class LoadBalancerFuzzBase { // and lb_->chooseHost(). void prefetch(); void chooseHost(); - ~LoadBalancerFuzzBase() = default; void replay(const Protobuf::RepeatedPtrField& actions); - void clearStaticHostsHealthFlags(); + virtual void clearStaticHostsState(); // These public objects shared amongst all types of load balancers will be used to construct load // balancers in specific load balancer fuzz classes @@ -45,12 +44,14 @@ class LoadBalancerFuzzBase { NiceMock runtime_; Random::PsuedoRandomGenerator64 random_; NiceMock priority_set_; - std::unique_ptr lb_; + std::unique_ptr lb_; -private: + virtual ~LoadBalancerFuzzBase() = default; + +protected: // Untrusted upstreams don't have the ability to change the host set size, so keep it constant // over the fuzz iteration. - void + virtual void initializeASingleHostSet(const test::common::upstream::SetupPriorityLevel& setup_priority_level, const uint8_t priority_level, uint16_t& port); @@ -58,6 +59,7 @@ class LoadBalancerFuzzBase { // random uint64 against this number. uint8_t num_priority_levels_ = 0; +private: // This map used when updating health flags - making sure the health flags are updated hosts in // localities Key - index of host within full host list, value - locality level host at index is // in diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts new file mode 100644 index 000000000000..c0c794caa55f --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts @@ -0,0 +1,62 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + num_degraded_hosts: 3 + num_excluded_hosts: 4 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 1000 + num_hosts_locality_b: 500 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 300 + num_hosts_locality_b: 1200 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +seed_for_prng: 1 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01\x02" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts new file mode 100644 index 000000000000..c3e6091cf7ff --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts @@ -0,0 +1,62 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + num_degraded_hosts: 3 + num_excluded_hosts: 4 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 1000 + num_hosts_locality_b: 500 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 300 + num_hosts_locality_b: 1200 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +seed_for_prng: 1 +} +need_local_priority_set: true +random_bytestring_for_weights: "\x01\x02" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority new file mode 100644 index 000000000000..f3502762e74a --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority @@ -0,0 +1,48 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + random_bytestring: 1 + random_bytestring: 2 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 2 + random_bytestring: 1 + random_bytestring: 2 +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: 1 + random_bytestring: 2 +} +seed_for_prng: 1 +} +need_local_priority_set: true +random_bytestring_for_weights: "\x01" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts new file mode 100644 index 000000000000..99fe34c09ed0 --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts @@ -0,0 +1,48 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 50 + random_bytestring: 1 + random_bytestring: 2 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 100 + random_bytestring: 1 + random_bytestring: 2 +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: 1 + random_bytestring: 2 +} +seed_for_prng: 1 +} +need_local_priority_set: true +random_bytestring_for_weights: "\x01" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_no_hosts b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_no_hosts new file mode 100644 index 000000000000..41e64fbf19b1 --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_no_hosts @@ -0,0 +1,28 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: "\x01\x02" +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: "\x01\x02" +} +seed_for_prng: 2 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01" +} diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_normal b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_normal new file mode 100644 index 000000000000..6086e00c5d87 --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin_normal @@ -0,0 +1,45 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + random_bytestring: "\x01\x02" + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 2 + random_bytestring: "\x01\x02" +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: "\x01\x02" +} +seed_for_prng: 1 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01" +} diff --git a/test/common/upstream/round_robin_load_balancer_fuzz.proto b/test/common/upstream/round_robin_load_balancer_fuzz.proto new file mode 100644 index 000000000000..a5ecf67ccc1c --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_fuzz.proto @@ -0,0 +1,12 @@ + +syntax = "proto3"; + +package test.common.upstream; + +import "validate/validate.proto"; +import "test/common/upstream/zone_aware_load_balancer_fuzz.proto"; + +message RoundRobinLoadBalancerTestCase { + test.common.upstream.ZoneAwareLoadBalancerTestCase zone_aware_load_balancer_test_case = 1 + [(validate.rules).message.required = true]; +} diff --git a/test/common/upstream/round_robin_load_balancer_fuzz_test.cc b/test/common/upstream/round_robin_load_balancer_fuzz_test.cc new file mode 100644 index 000000000000..4c1809a9a223 --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_fuzz_test.cc @@ -0,0 +1,45 @@ +#include + +#include "test/common/upstream/round_robin_load_balancer_fuzz.pb.validate.h" +#include "test/common/upstream/zone_aware_load_balancer_fuzz_base.h" +#include "test/fuzz/fuzz_runner.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Upstream { + +DEFINE_PROTO_FUZZER(const test::common::upstream::RoundRobinLoadBalancerTestCase& input) { + try { + TestUtility::validate(input); + } catch (const ProtoValidationException& e) { + ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what()); + return; + } + + const test::common::upstream::ZoneAwareLoadBalancerTestCase& zone_aware_load_balancer_test_case = + input.zone_aware_load_balancer_test_case(); + + ZoneAwareLoadBalancerFuzzBase zone_aware_load_balancer_fuzz = ZoneAwareLoadBalancerFuzzBase( + zone_aware_load_balancer_test_case.need_local_priority_set(), + zone_aware_load_balancer_test_case.random_bytestring_for_weights()); + zone_aware_load_balancer_fuzz.initializeLbComponents( + zone_aware_load_balancer_test_case.load_balancer_test_case()); + + try { + zone_aware_load_balancer_fuzz.lb_ = std::make_unique( + zone_aware_load_balancer_fuzz.priority_set_, + zone_aware_load_balancer_fuzz.local_priority_set_.get(), + zone_aware_load_balancer_fuzz.stats_, zone_aware_load_balancer_fuzz.runtime_, + zone_aware_load_balancer_fuzz.random_, + zone_aware_load_balancer_test_case.load_balancer_test_case().common_lb_config()); + } catch (EnvoyException& e) { + ENVOY_LOG_MISC(debug, "EnvoyException; {}", e.what()); + return; + } + + zone_aware_load_balancer_fuzz.replay( + zone_aware_load_balancer_test_case.load_balancer_test_case().actions()); +} + +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz.proto b/test/common/upstream/zone_aware_load_balancer_fuzz.proto new file mode 100644 index 000000000000..fa08df063c2e --- /dev/null +++ b/test/common/upstream/zone_aware_load_balancer_fuzz.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package test.common.upstream; + +import "validate/validate.proto"; +import "test/common/upstream/load_balancer_fuzz.proto"; + +message ZoneAwareLoadBalancerTestCase { + test.common.upstream.LoadBalancerTestCase load_balancer_test_case = 1 + [(validate.rules).message.required = true]; + // This determines whether ZoneAwareLoadBalancerFuzzBase will generate a local priority set to + // pass into Zone Aware constructor + bool need_local_priority_set = 2; + // This is used to determine the weights of each host - will wrap around if runs out of space + bytes random_bytestring_for_weights = 3 [(validate.rules).bytes = {min_len: 1, max_len: 2048}]; +} diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc b/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc new file mode 100644 index 000000000000..0f95e3d8c2f6 --- /dev/null +++ b/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc @@ -0,0 +1,84 @@ +#include "zone_aware_load_balancer_fuzz_base.h" + +#include "test/mocks/upstream/host_set.h" + +namespace Envoy { +namespace Upstream { + +void ZoneAwareLoadBalancerFuzzBase::initializeASingleHostSet( + const test::common::upstream::SetupPriorityLevel& setup_priority_level, + const uint8_t priority_level, uint16_t& port) { + LoadBalancerFuzzBase::initializeASingleHostSet(setup_priority_level, priority_level, port); + // Update local priority set if it exists - will mean load balancer is zone aware and has decided + // to construct local priority set + if (priority_level == 0 && local_priority_set_) { + // TODO(zasweq): Perhaps fuzz the local priority set as a distinct host set? rather than + // making it P = 0 of main Priority Set + const MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); + const HostVector empty_host_vector; + local_priority_set_->updateHosts(0, HostSetImpl::updateHostsParams(host_set), {}, + empty_host_vector, empty_host_vector, absl::nullopt); + } +} + +void ZoneAwareLoadBalancerFuzzBase::updateHealthFlagsForAHostSet( + const uint64_t host_priority, const uint32_t num_healthy_hosts, + const uint32_t num_degraded_hosts, const uint32_t num_excluded_hosts, + const Protobuf::RepeatedField& random_bytestring) { + LoadBalancerFuzzBase::updateHealthFlagsForAHostSet( + host_priority, num_healthy_hosts, num_degraded_hosts, num_excluded_hosts, random_bytestring); + // Update local priority set if it exists - will mean load balancer is zone aware and has decided + // to construct local priority set + const uint8_t priority_of_host_set = host_priority % num_priority_levels_; + if (priority_of_host_set == 0 && local_priority_set_) { + const MockHostSet& host_set = *priority_set_.getMockHostSet(priority_of_host_set); + const HostVector empty_host_vector; + local_priority_set_->updateHosts(0, HostSetImpl::updateHostsParams(host_set), {}, + empty_host_vector, empty_host_vector, absl::nullopt); + } +} + +void ZoneAwareLoadBalancerFuzzBase::initializeLbComponents( + const test::common::upstream::LoadBalancerTestCase& input) { + LoadBalancerFuzzBase::initializeLbComponents(input); + setupZoneAwareLoadBalancingSpecificLogic(); +} + +void ZoneAwareLoadBalancerFuzzBase::setupZoneAwareLoadBalancingSpecificLogic() { + // Having 3 possible weights, 1, 2, and 3 to provide the state space at least some variation + // in regards to weights, which do affect the load balancing algorithm. Cap the amount of + // weights at 3 for simplicity's sake + stats_.max_host_weight_.set(3UL); + addWeightsToHosts(); +} + +// Initialize the host set with weights once at setup +void ZoneAwareLoadBalancerFuzzBase::addWeightsToHosts() { + // Iterate through all the current host sets and update weights for each + for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); + for (auto& host : host_set.hosts_) { + // Make sure no weights persisted from previous fuzz iterations + ASSERT(host->weight() == 1); + host->weight( + (random_bytestring_[index_of_random_bytestring_ % random_bytestring_.length()] % 3) + 1); + ++index_of_random_bytestring_; + } + } +} + +void ZoneAwareLoadBalancerFuzzBase::clearStaticHostsState() { + LoadBalancerFuzzBase::clearStaticHostsState(); + // Clear out any set weights + for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); + for (auto& host : host_set.hosts_) { + host->weight(1); + } + } +} + +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz_base.h b/test/common/upstream/zone_aware_load_balancer_fuzz_base.h new file mode 100644 index 000000000000..ecb373fd123b --- /dev/null +++ b/test/common/upstream/zone_aware_load_balancer_fuzz_base.h @@ -0,0 +1,54 @@ +#include "test/mocks/upstream/priority_set.h" + +#include "load_balancer_fuzz_base.h" + +namespace Envoy { +namespace Upstream { +class ZoneAwareLoadBalancerFuzzBase : public LoadBalancerFuzzBase { +public: + ZoneAwareLoadBalancerFuzzBase(bool need_local_cluster, const std::string& random_bytestring) + : random_bytestring_(random_bytestring) { + if (need_local_cluster) { + local_priority_set_ = std::make_shared(); + local_priority_set_->getOrCreateHostSet(0); + } + } + + ~ZoneAwareLoadBalancerFuzzBase() override { + // This deletes the load balancer first. If constructed with a local priority set, load balancer + // with reference local priority set on destruction. Since local priority set is in a base + // class, it will be initialized second and thus destructed first. Thus, in order to avoid a use + // after free, you must delete the load balancer before deleting the priority set. + lb_.reset(); + } + + // These extend base class logic in order to handle local_priority_set_ if applicable. + void + initializeASingleHostSet(const test::common::upstream::SetupPriorityLevel& setup_priority_level, + const uint8_t priority_level, uint16_t& port) override; + + void initializeLbComponents(const test::common::upstream::LoadBalancerTestCase& input) override; + + void updateHealthFlagsForAHostSet( + const uint64_t host_priority, const uint32_t num_healthy_hosts, + const uint32_t num_degraded_hosts, const uint32_t num_excluded_hosts, + const Protobuf::RepeatedField& random_bytestring) override; + + void setupZoneAwareLoadBalancingSpecificLogic(); + + void addWeightsToHosts(); + + // If fuzzing Zone Aware Load Balancers, local priority set will get constructed sometimes. If not + // constructed, a local_priority_set_.get() call will return a nullptr. + std::shared_ptr local_priority_set_; + + void clearStaticHostsState() override; + +private: + // This bytestring will be iterated through representing randomness in order to choose + // weights for hosts. + const std::string random_bytestring_; + uint32_t index_of_random_bytestring_ = 0; +}; +} // namespace Upstream +} // namespace Envoy From cec00e61755b38c3115ef9bef0bbeab52baee9d1 Mon Sep 17 00:00:00 2001 From: Zach Reyes <39203661+zasweq@users.noreply.github.com> Date: Mon, 2 Nov 2020 10:15:32 -0500 Subject: [PATCH 007/117] [fuzz] Added status frame support to h2 fuzz test (#13807) * Added status frame support to h2 fuzz test Signed-off-by: Zach --- test/integration/h2_capture_fuzz.proto | 7 +++ .../h2_corpus/status_test_overflowed_status | 45 +++++++++++++++++++ test/integration/h2_fuzz.cc | 7 +++ 3 files changed, 59 insertions(+) create mode 100644 test/integration/h2_corpus/status_test_overflowed_status diff --git a/test/integration/h2_capture_fuzz.proto b/test/integration/h2_capture_fuzz.proto index 3943c3f7293e..d22d02ca0b85 100644 --- a/test/integration/h2_capture_fuzz.proto +++ b/test/integration/h2_capture_fuzz.proto @@ -132,6 +132,12 @@ message H2FrameMetadata { Metadata metadata = 3; } +// A header that contains an arbitrary status +message H2FrameStatus { + bytes status = 1; + uint32 stream_index = 2; +} + message H2TestFrame { // These values map to the frame creation methods in: // test/common/http/http2/http2_frame.h @@ -151,6 +157,7 @@ message H2TestFrame { H2FrameRequest request = 13; H2FramePostRequest post_request = 14; H2FrameGeneric generic = 15; + H2FrameStatus status = 16; H2FrameMetadata metadata = 77; } } diff --git a/test/integration/h2_corpus/status_test_overflowed_status b/test/integration/h2_corpus/status_test_overflowed_status new file mode 100644 index 000000000000..24bd81262da0 --- /dev/null +++ b/test/integration/h2_corpus/status_test_overflowed_status @@ -0,0 +1,45 @@ +events { + downstream_send_event { + h2_frames { + settings { + flags: NONE + } + } + h2_frames { + settings { + flags: ACK + } + } + h2_frames { + request { + stream_index: 1 + host: "host" + path: "/path/to/long/url" + } + } + } +} +events { + upstream_send_event { + h2_frames { + settings { + flags: NONE + } + } + h2_frames { + settings { + flags: ACK + } + } + } +} +events { + upstream_send_event { + h2_frames { + status { + value: 11111111111111111111111111111111111 + } + stream_index: 0 + } + } +} diff --git a/test/integration/h2_fuzz.cc b/test/integration/h2_fuzz.cc index cbaefd5e1617..ba3c39adfc31 100644 --- a/test/integration/h2_fuzz.cc +++ b/test/integration/h2_fuzz.cc @@ -146,6 +146,13 @@ void H2FuzzIntegrationTest::sendFrame(const test::integration::H2TestFrame& prot Http2Frame::makeMetadataFrameFromMetadataMap(stream_idx, metadata_map, metadata_flags); break; } + case test::integration::H2TestFrame::kStatus: { + const std::string status = proto_frame.status().status(); + const uint32_t stream_idx = proto_frame.status().stream_index(); + ENVOY_LOG_MISC(trace, "Sending status frame"); + h2_frame = Http2Frame::makeHeadersFrameWithStatus(status, stream_idx); + break; + } case test::integration::H2TestFrame::kGeneric: { const absl::string_view frame_bytes = proto_frame.generic().frame_bytes(); ENVOY_LOG_MISC(trace, "Sending generic frame"); From 3a6e4a26480721207799c502c666874cc8b3784b Mon Sep 17 00:00:00 2001 From: Joseph Griego Date: Mon, 2 Nov 2020 10:29:06 -0500 Subject: [PATCH 008/117] Fix divide-by-zero in GradientController (#13823) Fixes https://oss-fuzz.com/testcase-detail/6268043707285504 Signed-off-by: Joseph Griego --- .../adaptive_concurrency/controller/gradient_controller.cc | 2 +- ...zz-testcase-minimized-filter_fuzz_test-6268043707285504 | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 test/extensions/filters/http/common/fuzz/filter_corpus/clusterfuzz-testcase-minimized-filter_fuzz_test-6268043707285504 diff --git a/source/extensions/filters/http/adaptive_concurrency/controller/gradient_controller.cc b/source/extensions/filters/http/adaptive_concurrency/controller/gradient_controller.cc index d8063ec6723f..654e0aa252a9 100644 --- a/source/extensions/filters/http/adaptive_concurrency/controller/gradient_controller.cc +++ b/source/extensions/filters/http/adaptive_concurrency/controller/gradient_controller.cc @@ -132,7 +132,7 @@ std::chrono::milliseconds GradientController::applyJitter(std::chrono::milliseco return interval; } - const uint32_t jitter_range_ms = interval.count() * jitter_pct; + const uint32_t jitter_range_ms = std::ceil(interval.count() * jitter_pct); return std::chrono::milliseconds(interval.count() + (random_.random() % jitter_range_ms)); } diff --git a/test/extensions/filters/http/common/fuzz/filter_corpus/clusterfuzz-testcase-minimized-filter_fuzz_test-6268043707285504 b/test/extensions/filters/http/common/fuzz/filter_corpus/clusterfuzz-testcase-minimized-filter_fuzz_test-6268043707285504 new file mode 100644 index 000000000000..ef35670946f4 --- /dev/null +++ b/test/extensions/filters/http/common/fuzz/filter_corpus/clusterfuzz-testcase-minimized-filter_fuzz_test-6268043707285504 @@ -0,0 +1,7 @@ +config { + name: "envoy.filters.http.adaptive_concurrency" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.http.adaptive_concurrency.v3.AdaptiveConcurrency" + value: "\n\'\022\n\032\010\010\334\340\240\001\020\200~\032\031\n\010\010\334\340\230\001\020\200~\022\002\010\001\032\t\t\000\000\000\000\000\010\000\000" + } +} From 5ecbca2d9470775b8875a5e8e54d8a6aef06e1a3 Mon Sep 17 00:00:00 2001 From: Andrea Di Lisio Date: Mon, 2 Nov 2020 17:00:50 +0100 Subject: [PATCH 009/117] docs: cluster configuration updated directly on Envoy container's filesystem (#13854) Signed-off-by: Andrea Di Lisio --- examples/dynamic-config-fs/verify.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dynamic-config-fs/verify.sh b/examples/dynamic-config-fs/verify.sh index 1799bd9575f9..f9371cdf30b0 100755 --- a/examples/dynamic-config-fs/verify.sh +++ b/examples/dynamic-config-fs/verify.sh @@ -19,7 +19,7 @@ curl -s http://localhost:19000/config_dump \ | grep '"address": "service1"' run_log "Set upstream to service2" -sed -i s/service1/service2/ configs/cds.yaml +docker-compose exec -T proxy sed -i s/service1/service2/ /var/lib/envoy/cds.yaml run_log "Check for response comes from service2 upstream" responds_with \ From 633d5fc4f02200905a20788751b59a7162e68420 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Mon, 2 Nov 2020 08:09:15 -0800 Subject: [PATCH 010/117] tls: allow 4096-bit RSA keys in FIPS mode. (#13851) 4096-bit keys are allowed per latest Implementation Guide: https://csrc.nist.gov/csrc/media/projects/cryptographic-module-validation-program/documents/fips140-2/fips1402ig.pdf Based on a similar change in BoringSSL: https://boringssl.googlesource.com/boringssl/+/80e3f957e46bf2af828d67065bc19d9471f368fd Signed-off-by: Piotr Sikora --- docs/root/version_history/current.rst | 1 + .../quic_listeners/quiche/envoy_quic_utils.cc | 6 +++--- .../extensions/transport_sockets/tls/context_impl.cc | 5 +++-- .../transport_sockets/tls/context_impl_test.cc | 12 ++---------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 0f331b036a46..80916b08c6d9 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -50,6 +50,7 @@ New Features * ratelimit: added support for use of various :ref:`metadata ` as a ratelimit action. * ratelimit: added :ref:`disable_x_envoy_ratelimited_header ` option to disable `X-Envoy-RateLimited` header. * tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections ` action to reject incoming TCP connections. +* tls: added support for RSA certificates with 4096-bit keys in FIPS mode. * xds: added support for resource TTLs. A TTL is specified on the :ref:`Resource `. For SotW, a :ref:`Resource ` can be embedded in the list of resources to specify the TTL. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index c7a32fbf317d..473c01e617d6 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -170,9 +170,9 @@ int deduceSignatureAlgorithmFromPublicKey(const EVP_PKEY* public_key, std::strin ASSERT(rsa_public_key != nullptr); const unsigned rsa_key_length = RSA_size(rsa_public_key); #ifdef BORINGSSL_FIPS - if (rsa_key_length != 2048 / 8 && rsa_key_length != 3072 / 8) { - *error_details = "Invalid leaf cert, only RSA certificates with 2048-bit or 3072-bit keys " - "are supported in FIPS mode"; + if (rsa_key_length != 2048 / 8 && rsa_key_length != 3072 / 8 && rsa_key_length != 4096 / 8) { + *error_details = "Invalid leaf cert, only RSA certificates with 2048-bit, 3072-bit or " + "4096-bit keys are supported in FIPS mode"; break; } #else diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 8be424e42d06..cfcf1d6ce16a 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -364,10 +364,11 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c ASSERT(rsa_public_key != nullptr); const unsigned rsa_key_length = RSA_size(rsa_public_key); #ifdef BORINGSSL_FIPS - if (rsa_key_length != 2048 / 8 && rsa_key_length != 3072 / 8) { + if (rsa_key_length != 2048 / 8 && rsa_key_length != 3072 / 8 && + rsa_key_length != 4096 / 8) { throw EnvoyException( fmt::format("Failed to load certificate chain from {}, only RSA certificates with " - "2048-bit or 3072-bit keys are supported in FIPS mode", + "2048-bit, 3072-bit or 4096-bit keys are supported in FIPS mode", ctx.cert_chain_file_path_)); } #else diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index 575689e4b2ea..b48b4f273330 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -1247,7 +1247,7 @@ TEST_F(ClientContextConfigImplTest, RSA1024Cert) { std::string error_msg( "Failed to load certificate chain from .*selfsigned_rsa_1024_cert.pem, only RSA certificates " #ifdef BORINGSSL_FIPS - "with 2048-bit or 3072-bit keys are supported in FIPS mode" + "with 2048-bit, 3072-bit or 4096-bit keys are supported in FIPS mode" #else "with 2048-bit or larger keys are supported" #endif @@ -1274,8 +1274,7 @@ TEST_F(ClientContextConfigImplTest, RSA3072Cert) { manager.createSslClientContext(store, client_context_config, nullptr); } -// Validate that 4096-bit RSA certificates load successfully in non-FIPS builds, but are rejected -// in FIPS builds. +// Validate that 4096-bit RSA certificates load successfully. TEST_F(ClientContextConfigImplTest, RSA4096Cert) { envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; const std::string tls_certificate_yaml = R"EOF( @@ -1290,14 +1289,7 @@ TEST_F(ClientContextConfigImplTest, RSA4096Cert) { Event::SimulatedTimeSystem time_system; ContextManagerImpl manager(time_system); Stats::IsolatedStoreImpl store; -#ifdef BORINGSSL_FIPS - EXPECT_THROW_WITH_REGEX( - manager.createSslClientContext(store, client_context_config, nullptr), EnvoyException, - "Failed to load certificate chain from .*selfsigned_rsa_4096_cert.pem, only RSA certificates " - "with 2048-bit or 3072-bit keys are supported in FIPS mode"); -#else manager.createSslClientContext(store, client_context_config, nullptr); -#endif } // Validate that P256 ECDSA certs load. From 9025fba2cc64c9b7027a02241590a8e6ac3fbd87 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Mon, 2 Nov 2020 11:25:20 -0500 Subject: [PATCH 011/117] tls: Remove tls slot callback replacement-value (#13849) Commit Message: Removes the return-value semantics for runOnAllThreads callbacks; now the function type has void return, so we can remove the ASSERTs and simplify the code. Also changes a few more instances of SlotPtr to TypedSlot. Still about 30 remain. Moves Slot::runOnAllThreads to the protected section to prevent new dynamically uses from emerging. Additional Description: Risk Level: low Testing: //test/... Docs Changes: n/a Release Notes: n/a Signed-off-by: Joshua Marantz --- include/envoy/thread_local/thread_local.h | 24 ++++--- source/common/config/config_provider_impl.cc | 7 +- source/common/config/config_provider_impl.h | 15 ++--- .../common/thread_local/thread_local_impl.cc | 14 +--- .../common/tap/extension_config_base.cc | 21 ++---- .../common/tap/extension_config_base.h | 4 +- .../admission_control/admission_control.cc | 3 +- .../admission_control/admission_control.h | 8 +-- .../filters/http/admission_control/config.cc | 9 ++- .../thread_local/thread_local_impl_test.cc | 67 ++++++++----------- .../admission_control_filter_test.cc | 6 +- .../http/admission_control/config_test.cc | 3 +- .../filters/set_response_code_filter.cc | 5 +- test/mocks/thread_local/mocks.h | 5 +- 14 files changed, 78 insertions(+), 113 deletions(-) diff --git a/include/envoy/thread_local/thread_local.h b/include/envoy/thread_local/thread_local.h index 0ee6bab7be87..fcf7e54e2091 100644 --- a/include/envoy/thread_local/thread_local.h +++ b/include/envoy/thread_local/thread_local.h @@ -28,6 +28,8 @@ class ThreadLocalObject { using ThreadLocalObjectSharedPtr = std::shared_ptr; +template class TypedSlot; + /** * An individual allocated TLS slot. When the slot is destroyed the stored thread local will * be freed on each thread. @@ -75,20 +77,20 @@ class Slot { using InitializeCb = std::function; virtual void set(InitializeCb cb) PURE; +protected: + template friend class TypedSlot; + /** - * UpdateCb takes the current stored data, and must return the same data. The - * API was designed to allow replacement of the object via this API, but this - * is not currently used, and thus we are removing the functionality. In a future - * PR, the API will be removed. - * - * TLS will run the callback and assert the returned returned value matches - * the current value. + * UpdateCb takes is passed a shared point to the current stored data. Use of + * this API is deprecated; please use TypedSlot::runOnAllThreads instead. * * NOTE: The update callback is not supposed to capture the Slot, or its * owner, as the owner may be destructed in main thread before the update_cb * gets called in a worker thread. **/ - using UpdateCb = std::function; + using UpdateCb = std::function; + + // Callers must use the TypedSlot API, below. virtual void runOnAllThreads(const UpdateCb& update_cb) PURE; virtual void runOnAllThreads(const UpdateCb& update_cb, const Event::PostCb& complete_cb) PURE; virtual void runOnAllThreads(const Event::PostCb& cb) PURE; @@ -116,7 +118,7 @@ class SlotAllocator { // // TODO(jmarantz): Rename the Slot class to something like RawSlot, where the // only reference is from TypedSlot, which we can then rename to Slot. -template class TypedSlot { +template class TypedSlot { public: /** * Helper method to create a unique_ptr for a typed slot. This helper @@ -158,11 +160,13 @@ template class TypedSlot { * @return a reference to the thread local object. */ T& get() { return slot_->getTyped(); } + const T& get() const { return slot_->getTyped(); } /** * @return a pointer to the thread local object. */ T* operator->() { return &get(); } + const T* operator->() const { return &get(); } /** * UpdateCb is passed a mutable reference to the current stored data. @@ -191,7 +195,7 @@ template class TypedSlot { const SlotPtr slot_; }; -template using TypedSlotPtr = std::unique_ptr>; +template using TypedSlotPtr = std::unique_ptr>; /** * Interface for getting and setting thread local data as well as registering a thread diff --git a/source/common/config/config_provider_impl.cc b/source/common/config/config_provider_impl.cc index 3be865d2642d..b0098883d368 100644 --- a/source/common/config/config_provider_impl.cc +++ b/source/common/config/config_provider_impl.cc @@ -25,11 +25,8 @@ ConfigSubscriptionCommonBase::~ConfigSubscriptionCommonBase() { } void ConfigSubscriptionCommonBase::applyConfigUpdate(const ConfigUpdateCb& update_fn) { - tls_->runOnAllThreads([update_fn](ThreadLocal::ThreadLocalObjectSharedPtr previous) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - auto prev_thread_local_config = std::dynamic_pointer_cast(previous); - prev_thread_local_config->config_ = update_fn(prev_thread_local_config->config_); - return previous; + tls_.runOnAllThreads([update_fn](ThreadLocalConfig& thread_local_config) { + thread_local_config.config_ = update_fn(thread_local_config.config_); }); } diff --git a/source/common/config/config_provider_impl.h b/source/common/config/config_provider_impl.h index 144332fe23b0..7334796b377a 100644 --- a/source/common/config/config_provider_impl.h +++ b/source/common/config/config_provider_impl.h @@ -166,9 +166,7 @@ class ConfigSubscriptionCommonBase : protected Logger::Loggable& configInfo() const { return config_info_; } - ConfigProvider::ConfigConstSharedPtr getConfig() const { - return tls_->getTyped().config_; - } + ConfigProvider::ConfigConstSharedPtr getConfig() { return tls_->config_; } /** * Must be called by derived classes when the onConfigUpdate() callback associated with the @@ -199,7 +197,7 @@ class ConfigSubscriptionCommonBase : protected Logger::Loggable config_info_; // This slot holds a Config implementation in each thread, which is intended to be shared between // config providers from the same config source. - ThreadLocal::SlotPtr tls_; + ThreadLocal::TypedSlot tls_; private: // Local init target which signals first RPC interaction with management server. @@ -283,7 +281,7 @@ class ConfigSubscriptionInstance : public ConfigSubscriptionCommonBase { * with the underlying subscription, shared across all providers and workers. */ void initialize(const ConfigProvider::ConfigConstSharedPtr& initial_config) { - tls_->set([initial_config](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { + tls_.set([initial_config](Event::Dispatcher&) { return std::make_shared(initial_config); }); } @@ -328,9 +326,8 @@ class DeltaConfigSubscriptionInstance : public ConfigSubscriptionCommonBase { * underlying subscription for each worker thread. */ void initialize(const std::function& init_cb) { - tls_->set([init_cb](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(init_cb()); - }); + tls_.set( + [init_cb](Event::Dispatcher&) { return std::make_shared(init_cb()); }); } }; diff --git a/source/common/thread_local/thread_local_impl.cc b/source/common/thread_local/thread_local_impl.cc index d07f41e26c60..6c450449a942 100644 --- a/source/common/thread_local/thread_local_impl.cc +++ b/source/common/thread_local/thread_local_impl.cc @@ -76,19 +76,9 @@ Event::PostCb InstanceImpl::SlotImpl::dataCallback(const UpdateCb& cb) { // works, but incurs another indirection of lambda at runtime. As the // duplicated logic is only an if-statement and a bool function, it doesn't // seem worth factoring that out to a helper function. - if (still_alive_guard.expired()) { - return; + if (!still_alive_guard.expired()) { + cb(getWorker(index)); } - auto obj = getWorker(index); - auto new_obj = cb(obj); - // The API definition for runOnAllThreads allows for replacing the object - // via the callback return value. However, this never occurs in the codebase - // as of Oct 2020, and we plan to remove this API. To avoid PR races, we - // will add an assert to ensure such a dependency does not emerge. - // - // TODO(jmarantz): remove this once we phase out use of the untyped slot - // API, rename it, and change all call-sites to use TypedSlot. - ASSERT(obj.get() == new_obj.get()); }; } diff --git a/source/extensions/common/tap/extension_config_base.cc b/source/extensions/common/tap/extension_config_base.cc index 93d257c94b1c..d5538bff21fb 100644 --- a/source/extensions/common/tap/extension_config_base.cc +++ b/source/extensions/common/tap/extension_config_base.cc @@ -13,11 +13,8 @@ ExtensionConfigBase::ExtensionConfigBase( TapConfigFactoryPtr&& config_factory, Server::Admin& admin, Singleton::Manager& singleton_manager, ThreadLocal::SlotAllocator& tls, Event::Dispatcher& main_thread_dispatcher) - : proto_config_(proto_config), config_factory_(std::move(config_factory)), - tls_slot_(tls.allocateSlot()) { - tls_slot_->set([](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(); - }); + : proto_config_(proto_config), config_factory_(std::move(config_factory)), tls_slot_(tls) { + tls_slot_.set([](Event::Dispatcher&) { return std::make_shared(); }); switch (proto_config_.config_type_case()) { case envoy::extensions::common::tap::v3::CommonExtensionConfig::ConfigTypeCase::kAdminConfig: { @@ -60,22 +57,16 @@ const absl::string_view ExtensionConfigBase::adminId() { } void ExtensionConfigBase::clearTapConfig() { - tls_slot_->runOnAllThreads([](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().config_ = nullptr; - return object; - }); + tls_slot_.runOnAllThreads( + [](TlsFilterConfig& tls_filter_config) { tls_filter_config.config_ = nullptr; }); } void ExtensionConfigBase::installNewTap(const envoy::config::tap::v3::TapConfig& proto_config, Sink* admin_streamer) { TapConfigSharedPtr new_config = config_factory_->createConfigFromProto(proto_config, admin_streamer); - tls_slot_->runOnAllThreads([new_config](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - object->asType().config_ = new_config; - return object; - }); + tls_slot_.runOnAllThreads( + [new_config](TlsFilterConfig& tls_filter_config) { tls_filter_config.config_ = new_config; }); } void ExtensionConfigBase::newTapConfig(const envoy::config::tap::v3::TapConfig& proto_config, diff --git a/source/extensions/common/tap/extension_config_base.h b/source/extensions/common/tap/extension_config_base.h index 88cea02fae8f..7e0e00625dd9 100644 --- a/source/extensions/common/tap/extension_config_base.h +++ b/source/extensions/common/tap/extension_config_base.h @@ -34,7 +34,7 @@ class ExtensionConfigBase : public ExtensionConfig, Logger::Loggable std::shared_ptr currentConfigHelper() const { - return std::dynamic_pointer_cast(tls_slot_->getTyped().config_); + return std::dynamic_pointer_cast(tls_slot_->config_); } private: @@ -48,7 +48,7 @@ class ExtensionConfigBase : public ExtensionConfig, Logger::Loggable tls_slot_; AdminHandlerSharedPtr admin_handler_; }; diff --git a/source/extensions/filters/http/admission_control/admission_control.cc b/source/extensions/filters/http/admission_control/admission_control.cc index 0875429bd2df..9329e10ab351 100644 --- a/source/extensions/filters/http/admission_control/admission_control.cc +++ b/source/extensions/filters/http/admission_control/admission_control.cc @@ -34,7 +34,8 @@ static constexpr double defaultSuccessRateThreshold = 95.0; AdmissionControlFilterConfig::AdmissionControlFilterConfig( const AdmissionControlProto& proto_config, Runtime::Loader& runtime, - Random::RandomGenerator& random, Stats::Scope& scope, ThreadLocal::SlotPtr&& tls, + Random::RandomGenerator& random, Stats::Scope& scope, + ThreadLocal::TypedSlotPtr&& tls, std::shared_ptr response_evaluator) : random_(random), scope_(scope), tls_(std::move(tls)), admission_control_feature_(proto_config.enabled(), runtime), diff --git a/source/extensions/filters/http/admission_control/admission_control.h b/source/extensions/filters/http/admission_control/admission_control.h index 99554d5e5e76..79a903d361d1 100644 --- a/source/extensions/filters/http/admission_control/admission_control.h +++ b/source/extensions/filters/http/admission_control/admission_control.h @@ -55,13 +55,11 @@ class AdmissionControlFilterConfig { public: AdmissionControlFilterConfig(const AdmissionControlProto& proto_config, Runtime::Loader& runtime, Random::RandomGenerator& random, Stats::Scope& scope, - ThreadLocal::SlotPtr&& tls, + ThreadLocal::TypedSlotPtr&& tls, std::shared_ptr response_evaluator); virtual ~AdmissionControlFilterConfig() = default; - virtual ThreadLocalController& getController() const { - return tls_->getTyped(); - } + virtual ThreadLocalController& getController() const { return tls_->get(); } Random::RandomGenerator& random() const { return random_; } bool filterEnabled() const { return admission_control_feature_.enabled(); } @@ -73,7 +71,7 @@ class AdmissionControlFilterConfig { private: Random::RandomGenerator& random_; Stats::Scope& scope_; - const ThreadLocal::SlotPtr tls_; + const ThreadLocal::TypedSlotPtr tls_; Runtime::FeatureFlag admission_control_feature_; std::unique_ptr aggression_; std::unique_ptr sr_threshold_; diff --git a/source/extensions/filters/http/admission_control/config.cc b/source/extensions/filters/http/admission_control/config.cc index 01aef0125bbf..fcd8c42fcc9b 100644 --- a/source/extensions/filters/http/admission_control/config.cc +++ b/source/extensions/filters/http/admission_control/config.cc @@ -29,14 +29,13 @@ Http::FilterFactoryCb AdmissionControlFilterFactory::createFilterFactoryFromProt const std::string prefix = stats_prefix + "admission_control."; // Create the thread-local controller. - auto tls = context.threadLocal().allocateSlot(); + auto tls = ThreadLocal::TypedSlot::makeUnique(context.threadLocal()); auto sampling_window = std::chrono::seconds( PROTOBUF_GET_MS_OR_DEFAULT(config, sampling_window, 1000 * defaultSamplingWindow.count()) / 1000); - tls->set( - [sampling_window, &context](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(context.timeSource(), sampling_window); - }); + tls->set([sampling_window, &context](Event::Dispatcher&) { + return std::make_shared(context.timeSource(), sampling_window); + }); std::unique_ptr response_evaluator; switch (config.evaluation_criteria_case()) { diff --git a/test/common/thread_local/thread_local_impl_test.cc b/test/common/thread_local/thread_local_impl_test.cc index 6963a02e586b..9aa1d65734be 100644 --- a/test/common/thread_local/thread_local_impl_test.cc +++ b/test/common/thread_local/thread_local_impl_test.cc @@ -33,7 +33,7 @@ class ThreadLocalInstanceImplTest : public testing::Test { MOCK_METHOD(ThreadLocalObjectSharedPtr, createThreadLocal, (Event::Dispatcher & dispatcher)); - TestThreadLocalObject& setObject(Slot& slot) { + TestThreadLocalObject& setObject(TypedSlot<>& slot) { std::shared_ptr object(new TestThreadLocalObject()); TestThreadLocalObject& object_ref = *object; EXPECT_CALL(thread_dispatcher_, post(_)); @@ -57,13 +57,13 @@ TEST_F(ThreadLocalInstanceImplTest, All) { // Free a slot without ever calling set. EXPECT_CALL(thread_dispatcher_, post(_)); - SlotPtr slot1 = tls_.allocateSlot(); + TypedSlotPtr<> slot1 = TypedSlot<>::makeUnique(tls_); slot1.reset(); EXPECT_EQ(freeSlotIndexesListSize(), 1); // Create a new slot which should take the place of the old slot. ReturnPointee() is used to // avoid "leaks" when using InSequence and shared_ptr. - SlotPtr slot2 = tls_.allocateSlot(); + TypedSlotPtr<> slot2 = TypedSlot<>::makeUnique(tls_); TestThreadLocalObject& object_ref2 = setObject(*slot2); EXPECT_EQ(freeSlotIndexesListSize(), 0); @@ -75,9 +75,9 @@ TEST_F(ThreadLocalInstanceImplTest, All) { // Make two new slots, shutdown global threading, and delete them. We should not see any // cross-thread posts at this point. We should also see destruction in reverse order. - SlotPtr slot3 = tls_.allocateSlot(); + TypedSlotPtr<> slot3 = TypedSlot<>::makeUnique(tls_); TestThreadLocalObject& object_ref3 = setObject(*slot3); - SlotPtr slot4 = tls_.allocateSlot(); + TypedSlotPtr<> slot4 = TypedSlot<>::makeUnique(tls_); TestThreadLocalObject& object_ref4 = setObject(*slot4); tls_.shutdownGlobalThreading(); @@ -100,16 +100,16 @@ struct ThreadStatus { // data as an argument. class CallbackNotInvokedAfterDeletionTest : public ThreadLocalInstanceImplTest { protected: - CallbackNotInvokedAfterDeletionTest() : slot_(tls_.allocateSlot()) { + CallbackNotInvokedAfterDeletionTest() : slot_(TypedSlot<>::makeUnique(tls_)) { EXPECT_CALL(thread_dispatcher_, post(_)).Times(4).WillRepeatedly(Invoke([&](Event::PostCb cb) { // Holds the posted callback. holder_.push_back(cb); })); - slot_->set([this](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { + slot_->set([this](Event::Dispatcher&) { // Callbacks happen on the main thread but not the workers, so track the total. total_callbacks_++; - return nullptr; + return std::make_shared(); }); } @@ -132,7 +132,7 @@ class CallbackNotInvokedAfterDeletionTest : public ThreadLocalInstanceImplTest { // Allocate a slot and invoke all callback variants. Hold all callbacks and destroy the slot. // Make sure that recycling happens appropriately. - SlotPtr slot_; + TypedSlotPtr<> slot_; std::list holder_; uint32_t total_callbacks_{0}; ThreadStatus thread_status_; @@ -140,22 +140,16 @@ class CallbackNotInvokedAfterDeletionTest : public ThreadLocalInstanceImplTest { TEST_F(CallbackNotInvokedAfterDeletionTest, WithArg) { InSequence s; - slot_->runOnAllThreads( - [this](ThreadLocal::ThreadLocalObjectSharedPtr) -> ThreadLocal::ThreadLocalObjectSharedPtr { - // Callbacks happen on the main thread but not the workers, so track the total. - total_callbacks_++; - return nullptr; - }); - slot_->runOnAllThreads( - [this](ThreadLocal::ThreadLocalObjectSharedPtr) -> ThreadLocal::ThreadLocalObjectSharedPtr { - ++thread_status_.thread_local_calls_; - return nullptr; - }, - [this]() -> void { - // Callbacks happen on the main thread but not the workers. - EXPECT_EQ(thread_status_.thread_local_calls_, 1); - thread_status_.all_threads_complete_ = true; - }); + slot_->runOnAllThreads([this](ThreadLocalObject&) { + // Callbacks happen on the main thread but not the workers, so track the total. + total_callbacks_++; + }); + slot_->runOnAllThreads([this](ThreadLocalObject&) { ++thread_status_.thread_local_calls_; }, + [this]() { + // Callbacks happen on the main thread but not the workers. + EXPECT_EQ(thread_status_.thread_local_calls_, 1); + thread_status_.all_threads_complete_ = true; + }); } TEST_F(CallbackNotInvokedAfterDeletionTest, WithoutArg) { @@ -165,7 +159,7 @@ TEST_F(CallbackNotInvokedAfterDeletionTest, WithoutArg) { total_callbacks_++; }); slot_->runOnAllThreads([this]() { ++thread_status_.thread_local_calls_; }, - [this]() -> void { + [this]() { // Callbacks happen on the main thread but not the workers. EXPECT_EQ(thread_status_.thread_local_calls_, 1); thread_status_.all_threads_complete_ = true; @@ -176,18 +170,15 @@ TEST_F(CallbackNotInvokedAfterDeletionTest, WithoutArg) { TEST_F(ThreadLocalInstanceImplTest, UpdateCallback) { InSequence s; - SlotPtr slot = tls_.allocateSlot(); + TypedSlot<> slot(tls_); uint32_t update_called = 0; - TestThreadLocalObject& object_ref = setObject(*slot); - auto update_cb = [&update_called](ThreadLocalObjectSharedPtr obj) -> ThreadLocalObjectSharedPtr { - ++update_called; - return obj; - }; + TestThreadLocalObject& object_ref = setObject(slot); + auto update_cb = [&update_called](ThreadLocalObject&) { ++update_called; }; EXPECT_CALL(thread_dispatcher_, post(_)); EXPECT_CALL(object_ref, onDestroy()); - slot->runOnAllThreads(update_cb); + slot.runOnAllThreads(update_cb); EXPECT_EQ(2, update_called); // 1 worker, 1 main thread. @@ -232,7 +223,7 @@ TEST_F(ThreadLocalInstanceImplTest, TypedUpdateCallback) { // Validate ThreadLocal::runOnAllThreads behavior with all_thread_complete call back. TEST_F(ThreadLocalInstanceImplTest, RunOnAllThreads) { - SlotPtr tlsptr = tls_.allocateSlot(); + TypedSlotPtr<> tlsptr = TypedSlot<>::makeUnique(tls_); TestThreadLocalObject& object_ref = setObject(*tlsptr); EXPECT_CALL(thread_dispatcher_, post(_)); @@ -241,12 +232,8 @@ TEST_F(ThreadLocalInstanceImplTest, RunOnAllThreads) { // Ensure that the thread local call back and all_thread_complete call back are called. ThreadStatus thread_status; tlsptr->runOnAllThreads( - [&thread_status](ThreadLocal::ThreadLocalObjectSharedPtr object) - -> ThreadLocal::ThreadLocalObjectSharedPtr { - ++thread_status.thread_local_calls_; - return object; - }, - [&thread_status]() -> void { + [&thread_status](ThreadLocal::ThreadLocalObject&) { ++thread_status.thread_local_calls_; }, + [&thread_status]() { EXPECT_EQ(thread_status.thread_local_calls_, 2); thread_status.all_threads_complete_ = true; }); diff --git a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc index 2bfc77f46212..847c0c0bb8a9 100644 --- a/test/extensions/filters/http/admission_control/admission_control_filter_test.cc +++ b/test/extensions/filters/http/admission_control/admission_control_filter_test.cc @@ -48,7 +48,8 @@ class MockResponseEvaluator : public ResponseEvaluator { class TestConfig : public AdmissionControlFilterConfig { public: TestConfig(const AdmissionControlProto& proto_config, Runtime::Loader& runtime, - Random::RandomGenerator& random, Stats::Scope& scope, ThreadLocal::SlotPtr&& tls, + Random::RandomGenerator& random, Stats::Scope& scope, + ThreadLocal::TypedSlotPtr&& tls, MockThreadLocalController& controller, std::shared_ptr evaluator) : AdmissionControlFilterConfig(proto_config, runtime, random, scope, std::move(tls), std::move(evaluator)), @@ -66,7 +67,8 @@ class AdmissionControlTest : public testing::Test { std::shared_ptr makeConfig(const std::string& yaml) { AdmissionControlProto proto; TestUtility::loadFromYamlAndValidate(yaml, proto); - auto tls = context_.threadLocal().allocateSlot(); + auto tls = + ThreadLocal::TypedSlot::makeUnique(context_.threadLocal()); evaluator_ = std::make_shared(); return std::make_shared(proto, runtime_, random_, scope_, std::move(tls), diff --git a/test/extensions/filters/http/admission_control/config_test.cc b/test/extensions/filters/http/admission_control/config_test.cc index 2fba5f26016f..c5cc6917f8de 100644 --- a/test/extensions/filters/http/admission_control/config_test.cc +++ b/test/extensions/filters/http/admission_control/config_test.cc @@ -34,7 +34,8 @@ class AdmissionControlConfigTest : public testing::Test { std::shared_ptr makeConfig(const std::string& yaml) { AdmissionControlProto proto; TestUtility::loadFromYamlAndValidate(yaml, proto); - auto tls = context_.threadLocal().allocateSlot(); + auto tls = + ThreadLocal::TypedSlot::makeUnique(context_.threadLocal()); auto evaluator = std::make_unique(proto.success_criteria()); return std::make_shared(proto, runtime_, random_, scope_, std::move(tls), std::move(evaluator)); diff --git a/test/integration/filters/set_response_code_filter.cc b/test/integration/filters/set_response_code_filter.cc index 701ef857bbf3..e9964b8a58e5 100644 --- a/test/integration/filters/set_response_code_filter.cc +++ b/test/integration/filters/set_response_code_filter.cc @@ -18,14 +18,13 @@ class SetResponseCodeFilterConfig { public: SetResponseCodeFilterConfig(const std::string& prefix, uint32_t code, const std::string& body, Server::Configuration::FactoryContext& context) - : prefix_(prefix), code_(code), body_(body), tls_slot_(context.threadLocal().allocateSlot()) { - } + : prefix_(prefix), code_(code), body_(body), tls_slot_(context.threadLocal()) {} const std::string prefix_; const uint32_t code_; const std::string body_; // Allocate a slot to validate that it is destroyed on a main thread only. - ThreadLocal::SlotPtr tls_slot_; + ThreadLocal::TypedSlot<> tls_slot_; }; class SetResponseCodeFilter : public Http::PassThroughFilter { diff --git a/test/mocks/thread_local/mocks.h b/test/mocks/thread_local/mocks.h index bd8699fe24c5..6c5f7a9a3450 100644 --- a/test/mocks/thread_local/mocks.h +++ b/test/mocks/thread_local/mocks.h @@ -61,11 +61,10 @@ class MockInstance : public Instance { ThreadLocalObjectSharedPtr get() override { return parent_.data_[index_]; } bool currentThreadRegistered() override { return parent_.registered_; } void runOnAllThreads(const UpdateCb& cb) override { - parent_.runOnAllThreads([cb, this]() { parent_.data_[index_] = cb(parent_.data_[index_]); }); + parent_.runOnAllThreads([cb, this]() { cb(parent_.data_[index_]); }); } void runOnAllThreads(const UpdateCb& cb, const Event::PostCb& main_callback) override { - parent_.runOnAllThreads([cb, this]() { parent_.data_[index_] = cb(parent_.data_[index_]); }, - main_callback); + parent_.runOnAllThreads([cb, this]() { cb(parent_.data_[index_]); }, main_callback); } void runOnAllThreads(const Event::PostCb& cb) override { parent_.runOnAllThreads(cb); } void runOnAllThreads(const Event::PostCb& cb, const Event::PostCb& main_callback) override { From 1a119aafbd82b10a021fd39bb7f5745b56d65951 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Mon, 2 Nov 2020 14:05:23 -0800 Subject: [PATCH 012/117] build: exclude wee8/out from inputs (#13866) If you build without sandboxing for performance, the output files from this custom build genrule contained timestamps which caused it to rebuild every single build. Signed-off-by: Keith Smiley --- bazel/external/wee8.BUILD | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bazel/external/wee8.BUILD b/bazel/external/wee8.BUILD index ce16c32af799..0ea13769ccd4 100644 --- a/bazel/external/wee8.BUILD +++ b/bazel/external/wee8.BUILD @@ -29,7 +29,10 @@ cc_library( genrule( name = "build", - srcs = glob(["wee8/**"]), + srcs = glob( + ["wee8/**"], + exclude = ["wee8/out/**"], + ), outs = [ "libwee8.a", ], From 8d0e55898b76f828694bfdfc505ee3f6d700c0da Mon Sep 17 00:00:00 2001 From: williamsfu99 <32112201+williamsfu99@users.noreply.github.com> Date: Mon, 2 Nov 2020 14:33:25 -0800 Subject: [PATCH 013/117] add OAuth documentation and fix filter inconsistencies (#13750) Signed-off-by: William Fu --- .../http/http_filters/oauth2_filter.rst | 94 ++++++++++++++++++- .../extensions/filters/http/oauth2/filter.cc | 15 ++- .../filters/http/oauth2/oauth_client.cc | 1 + .../filters/http/oauth2/filter_test.cc | 50 ++++++++-- 4 files changed, 143 insertions(+), 17 deletions(-) diff --git a/docs/root/configuration/http/http_filters/oauth2_filter.rst b/docs/root/configuration/http/http_filters/oauth2_filter.rst index acbdda6780c7..6b8b9789a5c7 100644 --- a/docs/root/configuration/http/http_filters/oauth2_filter.rst +++ b/docs/root/configuration/http/http_filters/oauth2_filter.rst @@ -7,6 +7,35 @@ OAuth2 * :ref:`v3 API reference ` * This filter should be configured with the name *envoy.filters.http.oauth2*. +The OAuth filter's flow involves: + +* An unauthenticated user arrives at myapp.com, and the oauth filter redirects them to the + :ref:`authorization_endpoint ` + for login. The :ref:`client_id ` + and the :ref:`redirect_uri ` + are sent as query string parameters in this first redirect. +* After a successful login, the authn server should be configured to redirect the user back to the + :ref:`redirect_uri ` + provided in the query string in the first step. In the below code example, we choose /callback as the configured match path. + An "authorization grant" is included in the query string for this second redirect. +* Using this new grant and the :ref:`token_secret `, + the filter then attempts to retrieve an access token from + the :ref:`token_endpoint `. The filter knows it has to do this + instead of reinitiating another login because the incoming request has a path that matches the + :ref:`redirect_path_matcher ` criteria. +* Upon receiving an access token, the filter sets cookies so that subseqeuent requests can skip the full + flow. These cookies are calculated using the + :ref:`hmac_secret ` + to assist in encoding. +* The filter calls continueDecoding() to unblock the filter chain. + +When the authn server validates the client and returns an authorization token back to the OAuth filter, +no matter what format that token is, if +:ref:`forward_bearer_token ` +is set to true the filter will send over a +cookie named `BearerToken` to the upstream. Additionally, the `Authorization` header will be populated +with the same value. + .. attention:: The OAuth2 filter is currently under active development. @@ -43,7 +72,7 @@ The following is an example configuring the filter. sds_config: path: "/etc/envoy/hmac.yaml" -And the below code block is an example of how we employ it as one of +Below is a complete code example of how we employ the filter as one of :ref:`HttpConnectionManager HTTP filters ` @@ -86,10 +115,34 @@ And the below code block is an example of how we employ it as one of sds_config: path: "/etc/envoy/hmac.yaml" - name: envoy.router + tracing: {} + codec_type: "AUTO" + stat_prefix: ingress_http + route_config: + virtual_hosts: + - name: service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: service + timeout: 5s clusters: - name: service - # ... + connect_timeout: 5s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 - name: auth connect_timeout: 5s type: LOGICAL_DNS @@ -106,13 +159,44 @@ And the below code block is an example of how we employ it as one of tls_context: sni: auth.example.com +Finally, the following code block illustrates sample contents inside a yaml file containing both credential secrets. +Both the :ref:`token_secret ` +and the :ref:`hmac_secret ` +can be defined in one shared file. + +.. code-block:: yaml + + static_resources: + secrets: + - name: token + generic_secret: + secret: + - name: hmac + generic_secret: + secret: + + Notes ----- -This module does not currently provide much Cross-Site-Request-Forgery protection for the redirect -loop to the OAuth server and back. +When enabled, the OAuth filter does not protect against Cross-Site-Request-Forgery attacks on domains with +cached authentication (in the form of cookies). +It is recommended to pair this filter with the :ref:`CSRF Filter ` +to prevent malicious social engineering. + +The service must be served over HTTPS for this filter to work properly, as the cookies use `;secure`. Without https, your +:ref:`authorization_endpoint ` +provider will likely reject the incoming request, and your access cookies will not be cached to bypass future logins. + +The signout path will redirect the current user to '/', and clear all authentication cookies related to +the HMAC validation. Consequently, the OAuth filter will then restart the full OAuth flow at the root path, +sending the user to the configured auth endpoint. + +:ref:`pass_through_matcher ` provides +an interface for users to provide specific header matching criteria such that, when applicable, the OAuth flow is entirely skipped. +When this occurs, the `oauth_success` metric is still incremented. -The service must be served over HTTPS for this filter to work, as the cookies use `;secure`. +Generally, allowlisting is inadvisable from a security standpoint. Statistics ---------- diff --git a/source/extensions/filters/http/oauth2/filter.cc b/source/extensions/filters/http/oauth2/filter.cc index 1603b9b417e5..cfd341828b29 100644 --- a/source/extensions/filters/http/oauth2/filter.cc +++ b/source/extensions/filters/http/oauth2/filter.cc @@ -230,15 +230,18 @@ Http::FilterHeadersStatus OAuth2Filter::decodeHeaders(Http::RequestHeaderMap& he return Http::FilterHeadersStatus::Continue; } + // Save the request headers for later modification if needed. + if (config_->forwardBearerToken()) { + request_headers_ = &headers; + } + // If a bearer token is supplied as a header or param, we ingest it here and kick off the // user resolution immediately. Note this comes after HMAC validation, so technically this // header is sanitized in a way, as the validation check forces the correct Bearer Cookie value. access_token_ = extractAccessToken(headers); if (!access_token_.empty()) { found_bearer_token_ = true; - request_headers_ = &headers; finishFlow(); - return Http::FilterHeadersStatus::Continue; } @@ -327,7 +330,9 @@ bool OAuth2Filter::canSkipOAuth(Http::RequestHeaderMap& headers) const { validator_->setParams(headers, config_->tokenSecret()); if (validator_->isValid()) { config_->stats().oauth_success_.inc(); - setBearerToken(headers, validator_->token()); + if (config_->forwardBearerToken() && !validator_->token().empty()) { + setBearerToken(headers, validator_->token()); + } return true; } @@ -373,7 +378,9 @@ void OAuth2Filter::finishFlow() { // We have fully completed the entire OAuth flow, whether through Authorization header or from // user redirection to the auth server. if (found_bearer_token_) { - setBearerToken(*request_headers_, access_token_); + if (config_->forwardBearerToken()) { + setBearerToken(*request_headers_, access_token_); + } config_->stats().oauth_success_.inc(); decoder_callbacks_->continueDecoding(); return; diff --git a/source/extensions/filters/http/oauth2/oauth_client.cc b/source/extensions/filters/http/oauth2/oauth_client.cc index a25672c81083..71db1ce74020 100644 --- a/source/extensions/filters/http/oauth2/oauth_client.cc +++ b/source/extensions/filters/http/oauth2/oauth_client.cc @@ -39,6 +39,7 @@ void OAuth2ClientImpl::asyncGetAccessToken(const std::string& auth_code, Http::RequestMessagePtr request = createPostRequest(); const std::string body = fmt::format(GetAccessTokenBodyFormatString, auth_code, encoded_client_id, encoded_secret, encoded_cb_url); + request->body().add(body); ENVOY_LOG(debug, "Dispatching OAuth request for access token."); dispatchRequest(std::move(request)); diff --git a/test/extensions/filters/http/oauth2/filter_test.cc b/test/extensions/filters/http/oauth2/filter_test.cc index eed8d311c1a0..83f5e7f08833 100644 --- a/test/extensions/filters/http/oauth2/filter_test.cc +++ b/test/extensions/filters/http/oauth2/filter_test.cc @@ -205,7 +205,7 @@ TEST_F(OAuth2Test, OAuthOkPass) { // Sanitized return reference mocking std::string legit_token{"legit_token"}; - EXPECT_CALL(*validator_, token()).WillOnce(ReturnRef(legit_token)); + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(mock_request_headers, false)); @@ -414,7 +414,7 @@ TEST_F(OAuth2Test, OAuthTestInvalidUrlInStateQueryParam) { EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); std::string legit_token{"legit_token"}; - EXPECT_CALL(*validator_, token()).WillOnce(ReturnRef(legit_token)); + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), false)); EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndBuffer, @@ -436,7 +436,7 @@ TEST_F(OAuth2Test, OAuthTestCallbackUrlInStateQueryParam) { "RlNjMxZTJmNTZkYzRmZTM0ZQ====;version=test"}, }; - Http::TestRequestHeaderMapImpl expected_headers{ + Http::TestRequestHeaderMapImpl expected_response_headers{ {Http::Headers::get().Status.get(), "401"}, {Http::Headers::get().ContentLength.get(), "18"}, {Http::Headers::get().ContentType.get(), "text/plain"}, @@ -447,11 +447,28 @@ TEST_F(OAuth2Test, OAuthTestCallbackUrlInStateQueryParam) { EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); std::string legit_token{"legit_token"}; - EXPECT_CALL(*validator_, token()).WillOnce(ReturnRef(legit_token)); + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), false)); + EXPECT_CALL(decoder_callbacks_, + encodeHeaders_(HeaderMapEqualRef(&expected_response_headers), false)); EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndBuffer, filter_->decodeHeaders(request_headers, false)); + + Http::TestRequestHeaderMapImpl final_request_headers{ + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + {Http::Headers::get().Path.get(), "/_oauth?code=abcdefxyz123&scope=user&" + "state=https%3A%2F%2Ftraffic.example.com%2F_oauth"}, + {Http::Headers::get().Cookie.get(), "OauthExpires=123;version=test"}, + {Http::Headers::get().Cookie.get(), "BearerToken=legit_token;version=test"}, + {Http::Headers::get().Cookie.get(), + "OauthHMAC=" + "ZTRlMzU5N2Q4ZDIwZWE5ZTU5NTg3YTU3YTcxZTU0NDFkMzY1ZTc1NjMyODYyMj" + "RlNjMxZTJmNTZkYzRmZTM0ZQ====;version=test"}, + {Http::CustomHeaders::get().Authorization.get(), "Bearer legit_token"}, + }; + + EXPECT_EQ(request_headers, final_request_headers); } /** @@ -475,7 +492,7 @@ TEST_F(OAuth2Test, OAuthTestUpdatePathAfterSuccess) { "RlNjMxZTJmNTZkYzRmZTM0ZQ====;version=test"}, }; - Http::TestRequestHeaderMapImpl expected_headers{ + Http::TestRequestHeaderMapImpl expected_response_headers{ {Http::Headers::get().Status.get(), "302"}, {Http::Headers::get().Location.get(), "https://traffic.example.com/original_path"}, }; @@ -485,10 +502,27 @@ TEST_F(OAuth2Test, OAuthTestUpdatePathAfterSuccess) { EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true)); std::string legit_token{"legit_token"}; - EXPECT_CALL(*validator_, token()).WillOnce(ReturnRef(legit_token)); + EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token)); - EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&expected_headers), true)); + EXPECT_CALL(decoder_callbacks_, + encodeHeaders_(HeaderMapEqualRef(&expected_response_headers), true)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + + Http::TestRequestHeaderMapImpl final_request_headers{ + {Http::Headers::get().Host.get(), "traffic.example.com"}, + {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get}, + {Http::Headers::get().Path.get(), "/_oauth?code=abcdefxyz123&scope=user&" + "state=https%3A%2F%2Ftraffic.example.com%2Foriginal_path"}, + {Http::Headers::get().Cookie.get(), "OauthExpires=123;version=test"}, + {Http::Headers::get().Cookie.get(), "BearerToken=legit_token;version=test"}, + {Http::Headers::get().Cookie.get(), + "OauthHMAC=" + "ZTRlMzU5N2Q4ZDIwZWE5ZTU5NTg3YTU3YTcxZTU0NDFkMzY1ZTc1NjMyODYyMj" + "RlNjMxZTJmNTZkYzRmZTM0ZQ====;version=test"}, + {Http::CustomHeaders::get().Authorization.get(), "Bearer legit_token"}, + }; + + EXPECT_EQ(request_headers, final_request_headers); } /** From 29adb9afef2c4a8b348e390588d1267d2ab1588f Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 2 Nov 2020 22:52:33 +0000 Subject: [PATCH 014/117] docs/examples: Update dynamic fs sandbox (#13861) Signed-off-by: Ryan Northey --- .../dynamic-configuration-filesystem.rst | 32 +++++++------------ examples/dynamic-config-fs/Dockerfile-proxy | 4 ++- .../dynamic-config-fs/docker-compose.yaml | 2 -- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst b/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst index d71bee0e9673..b746257f9692 100644 --- a/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst +++ b/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst @@ -15,20 +15,6 @@ Change directory to ``examples/dynamic-config-fs`` in the Envoy repository. Step 3: Start the proxy container ********************************* -.. note:: - - If you are running on a system with strict ``umask`` you will need to ``chmod`` the dynamic config - files which are mounted into the container: - - .. code-block:: console - - $ umask - 027 - $ pwd - envoy/examples/dynamic-config-fs - $ chmod go+r configs/* - $ chmod go+x configs - Build and start the containers. This should also start two upstream ``HTTP`` echo servers, ``service1`` and ``service2``. @@ -80,17 +66,17 @@ the ``example_proxy_cluster`` pointing to ``service1``. :language: json :emphasize-lines: 10, 18-19 -Step 5: Edit ``configs/cds.yaml`` file to update upstream cluster -***************************************************************** +Step 5: Edit ``cds.yaml`` inside the container to update upstream cluster +************************************************************************* -The example setup provides two dynamic configuration files: +The example setup provides Envoy with two dynamic configuration files: -- :download:`configs/cds.yaml <_include/dynamic-config-fs/configs/cds.yaml>` to provide a :ref:`Cluster +- :download:`cds.yaml <_include/dynamic-config-fs/configs/cds.yaml>` to provide a :ref:`Cluster discovery service (CDS) `. -- :download:`configs/lds.yaml <_include/dynamic-config-fs/configs/lds.yaml>` to provide a :ref:`Listener +- :download:`lds.yaml <_include/dynamic-config-fs/configs/lds.yaml>` to provide a :ref:`Listener discovery service (CDS) `. -Edit ``configs/cds.yaml`` in the dynamic configuration example folder and change the cluster address +Edit ``cds.yaml`` inside the container and change the cluster address from ``service1`` to ``service2``: .. literalinclude:: _include/dynamic-config-fs/configs/cds.yaml @@ -100,6 +86,12 @@ from ``service1`` to ``service2``: :lineno-start: 7 :emphasize-lines: 8 +You can do this using ``sed`` inside the container: + +.. code-block:: console + + docker-compose exec -T proxy sed -i s/service1/service2/ /var/lib/envoy/cds.yaml + Step 6: Check Envoy uses updated configuration ********************************************** diff --git a/examples/dynamic-config-fs/Dockerfile-proxy b/examples/dynamic-config-fs/Dockerfile-proxy index f70f44311461..1728cbefb07c 100644 --- a/examples/dynamic-config-fs/Dockerfile-proxy +++ b/examples/dynamic-config-fs/Dockerfile-proxy @@ -1,5 +1,7 @@ FROM envoyproxy/envoy-dev:latest COPY ./envoy.yaml /etc/envoy.yaml -RUN chmod go+r /etc/envoy.yaml +COPY ./configs /var/lib/envoy +RUN chmod go+x /var/lib/envoy \ + && chmod go+r /etc/envoy.yaml /var/lib/envoy/* CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/dynamic-config-fs/docker-compose.yaml b/examples/dynamic-config-fs/docker-compose.yaml index b3aed8c7e8ad..1df3161e59f0 100644 --- a/examples/dynamic-config-fs/docker-compose.yaml +++ b/examples/dynamic-config-fs/docker-compose.yaml @@ -11,8 +11,6 @@ services: ports: - 10000:10000 - 19000:19000 - volumes: - - ./configs:/var/lib/envoy service1: image: jmalloc/echo-server From 06f63510c4b5eff7b61d703674fec26b3b1bbde2 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Mon, 2 Nov 2020 14:53:54 -0800 Subject: [PATCH 015/117] tls: remove deprecated cipher suites from client defaults. (#13850) Signed-off-by: Piotr Sikora --- docs/root/version_history/current.rst | 1 + .../transport_sockets/tls/context_config_impl.cc | 10 +--------- .../transport_sockets/tls/ssl_socket_test.cc | 11 +++++++++++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 80916b08c6d9..f43c78832b0d 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -14,6 +14,7 @@ Minor Behavior Changes * ext_authz filter: disable `envoy.reloadable_features.ext_authz_measure_timeout_on_check_created` by default. * ext_authz filter: the deprecated field :ref:`use_alpha ` is no longer supported and cannot be set anymore. * grpc_web filter: if a `grpc-accept-encoding` header is present it's passed as-is to the upstream and if it isn't `grpc-accept-encoding:identity` is sent instead. The header was always overwriten with `grpc-accept-encoding:identity,deflate,gzip` before. +* tls: removed RSA key transport and SHA-1 cipher suites from the client-side defaults. * watchdog: the watchdog action :ref:`abort_action ` is now the default action to terminate the process if watchdog kill / multikill is enabled. * xds: to support TTLs, heartbeating has been added to xDS. As a result, responses that contain empty resources without updating the version will no longer be propagated to the subscribers. To undo this for VHDS (which is the only subscriber that wants empty resources), the `envoy.reloadable_features.vhds_heartbeats` can be set to "false". diff --git a/source/extensions/transport_sockets/tls/context_config_impl.cc b/source/extensions/transport_sockets/tls/context_config_impl.cc index 7e6a59d85919..21d8e983a5c0 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.cc +++ b/source/extensions/transport_sockets/tls/context_config_impl.cc @@ -337,16 +337,8 @@ const std::string ClientContextConfigImpl::DEFAULT_CIPHER_SUITES = "ECDHE-ECDSA-AES128-GCM-SHA256:" "ECDHE-RSA-AES128-GCM-SHA256:" #endif - "ECDHE-ECDSA-AES128-SHA:" - "ECDHE-RSA-AES128-SHA:" - "AES128-GCM-SHA256:" - "AES128-SHA:" "ECDHE-ECDSA-AES256-GCM-SHA384:" - "ECDHE-RSA-AES256-GCM-SHA384:" - "ECDHE-ECDSA-AES256-SHA:" - "ECDHE-RSA-AES256-SHA:" - "AES256-GCM-SHA384:" - "AES256-SHA"; + "ECDHE-RSA-AES256-GCM-SHA384:"; const std::string ClientContextConfigImpl::DEFAULT_CURVES = #ifndef BORINGSSL_FIPS diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index d7718e17997b..7d55f69361ce 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -3684,6 +3684,11 @@ TEST_P(SslSocketTest, ProtocolVersions) { envoy::extensions::transport_sockets::tls::v3::TlsParameters* client_params = client.mutable_common_tls_context()->mutable_tls_params(); + // Note: There aren't any valid TLSv1.0 or TLSv1.1 cipher suites enabled by default, + // so enable them to avoid false positives. + client_params->add_cipher_suites("ECDHE-RSA-AES128-SHA"); + server_params->add_cipher_suites("ECDHE-RSA-AES128-SHA"); + // Connection using defaults (client & server) succeeds, negotiating TLSv1.2. TestUtilOptionsV2 tls_v1_2_test_options = createProtocolTestOptions(listener, client, GetParam(), "TLSv1.2"); @@ -3983,6 +3988,12 @@ TEST_P(SslSocketTest, CipherSuites) { client_params->clear_cipher_suites(); server_params->clear_cipher_suites(); + // Client connects to a server offering only deprecated cipher suites, connection fails. + server_params->add_cipher_suites("ECDHE-RSA-AES128-SHA"); + error_test_options.setExpectedServerStats("ssl.connection_error"); + testUtilV2(error_test_options); + server_params->clear_cipher_suites(); + // Verify that ECDHE-RSA-CHACHA20-POLY1305 is not offered by default in FIPS builds. client_params->add_cipher_suites(common_cipher_suite); #ifdef BORINGSSL_FIPS From fe74d85d7d916eb975342e8298f2dc76279c1978 Mon Sep 17 00:00:00 2001 From: Wayne Zhang Date: Mon, 2 Nov 2020 19:35:47 -0800 Subject: [PATCH 016/117] jwt_authn: Support per-route config (#13773) Adding support on per-route config with following goal: not to do RouteMatch twice. Currently the filter is using RouteMatch to match a request with a specific Jwt requirement. But RouteMatch is also performed at routing, so the RouteMatch is done twice. If a Jwt requirement can be specified at the per-route config, one of RouteMatch can be eliminated. 1) Add a `requirement_map` to associate a requirement_name with a JwtRequirement in the filter config `JwtAuthentication`. 2) Add per-route-config as followings: * a requirement_name to specify a JwtRequirement. or * `disabled` flag to bypass Jwt verification if it is true. Risk Level: None. Added a new feature, old features are not impacted. Testing: Added unit-tests and integration tests. Docs Changes: Yes Release Notes: Added Platform Specific Features: None Signed-off-by: Wayne Zhang --- .../filters/http/jwt_authn/v3/config.proto | 36 ++++- .../http/jwt_authn/v4alpha/config.proto | 39 ++++- docs/root/version_history/current.rst | 1 + .../filters/http/jwt_authn/v3/config.proto | 36 ++++- .../http/jwt_authn/v4alpha/config.proto | 39 ++++- .../filters/http/jwt_authn/filter.cc | 37 ++++- .../filters/http/jwt_authn/filter_config.cc | 55 ++++++- .../filters/http/jwt_authn/filter_config.h | 28 ++++ .../filters/http/jwt_authn/filter_factory.cc | 8 + .../filters/http/jwt_authn/filter_factory.h | 10 +- .../http/jwt_authn/filter_config_test.cc | 152 ++++++++++++++++++ .../http/jwt_authn/filter_factory_test.cc | 49 ++++++ .../http/jwt_authn/filter_integration_test.cc | 136 ++++++++++++++++ .../filters/http/jwt_authn/filter_test.cc | 147 ++++++++++++++++- 14 files changed, 751 insertions(+), 22 deletions(-) diff --git a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 5588961bf512..0e4294608384 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -388,8 +388,18 @@ message RequirementRule { // config.route.v3.RouteMatch match = 1 [(validate.rules).message = {required: true}]; - // Specify a Jwt Requirement. Please detail comment in message JwtRequirement. - JwtRequirement requires = 2; + // Specify a Jwt requirement. + // If not specified, Jwt verification is disabled. + oneof requirement_type { + // Specify a Jwt requirement. Please see detail comment in message JwtRequirement. + JwtRequirement requires = 2; + + // Use requirement_name to specify a Jwt requirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. + string requirement_name = 3 [(validate.rules).string = {min_len: 1}]; + } } // This message specifies Jwt requirements based on stream_info.filterState. @@ -462,6 +472,7 @@ message FilterStateRule { // - provider_name: provider1 // - provider_name: provider2 // +// [#next-free-field: 6] message JwtAuthentication { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.jwt_authn.v2alpha.JwtAuthentication"; @@ -528,4 +539,25 @@ message JwtAuthentication { // `_ regardless of JWT // requirements specified in the rules. bool bypass_cors_preflight = 4; + + // A map of unique requirement_names to JwtRequirements. + // :ref:`requirement_name ` + // in `PerRouteConfig` uses this map to specify a JwtRequirement. + map requirement_map = 5; +} + +// Specify per-route config. +message PerRouteConfig { + oneof requirement_specifier { + option (validate.required) = true; + + // Disable Jwt Authentication for this route. + bool disabled = 1 [(validate.rules).bool = {const: true}]; + + // Use requirement_name to specify a JwtRequirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. If no, the requests using this route will be rejected with 403. + string requirement_name = 2 [(validate.rules).string = {min_len: 1}]; + } } diff --git a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto index 12d4fa5fe1d3..53ee84fd65ea 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -388,8 +388,18 @@ message RequirementRule { // config.route.v4alpha.RouteMatch match = 1 [(validate.rules).message = {required: true}]; - // Specify a Jwt Requirement. Please detail comment in message JwtRequirement. - JwtRequirement requires = 2; + // Specify a Jwt requirement. + // If not specified, Jwt verification is disabled. + oneof requirement_type { + // Specify a Jwt requirement. Please see detail comment in message JwtRequirement. + JwtRequirement requires = 2; + + // Use requirement_name to specify a Jwt requirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. + string requirement_name = 3 [(validate.rules).string = {min_len: 1}]; + } } // This message specifies Jwt requirements based on stream_info.filterState. @@ -462,6 +472,7 @@ message FilterStateRule { // - provider_name: provider1 // - provider_name: provider2 // +// [#next-free-field: 6] message JwtAuthentication { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication"; @@ -528,4 +539,28 @@ message JwtAuthentication { // `_ regardless of JWT // requirements specified in the rules. bool bypass_cors_preflight = 4; + + // A map of unique requirement_names to JwtRequirements. + // :ref:`requirement_name ` + // in `PerRouteConfig` uses this map to specify a JwtRequirement. + map requirement_map = 5; +} + +// Specify per-route config. +message PerRouteConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig"; + + oneof requirement_specifier { + option (validate.required) = true; + + // Disable Jwt Authentication for this route. + bool disabled = 1 [(validate.rules).bool = {const: true}]; + + // Use requirement_name to specify a JwtRequirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. If no, the requests using this route will be rejected with 403. + string requirement_name = 2 [(validate.rules).string = {min_len: 1}]; + } } diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index f43c78832b0d..f93681ce5c29 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -43,6 +43,7 @@ New Features * hds: added support for delta updates in the :ref:`HealthCheckSpecifier `, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS. * health_check: added option to use :ref:`no_traffic_healthy_interval ` which allows a different no traffic interval when the host is healthy. * http: added frame flood and abuse checks to the upstream HTTP/2 codec. This check is off by default and can be enabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to true. +* jwt_authn: added support for :ref:`per-route config `. * listener: added an optional :ref:`default filter chain `. If this field is supplied, and none of the :ref:`filter_chains ` matches, this default filter chain is used to serve the connection. * lua: added `downstreamDirectRemoteAddress()` and `downstreamLocalAddress()` APIs to :ref:`streamInfo() `. * mongo_proxy: the list of commands to produce metrics for is now :ref:`configurable `. diff --git a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 5588961bf512..0e4294608384 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -388,8 +388,18 @@ message RequirementRule { // config.route.v3.RouteMatch match = 1 [(validate.rules).message = {required: true}]; - // Specify a Jwt Requirement. Please detail comment in message JwtRequirement. - JwtRequirement requires = 2; + // Specify a Jwt requirement. + // If not specified, Jwt verification is disabled. + oneof requirement_type { + // Specify a Jwt requirement. Please see detail comment in message JwtRequirement. + JwtRequirement requires = 2; + + // Use requirement_name to specify a Jwt requirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. + string requirement_name = 3 [(validate.rules).string = {min_len: 1}]; + } } // This message specifies Jwt requirements based on stream_info.filterState. @@ -462,6 +472,7 @@ message FilterStateRule { // - provider_name: provider1 // - provider_name: provider2 // +// [#next-free-field: 6] message JwtAuthentication { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.jwt_authn.v2alpha.JwtAuthentication"; @@ -528,4 +539,25 @@ message JwtAuthentication { // `_ regardless of JWT // requirements specified in the rules. bool bypass_cors_preflight = 4; + + // A map of unique requirement_names to JwtRequirements. + // :ref:`requirement_name ` + // in `PerRouteConfig` uses this map to specify a JwtRequirement. + map requirement_map = 5; +} + +// Specify per-route config. +message PerRouteConfig { + oneof requirement_specifier { + option (validate.required) = true; + + // Disable Jwt Authentication for this route. + bool disabled = 1 [(validate.rules).bool = {const: true}]; + + // Use requirement_name to specify a JwtRequirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. If no, the requests using this route will be rejected with 403. + string requirement_name = 2 [(validate.rules).string = {min_len: 1}]; + } } diff --git a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto index 12d4fa5fe1d3..53ee84fd65ea 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -388,8 +388,18 @@ message RequirementRule { // config.route.v4alpha.RouteMatch match = 1 [(validate.rules).message = {required: true}]; - // Specify a Jwt Requirement. Please detail comment in message JwtRequirement. - JwtRequirement requires = 2; + // Specify a Jwt requirement. + // If not specified, Jwt verification is disabled. + oneof requirement_type { + // Specify a Jwt requirement. Please see detail comment in message JwtRequirement. + JwtRequirement requires = 2; + + // Use requirement_name to specify a Jwt requirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. + string requirement_name = 3 [(validate.rules).string = {min_len: 1}]; + } } // This message specifies Jwt requirements based on stream_info.filterState. @@ -462,6 +472,7 @@ message FilterStateRule { // - provider_name: provider1 // - provider_name: provider2 // +// [#next-free-field: 6] message JwtAuthentication { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication"; @@ -528,4 +539,28 @@ message JwtAuthentication { // `_ regardless of JWT // requirements specified in the rules. bool bypass_cors_preflight = 4; + + // A map of unique requirement_names to JwtRequirements. + // :ref:`requirement_name ` + // in `PerRouteConfig` uses this map to specify a JwtRequirement. + map requirement_map = 5; +} + +// Specify per-route config. +message PerRouteConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig"; + + oneof requirement_specifier { + option (validate.required) = true; + + // Disable Jwt Authentication for this route. + bool disabled = 1 [(validate.rules).bool = {const: true}]; + + // Use requirement_name to specify a JwtRequirement. + // This requirement_name MUST be specified at the + // :ref:`requirement_map ` + // in `JwtAuthentication`. If no, the requests using this route will be rejected with 403. + string requirement_name = 2 [(validate.rules).string = {min_len: 1}]; + } } diff --git a/source/extensions/filters/http/jwt_authn/filter.cc b/source/extensions/filters/http/jwt_authn/filter.cc index 7dfa78a4257b..343ebc1cbeff 100644 --- a/source/extensions/filters/http/jwt_authn/filter.cc +++ b/source/extensions/filters/http/jwt_authn/filter.cc @@ -5,6 +5,7 @@ #include "extensions/filters/http/well_known_names.h" +#include "absl/strings/str_split.h" #include "jwt_verify_lib/status.h" using ::google::jwt_verify::Status; @@ -29,6 +30,14 @@ bool isCorsPreflightRequest(const Http::RequestHeaderMap& headers) { // The prefix used in the response code detail sent from jwt authn filter. constexpr absl::string_view kRcDetailJwtAuthnPrefix = "jwt_authn_access_denied"; + +std::string generateRcDetails(absl::string_view error_msg) { + // Replace space with underscore since RCDetails may be written to access log. + // Some log processors assume each log segment is separated by whitespace. + return absl::StrCat(kRcDetailJwtAuthnPrefix, "{", + absl::StrJoin(absl::StrSplit(error_msg, ' '), "_"), "}"); +} + } // namespace Filter::Filter(FilterConfigSharedPtr config) @@ -56,12 +65,29 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, return Http::FilterHeadersStatus::Continue; } - // Verify the JWT token, onComplete() will be called when completed. - const auto* verifier = - config_->findVerifier(headers, *decoder_callbacks_->streamInfo().filterState()); - if (!verifier) { + const Verifier* verifier = nullptr; + const auto* per_route_config = + Http::Utility::resolveMostSpecificPerFilterConfig( + HttpFilterNames::get().JwtAuthn, decoder_callbacks_->route()); + if (per_route_config != nullptr) { + std::string error_msg; + std::tie(verifier, error_msg) = config_->findPerRouteVerifier(*per_route_config); + if (!error_msg.empty()) { + stats_.denied_.inc(); + state_ = Responded; + decoder_callbacks_->sendLocalReply(Http::Code::Forbidden, + absl::StrCat("Failed JWT authentication: ", error_msg), + nullptr, absl::nullopt, generateRcDetails(error_msg)); + return Http::FilterHeadersStatus::StopIteration; + } + } else { + verifier = config_->findVerifier(headers, *decoder_callbacks_->streamInfo().filterState()); + } + + if (verifier == nullptr) { onComplete(Status::Ok); } else { + // Verify the JWT token, onComplete() will be called when completed. context_ = Verifier::createContext(headers, decoder_callbacks_->activeSpan(), this); verifier->verify(context_); } @@ -94,8 +120,7 @@ void Filter::onComplete(const Status& status) { // return failure reason as message body decoder_callbacks_->sendLocalReply( code, ::google::jwt_verify::getStatusString(status), nullptr, absl::nullopt, - absl::StrCat(kRcDetailJwtAuthnPrefix, "{", ::google::jwt_verify::getStatusString(status), - "}")); + generateRcDetails(::google::jwt_verify::getStatusString(status))); return; } stats_.allowed_.inc(); diff --git a/source/extensions/filters/http/jwt_authn/filter_config.cc b/source/extensions/filters/http/jwt_authn/filter_config.cc index 860236284692..82d0fdd3ae42 100644 --- a/source/extensions/filters/http/jwt_authn/filter_config.cc +++ b/source/extensions/filters/http/jwt_authn/filter_config.cc @@ -1,5 +1,11 @@ #include "extensions/filters/http/jwt_authn/filter_config.h" +#include // std::sort + +#include "common/common/empty_string.h" + +using envoy::extensions::filters::http::jwt_authn::v3::RequirementRule; + namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -17,9 +23,38 @@ void FilterConfigImpl::init() { shared_this->api_); }); + std::vector names; + for (const auto& it : proto_config_.requirement_map()) { + names.push_back(it.first); + name_verifiers_.emplace(it.first, + Verifier::create(it.second, proto_config_.providers(), *this)); + } + // sort is just for unit-test since protobuf map order is not deterministic. + std::sort(names.begin(), names.end()); + all_requirement_names_ = absl::StrJoin(names, ","); + for (const auto& rule : proto_config_.rules()) { - rule_pairs_.emplace_back(Matcher::create(rule), - Verifier::create(rule.requires(), proto_config_.providers(), *this)); + switch (rule.requirement_type_case()) { + case RequirementRule::RequirementTypeCase::kRequires: + rule_pairs_.emplace_back(Matcher::create(rule), + Verifier::create(rule.requires(), proto_config_.providers(), *this)); + break; + case RequirementRule::RequirementTypeCase::kRequirementName: { + // Use requirement_name to lookup requirement_map. + auto map_it = proto_config_.requirement_map().find(rule.requirement_name()); + if (map_it == proto_config_.requirement_map().end()) { + throw EnvoyException(fmt::format("Wrong requirement_name: {}. It should be one of [{}]", + rule.requirement_name(), all_requirement_names_)); + } + rule_pairs_.emplace_back(Matcher::create(rule), + Verifier::create(map_it->second, proto_config_.providers(), *this)); + } break; + case RequirementRule::RequirementTypeCase::REQUIREMENT_TYPE_NOT_SET: + rule_pairs_.emplace_back(Matcher::create(rule), nullptr); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } } if (proto_config_.has_filter_state_rules()) { @@ -31,6 +66,22 @@ void FilterConfigImpl::init() { } } +std::pair +FilterConfigImpl::findPerRouteVerifier(const PerRouteFilterConfig& per_route) const { + if (per_route.config().disabled()) { + return std::make_pair(nullptr, EMPTY_STRING); + } + + const auto& it = name_verifiers_.find(per_route.config().requirement_name()); + if (it != name_verifiers_.end()) { + return std::make_pair(it->second.get(), EMPTY_STRING); + } + + return std::make_pair( + nullptr, absl::StrCat("Wrong requirement_name: ", per_route.config().requirement_name(), + ". It should be one of [", all_requirement_names_, "]")); +} + } // namespace JwtAuthn } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/jwt_authn/filter_config.h b/source/extensions/filters/http/jwt_authn/filter_config.h index 53c7cc965be3..9b6e64b7e6d7 100644 --- a/source/extensions/filters/http/jwt_authn/filter_config.h +++ b/source/extensions/filters/http/jwt_authn/filter_config.h @@ -54,6 +54,23 @@ struct JwtAuthnFilterStats { ALL_JWT_AUTHN_FILTER_STATS(GENERATE_COUNTER_STRUCT) }; +/** + * The per-route filter config + */ +class PerRouteFilterConfig : public Envoy::Router::RouteSpecificFilterConfig { +public: + PerRouteFilterConfig( + const envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig& config) + : config_(config) {} + + const envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig& config() const { + return config_; + } + +private: + const envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig config_; +}; + /** * The filter config interface. It is an interface so that we can mock it in tests. */ @@ -68,6 +85,10 @@ class FilterConfig { // Finds the matcher that matched the header virtual const Verifier* findVerifier(const Http::RequestHeaderMap& headers, const StreamInfo::FilterState& filter_state) const PURE; + + // Finds the verifier based on per-route config. If fail, pair.second has the error message. + virtual std::pair + findPerRouteVerifier(const PerRouteFilterConfig& per_route) const PURE; }; using FilterConfigSharedPtr = std::shared_ptr; @@ -123,6 +144,9 @@ class FilterConfigImpl : public Logger::Loggable, return nullptr; } + std::pair + findPerRouteVerifier(const PerRouteFilterConfig& per_route) const override; + // methods for AuthFactory interface. Factory method to help create authenticators. AuthenticatorPtr create(const ::google::jwt_verify::CheckAudience* check_audience, const absl::optional& provider, bool allow_failed, @@ -168,6 +192,10 @@ class FilterConfigImpl : public Logger::Loggable, std::string filter_state_name_; // The filter state verifier map from filter_state_rules. absl::flat_hash_map filter_state_verifiers_; + // The requirement_name to verifier map. + absl::flat_hash_map name_verifiers_; + // all requirement_names for debug + std::string all_requirement_names_; TimeSource& time_source_; Api::Api& api_; }; diff --git a/source/extensions/filters/http/jwt_authn/filter_factory.cc b/source/extensions/filters/http/jwt_authn/filter_factory.cc index 60d938a76932..08b2669d65a2 100644 --- a/source/extensions/filters/http/jwt_authn/filter_factory.cc +++ b/source/extensions/filters/http/jwt_authn/filter_factory.cc @@ -51,6 +51,14 @@ FilterFactory::createFilterFactoryFromProtoTyped(const JwtAuthentication& proto_ }; } +Envoy::Router::RouteSpecificFilterConfigConstSharedPtr +FilterFactory::createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig& per_route, + Envoy::Server::Configuration::ServerFactoryContext&, + Envoy::ProtobufMessage::ValidationVisitor&) { + return std::make_shared(per_route); +} + /** * Static registration for this jwt_authn filter. @see RegisterFactory. */ diff --git a/source/extensions/filters/http/jwt_authn/filter_factory.h b/source/extensions/filters/http/jwt_authn/filter_factory.h index b085300edd1d..c0398c71e421 100644 --- a/source/extensions/filters/http/jwt_authn/filter_factory.h +++ b/source/extensions/filters/http/jwt_authn/filter_factory.h @@ -15,8 +15,9 @@ namespace JwtAuthn { /** * Config registration for jwt_authn filter. */ -class FilterFactory : public Common::FactoryBase< - envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication> { +class FilterFactory + : public Common::FactoryBase { public: FilterFactory() : FactoryBase(HttpFilterNames::get().JwtAuthn) {} @@ -24,6 +25,11 @@ class FilterFactory : public Common::FactoryBase< Http::FilterFactoryCb createFilterFactoryFromProtoTyped( const envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override; + + Envoy::Router::RouteSpecificFilterConfigConstSharedPtr createRouteSpecificFilterConfigTyped( + const envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig& per_route, + Envoy::Server::Configuration::ServerFactoryContext&, + Envoy::ProtobufMessage::ValidationVisitor&) override; }; } // namespace JwtAuthn diff --git a/test/extensions/filters/http/jwt_authn/filter_config_test.cc b/test/extensions/filters/http/jwt_authn/filter_config_test.cc index 45da3fea600f..6aae1a8e1b39 100644 --- a/test/extensions/filters/http/jwt_authn/filter_config_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_config_test.cc @@ -13,6 +13,7 @@ #include "gtest/gtest.h" using envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication; +using envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig; using testing::ReturnRef; namespace Envoy { @@ -57,6 +58,100 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByMatch) { filter_state) == nullptr); } +TEST(HttpJwtAuthnFilterConfigTest, FindByMatchDisabled) { + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + local_jwks: + inline_string: jwks +rules: +- match: + path: /path1 +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + + StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::FilterChain); + EXPECT_TRUE(filter_conf->findVerifier( + Http::TestRequestHeaderMapImpl{ + {":path", "/path1"}, + }, + filter_state) == nullptr); +} + +TEST(HttpJwtAuthnFilterConfigTest, FindByMatchWrongRequirementName) { + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + local_jwks: + inline_string: jwks +rules: +- match: + path: /path1 + requirement_name: rr +requirement_map: + r1: + provider_name: provider1 +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + EXPECT_THROW_WITH_MESSAGE(FilterConfigImpl::create(proto_config, "", context), EnvoyException, + "Wrong requirement_name: rr. It should be one of [r1]"); +} + +TEST(HttpJwtAuthnFilterConfigTest, FindByMatchRequirementName) { + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + local_jwks: + inline_string: jwks + provider2: + issuer: issuer2 + local_jwks: + inline_string: jwks +rules: +- match: + path: /path1 + requirement_name: r1 +- match: + path: /path2 + requirement_name: r2 +requirement_map: + r1: + provider_name: provider1 + r2: + provider_name: provider2 +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + StreamInfo::FilterStateImpl filter_state(StreamInfo::FilterState::LifeSpan::FilterChain); + + EXPECT_TRUE(filter_conf->findVerifier( + Http::TestRequestHeaderMapImpl{ + {":path", "/path1"}, + }, + filter_state) != nullptr); + EXPECT_TRUE(filter_conf->findVerifier( + Http::TestRequestHeaderMapImpl{ + {":path", "/path2"}, + }, + filter_state) != nullptr); +} + TEST(HttpJwtAuthnFilterConfigTest, VerifyTLSLifetime) { const char config[] = R"( providers: @@ -159,6 +254,63 @@ TEST(HttpJwtAuthnFilterConfigTest, FindByFilterState) { nullptr); } +TEST(HttpJwtAuthnFilterConfigTest, FindByRequiremenMap) { + const char config[] = R"( +providers: + provider1: + issuer: issuer1 + local_jwks: + inline_string: jwks + provider2: + issuer: issuer2 + local_jwks: + inline_string: jwks +requirement_map: + r1: + provider_name: provider1 + r2: + provider_name: provider2 +)"; + + JwtAuthentication proto_config; + TestUtility::loadFromYaml(config, proto_config); + + NiceMock context; + auto filter_conf = FilterConfigImpl::create(proto_config, "", context); + + PerRouteConfig per_route; + const Verifier* verifier; + std::string error_msg; + + per_route.Clear(); + per_route.set_disabled(true); + std::tie(verifier, error_msg) = + filter_conf->findPerRouteVerifier(PerRouteFilterConfig(per_route)); + EXPECT_EQ(verifier, nullptr); + EXPECT_EQ(error_msg, EMPTY_STRING); + + per_route.Clear(); + per_route.set_requirement_name("r1"); + std::tie(verifier, error_msg) = + filter_conf->findPerRouteVerifier(PerRouteFilterConfig(per_route)); + EXPECT_NE(verifier, nullptr); + EXPECT_EQ(error_msg, EMPTY_STRING); + + per_route.Clear(); + per_route.set_requirement_name("r2"); + std::tie(verifier, error_msg) = + filter_conf->findPerRouteVerifier(PerRouteFilterConfig(per_route)); + EXPECT_NE(verifier, nullptr); + EXPECT_EQ(error_msg, EMPTY_STRING); + + per_route.Clear(); + per_route.set_requirement_name("wrong-name"); + std::tie(verifier, error_msg) = + filter_conf->findPerRouteVerifier(PerRouteFilterConfig(per_route)); + EXPECT_EQ(verifier, nullptr); + EXPECT_EQ(error_msg, "Wrong requirement_name: wrong-name. It should be one of [r1,r2]"); +} + } // namespace } // namespace JwtAuthn } // namespace HttpFilters diff --git a/test/extensions/filters/http/jwt_authn/filter_factory_test.cc b/test/extensions/filters/http/jwt_authn/filter_factory_test.cc index ea89ea616225..f58adfe62226 100644 --- a/test/extensions/filters/http/jwt_authn/filter_factory_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_factory_test.cc @@ -1,6 +1,7 @@ #include "envoy/extensions/filters/http/jwt_authn/v3/config.pb.h" #include "envoy/extensions/filters/http/jwt_authn/v3/config.pb.validate.h" +#include "extensions/filters/http/jwt_authn/filter_config.h" #include "extensions/filters/http/jwt_authn/filter_factory.h" #include "test/extensions/filters/http/jwt_authn/test_common.h" @@ -10,6 +11,7 @@ #include "gtest/gtest.h" using envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication; +using envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig; using testing::_; namespace Envoy { @@ -56,6 +58,53 @@ TEST(HttpJwtAuthnFilterFactoryTest, BadLocalJwks) { EXPECT_THROW(factory.createFilterFactoryFromProto(proto_config, "stats", context), EnvoyException); } + +TEST(HttpJwtAuthnFilterFactoryTest, EmptyPerRouteConfig) { + PerRouteConfig per_route; + NiceMock context; + FilterFactory factory; + EXPECT_THROW(factory.createRouteSpecificFilterConfig(per_route, context, + context.messageValidationVisitor()), + EnvoyException); +} + +TEST(HttpJwtAuthnFilterFactoryTest, WrongPerRouteConfigType) { + JwtAuthentication per_route; + NiceMock context; + FilterFactory factory; + EXPECT_THROW(factory.createRouteSpecificFilterConfig(per_route, context, + context.messageValidationVisitor()), + std::bad_cast); +} + +TEST(HttpJwtAuthnFilterFactoryTest, DisabledPerRouteConfig) { + PerRouteConfig per_route; + per_route.set_disabled(true); + + NiceMock context; + FilterFactory factory; + auto base_ptr = factory.createRouteSpecificFilterConfig(per_route, context, + context.messageValidationVisitor()); + EXPECT_NE(base_ptr, nullptr); + const PerRouteFilterConfig* typed_ptr = dynamic_cast(base_ptr.get()); + EXPECT_NE(typed_ptr, nullptr); + EXPECT_TRUE(typed_ptr->config().disabled()); +} + +TEST(HttpJwtAuthnFilterFactoryTest, GoodPerRouteConfig) { + PerRouteConfig per_route; + per_route.set_requirement_name("name"); + + NiceMock context; + FilterFactory factory; + auto base_ptr = factory.createRouteSpecificFilterConfig(per_route, context, + context.messageValidationVisitor()); + EXPECT_NE(base_ptr, nullptr); + const PerRouteFilterConfig* typed_ptr = dynamic_cast(base_ptr.get()); + EXPECT_NE(typed_ptr, nullptr); + EXPECT_EQ(typed_ptr->config().requirement_name(), "name"); +} + } // namespace } // namespace JwtAuthn } // namespace HttpFilters diff --git a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc index 4c71a7bef275..bf0af7934ab3 100644 --- a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc @@ -13,6 +13,7 @@ #include "test/test_common/registry.h" using envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication; +using envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig; using envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter; namespace Envoy { @@ -447,6 +448,141 @@ TEST_P(RemoteJwksIntegrationTest, FetchFailedMissingCluster) { cleanup(); } +class PerRouteIntegrationTest : public HttpProtocolIntegrationTest { +public: + void setup(const std::string& filter_config, const PerRouteConfig& per_route) { + config_helper_.addFilter(getAuthFilterConfig(filter_config, true)); + + config_helper_.addConfigModifier( + [per_route]( + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + auto* virtual_host = hcm.mutable_route_config()->mutable_virtual_hosts(0); + auto& per_route_any = + (*virtual_host->mutable_routes(0) + ->mutable_typed_per_filter_config())[HttpFilterNames::get().JwtAuthn]; + per_route_any.PackFrom(per_route); + }); + + initialize(); + } +}; + +INSTANTIATE_TEST_SUITE_P(Protocols, PerRouteIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams()), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +// This test verifies per-route config disabled. +TEST_P(PerRouteIntegrationTest, PerRouteConfigDisabled) { + // per-route config has disabled flag. + PerRouteConfig per_route; + per_route.set_disabled(true); + // Use a normal filter config that requires jwt_auth. + setup(ExampleConfig, per_route); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // So the request without a JWT token is OK. + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + }); + + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); +} + +// This test verifies per-route config with wrong requirement_name +TEST_P(PerRouteIntegrationTest, PerRouteConfigWrongRequireName) { + // A config with a requirement_map + const std::string filter_conf = R"( + providers: + example_provider: + issuer: https://example.com + audiences: + - example_service + requirement_map: + abc: + provider_name: "example_provider" +)"; + + // Per-route config has a wrong requirement_name. + PerRouteConfig per_route; + per_route.set_requirement_name("wrong-requirement-name"); + setup(filter_conf, per_route); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // So the request with a good Jwt token is rejected. + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"Authorization", "Bearer " + std::string(GoodToken)}, + }); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("403", response->headers().getStatusValue()); +} + +// This test verifies per-route config with correct requirement_name +TEST_P(PerRouteIntegrationTest, PerRouteConfigOK) { + // A config with a requirement_map + const std::string filter_conf = R"( + providers: + example_provider: + issuer: https://example.com + audiences: + - example_service + requirement_map: + abc: + provider_name: "example_provider" +)"; + + // Per-route config with correct requirement_name + PerRouteConfig per_route; + per_route.set_requirement_name("abc"); + setup(filter_conf, per_route); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // So the request with a JWT token is OK. + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"Authorization", "Bearer " + std::string(GoodToken)}, + }); + + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + + // A request with missing token is rejected. + auto response1 = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + }); + + response1->waitForEndStream(); + ASSERT_TRUE(response1->complete()); + EXPECT_EQ("401", response1->headers().getStatusValue()); +} + } // namespace } // namespace JwtAuthn } // namespace HttpFilters diff --git a/test/extensions/filters/http/jwt_authn/filter_test.cc b/test/extensions/filters/http/jwt_authn/filter_test.cc index 059f9f6656b8..8983d94aafd2 100644 --- a/test/extensions/filters/http/jwt_authn/filter_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_test.cc @@ -42,6 +42,8 @@ class MockFilterConfig : public FilterConfig { MOCK_METHOD(const Verifier*, findVerifier, (const Http::RequestHeaderMap& headers, const StreamInfo::FilterState& filter_state), (const)); + MOCK_METHOD((std::pair), findPerRouteVerifier, + (const PerRouteFilterConfig& per_route), (const)); MOCK_METHOD(bool, bypassCorsPreflightRequest, (), (const)); MOCK_METHOD(JwtAuthnFilterStats&, stats, ()); @@ -57,6 +59,10 @@ class FilterTest : public testing::Test { mock_verifier_ = std::make_unique(); filter_ = std::make_unique(mock_config_); filter_->setDecoderFilterCallbacks(filter_callbacks_); + + mock_route_ = std::make_shared>(); + envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig per_route; + per_route_config_ = std::make_shared(per_route); } void setupMockConfig() { @@ -69,6 +75,8 @@ class FilterTest : public testing::Test { std::unique_ptr mock_verifier_; NiceMock verifier_callback_; Http::TestRequestTrailerMapImpl trailers_; + std::shared_ptr> mock_route_; + std::shared_ptr per_route_config_; }; // This test verifies Verifier::Callback is called inline with OK status. @@ -179,8 +187,8 @@ TEST_F(FilterTest, InlineUnauthorizedFailure) { Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); - EXPECT_EQ(filter_callbacks_.details(), "jwt_authn_access_denied{Jwt is not in the form of " - "Header.Payload.Signature with two dots and 3 sections}"); + EXPECT_EQ(filter_callbacks_.details(), "jwt_authn_access_denied{Jwt_is_not_in_the_form_of_" + "Header.Payload.Signature_with_two_dots_and_3_sections}"); } // This test verifies Verifier::Callback is called inline with a failure(403 Forbidden) status. @@ -202,7 +210,7 @@ TEST_F(FilterTest, InlineForbiddenFailure) { EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); EXPECT_EQ(filter_callbacks_.details(), - "jwt_authn_access_denied{Audiences in Jwt are not allowed}"); + "jwt_authn_access_denied{Audiences_in_Jwt_are_not_allowed}"); } // This test verifies Verifier::Callback is called with OK status after verify(). @@ -288,8 +296,139 @@ TEST_F(FilterTest, OutBoundForbiddenFailure) { } // Test verifies that if no route matched requirement, then request is allowed. -TEST_F(FilterTest, TestNoRouteMatched) { +TEST_F(FilterTest, TestNoRequirementMatched) { + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).WillOnce(Return(nullptr)); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().allowed_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); +} + +// Test if route() return null, fallback to call config config. +TEST_F(FilterTest, TestNoRoute) { + // route() call return nullptr + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(nullptr)); + + // Calling the findVerifier from filter config. + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).WillOnce(Return(nullptr)); + // findPerRouteVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)).Times(0); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().allowed_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); +} + +// Test if routeEntry() return null, fallback to call config config. +TEST_F(FilterTest, TestNoRouteEnty) { + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(mock_route_)); + // routeEntry() call return nullptr + EXPECT_CALL(*mock_route_, routeEntry()).WillOnce(Return(nullptr)); + + // Calling the findVerifier from filter config. EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).WillOnce(Return(nullptr)); + // findPerRouteVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)).Times(0); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().allowed_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); +} + +// Test if no per-route config, fallback to call config config. +TEST_F(FilterTest, TestNoPerRouteConfig) { + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(mock_route_)); + // perFilterConfig return nullptr. + EXPECT_CALL(mock_route_->route_entry_, perFilterConfig(HttpFilterNames::get().JwtAuthn)) + .WillOnce(Return(nullptr)); + + // Calling the findVerifier from filter config. + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).WillOnce(Return(nullptr)); + // findPerRouteVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)).Times(0); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().allowed_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); +} + +// Test bypass requirement from per-route config +TEST_F(FilterTest, TestPerRouteBypass) { + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(mock_route_)); + EXPECT_CALL(mock_route_->route_entry_, perFilterConfig(HttpFilterNames::get().JwtAuthn)) + .WillOnce(Return(per_route_config_.get())); + + // findVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).Times(0); + // If findPerRouteVerifier is called, and return nullptr, it means bypass + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)) + .WillOnce(Return(std::make_pair(nullptr, EMPTY_STRING))); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().allowed_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); +} + +// Test per-route config with wrong requirement_name +TEST_F(FilterTest, TestPerRouteWrongRequirementName) { + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(mock_route_)); + EXPECT_CALL(mock_route_->route_entry_, perFilterConfig(HttpFilterNames::get().JwtAuthn)) + .WillOnce(Return(per_route_config_.get())); + + // findVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).Times(0); + // If findPerRouteVerifier is called, and return error message. + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)) + .WillOnce( + Return(std::make_pair(nullptr, "Wrong requirement_name: abc. Correct names are: r1,r2"))); + + auto headers = Http::TestRequestHeaderMapImpl{}; + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_->decodeHeaders(headers, false)); + EXPECT_EQ(1U, mock_config_->stats().denied_.value()); + + Buffer::OwnedImpl data(""); + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); + EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(trailers_)); + EXPECT_EQ(filter_callbacks_.details(), + "jwt_authn_access_denied{Wrong_requirement_name:_abc._Correct_names_are:_r1,r2}"); +} + +// Test verifier from per-route config +TEST_F(FilterTest, TestPerRouteVerifierOK) { + EXPECT_CALL(filter_callbacks_, route()).WillOnce(Return(mock_route_)); + EXPECT_CALL(mock_route_->route_entry_, perFilterConfig(HttpFilterNames::get().JwtAuthn)) + .WillOnce(Return(per_route_config_.get())); + + // findVerifier is not called. + EXPECT_CALL(*mock_config_.get(), findVerifier(_, _)).Times(0); + // If findPerRouteVerifier is called, and return the mock_verifier_. + EXPECT_CALL(*mock_config_.get(), findPerRouteVerifier(_)) + .WillOnce(Return(std::make_pair(mock_verifier_.get(), EMPTY_STRING))); + + // A successful authentication + EXPECT_CALL(*mock_verifier_, verify(_)).WillOnce(Invoke([](ContextSharedPtr context) { + context->callback()->onComplete(Status::Ok); + })); auto headers = Http::TestRequestHeaderMapImpl{}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); From bd73f3c4da0efffb2593d7c9ecf87788856dc052 Mon Sep 17 00:00:00 2001 From: Nupur Garg <37600866+gargnupur@users.noreply.github.com> Date: Mon, 2 Nov 2020 19:39:22 -0800 Subject: [PATCH 017/117] Add Connection_Termination_Details as a CEL property (#13821) Commit Message: Add Connection_Termination_Details as a CEL property Additional Description: Risk Level: low Testing: unit tests Docs Changes: yes Release Notes: yes Signed-off-by: gargnupur --- docs/root/intro/arch_overview/security/rbac_filter.rst | 1 + docs/root/version_history/current.rst | 1 + source/extensions/filters/common/expr/context.cc | 5 +++++ source/extensions/filters/common/expr/context.h | 1 + test/extensions/filters/common/expr/context_test.cc | 10 ++++++++++ 5 files changed, 18 insertions(+) diff --git a/docs/root/intro/arch_overview/security/rbac_filter.rst b/docs/root/intro/arch_overview/security/rbac_filter.rst index b12d568a25ad..b8c1190234b0 100644 --- a/docs/root/intro/arch_overview/security/rbac_filter.rst +++ b/docs/root/intro/arch_overview/security/rbac_filter.rst @@ -102,6 +102,7 @@ The following attributes are exposed to the language runtime: connection.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the downstream TLS connection connection.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the downstream TLS connection connection.id, uint, Downstream connection ID + connection.termination_details, string, The termination details of the connection upstream.address, string, Upstream connection remote address upstream.port, int, Upstream connection remote port upstream.tls_version, string, TLS version of the upstream TLS connection diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index f93681ce5c29..43c4ce2f72b5 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -11,6 +11,7 @@ Minor Behavior Changes * build: the Alpine based debug images are no longer built in CI, use Ubuntu based images instead. * cluster manager: the cluster which can't extract secret entity by SDS to be warming and never activate. This feature is disabled by default and is controlled by runtime guard `envoy.reloadable_features.cluster_keep_warming_no_secret_entity`. +* expr filter: added `connection.termination_details` property support. * ext_authz filter: disable `envoy.reloadable_features.ext_authz_measure_timeout_on_check_created` by default. * ext_authz filter: the deprecated field :ref:`use_alpha ` is no longer supported and cannot be set anymore. * grpc_web filter: if a `grpc-accept-encoding` header is present it's passed as-is to the upstream and if it isn't `grpc-accept-encoding:identity` is sent instead. The header was always overwriten with `grpc-accept-encoding:identity,deflate,gzip` before. diff --git a/source/extensions/filters/common/expr/context.cc b/source/extensions/filters/common/expr/context.cc index 9313a550695e..a0298a5095b4 100644 --- a/source/extensions/filters/common/expr/context.cc +++ b/source/extensions/filters/common/expr/context.cc @@ -190,6 +190,11 @@ absl::optional ConnectionWrapper::operator[](CelValue key) const { return CelValue::CreateUint64(id.value()); } return {}; + } else if (value == ConnectionTerminationDetails) { + if (info_.connectionTerminationDetails().has_value()) { + return CelValue::CreateString(&info_.connectionTerminationDetails().value()); + } + return {}; } auto ssl_info = info_.downstreamSslConnection(); diff --git a/source/extensions/filters/common/expr/context.h b/source/extensions/filters/common/expr/context.h index fd4b386a9a32..2faf80b0fd8f 100644 --- a/source/extensions/filters/common/expr/context.h +++ b/source/extensions/filters/common/expr/context.h @@ -54,6 +54,7 @@ constexpr absl::string_view Connection = "connection"; constexpr absl::string_view MTLS = "mtls"; constexpr absl::string_view RequestedServerName = "requested_server_name"; constexpr absl::string_view TLSVersion = "tls_version"; +constexpr absl::string_view ConnectionTerminationDetails = "termination_details"; constexpr absl::string_view SubjectLocalCertificate = "subject_local_certificate"; constexpr absl::string_view SubjectPeerCertificate = "subject_peer_certificate"; constexpr absl::string_view URISanLocalCertificate = "uri_san_local_certificate"; diff --git a/test/extensions/filters/common/expr/context_test.cc b/test/extensions/filters/common/expr/context_test.cc index 88f4c980d62d..208d5817c853 100644 --- a/test/extensions/filters/common/expr/context_test.cc +++ b/test/extensions/filters/common/expr/context_test.cc @@ -448,6 +448,9 @@ TEST(Context, ConnectionAttributes) { EXPECT_CALL(info, upstreamTransportFailureReason()) .WillRepeatedly(ReturnRef(upstream_transport_failure_reason)); EXPECT_CALL(info, connectionID()).WillRepeatedly(Return(123)); + const absl::optional connection_termination_details = "unauthorized"; + EXPECT_CALL(info, connectionTerminationDetails()) + .WillRepeatedly(ReturnRef(connection_termination_details)); EXPECT_CALL(*downstream_ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true)); EXPECT_CALL(*upstream_host, address()).WillRepeatedly(Return(upstream_address)); @@ -611,6 +614,13 @@ TEST(Context, ConnectionAttributes) { EXPECT_EQ(123, value.value().Uint64OrDie()); } + { + auto value = connection[CelValue::CreateStringView(ConnectionTerminationDetails)]; + EXPECT_TRUE(value.has_value()); + ASSERT_TRUE(value.value().IsString()); + EXPECT_EQ(connection_termination_details.value(), value.value().StringOrDie().value()); + } + { auto value = upstream[CelValue::CreateStringView(TLSVersion)]; EXPECT_TRUE(value.has_value()); From e7a0f7c891bfcdb84c7b7d03bb5b48f1f148a47e Mon Sep 17 00:00:00 2001 From: Matt Klein Date: Tue, 3 Nov 2020 07:06:40 -0800 Subject: [PATCH 018/117] tap: warn when using admin tap and admin is listening on a pipe (#13838) Docs were done in a previous PR. Fixes #6387 Risk Level: Low Testing: New UT Docs Changes: Already done Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Matt Klein --- source/extensions/common/tap/admin.cc | 4 ++++ test/extensions/common/tap/BUILD | 1 + test/extensions/common/tap/admin_test.cc | 18 +++++++++++++++++- test/mocks/server/BUILD | 1 + test/mocks/server/admin.cc | 9 +++++++-- test/mocks/server/admin.h | 9 ++++++++- 6 files changed, 38 insertions(+), 4 deletions(-) diff --git a/source/extensions/common/tap/admin.cc b/source/extensions/common/tap/admin.cc index 79de7a296895..dbc0d3f50207 100644 --- a/source/extensions/common/tap/admin.cc +++ b/source/extensions/common/tap/admin.cc @@ -31,6 +31,10 @@ AdminHandler::AdminHandler(Server::Admin& admin, Event::Dispatcher& main_thread_ const bool rc = admin_.addHandler("/tap", "tap filter control", MAKE_ADMIN_HANDLER(handler), true, true); RELEASE_ASSERT(rc, "/tap admin endpoint is taken"); + if (admin_.socket().addressType() == Network::Address::Type::Pipe) { + ENVOY_LOG(warn, "Admin tapping (via /tap) is unreliable when the admin endpoint is a pipe and " + "the connection is HTTP/1. Either use an IP address or connect using HTTP/2."); + } } AdminHandler::~AdminHandler() { diff --git a/test/extensions/common/tap/BUILD b/test/extensions/common/tap/BUILD index c5a459721faf..5a5948d3eee0 100644 --- a/test/extensions/common/tap/BUILD +++ b/test/extensions/common/tap/BUILD @@ -27,6 +27,7 @@ envoy_cc_test( "//source/extensions/common/tap:admin", "//test/mocks/server:admin_mocks", "//test/mocks/server:admin_stream_mocks", + "//test/test_common:logging_lib", "@envoy_api//envoy/config/tap/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/common/tap/admin_test.cc b/test/extensions/common/tap/admin_test.cc index 6daa71156b0e..020f563600d5 100644 --- a/test/extensions/common/tap/admin_test.cc +++ b/test/extensions/common/tap/admin_test.cc @@ -4,6 +4,7 @@ #include "test/mocks/server/admin.h" #include "test/mocks/server/admin_stream.h" +#include "test/test_common/logging.h" #include "gtest/gtest.h" @@ -28,9 +29,11 @@ class MockExtensionConfig : public ExtensionConfig { class AdminHandlerTest : public testing::Test { public: - AdminHandlerTest() { + void setup(Network::Address::Type socket_type = Network::Address::Type::Ip) { + ON_CALL(admin_.socket_, addressType()).WillByDefault(Return(socket_type)); EXPECT_CALL(admin_, addHandler("/tap", "tap filter control", _, true, true)) .WillOnce(DoAll(SaveArg<2>(&cb_), Return(true))); + EXPECT_CALL(admin_, socket()); handler_ = std::make_unique(admin_, main_thread_dispatcher_); } @@ -58,8 +61,18 @@ config_id: test_config_id )EOF"; }; +// Make sure warn if using a pipe address for the admin handler. +TEST_F(AdminHandlerTest, AdminWithPipeSocket) { + EXPECT_LOG_CONTAINS( + "warn", + "Admin tapping (via /tap) is unreliable when the admin endpoint is a pipe and the connection " + "is HTTP/1. Either use an IP address or connect using HTTP/2.", + setup(Network::Address::Type::Pipe)); +} + // Request with no config body. TEST_F(AdminHandlerTest, NoBody) { + setup(); EXPECT_CALL(admin_stream_, getRequestBody()); EXPECT_EQ(Http::Code::BadRequest, cb_("/tap", response_headers_, response_, admin_stream_)); EXPECT_EQ("/tap requires a JSON/YAML body", response_.toString()); @@ -67,6 +80,7 @@ TEST_F(AdminHandlerTest, NoBody) { // Request with a config body that doesn't parse/verify. TEST_F(AdminHandlerTest, BadBody) { + setup(); Buffer::OwnedImpl bad_body("hello"); EXPECT_CALL(admin_stream_, getRequestBody()).WillRepeatedly(Return(&bad_body)); EXPECT_EQ(Http::Code::BadRequest, cb_("/tap", response_headers_, response_, admin_stream_)); @@ -75,6 +89,7 @@ TEST_F(AdminHandlerTest, BadBody) { // Request that references an unknown config ID. TEST_F(AdminHandlerTest, UnknownConfigId) { + setup(); Buffer::OwnedImpl body(admin_request_yaml_); EXPECT_CALL(admin_stream_, getRequestBody()).WillRepeatedly(Return(&body)); EXPECT_EQ(Http::Code::BadRequest, cb_("/tap", response_headers_, response_, admin_stream_)); @@ -84,6 +99,7 @@ TEST_F(AdminHandlerTest, UnknownConfigId) { // Request while there is already an active tap session. TEST_F(AdminHandlerTest, RequestTapWhileAttached) { + setup(); MockExtensionConfig extension_config; handler_->registerConfig(extension_config, "test_config_id"); diff --git a/test/mocks/server/BUILD b/test/mocks/server/BUILD index a80a10220f4e..d38b00d01c0c 100644 --- a/test/mocks/server/BUILD +++ b/test/mocks/server/BUILD @@ -23,6 +23,7 @@ envoy_cc_mock( hdrs = ["admin.h"], deps = [ "//include/envoy/server:admin_interface", + "//test/mocks/network:socket_mocks", "//test/mocks/server:config_tracker_mocks", ], ) diff --git a/test/mocks/server/admin.cc b/test/mocks/server/admin.cc index 2f873c547633..61d5e6dea175 100644 --- a/test/mocks/server/admin.cc +++ b/test/mocks/server/admin.cc @@ -3,11 +3,16 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Return; +using testing::ReturnRef; + namespace Envoy { namespace Server { + MockAdmin::MockAdmin() { - ON_CALL(*this, getConfigTracker()).WillByDefault(testing::ReturnRef(config_tracker_)); - ON_CALL(*this, concurrency()).WillByDefault(testing::Return(1)); + ON_CALL(*this, getConfigTracker()).WillByDefault(ReturnRef(config_tracker_)); + ON_CALL(*this, concurrency()).WillByDefault(Return(1)); + ON_CALL(*this, socket()).WillByDefault(ReturnRef(socket_)); } MockAdmin::~MockAdmin() = default; diff --git a/test/mocks/server/admin.h b/test/mocks/server/admin.h index 8805ee969709..1e8802f6abd6 100644 --- a/test/mocks/server/admin.h +++ b/test/mocks/server/admin.h @@ -4,12 +4,17 @@ #include "envoy/server/admin.h" +#include "test/mocks/network/socket.h" + #include "absl/strings/string_view.h" #include "config_tracker.h" #include "gmock/gmock.h" +using testing::NiceMock; + namespace Envoy { namespace Server { + class MockAdmin : public Admin { public: MockAdmin(); @@ -33,7 +38,9 @@ class MockAdmin : public Admin { MOCK_METHOD(void, addListenerToHandler, (Network::ConnectionHandler * handler)); MOCK_METHOD(uint32_t, concurrency, (), (const)); - ::testing::NiceMock config_tracker_; + NiceMock config_tracker_; + NiceMock socket_; }; + } // namespace Server } // namespace Envoy From fb4a55e724fe550864536eae3834ba9a149d0a0f Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 3 Nov 2020 15:09:40 +0000 Subject: [PATCH 019/117] examples: Fix verify.sh sed commands for mac compatibility (#13876) Signed-off-by: Ryan Northey --- examples/dynamic-config-cp/verify.sh | 4 ++-- examples/wasm-cc/verify.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/dynamic-config-cp/verify.sh b/examples/dynamic-config-cp/verify.sh index 0cc02650a8dc..37bd30d66182 100755 --- a/examples/dynamic-config-cp/verify.sh +++ b/examples/dynamic-config-cp/verify.sh @@ -56,8 +56,8 @@ curl -s http://localhost:19000/config_dump \ | grep '"address": "service1"' run_log "Edit resource.go" -sed -i s/service1/service2/ resource.go -sed -i s/\"1\",/\"2\",/ resource.go +sed -i'.bak' s/service1/service2/ resource.go +sed -i'.bak' s/\"1\",/\"2\",/ resource.go run_log "Bring back up the control plane" docker-compose up --build -d go-control-plane diff --git a/examples/wasm-cc/verify.sh b/examples/wasm-cc/verify.sh index 17e5c6f878f2..c7bedbdfb396 100755 --- a/examples/wasm-cc/verify.sh +++ b/examples/wasm-cc/verify.sh @@ -30,7 +30,7 @@ run_log "Check for the compiled update" ls -l lib/*updated*wasm run_log "Edit the Docker recipe to use the updated binary" -sed -i s/\\.\\/lib\\/envoy_filter_http_wasm_example.wasm/.\\/lib\\/envoy_filter_http_wasm_updated_example.wasm/ Dockerfile-proxy +sed -i'.bak' s/\\.\\/lib\\/envoy_filter_http_wasm_example.wasm/.\\/lib\\/envoy_filter_http_wasm_updated_example.wasm/ Dockerfile-proxy run_log "Bring the proxy back up" docker-compose up --build -d proxy From 5df0a6beb6f4542ed122baaa07c9ffaeca8adf08 Mon Sep 17 00:00:00 2001 From: William A Rowe Jr Date: Tue, 3 Nov 2020 10:52:37 -0600 Subject: [PATCH 020/117] [Windows] Address status/fix various flaky_on_windows tests (#13837) These fixes reflect that the timing on windows is often less synchronous than linux observations, due to the design of the underlying socket stack. - Correct listener_impl_test, fixes TcpListenerImplTest.SetListenerRejectFractionIntermediate - In QuicHttpIntegrationTest, the MultipleQuicConnectionsWithBPF is renamed to capture the intent of the test. - Enable no-longer-failing tests (no failure observed in RBE or locally) //test/extensions/grpc_credentials/file_based_metadata:integration_test //test/extensions/transport_sockets/alts:tsi_handshaker_test //test/integration:http2_flood_integration_test //test/integration:http2_upstream_integration_test //test/integration:overload_integration_test //test/server:guarddog_impl_test Co-authored-by: Sunjay Bhatia Co-authored-by: William A Rowe Jr Signed-off-by: Sunjay Bhatia Signed-off-by: William A Rowe Jr --- test/common/network/listener_impl_test.cc | 8 +++--- test/extensions/filters/http/ratelimit/BUILD | 1 + .../file_based_metadata/BUILD | 5 ++-- ...redentials_test.cc => integration_test.cc} | 4 +-- .../quic_listeners/quiche/integration/BUILD | 6 ++++- .../integration/quic_http_integration_test.cc | 8 +++++- .../stats_sinks/metrics_service/BUILD | 1 + test/extensions/transport_sockets/alts/BUILD | 2 -- test/integration/BUILD | 26 ++++++++++++------- test/server/BUILD | 2 -- 10 files changed, 39 insertions(+), 24 deletions(-) rename test/extensions/grpc_credentials/file_based_metadata/{file_based_metadata_grpc_credentials_test.cc => integration_test.cc} (99%) diff --git a/test/common/network/listener_impl_test.cc b/test/common/network/listener_impl_test.cc index 21e4673ce643..9bbfe6643c42 100644 --- a/test/common/network/listener_impl_test.cc +++ b/test/common/network/listener_impl_test.cc @@ -412,13 +412,13 @@ TEST_P(TcpListenerImplTest, SetListenerRejectFractionIntermediate) { { testing::InSequence s1; EXPECT_CALL(random_generator, random()).WillOnce(Return(std::numeric_limits::max())); - EXPECT_CALL(listener_callbacks, onAccept_(_)); + // Exiting dispatcher on client side connect event can cause a race, listener accept callback + // may not have been triggered, exit dispatcher here to prevent this. + EXPECT_CALL(listener_callbacks, onAccept_(_)).WillOnce([&] { dispatcher_->exit(); }); } { testing::InSequence s2; - EXPECT_CALL(connection_callbacks, onEvent(ConnectionEvent::Connected)).WillOnce([&] { - dispatcher_->exit(); - }); + EXPECT_CALL(connection_callbacks, onEvent(ConnectionEvent::Connected)); EXPECT_CALL(connection_callbacks, onEvent(ConnectionEvent::RemoteClose)).Times(0); } diff --git a/test/extensions/filters/http/ratelimit/BUILD b/test/extensions/filters/http/ratelimit/BUILD index 09550058b265..f5bd1b2b7201 100644 --- a/test/extensions/filters/http/ratelimit/BUILD +++ b/test/extensions/filters/http/ratelimit/BUILD @@ -51,6 +51,7 @@ envoy_extension_cc_test( name = "ratelimit_integration_test", srcs = ["ratelimit_integration_test.cc"], extension_name = "envoy.filters.http.ratelimit", + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ "//source/common/buffer:zero_copy_input_stream_lib", diff --git a/test/extensions/grpc_credentials/file_based_metadata/BUILD b/test/extensions/grpc_credentials/file_based_metadata/BUILD index a7c18341b47b..8ccb3fe5b848 100644 --- a/test/extensions/grpc_credentials/file_based_metadata/BUILD +++ b/test/extensions/grpc_credentials/file_based_metadata/BUILD @@ -10,10 +10,9 @@ licenses(["notice"]) # Apache 2 envoy_package() envoy_cc_test( - name = "file_based_metadata_grpc_credentials_test", - srcs = ["file_based_metadata_grpc_credentials_test.cc"], + name = "integration_test", + srcs = ["integration_test.cc"], data = ["//test/config/integration/certs"], - tags = ["flaky_on_windows"], deps = [ "//source/extensions/grpc_credentials:well_known_names", "//source/extensions/grpc_credentials/file_based_metadata:config", diff --git a/test/extensions/grpc_credentials/file_based_metadata/file_based_metadata_grpc_credentials_test.cc b/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc similarity index 99% rename from test/extensions/grpc_credentials/file_based_metadata/file_based_metadata_grpc_credentials_test.cc rename to test/extensions/grpc_credentials/file_based_metadata/integration_test.cc index f9be83ec5b6e..1a2c90929e9e 100644 --- a/test/extensions/grpc_credentials/file_based_metadata/file_based_metadata_grpc_credentials_test.cc +++ b/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc @@ -41,7 +41,7 @@ class GrpcFileBasedMetadataClientIntegrationTest : public GrpcSslClientIntegrati TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem")); if (!header_value_1_.empty()) { const std::string yaml1 = fmt::format(R"EOF( -"@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.FileBasedMetadataConfig +"@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.FileBasedMetadataConfig secret_data: inline_string: {} header_key: {} @@ -56,7 +56,7 @@ header_prefix: {} if (!header_value_2_.empty()) { // uses default key/prefix const std::string yaml2 = fmt::format(R"EOF( -"@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.FileBasedMetadataConfig +"@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.FileBasedMetadataConfig secret_data: inline_string: {} )EOF", diff --git a/test/extensions/quic_listeners/quiche/integration/BUILD b/test/extensions/quic_listeners/quiche/integration/BUILD index dbef14c2195f..998625c54e72 100644 --- a/test/extensions/quic_listeners/quiche/integration/BUILD +++ b/test/extensions/quic_listeners/quiche/integration/BUILD @@ -13,7 +13,11 @@ envoy_cc_test( size = "medium", srcs = ["quic_http_integration_test.cc"], data = ["//test/config/integration/certs"], - # TODO(envoyproxy/windows-dev): Diagnose msvc-cl opt build gcp rbe CI flake (passes locally) + # TODO(envoyproxy/windows-dev): Diagnose why opt build test under Windows GCP RBE + # takes 10x as long as on linux (>300s vs ~30s). Shards = 2 solves for windows, see: + # https://github.com/envoyproxy/envoy/pull/13713/files#r512160087 + # Each of these tests exceeds 20s; + # QuicHttpIntegrationTests/QuicHttpIntegrationTest.MultipleQuicConnections[With|No]BPF* tags = [ "flaky_on_windows", "nofips", diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index 11cd3959edad..d6121ff2ec6a 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -249,6 +249,8 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, public QuicMultiVers // create connections with the first 4 bytes of connection id different from each // other so they should be evenly distributed. designated_connection_ids_.push_back(quic::test::TestConnectionId(i << 32)); + // TODO(sunjayBhatia,wrowe): deserialize this, establishing all connections in parallel + // (Expected to save ~14s each across 6 tests on Windows) codec_clients.push_back(makeHttpConnection(lookupPort("http"))); } constexpr auto timeout_first = std::chrono::seconds(15); @@ -391,9 +393,13 @@ TEST_P(QuicHttpIntegrationTest, TestDelayedConnectionTeardownTimeoutTrigger) { 1); } -TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsWithBPF) { testMultipleQuicConnections(); } +// Ensure multiple quic connections work, regardless of platform BPF support +TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsDefaultMode) { + testMultipleQuicConnections(); +} TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsNoBPF) { + // Note: This runtime override is a no-op on platforms without BPF config_helper_.addRuntimeOverride( "envoy.reloadable_features.prefer_quic_kernel_bpf_packet_routing", "false"); diff --git a/test/extensions/stats_sinks/metrics_service/BUILD b/test/extensions/stats_sinks/metrics_service/BUILD index 71a859379b27..cbc6820bf66e 100644 --- a/test/extensions/stats_sinks/metrics_service/BUILD +++ b/test/extensions/stats_sinks/metrics_service/BUILD @@ -45,6 +45,7 @@ envoy_extension_cc_test( name = "metrics_service_integration_test", srcs = ["metrics_service_integration_test.cc"], extension_name = "envoy.stat_sinks.metrics_service", + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ "//source/common/buffer:zero_copy_input_stream_lib", diff --git a/test/extensions/transport_sockets/alts/BUILD b/test/extensions/transport_sockets/alts/BUILD index 1002501c3ef1..145711ab4720 100644 --- a/test/extensions/transport_sockets/alts/BUILD +++ b/test/extensions/transport_sockets/alts/BUILD @@ -39,8 +39,6 @@ envoy_extension_cc_test( name = "tsi_handshaker_test", srcs = ["tsi_handshaker_test.cc"], extension_name = "envoy.transport_sockets.alts", - # Fails intermittantly on local build - tags = ["flaky_on_windows"], deps = [ "//include/envoy/event:dispatcher_interface", "//source/extensions/transport_sockets/alts:tsi_handshaker", diff --git a/test/integration/BUILD b/test/integration/BUILD index dfeba31c4d25..81a632d40f79 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -135,6 +135,7 @@ envoy_cc_test( envoy_cc_test( name = "eds_integration_test", srcs = ["eds_integration_test.cc"], + # TODO(envoyproxy/windows-dev): Diagnose timeout observed in opt build test tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -318,7 +319,6 @@ envoy_cc_test( "http2_flood_integration_test.cc", ], shard_count = 4, - tags = ["flaky_on_windows"], deps = [ ":autonomous_upstream_lib", ":http_integration_lib", @@ -343,6 +343,7 @@ envoy_cc_test( "http2_integration_test.h", ], shard_count = 4, + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -372,7 +373,7 @@ envoy_cc_test( srcs = [ "http_subset_lb_integration_test.cc", ], - # Consistently times out in CI on Windows, but observed to pass locally + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -434,6 +435,9 @@ envoy_cc_test( # As this test has many H1/H2/v4/v6 tests it takes a while to run. # Shard it enough to bring the run time in line with other integration tests. shard_count = 5, + # TODO(envoyproxy/windows-dev): Largely resolved by Level Events PR #13787 - diagose remaining rare failure; + # Protocols/DownstreamProtocolIntegrationTest.LargeRequestUrlRejected/IPv6_HttpDownstream_HttpUpstream + # test/integration/http_integration.cc(1035): error: Value of: response->complete() Actual: false Expected: true tags = ["flaky_on_windows"], deps = [ ":http_protocol_integration_lib", @@ -458,6 +462,8 @@ envoy_cc_test( "http2_upstream_integration_test.cc", "http2_upstream_integration_test.h", ], + # TODO(envoyproxy/windows-dev): Diagnose test timeout observed in msvc-cl opt build + # in Http2UpstreamIntegrationTest.RouterUpstreamDisconnectBeforeResponseComplete/IPv4 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -565,7 +571,7 @@ envoy_cc_test( # As this test has many pauses for idle timeouts, it takes a while to run. # Shard it enough to bring the run time in line with other integration tests. shard_count = 2, - # Consistently fails in CI on Windows, but observed to pass locally + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_protocol_integration_lib", @@ -874,7 +880,7 @@ envoy_cc_test( "integration_test.h", ], shard_count = 2, - # Times out on Windows + # TODO(envoyproxy/windows-dev): Diagnose timeout observed in clang-cl opt build test tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -921,7 +927,7 @@ envoy_cc_test( "websocket_integration_test.cc", "websocket_integration_test.h", ], - # Consistently fails in CI on Windows, but observed to pass locally + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_protocol_integration_lib", @@ -984,7 +990,8 @@ envoy_cc_test( envoy_cc_test( name = "load_stats_integration_test", srcs = ["load_stats_integration_test.cc"], - # Consistently timing out on Windows, observed to pass locally + # TODO(envoyproxy/windows-dev): Diagnose timeout observed in opt build test, hangs at + # IpVersionsClientType/LoadStatsIntegrationTest.NoLocalLocality/3 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -1007,7 +1014,7 @@ envoy_cc_test( "//test/config/integration/certs", ], shard_count = 2, - # Alternately timing out and failing in CI on windows; observed to pass locally + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -1042,7 +1049,6 @@ envoy_cc_test( envoy_cc_test( name = "overload_integration_test", srcs = ["overload_integration_test.cc"], - tags = ["flaky_on_windows"], deps = [ ":http_protocol_integration_lib", "//test/common/config:dummy_config_proto_cc_proto", @@ -1189,7 +1195,7 @@ envoy_cc_test( "//test/config/integration/certs", ], shard_count = 2, - # Fails intermittantly on local build + # TODO(envoyproxy/windows-dev): Diagnose timeout observed in opt build test tags = ["flaky_on_windows"], deps = [ ":integration_lib", @@ -1225,6 +1231,7 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], + # TODO(envoyproxy/windows-dev): Apparently resolved by Level Events PR #13787 tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", @@ -1305,6 +1312,7 @@ envoy_cc_test( "//test/config/integration:server_xds_files", "//test/config/integration/certs", ], + # TODO(envoyproxy/windows-dev): Diagnose test failure observed in opt build tags = ["flaky_on_windows"], deps = [ ":http_integration_lib", diff --git a/test/server/BUILD b/test/server/BUILD index 39f3d0df60e0..36d68688f8d9 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -128,8 +128,6 @@ envoy_cc_test( name = "guarddog_impl_test", size = "small", srcs = ["guarddog_impl_test.cc"], - # Fails intermittantly on local build - tags = ["flaky_on_windows"], deps = [ "//include/envoy/common:time_interface", "//source/common/api:api_lib", From 5236bc70ee0ffadf02dee0fb05750ac8349a54d3 Mon Sep 17 00:00:00 2001 From: asraa Date: Tue, 3 Nov 2020 15:30:16 -0500 Subject: [PATCH 021/117] [ci] limit number of codeql targets conservatively to 3 (#13881) Signed-off-by: Asra Ali --- .github/workflows/get_build_targets.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/get_build_targets.sh b/.github/workflows/get_build_targets.sh index c8d63b8dad8c..25f67b74af50 100755 --- a/.github/workflows/get_build_targets.sh +++ b/.github/workflows/get_build_targets.sh @@ -18,8 +18,8 @@ function get_targets() { # This chain of commands from left to right are: # 1. Excluding the redundant .cc/.h targets that bazel query emits. # 2. Storing only the unique output. - # 3. Limiting to the first 10 targets. - done | grep -v '\.cc\|\.h' | sort -u | head -n 10 + # 3. Limiting to the first 3 targets. + done | grep -v '\.cc\|\.h' | sort -u | head -n 3 } # Fetching the upstream HEAD to compare with and stored in FETCH_HEAD. From 6cb741942fad6b98a58f895b2741d2f3d83db327 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 3 Nov 2020 15:36:02 -0500 Subject: [PATCH 022/117] http2: moving functionality from http2 pool to client (#13869) As part of #3431 we need to move the logic from HTTP/1 and HTTP/2 connection pools to the active client, so the new mixed connection pool can just create an active client of the right type and have all the logic in the client, not split between the client and the pool. This removes functions for the HTTP/2 pool not moved in #13867 Risk Level: Medium (data plane refactor) Testing: existing tests pass Docs Changes: n/a Release Notes: n/a Part of #3431 Signed-off-by: Alyssa Wilk --- source/common/http/http2/conn_pool.cc | 49 ++++++++++++++------------- source/common/http/http2/conn_pool.h | 25 ++++---------- 2 files changed, 32 insertions(+), 42 deletions(-) diff --git a/source/common/http/http2/conn_pool.cc b/source/common/http/http2/conn_pool.cc index d83def2ec18e..70b9ee71b976 100644 --- a/source/common/http/http2/conn_pool.cc +++ b/source/common/http/http2/conn_pool.cc @@ -12,6 +12,10 @@ namespace Envoy { namespace Http { namespace Http2 { +// All streams are 2^31. Client streams are half that, minus stream 0. Just to be on the safe +// side we do 2^29. +static const uint64_t DEFAULT_MAX_STREAMS = (1 << 29); + ConnPoolImpl::ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, const Network::ConnectionSocket::OptionsSharedPtr& options, @@ -25,54 +29,53 @@ ConnPoolImpl::~ConnPoolImpl() { destructAllConnections(); } Envoy::ConnectionPool::ActiveClientPtr ConnPoolImpl::instantiateActiveClient() { return std::make_unique(*this); } -void ConnPoolImpl::onGoAway(ActiveClient& client, Http::GoAwayErrorCode) { - ENVOY_CONN_LOG(debug, "remote goaway", *client.codec_client_); - host_->cluster().stats().upstream_cx_close_notify_.inc(); - if (client.state_ != ActiveClient::State::DRAINING) { - if (client.codec_client_->numActiveRequests() == 0) { - client.codec_client_->close(); + +void ConnPoolImpl::ActiveClient::onGoAway(Http::GoAwayErrorCode) { + ENVOY_CONN_LOG(debug, "remote goaway", *codec_client_); + parent_.host()->cluster().stats().upstream_cx_close_notify_.inc(); + if (state_ != ActiveClient::State::DRAINING) { + if (codec_client_->numActiveRequests() == 0) { + codec_client_->close(); } else { - transitionActiveClientState(client, ActiveClient::State::DRAINING); + parent_.transitionActiveClientState(*this, ActiveClient::State::DRAINING); } } } -void ConnPoolImpl::onStreamDestroy(ActiveClient& client) { - onStreamClosed(client, false); +void ConnPoolImpl::ActiveClient::onStreamDestroy() { + parent().onStreamClosed(*this, false); // If we are destroying this stream because of a disconnect, do not check for drain here. We will // wait until the connection has been fully drained of streams and then check in the connection // event callback. - if (!client.closed_with_active_rq_) { - checkForDrained(); + if (!closed_with_active_rq_) { + parent().checkForDrained(); } } -void ConnPoolImpl::onStreamReset(ActiveClient& client, Http::StreamResetReason reason) { +void ConnPoolImpl::ActiveClient::onStreamReset(Http::StreamResetReason reason) { if (reason == StreamResetReason::ConnectionTermination || reason == StreamResetReason::ConnectionFailure) { - host_->cluster().stats().upstream_rq_pending_failure_eject_.inc(); - client.closed_with_active_rq_ = true; + parent_.host()->cluster().stats().upstream_rq_pending_failure_eject_.inc(); + closed_with_active_rq_ = true; } else if (reason == StreamResetReason::LocalReset) { - host_->cluster().stats().upstream_rq_tx_reset_.inc(); + parent_.host()->cluster().stats().upstream_rq_tx_reset_.inc(); } else if (reason == StreamResetReason::RemoteReset) { - host_->cluster().stats().upstream_rq_rx_reset_.inc(); + parent_.host()->cluster().stats().upstream_rq_rx_reset_.inc(); } } -uint64_t ConnPoolImpl::maxStreamsPerConnection() { - uint64_t max_streams_config = host_->cluster().maxRequestsPerConnection(); +uint64_t maxStreamsPerConnection(uint64_t max_streams_config) { return (max_streams_config != 0) ? max_streams_config : DEFAULT_MAX_STREAMS; } -ConnPoolImpl::ActiveClient::ActiveClient(ConnPoolImpl& parent) +ConnPoolImpl::ActiveClient::ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent) : Envoy::Http::ActiveClient( - parent, parent.maxStreamsPerConnection(), - parent.host_->cluster().http2Options().max_concurrent_streams().value()) { + parent, maxStreamsPerConnection(parent.host()->cluster().maxRequestsPerConnection()), + parent.host()->cluster().http2Options().max_concurrent_streams().value()) { codec_client_->setCodecClientCallbacks(*this); codec_client_->setCodecConnectionCallbacks(*this); - - parent.host_->cluster().stats().upstream_cx_http2_total_.inc(); + parent.host()->cluster().stats().upstream_cx_http2_total_.inc(); } bool ConnPoolImpl::ActiveClient::closingWithIncompleteStream() const { diff --git a/source/common/http/http2/conn_pool.h b/source/common/http/http2/conn_pool.h index 63aabe08e2a5..85b5c3949752 100644 --- a/source/common/http/http2/conn_pool.h +++ b/source/common/http/http2/conn_pool.h @@ -31,12 +31,13 @@ class ConnPoolImpl : public Envoy::Http::HttpConnPoolImplBase { // ConnPoolImplBase Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override; -protected: class ActiveClient : public CodecClientCallbacks, public Http::ConnectionCallbacks, public Envoy::Http::ActiveClient { public: - ActiveClient(ConnPoolImpl& parent); + ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent); + ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent, + Upstream::Host::CreateConnectionData& data); ~ActiveClient() override = default; ConnPoolImpl& parent() { return static_cast(parent_); } @@ -46,29 +47,15 @@ class ConnPoolImpl : public Envoy::Http::HttpConnPoolImplBase { RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override; // CodecClientCallbacks - void onStreamDestroy() override { parent().onStreamDestroy(*this); } - void onStreamReset(Http::StreamResetReason reason) override { - parent().onStreamReset(*this, reason); - } + void onStreamDestroy() override; + void onStreamReset(Http::StreamResetReason reason) override; // Http::ConnectionCallbacks - void onGoAway(Http::GoAwayErrorCode error_code) override { - parent().onGoAway(*this, error_code); - } + void onGoAway(Http::GoAwayErrorCode error_code) override; bool closed_with_active_rq_{}; }; - uint64_t maxStreamsPerConnection(); - void movePrimaryClientToDraining(); - void onGoAway(ActiveClient& client, Http::GoAwayErrorCode error_code); - void onStreamDestroy(ActiveClient& client); - void onStreamReset(ActiveClient& client, Http::StreamResetReason reason); - - // All streams are 2^31. Client streams are half that, minus stream 0. Just to be on the safe - // side we do 2^29. - static const uint64_t DEFAULT_MAX_STREAMS = (1 << 29); - Random::RandomGenerator& random_generator_; }; From 1bf6753b7410d5783fec25cc853dcc438d7e4040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Guti=C3=A9rrez=20Segal=C3=A9s?= Date: Tue, 3 Nov 2020 15:40:35 -0500 Subject: [PATCH 023/117] Revert "sds: keep warming when dynamic inserted cluster can't be extracted secret entity (#13344)" (#13886) This reverts commit e5a8c21b53eab49770c7ab94c1dd357d4191d413. Signed-off-by: Raul Gutierrez Segales --- include/envoy/network/transport_socket.h | 5 - include/envoy/ssl/context_config.h | 5 - source/common/network/raw_buffer_socket.cc | 2 - source/common/network/raw_buffer_socket.h | 1 - source/common/runtime/runtime_features.cc | 3 +- .../common/upstream/cluster_manager_impl.cc | 15 -- .../quiche/quic_transport_socket_factory.h | 1 - .../transport_sockets/alts/tsi_socket.cc | 1 - .../transport_sockets/alts/tsi_socket.h | 1 - .../proxy_protocol/proxy_protocol.cc | 6 +- .../proxy_protocol/proxy_protocol.h | 1 - .../extensions/transport_sockets/tap/tap.cc | 2 - source/extensions/transport_sockets/tap/tap.h | 1 - .../tls/context_config_impl.cc | 15 +- .../tls/context_config_impl.h | 17 +- .../transport_sockets/tls/ssl_socket.cc | 4 - .../transport_sockets/tls/ssl_socket.h | 5 +- test/common/upstream/BUILD | 2 - .../upstream/cluster_manager_impl_test.cc | 150 ------------------ .../upstream/transport_socket_matcher_test.cc | 2 - .../proxy_protocol/proxy_protocol_test.cc | 7 - .../tls/context_impl_test.cc | 56 ------- .../transport_sockets/tls/ssl_socket_test.cc | 10 -- test/integration/xfcc_integration_test.cc | 4 +- test/mocks/network/transport_socket.h | 1 - test/mocks/ssl/mocks.h | 1 - 26 files changed, 17 insertions(+), 301 deletions(-) diff --git a/include/envoy/network/transport_socket.h b/include/envoy/network/transport_socket.h index 63169d3624e7..fe054ce2f16d 100644 --- a/include/envoy/network/transport_socket.h +++ b/include/envoy/network/transport_socket.h @@ -226,11 +226,6 @@ class TransportSocketFactory { */ virtual TransportSocketPtr createTransportSocket(TransportSocketOptionsSharedPtr options) const PURE; - - /** - * Check whether matched transport socket which required to use secret information is available. - */ - virtual bool isReady() const PURE; }; using TransportSocketFactoryPtr = std::unique_ptr; diff --git a/include/envoy/ssl/context_config.h b/include/envoy/ssl/context_config.h index 91bdddf57a36..675bddde27de 100644 --- a/include/envoy/ssl/context_config.h +++ b/include/envoy/ssl/context_config.h @@ -111,11 +111,6 @@ class ClientContextConfig : public virtual ContextConfig { * for names. */ virtual const std::string& signingAlgorithmsForTest() const PURE; - - /** - * Check whether TLS certificate entity and certificate validation context entity is available - */ - virtual bool isSecretReady() const PURE; }; using ClientContextConfigPtr = std::unique_ptr; diff --git a/source/common/network/raw_buffer_socket.cc b/source/common/network/raw_buffer_socket.cc index 69f9d7095313..c539add2f71a 100644 --- a/source/common/network/raw_buffer_socket.cc +++ b/source/common/network/raw_buffer_socket.cc @@ -93,7 +93,5 @@ RawBufferSocketFactory::createTransportSocket(TransportSocketOptionsSharedPtr) c } bool RawBufferSocketFactory::implementsSecureTransport() const { return false; } - -bool RawBufferSocketFactory::isReady() const { return true; } } // namespace Network } // namespace Envoy diff --git a/source/common/network/raw_buffer_socket.h b/source/common/network/raw_buffer_socket.h index 9fb37b31613d..fe87bbeda605 100644 --- a/source/common/network/raw_buffer_socket.h +++ b/source/common/network/raw_buffer_socket.h @@ -32,7 +32,6 @@ class RawBufferSocketFactory : public TransportSocketFactory { // Network::TransportSocketFactory TransportSocketPtr createTransportSocket(TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; - bool isReady() const override; }; } // namespace Network diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index e1dbe6eebcd0..64ce93c8be1c 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -112,8 +112,7 @@ constexpr const char* disabled_runtime_features[] = { "envoy.reloadable_features.test_feature_false", // gRPC Timeout header is missing (#13580) "envoy.reloadable_features.ext_authz_measure_timeout_on_check_created", - // The cluster which can't extract secret entity by SDS to be warming and never activate. - "envoy.reloadable_features.cluster_keep_warming_no_secret_entity", + }; RuntimeFeatures::RuntimeFeatures() { diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index edb4c5b070d0..d467ab417034 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -421,21 +421,6 @@ void ClusterManagerImpl::onClusterInit(Cluster& cluster) { // We have a situation that clusters will be immediately active, such as static and primary // cluster. So we must have this prevention logic here. if (cluster_data != warming_clusters_.end()) { - Network::TransportSocketFactory& factory = - cluster.info()->transportSocketMatcher().resolve(&cluster.info()->metadata()).factory_; - // If there is no secret entity, currently supports only TLS Certificate and Validation - // Context, when it failed to extract them via SDS, it will fail to change cluster status from - // warming to active. In current implementation, there is no strategy to activate clusters - // which failed to initialize at once. - // TODO(shikugawa): To implement to be available by keeping warming after no-available secret - // entity behavior occurred. And remove - // `envoy.reloadable_features.cluster_keep_warming_no_secret_entity` runtime feature flag. - const bool keep_warming_enabled = Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.cluster_keep_warming_no_secret_entity"); - if (!factory.isReady() && keep_warming_enabled) { - ENVOY_LOG(warn, "Failed to activate {}", cluster.info()->name()); - return; - } clusterWarmingToActive(cluster.info()->name()); updateClusterCounts(); } diff --git a/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h b/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h index b974a36725d1..2ada9e2de17b 100644 --- a/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h +++ b/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h @@ -24,7 +24,6 @@ class QuicTransportSocketFactoryBase : public Network::TransportSocketFactory { NOT_REACHED_GCOVR_EXCL_LINE; } bool implementsSecureTransport() const override { return true; } - bool isReady() const override { return true; } }; // TODO(danzh): when implement ProofSource, examine of it's necessary to diff --git a/source/extensions/transport_sockets/alts/tsi_socket.cc b/source/extensions/transport_sockets/alts/tsi_socket.cc index 7ba6b2cab798..0fe5b752ceca 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.cc +++ b/source/extensions/transport_sockets/alts/tsi_socket.cc @@ -261,7 +261,6 @@ TsiSocketFactory::createTransportSocket(Network::TransportSocketOptionsSharedPtr return std::make_unique(handshaker_factory_, handshake_validator_); } -bool TsiSocketFactory::isReady() const { return true; } } // namespace Alts } // namespace TransportSockets } // namespace Extensions diff --git a/source/extensions/transport_sockets/alts/tsi_socket.h b/source/extensions/transport_sockets/alts/tsi_socket.h index 529e316c95ff..0acba405022d 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.h +++ b/source/extensions/transport_sockets/alts/tsi_socket.h @@ -100,7 +100,6 @@ class TsiSocketFactory : public Network::TransportSocketFactory { bool implementsSecureTransport() const override; Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; - bool isReady() const override; private: HandshakerFactory handshaker_factory_; diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc index ac2716c96d72..3d4f716421e7 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.cc @@ -123,11 +123,7 @@ bool UpstreamProxyProtocolSocketFactory::implementsSecureTransport() const { return transport_socket_factory_->implementsSecureTransport(); } -bool UpstreamProxyProtocolSocketFactory::isReady() const { - return transport_socket_factory_->isReady(); -} - } // namespace ProxyProtocol } // namespace TransportSockets } // namespace Extensions -} // namespace Envoy +} // namespace Envoy \ No newline at end of file diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h index cc7fcee7e79a..4a191ebf539d 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h @@ -49,7 +49,6 @@ class UpstreamProxyProtocolSocketFactory : public Network::TransportSocketFactor Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; - bool isReady() const override; private: Network::TransportSocketFactoryPtr transport_socket_factory_; diff --git a/source/extensions/transport_sockets/tap/tap.cc b/source/extensions/transport_sockets/tap/tap.cc index 5b8d83fc8186..7674ba6b584d 100644 --- a/source/extensions/transport_sockets/tap/tap.cc +++ b/source/extensions/transport_sockets/tap/tap.cc @@ -66,8 +66,6 @@ bool TapSocketFactory::implementsSecureTransport() const { return transport_socket_factory_->implementsSecureTransport(); } -bool TapSocketFactory::isReady() const { return transport_socket_factory_->isReady(); } - } // namespace Tap } // namespace TransportSockets } // namespace Extensions diff --git a/source/extensions/transport_sockets/tap/tap.h b/source/extensions/transport_sockets/tap/tap.h index c2d91e1f571a..33156b705153 100644 --- a/source/extensions/transport_sockets/tap/tap.h +++ b/source/extensions/transport_sockets/tap/tap.h @@ -41,7 +41,6 @@ class TapSocketFactory : public Network::TransportSocketFactory, Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; - bool isReady() const override; private: Network::TransportSocketFactoryPtr transport_socket_factory_; diff --git a/source/extensions/transport_sockets/tls/context_config_impl.cc b/source/extensions/transport_sockets/tls/context_config_impl.cc index 21d8e983a5c0..348a02d6dd9e 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.cc +++ b/source/extensions/transport_sockets/tls/context_config_impl.cc @@ -169,14 +169,14 @@ ContextConfigImpl::ContextConfigImpl( const std::string& default_cipher_suites, const std::string& default_curves, Server::Configuration::TransportSocketFactoryContext& factory_context) : api_(factory_context.api()), - tls_certificate_providers_(getTlsCertificateConfigProviders(config, factory_context)), - certificate_validation_context_provider_( - getCertificateValidationContextConfigProvider(config, factory_context, &default_cvc_)), alpn_protocols_(RepeatedPtrUtil::join(config.alpn_protocols(), ",")), cipher_suites_(StringUtil::nonEmptyStringOrDefault( RepeatedPtrUtil::join(config.tls_params().cipher_suites(), ":"), default_cipher_suites)), ecdh_curves_(StringUtil::nonEmptyStringOrDefault( RepeatedPtrUtil::join(config.tls_params().ecdh_curves(), ":"), default_curves)), + tls_certificate_providers_(getTlsCertificateConfigProviders(config, factory_context)), + certificate_validation_context_provider_( + getCertificateValidationContextConfigProvider(config, factory_context, &default_cvc_)), min_protocol_version_(tlsVersionFromProto(config.tls_params().tls_minimum_protocol_version(), default_min_protocol_version)), max_protocol_version_(tlsVersionFromProto(config.tls_params().tls_maximum_protocol_version(), @@ -367,15 +367,6 @@ ClientContextConfigImpl::ClientContextConfigImpl( } } -bool ClientContextConfigImpl::isSecretReady() const { - for (const auto& provider : tls_certificate_providers_) { - if (provider->secret() == nullptr) { - return false; - } - } - return certificate_validation_context_provider_->secret() != nullptr; -} - const unsigned ServerContextConfigImpl::DEFAULT_MIN_VERSION = TLS1_VERSION; const unsigned ServerContextConfigImpl::DEFAULT_MAX_VERSION = TLS1_3_VERSION; diff --git a/source/extensions/transport_sockets/tls/context_config_impl.h b/source/extensions/transport_sockets/tls/context_config_impl.h index d40bb627a51d..44c5a8cc619d 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.h +++ b/source/extensions/transport_sockets/tls/context_config_impl.h @@ -69,14 +69,6 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { const std::string& default_cipher_suites, const std::string& default_curves, Server::Configuration::TransportSocketFactoryContext& factory_context); Api::Api& api_; - // If certificate validation context type is combined_validation_context. default_cvc_ - // holds a copy of CombinedCertificateValidationContext::default_validation_context. - // Otherwise, default_cvc_ is nullptr. - std::unique_ptr - default_cvc_; - std::vector tls_certificate_providers_; - Secret::CertificateValidationContextConfigProviderSharedPtr - certificate_validation_context_provider_; private: static unsigned tlsVersionFromProto( @@ -89,8 +81,16 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { std::vector tls_certificate_configs_; Ssl::CertificateValidationContextConfigPtr validation_context_config_; + // If certificate validation context type is combined_validation_context. default_cvc_ + // holds a copy of CombinedCertificateValidationContext::default_validation_context. + // Otherwise, default_cvc_ is nullptr. + std::unique_ptr + default_cvc_; + std::vector tls_certificate_providers_; // Handle for TLS certificate dynamic secret callback. Envoy::Common::CallbackHandle* tc_update_callback_handle_{}; + Secret::CertificateValidationContextConfigProviderSharedPtr + certificate_validation_context_provider_; // Handle for certificate validation context dynamic secret callback. Envoy::Common::CallbackHandle* cvc_update_callback_handle_{}; Envoy::Common::CallbackHandle* cvc_validation_callback_handle_{}; @@ -120,7 +120,6 @@ class ClientContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::Cli bool allowRenegotiation() const override { return allow_renegotiation_; } size_t maxSessionKeys() const override { return max_session_keys_; } const std::string& signingAlgorithmsForTest() const override { return sigalgs_; } - bool isSecretReady() const override; private: static const unsigned DEFAULT_MIN_VERSION; diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index 99da62f9ae10..65db92e12d4e 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -375,8 +375,6 @@ void ClientSslSocketFactory::onAddOrUpdateSecret() { stats_.ssl_context_update_by_sds_.inc(); } -bool ClientSslSocketFactory::isReady() const { return config_->isSecretReady(); } - ServerSslSocketFactory::ServerSslSocketFactory(Envoy::Ssl::ServerContextConfigPtr config, Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope, @@ -418,8 +416,6 @@ void ServerSslSocketFactory::onAddOrUpdateSecret() { stats_.ssl_context_update_by_sds_.inc(); } -bool ServerSslSocketFactory::isReady() const { return true; } - } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/source/extensions/transport_sockets/tls/ssl_socket.h b/source/extensions/transport_sockets/tls/ssl_socket.h index 4ea674062142..0e539324f91f 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.h +++ b/source/extensions/transport_sockets/tls/ssl_socket.h @@ -106,11 +106,10 @@ class ClientSslSocketFactory : public Network::TransportSocketFactory, ClientSslSocketFactory(Envoy::Ssl::ClientContextConfigPtr config, Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope); - // Network::TransportSocketFactory Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; - bool isReady() const override; + // Secret::SecretCallbacks void onAddOrUpdateSecret() override; @@ -134,7 +133,7 @@ class ServerSslSocketFactory : public Network::TransportSocketFactory, Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; - bool isReady() const override; + // Secret::SecretCallbacks void onAddOrUpdateSecret() override; diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 849159b5fa72..2260de1e447a 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -46,12 +46,10 @@ envoy_cc_test( "//test/mocks/upstream:health_checker_mocks", "//test/mocks/upstream:load_balancer_context_mock", "//test/mocks/upstream:thread_aware_load_balancer_mocks", - "//test/test_common:test_runtime_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", ], ) diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 34d8403cf5d0..34c31a7f4f03 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -3,7 +3,6 @@ #include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/config/cluster/v3/cluster.pb.validate.h" #include "envoy/config/core/v3/base.pb.h" -#include "envoy/extensions/transport_sockets/tls/v3/secret.pb.h" #include "test/common/upstream/test_cluster_manager.h" #include "test/mocks/upstream/cds_api.h" @@ -13,7 +12,6 @@ #include "test/mocks/upstream/health_checker.h" #include "test/mocks/upstream/load_balancer_context.h" #include "test/mocks/upstream/thread_aware_load_balancer.h" -#include "test/test_common/test_runtime.h" namespace Envoy { namespace Upstream { @@ -2308,154 +2306,6 @@ TEST_F(ClusterManagerImplTest, DynamicHostRemoveDefaultPriority) { factory_.tls_.shutdownThread(); } -TEST_F(ClusterManagerImplTest, - DynamicAddedAndKeepWarmingWithoutCertificateValidationContextEntity) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.cluster_keep_warming_no_secret_entity", "true"}}); - create(defaultConfig()); - - ReadyWatcher initialized; - EXPECT_CALL(initialized, ready()); - cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); - - std::shared_ptr cluster1(new NiceMock()); - cluster1->info_->name_ = "fake_cluster"; - - auto transport_socket_factory = std::make_unique(); - EXPECT_CALL(*transport_socket_factory, isReady()).WillOnce(Return(false)); - - auto transport_socket_matcher = std::make_unique>( - std::move(transport_socket_factory)); - cluster1->info_->transport_socket_matcher_ = std::move(transport_socket_matcher); - - EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) - .WillOnce(Return(std::make_pair(cluster1, nullptr))); - EXPECT_CALL(*cluster1, initializePhase()).Times(0); - EXPECT_CALL(*cluster1, initialize(_)); - EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "")); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); - cluster1->initialize_callback_(); - - // Check to be keep warming fake_cluster after callback invoked. - EXPECT_EQ(cluster1->info_, cluster_manager_->get("fake_cluster")->info()); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - - EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); -} - -TEST_F(ClusterManagerImplTest, - DynamicAddedAndKeepWarmingDisabledWithoutCertificateValidationContextEntity) { - create(defaultConfig()); - - ReadyWatcher initialized; - EXPECT_CALL(initialized, ready()); - cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); - - std::shared_ptr cluster1(new NiceMock()); - cluster1->info_->name_ = "fake_cluster"; - - auto transport_socket_factory = std::make_unique(); - EXPECT_CALL(*transport_socket_factory, isReady()).WillOnce(Return(false)); - - auto transport_socket_matcher = std::make_unique>( - std::move(transport_socket_factory)); - cluster1->info_->transport_socket_matcher_ = std::move(transport_socket_matcher); - - EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) - .WillOnce(Return(std::make_pair(cluster1, nullptr))); - EXPECT_CALL(*cluster1, initializePhase()).Times(0); - EXPECT_CALL(*cluster1, initialize(_)); - EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "")); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); - cluster1->initialize_callback_(); - - // Check to be keep warming fake_cluster after callback invoked. - EXPECT_EQ(cluster1->info_, cluster_manager_->get("fake_cluster")->info()); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 1 /*active*/, 0 /*warming*/); - EXPECT_EQ(0, cluster_manager_->warmingClusterCount()); - - EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); -} - -TEST_F(ClusterManagerImplTest, DynamicAddedAndKeepWarmingWithoutTlsCertificateEntity) { - TestScopedRuntime scoped_runtime; - Runtime::LoaderSingleton::getExisting()->mergeValues( - {{"envoy.reloadable_features.cluster_keep_warming_no_secret_entity", "true"}}); - create(defaultConfig()); - - ReadyWatcher initialized; - EXPECT_CALL(initialized, ready()); - cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); - - std::shared_ptr cluster1(new NiceMock()); - cluster1->info_->name_ = "fake_cluster"; - - auto transport_socket_factory = std::make_unique(); - EXPECT_CALL(*transport_socket_factory, isReady()).WillOnce(Return(false)); - - auto transport_socket_matcher = std::make_unique>( - std::move(transport_socket_factory)); - cluster1->info_->transport_socket_matcher_ = std::move(transport_socket_matcher); - - EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) - .WillOnce(Return(std::make_pair(cluster1, nullptr))); - EXPECT_CALL(*cluster1, initializePhase()).Times(0); - EXPECT_CALL(*cluster1, initialize(_)); - EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "")); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); - cluster1->initialize_callback_(); - - // Check to be keep warming fake_cluster after callback invoked. - EXPECT_EQ(cluster1->info_, cluster_manager_->get("fake_cluster")->info()); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - - EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); -} - -TEST_F(ClusterManagerImplTest, DynamicAddedAndKeepWarmingDisabledWithoutTlsCertificateEntity) { - create(defaultConfig()); - - ReadyWatcher initialized; - EXPECT_CALL(initialized, ready()); - cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); - - std::shared_ptr cluster1(new NiceMock()); - cluster1->info_->name_ = "fake_cluster"; - - auto transport_socket_factory = std::make_unique(); - EXPECT_CALL(*transport_socket_factory, isReady()).WillOnce(Return(false)); - - auto transport_socket_matcher = std::make_unique>( - std::move(transport_socket_factory)); - cluster1->info_->transport_socket_matcher_ = std::move(transport_socket_matcher); - - EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) - .WillOnce(Return(std::make_pair(cluster1, nullptr))); - EXPECT_CALL(*cluster1, initializePhase()).Times(0); - EXPECT_CALL(*cluster1, initialize(_)); - EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "")); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); - EXPECT_EQ(1, cluster_manager_->warmingClusterCount()); - EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); - cluster1->initialize_callback_(); - - // Check to be keep warming fake_cluster after callback invoked. - EXPECT_EQ(cluster1->info_, cluster_manager_->get("fake_cluster")->info()); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 1 /*active*/, 0 /*warming*/); - EXPECT_EQ(0, cluster_manager_->warmingClusterCount()); - - EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); -} - class MockConnPoolWithDestroy : public Http::ConnectionPool::MockInstance { public: ~MockConnPoolWithDestroy() override { onDestroy(); } diff --git a/test/common/upstream/transport_socket_matcher_test.cc b/test/common/upstream/transport_socket_matcher_test.cc index 61e5ab2cec43..cfde130d1d1f 100644 --- a/test/common/upstream/transport_socket_matcher_test.cc +++ b/test/common/upstream/transport_socket_matcher_test.cc @@ -33,7 +33,6 @@ class FakeTransportSocketFactory : public Network::TransportSocketFactory { MOCK_METHOD(bool, implementsSecureTransport, (), (const)); MOCK_METHOD(Network::TransportSocketPtr, createTransportSocket, (Network::TransportSocketOptionsSharedPtr), (const)); - MOCK_METHOD(bool, isReady, (), (const)); FakeTransportSocketFactory(std::string id) : id_(std::move(id)) {} std::string id() const { return id_; } @@ -49,7 +48,6 @@ class FooTransportSocketFactory MOCK_METHOD(bool, implementsSecureTransport, (), (const)); MOCK_METHOD(Network::TransportSocketPtr, createTransportSocket, (Network::TransportSocketOptionsSharedPtr), (const)); - MOCK_METHOD(bool, isReady, (), (const)); Network::TransportSocketFactoryPtr createTransportSocketFactory(const Protobuf::Message& proto, diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 32a2281cf0af..953e5999a5fb 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -469,13 +469,6 @@ TEST_F(ProxyProtocolSocketFactoryTest, ImplementsSecureTransportCallInnerFactory ASSERT_TRUE(factory_->implementsSecureTransport()); } -// Test isReady calls inner factory -TEST_F(ProxyProtocolSocketFactoryTest, IsReadyCallInnerFactory) { - initialize(); - EXPECT_CALL(*inner_factory_, isReady()).WillOnce(Return(true)); - ASSERT_TRUE(factory_->isReady()); -} - } // namespace } // namespace ProxyProtocol } // namespace TransportSockets diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index b48b4f273330..49445e4ec616 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -1648,62 +1648,6 @@ TEST_F(ClientContextConfigImplTest, MissingStaticCertificateValidationContext) { "Unknown static certificate validation context: missing"); } -TEST_F(ClientContextConfigImplTest, ValidationContextEntityNotExist) { - envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; - auto* validation_context_sds_secret_config = - tls_context.mutable_common_tls_context()->mutable_validation_context_sds_secret_config(); - validation_context_sds_secret_config->set_name("sds_validation_context"); - auto* config_source = validation_context_sds_secret_config->mutable_sds_config(); - auto* api_config_source = config_source->mutable_api_config_source(); - api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); - - NiceMock local_info; - Stats::IsolatedStoreImpl stats; - NiceMock init_manager; - NiceMock dispatcher; - EXPECT_CALL(factory_context_, localInfo()).WillOnce(ReturnRef(local_info)); - EXPECT_CALL(factory_context_, stats()).WillOnce(ReturnRef(stats)); - EXPECT_CALL(factory_context_, initManager()).WillRepeatedly(ReturnRef(init_manager)); - EXPECT_CALL(factory_context_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher)); - - ClientContextConfigImpl client_context_config(tls_context, factory_context_); - EXPECT_FALSE(client_context_config.isSecretReady()); - - NiceMock secret_callback; - client_context_config.setSecretUpdateCallback( - [&secret_callback]() { secret_callback.onAddOrUpdateSecret(); }); - client_context_config.setSecretUpdateCallback([]() {}); -} - -TEST_F(ClientContextConfigImplTest, TlsCertificateEntityNotExist) { - envoy::extensions::transport_sockets::tls::v3::SdsSecretConfig secret_config; - secret_config.set_name("sds_tls_certificate"); - auto* config_source = secret_config.mutable_sds_config(); - auto* api_config_source = config_source->mutable_api_config_source(); - api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); - envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; - auto tls_certificate_sds_secret_configs = - tls_context.mutable_common_tls_context()->mutable_tls_certificate_sds_secret_configs(); - *tls_certificate_sds_secret_configs->Add() = secret_config; - - NiceMock local_info; - Stats::IsolatedStoreImpl stats; - NiceMock init_manager; - NiceMock dispatcher; - EXPECT_CALL(factory_context_, localInfo()).WillOnce(ReturnRef(local_info)); - EXPECT_CALL(factory_context_, stats()).WillOnce(ReturnRef(stats)); - EXPECT_CALL(factory_context_, initManager()).WillRepeatedly(ReturnRef(init_manager)); - EXPECT_CALL(factory_context_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher)); - - ClientContextConfigImpl client_context_config(tls_context, factory_context_); - EXPECT_FALSE(client_context_config.isSecretReady()); - - NiceMock secret_callback; - client_context_config.setSecretUpdateCallback( - [&secret_callback]() { secret_callback.onAddOrUpdateSecret(); }); - client_context_config.setSecretUpdateCallback([]() {}); -} - class ServerContextConfigImplTest : public SslCertsTest {}; // Multiple TLS certificates are supported. diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index 7d55f69361ce..89a58bd4e8c6 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -4541,7 +4541,6 @@ TEST_P(SslSocketTest, UpstreamNotReadySslSocket) { EXPECT_FALSE(client_cfg->isReady()); ContextManagerImpl manager(time_system_); - ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, stats_store); auto transport_socket = client_ssl_socket_factory.createTransportSocket(nullptr); EXPECT_EQ(EMPTY_STRING, transport_socket->protocol()); @@ -5751,15 +5750,6 @@ TEST_P(SslSocketTest, TestConnectionFailsOnMultipleCertificatesNonePassOcspPolic testUtil(test_options.setExpectedServerStats("ssl.ocsp_staple_failed").enableOcspStapling()); } -TEST_P(SslSocketTest, ClientSocketFactoryIsReadyTest) { - ContextManagerImpl manager(time_system_); - Stats::TestUtil::TestStore stats_store; - auto client_cfg = std::make_unique>(); - EXPECT_CALL(*client_cfg, isSecretReady()).WillOnce(Return(true)); - ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, stats_store); - EXPECT_TRUE(client_ssl_socket_factory.isReady()); -} - } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc index 7fa5dee96696..3b299de13c51 100644 --- a/test/integration/xfcc_integration_test.cc +++ b/test/integration/xfcc_integration_test.cc @@ -46,7 +46,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createClientSslContext(b validation_context: trusted_ca: filename: {{ test_rundir }}/test/config/integration/certs/cacert.pem - match_subject_alt_names: + match_subject_alt_names: exact: "spiffe://lyft.com/backend-team" exact: "lyft.com" exact: "www.lyft.com" @@ -57,7 +57,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createClientSslContext(b validation_context: trusted_ca: filename: {{ test_rundir }}/test/config/integration/certs/cacert.pem - match_subject_alt_names: + match_subject_alt_names: exact: "spiffe://lyft.com/backend-team" exact: "lyft.com" exact: "www.lyft.com" diff --git a/test/mocks/network/transport_socket.h b/test/mocks/network/transport_socket.h index 164e393c23ff..ee53570c20ac 100644 --- a/test/mocks/network/transport_socket.h +++ b/test/mocks/network/transport_socket.h @@ -38,7 +38,6 @@ class MockTransportSocketFactory : public TransportSocketFactory { MOCK_METHOD(bool, implementsSecureTransport, (), (const)); MOCK_METHOD(TransportSocketPtr, createTransportSocket, (TransportSocketOptionsSharedPtr), (const)); - MOCK_METHOD(bool, isReady, (), (const)); }; } // namespace Network diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index 8fafd921cc48..3cc0ac4e7d55 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -97,7 +97,6 @@ class MockClientContextConfig : public ClientContextConfig { MOCK_METHOD(bool, allowRenegotiation, (), (const)); MOCK_METHOD(size_t, maxSessionKeys, (), (const)); MOCK_METHOD(const std::string&, signingAlgorithmsForTest, (), (const)); - MOCK_METHOD(bool, isSecretReady, (), (const)); }; class MockServerContextConfig : public ServerContextConfig { From 9553f34b6a74d1d1a47ea68187823f294ac07d76 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Tue, 3 Nov 2020 15:27:45 -0800 Subject: [PATCH 024/117] codeowners: adding ggreenway to some extensions (#13888) Signed-off-by: Greg Greenway --- CODEOWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 66d902367b36..1b7fcd94557b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,7 +37,7 @@ extensions/filters/common/original_src @snowp @klarose # alts transport socket extension /*/extensions/transport_sockets/alts @htuch @yangminzhu # tls transport socket extension -/*/extensions/transport_sockets/tls @PiotrSikora @lizan @asraa +/*/extensions/transport_sockets/tls @PiotrSikora @lizan @asraa @ggreenway # proxy protocol socket extension /*/extensions/transport_sockets/proxy_protocol @alyssawilk @wez470 # common transport socket @@ -55,7 +55,7 @@ extensions/filters/common/original_src @snowp @klarose # postgres_proxy extension /*/extensions/filters/network/postgres_proxy @fabriziomello @cpakulski @dio # quic extension -/*/extensions/quic_listeners/ @alyssawilk @danzh2010 @mattklein123 @mpwarres @wu-bin +/*/extensions/quic_listeners/ @alyssawilk @danzh2010 @mattklein123 @mpwarres @wu-bin @ggreenway # zookeeper_proxy extension /*/extensions/filters/network/zookeeper_proxy @rgs1 @snowp # redis cluster extension @@ -125,7 +125,7 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/retry/host @snowp @alyssawilk /*/extensions/filters/network/http_connection_manager @alyssawilk @mattklein123 /*/extensions/filters/network/ext_authz @gsagula @dio -/*/extensions/filters/network/tcp_proxy @alyssawilk @zuercher +/*/extensions/filters/network/tcp_proxy @alyssawilk @zuercher @ggreenway /*/extensions/filters/network/echo @htuch @alyssawilk /*/extensions/filters/udp/dns_filter @abaptiste @mattklein123 /*/extensions/filters/network/direct_response @kyessenov @zuercher From 7d0f89b1011503ecd22f28e347cf7f76cba73057 Mon Sep 17 00:00:00 2001 From: code Date: Wed, 4 Nov 2020 10:04:00 +0800 Subject: [PATCH 025/117] tracer: Add SkyWalking tracer (#13060) This patch adds a new tracer to support the SkyWalking tracing mechanism and format. Risk Level: Low, a new extension. Testing: Unit Docs Changes: Added Release Notes: Added Signed-off-by: wbpcode --- CODEOWNERS | 2 + api/bazel/repositories.bzl | 31 ++ api/bazel/repository_locations.bzl | 11 + api/envoy/config/trace/v3/skywalking.proto | 66 ++++ .../tracers/skywalking/v4alpha/BUILD | 13 + .../skywalking/v4alpha/skywalking.proto | 68 ++++ docs/root/start/sandboxes/index.rst | 1 + .../start/sandboxes/skywalking_tracing.rst | 89 +++++ docs/root/version_history/current.rst | 1 + examples/front-proxy/service.py | 5 +- .../skywalking-tracing/Dockerfile-frontenvoy | 7 + examples/skywalking-tracing/README.md | 2 + .../skywalking-tracing/docker-compose.yaml | 85 +++++ .../front-envoy-skywalking.yaml | 80 ++++ .../service1-envoy-skywalking.yaml | 128 +++++++ .../service2-envoy-skywalking.yaml | 72 ++++ examples/skywalking-tracing/verify.sh | 51 +++ generated_api_shadow/bazel/repositories.bzl | 31 ++ .../bazel/repository_locations.bzl | 11 + .../envoy/config/trace/v3/skywalking.proto | 66 ++++ .../tracers/skywalking/v4alpha/BUILD | 13 + .../skywalking/v4alpha/skywalking.proto | 68 ++++ source/common/http/headers.h | 1 + source/extensions/extensions_build_config.bzl | 1 + source/extensions/tracers/skywalking/BUILD | 107 ++++++ .../extensions/tracers/skywalking/config.cc | 36 ++ source/extensions/tracers/skywalking/config.h | 31 ++ .../skywalking/skywalking_client_config.cc | 43 +++ .../skywalking/skywalking_client_config.h | 43 +++ .../tracers/skywalking/skywalking_stats.h | 21 ++ .../skywalking/skywalking_tracer_impl.cc | 63 ++++ .../skywalking/skywalking_tracer_impl.h | 48 +++ .../tracers/skywalking/skywalking_types.cc | 175 +++++++++ .../tracers/skywalking/skywalking_types.h | 313 ++++++++++++++++ .../skywalking/trace_segment_reporter.cc | 178 +++++++++ .../skywalking/trace_segment_reporter.h | 83 +++++ .../extensions/tracers/skywalking/tracer.cc | 82 +++++ source/extensions/tracers/skywalking/tracer.h | 117 ++++++ test/extensions/tracers/skywalking/BUILD | 113 ++++++ .../tracers/skywalking/config_test.cc | 88 +++++ .../skywalking_client_config_test.cc | 100 +++++ .../skywalking/skywalking_test_helper.h | 77 ++++ .../skywalking/skywalking_tracer_impl_test.cc | 179 +++++++++ .../skywalking/skywalking_types_test.cc | 343 ++++++++++++++++++ .../skywalking/trace_segment_reporter_test.cc | 246 +++++++++++++ .../tracers/skywalking/tracer_test.cc | 193 ++++++++++ tools/spelling/spelling_dictionary.txt | 1 + 47 files changed, 3582 insertions(+), 1 deletion(-) create mode 100644 api/envoy/config/trace/v3/skywalking.proto create mode 100644 api/envoy/extensions/tracers/skywalking/v4alpha/BUILD create mode 100644 api/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto create mode 100644 docs/root/start/sandboxes/skywalking_tracing.rst create mode 100644 examples/skywalking-tracing/Dockerfile-frontenvoy create mode 100644 examples/skywalking-tracing/README.md create mode 100644 examples/skywalking-tracing/docker-compose.yaml create mode 100644 examples/skywalking-tracing/front-envoy-skywalking.yaml create mode 100644 examples/skywalking-tracing/service1-envoy-skywalking.yaml create mode 100644 examples/skywalking-tracing/service2-envoy-skywalking.yaml create mode 100755 examples/skywalking-tracing/verify.sh create mode 100644 generated_api_shadow/envoy/config/trace/v3/skywalking.proto create mode 100644 generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/BUILD create mode 100644 generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto create mode 100644 source/extensions/tracers/skywalking/BUILD create mode 100644 source/extensions/tracers/skywalking/config.cc create mode 100644 source/extensions/tracers/skywalking/config.h create mode 100644 source/extensions/tracers/skywalking/skywalking_client_config.cc create mode 100644 source/extensions/tracers/skywalking/skywalking_client_config.h create mode 100644 source/extensions/tracers/skywalking/skywalking_stats.h create mode 100644 source/extensions/tracers/skywalking/skywalking_tracer_impl.cc create mode 100644 source/extensions/tracers/skywalking/skywalking_tracer_impl.h create mode 100644 source/extensions/tracers/skywalking/skywalking_types.cc create mode 100644 source/extensions/tracers/skywalking/skywalking_types.h create mode 100644 source/extensions/tracers/skywalking/trace_segment_reporter.cc create mode 100644 source/extensions/tracers/skywalking/trace_segment_reporter.h create mode 100644 source/extensions/tracers/skywalking/tracer.cc create mode 100644 source/extensions/tracers/skywalking/tracer.h create mode 100644 test/extensions/tracers/skywalking/BUILD create mode 100644 test/extensions/tracers/skywalking/config_test.cc create mode 100644 test/extensions/tracers/skywalking/skywalking_client_config_test.cc create mode 100644 test/extensions/tracers/skywalking/skywalking_test_helper.h create mode 100644 test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc create mode 100644 test/extensions/tracers/skywalking/skywalking_types_test.cc create mode 100644 test/extensions/tracers/skywalking/trace_segment_reporter_test.cc create mode 100644 test/extensions/tracers/skywalking/tracer_test.cc diff --git a/CODEOWNERS b/CODEOWNERS index 1b7fcd94557b..99cda541b1cc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -50,6 +50,8 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/tracers/datadog @cgilmour @palazzem @mattklein123 # tracers.xray extension /*/extensions/tracers/xray @marcomagdy @lavignes @mattklein123 +# tracers.skywalking extension +/*/extensions/tracers/skywalking @wbpcode @dio @lizan # mysql_proxy extension /*/extensions/filters/network/mysql_proxy @rshriram @venilnoronha @mattklein123 # postgres_proxy extension diff --git a/api/bazel/repositories.bzl b/api/bazel/repositories.bzl index a12a0ea98b3a..983f15967b28 100644 --- a/api/bazel/repositories.bzl +++ b/api/bazel/repositories.bzl @@ -40,6 +40,10 @@ def api_dependencies(): name = "com_github_openzipkin_zipkinapi", build_file_content = ZIPKINAPI_BUILD_CONTENT, ) + external_http_archive( + name = "com_github_apache_skywalking_data_collect_protocol", + build_file_content = SKYWALKING_DATA_COLLECT_PROTOCOL_BUILD_CONTENT, + ) PROMETHEUSMETRICS_BUILD_CONTENT = """ load("@envoy_api//bazel:api_build_system.bzl", "api_cc_py_proto_library") @@ -101,3 +105,30 @@ go_proto_library( visibility = ["//visibility:public"], ) """ + +SKYWALKING_DATA_COLLECT_PROTOCOL_BUILD_CONTENT = """ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") + +proto_library( + name = "protocol", + srcs = [ + "common/Common.proto", + "language-agent/Tracing.proto", + ], + visibility = ["//visibility:public"], +) + +cc_proto_library( + name = "protocol_cc_proto", + deps = [":protocol"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "protocol_go_proto", + proto = ":protocol", + visibility = ["//visibility:public"], +) +""" diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index e46f7d77f8e5..d72069046b85 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -88,4 +88,15 @@ REPOSITORY_LOCATIONS_SPEC = dict( release_date = "2020-08-17", use_category = ["api"], ), + com_github_apache_skywalking_data_collect_protocol = dict( + project_name = "SkyWalking API", + project_desc = "SkyWalking's language independent model and gRPC API Definitions", + project_url = "https://github.com/apache/skywalking-data-collect-protocol", + version = "8.1.0", + sha256 = "ebea8a6968722524d1bcc4426fb6a29907ddc2902aac7de1559012d3eee90cf9", + strip_prefix = "skywalking-data-collect-protocol-{version}", + urls = ["https://github.com/apache/skywalking-data-collect-protocol/archive/v{version}.tar.gz"], + release_date = "2020-07-29", + use_category = ["api"], + ), ) diff --git a/api/envoy/config/trace/v3/skywalking.proto b/api/envoy/config/trace/v3/skywalking.proto new file mode 100644 index 000000000000..224d474ccf98 --- /dev/null +++ b/api/envoy/config/trace/v3/skywalking.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package envoy.config.trace.v3; + +import "envoy/config/core/v3/grpc_service.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/migrate.proto"; +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.config.trace.v3"; +option java_outer_classname = "SkywalkingProto"; +option java_multiple_files = true; +option (udpa.annotations.file_migrate).move_to_package = + "envoy.extensions.tracers.skywalking.v4alpha"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: SkyWalking tracer] + +// Configuration for the SkyWalking tracer. Please note that if SkyWalking tracer is used as the +// provider of http tracer, then +// :ref:`start_child_span ` +// in the router must be set to true to get the correct topology and tracing data. Moreover, SkyWalking +// Tracer does not support SkyWalking extension header (``sw8-x``) temporarily. +// [#extension: envoy.tracers.skywalking] +message SkyWalkingConfig { + // SkyWalking collector service. + core.v3.GrpcService grpc_service = 1 [(validate.rules).message = {required: true}]; + + ClientConfig client_config = 2; +} + +// Client config for SkyWalking tracer. +message ClientConfig { + // Service name for SkyWalking tracer. If this field is empty, then local service cluster name + // that configured by :ref:`Bootstrap node ` + // message's :ref:`cluster ` field or command line + // option :option:`--service-cluster` will be used. If both this field and local service cluster + // name are empty, ``EnvoyProxy`` is used as the service name by default. + string service_name = 1; + + // Service instance name for SkyWalking tracer. If this field is empty, then local service node + // that configured by :ref:`Bootstrap node ` + // message's :ref:`id ` field or command line option + // :option:`--service-node` will be used. If both this field and local service node are empty, + // ``EnvoyProxy`` is used as the instance name by default. + string instance_name = 2; + + // Authentication token config for SkyWalking. SkyWalking can use token authentication to secure + // that monitoring application data can be trusted. In current version, Token is considered as a + // simple string. + // [#comment:TODO(wbpcode): Get backend token through the SDS API.] + oneof backend_token_specifier { + // Inline authentication token string. + string backend_token = 3 [(udpa.annotations.sensitive) = true]; + } + + // Envoy caches the segment in memory when the SkyWalking backend service is temporarily unavailable. + // This field specifies the maximum number of segments that can be cached. If not specified, the + // default is 1024. + google.protobuf.UInt32Value max_cache_size = 4; +} diff --git a/api/envoy/extensions/tracers/skywalking/v4alpha/BUILD b/api/envoy/extensions/tracers/skywalking/v4alpha/BUILD new file mode 100644 index 000000000000..1d56979cc466 --- /dev/null +++ b/api/envoy/extensions/tracers/skywalking/v4alpha/BUILD @@ -0,0 +1,13 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/config/core/v4alpha:pkg", + "//envoy/config/trace/v3:pkg", + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto b/api/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto new file mode 100644 index 000000000000..37936faa6133 --- /dev/null +++ b/api/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package envoy.extensions.tracers.skywalking.v4alpha; + +import "envoy/config/core/v4alpha/grpc_service.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.tracers.skywalking.v4alpha"; +option java_outer_classname = "SkywalkingProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSION_CANDIDATE; + +// [#protodoc-title: SkyWalking tracer] + +// Configuration for the SkyWalking tracer. Please note that if SkyWalking tracer is used as the +// provider of http tracer, then +// :ref:`start_child_span ` +// in the router must be set to true to get the correct topology and tracing data. Moreover, SkyWalking +// Tracer does not support SkyWalking extension header (``sw8-x``) temporarily. +// [#extension: envoy.tracers.skywalking] +message SkyWalkingConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.trace.v3.SkyWalkingConfig"; + + // SkyWalking collector service. + config.core.v4alpha.GrpcService grpc_service = 1 [(validate.rules).message = {required: true}]; + + ClientConfig client_config = 2; +} + +// Client config for SkyWalking tracer. +message ClientConfig { + option (udpa.annotations.versioning).previous_message_type = "envoy.config.trace.v3.ClientConfig"; + + // Service name for SkyWalking tracer. If this field is empty, then local service cluster name + // that configured by :ref:`Bootstrap node ` + // message's :ref:`cluster ` field or command line + // option :option:`--service-cluster` will be used. If both this field and local service cluster + // name are empty, ``EnvoyProxy`` is used as the service name by default. + string service_name = 1; + + // Service instance name for SkyWalking tracer. If this field is empty, then local service node + // that configured by :ref:`Bootstrap node ` + // message's :ref:`id ` field or command line option + // :option:`--service-node` will be used. If both this field and local service node are empty, + // ``EnvoyProxy`` is used as the instance name by default. + string instance_name = 2; + + // Authentication token config for SkyWalking. SkyWalking can use token authentication to secure + // that monitoring application data can be trusted. In current version, Token is considered as a + // simple string. + // [#comment:TODO(wbpcode): Get backend token through the SDS API.] + oneof backend_token_specifier { + // Inline authentication token string. + string backend_token = 3 [(udpa.annotations.sensitive) = true]; + } + + // Envoy caches the segment in memory when the SkyWalking backend service is temporarily unavailable. + // This field specifies the maximum number of segments that can be cached. If not specified, the + // default is 1024. + google.protobuf.UInt32Value max_cache_size = 4; +} diff --git a/docs/root/start/sandboxes/index.rst b/docs/root/start/sandboxes/index.rst index 88db6644e856..6c181e5fb3c7 100644 --- a/docs/root/start/sandboxes/index.rst +++ b/docs/root/start/sandboxes/index.rst @@ -29,3 +29,4 @@ features. The following sandboxes are available: redis wasm-cc zipkin_tracing + skywalking_tracing diff --git a/docs/root/start/sandboxes/skywalking_tracing.rst b/docs/root/start/sandboxes/skywalking_tracing.rst new file mode 100644 index 000000000000..66f07dd15718 --- /dev/null +++ b/docs/root/start/sandboxes/skywalking_tracing.rst @@ -0,0 +1,89 @@ +.. _install_sandboxes_skywalking_tracing: + +SkyWalking Tracing +================== + +The SkyWalking tracing sandbox demonstrates Envoy's :ref:`request tracing ` +capabilities using `SkyWalking `_ as the tracing provider. This sandbox +is very similar to the Zipkin sandbox. All containers will be deployed inside a virtual network +called ``envoymesh``. + +All incoming requests are routed via the front Envoy, which is acting as a reverse proxy +sitting on the edge of the ``envoymesh`` network. Port ``8000`` is exposed +by docker compose (see :repo:`/examples/skywalking-tracing/docker-compose.yaml`). Notice that +all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in +:repo:`/examples/skywalking-tracing/front-envoy-skywalking.yaml`) and setup to propagate the spans generated +by the SkyWalking tracer to a SkyWalking cluster (trace driver setup +in :repo:`/examples/skywalking-tracing/front-envoy-skywalking.yaml`). + +When service1 accepts the request forwarded from front envoy, it will make an API call to service2 before +returning a response. + +.. include:: _include/docker-env-setup.rst + +Step 3: Build the sandbox +************************* + +To build this sandbox example, and start the example apps run the following commands: + +.. code-block:: console + + $ pwd + envoy/examples/skywalking-tracing + $ docker-compose pull + $ docker-compose up --build -d + $ docker-compose ps + + Name Command State Ports + -------------------------------------------------------------------------------------------------------------------------------------------------- + skywalking-tracing_elasticsearch_1 /tini -- /usr/local/bin/do ... Up (healthy) 0.0.0.0:9200->9200/tcp, 9300/tcp + skywalking-tracing_front-envoy_1 /docker-entrypoint.sh /bin ... Up 10000/tcp, 0.0.0.0:8000->8000/tcp, 0.0.0.0:8001->8001/tcp + skywalking-tracing_service1_1 /bin/sh /usr/local/bin/sta ... Up 10000/tcp + skywalking-tracing_service2_1 /bin/sh /usr/local/bin/sta ... Up 10000/tcp + skywalking-tracing_skywalking-oap_1 bash docker-entrypoint.sh Up (healthy) 0.0.0.0:11800->11800/tcp, 1234/tcp, 0.0.0.0:12800->12800/tcp + skywalking-tracing_skywalking-ui_1 bash docker-entrypoint.sh Up 0.0.0.0:8080->8080/tcp + +Step 4: Generate some load +************************** + +You can now send a request to service1 via the front-envoy as follows: + +.. code-block:: console + + $ curl -v localhost:8000/trace/1 + * Trying ::1... + * TCP_NODELAY set + * Connected to localhost (::1) port 8000 (#0) + > GET /trace/1 HTTP/1.1 + > Host: localhost:8000 + > User-Agent: curl/7.58.0 + > Accept: */* + > + < HTTP/1.1 200 OK + < content-type: text/html; charset=utf-8 + < content-length: 89 + < server: envoy + < date: Sat, 10 Oct 2020 01:56:08 GMT + < x-envoy-upstream-service-time: 27 + < + Hello from behind Envoy (service 1)! hostname: 1a2ba43d6d84 resolvedhostname: 172.19.0.6 + * Connection #0 to host localhost left intact + +You can get SkyWalking stats of front-envoy after some requests as follows: + +.. code-block:: console + + $ curl -s localhost:8001/stats | grep tracing.skywalking + tracing.skywalking.cache_flushed: 0 + tracing.skywalking.segments_dropped: 0 + tracing.skywalking.segments_flushed: 0 + tracing.skywalking.segments_sent: 13 + +Step 5: View the traces in SkyWalking UI +**************************************** + +Point your browser to http://localhost:8080 . You should see the SkyWalking dashboard. +Set the service to "front-envoy" and set the start time to a few minutes before +the start of the test (step 2) and hit enter. You should see traces from the front-proxy. +Click on a trace to explore the path taken by the request from front-proxy to service1 +to service2, as well as the latency incurred at each hop. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 43c4ce2f72b5..041ea6adaeac 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -54,6 +54,7 @@ New Features * ratelimit: added :ref:`disable_x_envoy_ratelimited_header ` option to disable `X-Envoy-RateLimited` header. * tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections ` action to reject incoming TCP connections. * tls: added support for RSA certificates with 4096-bit keys in FIPS mode. +* tracing: added SkyWalking tracer. * xds: added support for resource TTLs. A TTL is specified on the :ref:`Resource `. For SotW, a :ref:`Resource ` can be embedded in the list of resources to specify the TTL. diff --git a/examples/front-proxy/service.py b/examples/front-proxy/service.py index 1d5d5920a8e3..c0e008d73404 100644 --- a/examples/front-proxy/service.py +++ b/examples/front-proxy/service.py @@ -19,7 +19,10 @@ 'X-B3-Flags', # Jaeger header (for native client) - "uber-trace-id" + "uber-trace-id", + + # SkyWalking headers. + "sw8" ] diff --git a/examples/skywalking-tracing/Dockerfile-frontenvoy b/examples/skywalking-tracing/Dockerfile-frontenvoy new file mode 100644 index 000000000000..86d0a6b91b8b --- /dev/null +++ b/examples/skywalking-tracing/Dockerfile-frontenvoy @@ -0,0 +1,7 @@ +FROM envoyproxy/envoy-dev:latest + +RUN apt-get update && apt-get -q install -y \ + curl +COPY ./front-envoy-skywalking.yaml /etc/front-envoy.yaml +RUN chmod go+r /etc/front-envoy.yaml +CMD /usr/local/bin/envoy -c /etc/front-envoy.yaml --service-cluster front-proxy diff --git a/examples/skywalking-tracing/README.md b/examples/skywalking-tracing/README.md new file mode 100644 index 000000000000..5a9375b74006 --- /dev/null +++ b/examples/skywalking-tracing/README.md @@ -0,0 +1,2 @@ +To learn about this sandbox and for instructions on how to run it please head over +to the [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/skywalking_tracing) diff --git a/examples/skywalking-tracing/docker-compose.yaml b/examples/skywalking-tracing/docker-compose.yaml new file mode 100644 index 000000000000..5ac0e647a7fe --- /dev/null +++ b/examples/skywalking-tracing/docker-compose.yaml @@ -0,0 +1,85 @@ +version: "3.7" +services: + # Front envoy. + front-envoy: + build: + context: . + dockerfile: Dockerfile-frontenvoy + networks: + - envoymesh + ports: + - 8000:8000 + - 8001:8001 + depends_on: + - skywalking-oap + # First service. + service1: + build: + context: ../front-proxy + dockerfile: Dockerfile-service + volumes: + - ./service1-envoy-skywalking.yaml:/etc/service-envoy.yaml + networks: + - envoymesh + environment: + - SERVICE_NAME=1 + depends_on: + - skywalking-oap + # Second service. + service2: + build: + context: ../front-proxy + dockerfile: Dockerfile-service + volumes: + - ./service2-envoy-skywalking.yaml:/etc/service-envoy.yaml + networks: + - envoymesh + environment: + - SERVICE_NAME=2 + depends_on: + - skywalking-oap + # Skywalking components. + elasticsearch: + image: elasticsearch:7.9.2 + networks: + - envoymesh + healthcheck: + test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + environment: + discovery.type: single-node + ulimits: + memlock: + soft: -1 + hard: -1 + skywalking-oap: + image: apache/skywalking-oap-server:8.1.0-es7 + networks: + - envoymesh + depends_on: + - elasticsearch + environment: + SW_STORAGE: elasticsearch7 + SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200 + healthcheck: + test: ["CMD-SHELL", "/skywalking/bin/swctl"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + restart: on-failure + skywalking-ui: + image: apache/skywalking-ui:8.1.0 + networks: + - envoymesh + depends_on: + - skywalking-oap + ports: + - 8080:8080 + environment: + SW_OAP_ADDRESS: skywalking-oap:12800 +networks: + envoymesh: {} diff --git a/examples/skywalking-tracing/front-envoy-skywalking.yaml b/examples/skywalking-tracing/front-envoy-skywalking.yaml new file mode 100644 index 000000000000..32d7de94dd07 --- /dev/null +++ b/examples/skywalking-tracing/front-envoy-skywalking.yaml @@ -0,0 +1,80 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 8000 + traffic_direction: OUTBOUND + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + generate_request_id: true + tracing: + provider: + name: envoy.tracers.skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: skywalking + timeout: 0.250s + client_config: + service_name: front-envoy + instance_name: front-envoy-1 + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service1 + decorator: + operation: checkAvailability + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + start_child_span: true + clusters: + - name: service1 + connect_timeout: 0.250s + type: strict_dns + lb_policy: round_robin + http2_protocol_options: {} + load_assignment: + cluster_name: service1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service1 + port_value: 8000 + - name: skywalking + connect_timeout: 1s + type: strict_dns + lb_policy: round_robin + http2_protocol_options: {} + load_assignment: + cluster_name: skywalking + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: skywalking-oap + port_value: 11800 +admin: + access_log_path: "/dev/null" + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 diff --git a/examples/skywalking-tracing/service1-envoy-skywalking.yaml b/examples/skywalking-tracing/service1-envoy-skywalking.yaml new file mode 100644 index 000000000000..a030c63f8c9f --- /dev/null +++ b/examples/skywalking-tracing/service1-envoy-skywalking.yaml @@ -0,0 +1,128 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 8000 + traffic_direction: INBOUND + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + tracing: + provider: + name: envoy.tracers.skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: skywalking + timeout: 0.250s + client_config: + service_name: service1-envoy + instance_name: service1-envoy-1 + codec_type: auto + stat_prefix: ingress_http + route_config: + name: service1_route + virtual_hosts: + - name: service1 + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: local_service + decorator: + operation: checkAvailability + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + start_child_span: true + - address: + socket_address: + address: 0.0.0.0 + port_value: 9000 + traffic_direction: OUTBOUND + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + tracing: + provider: + name: envoy.tracers.skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: skywalking + timeout: 0.250s + client_config: + service_name: service1-envoy + instance_name: service1-envoy-1 + codec_type: auto + stat_prefix: egress_http + route_config: + name: service2_route + virtual_hosts: + - name: service2 + domains: + - "*" + routes: + - match: + prefix: "/trace/2" + route: + cluster: service2 + decorator: + operation: checkStock + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + start_child_span: true + clusters: + - name: local_service + connect_timeout: 0.250s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: local_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + - name: service2 + connect_timeout: 0.250s + type: strict_dns + lb_policy: round_robin + http2_protocol_options: {} + load_assignment: + cluster_name: service2 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service2 + port_value: 8000 + - name: skywalking + connect_timeout: 1s + type: strict_dns + lb_policy: round_robin + http2_protocol_options: {} + load_assignment: + cluster_name: skywalking + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: skywalking-oap + port_value: 11800 diff --git a/examples/skywalking-tracing/service2-envoy-skywalking.yaml b/examples/skywalking-tracing/service2-envoy-skywalking.yaml new file mode 100644 index 000000000000..5f0ee1b834ea --- /dev/null +++ b/examples/skywalking-tracing/service2-envoy-skywalking.yaml @@ -0,0 +1,72 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 8000 + traffic_direction: INBOUND + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + tracing: + provider: + name: envoy.tracers.skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: skywalking + timeout: 0.250s + client_config: + service_name: service2-envoy + instance_name: service2-envoy-1 + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: service2 + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: local_service + decorator: + operation: checkStock + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + start_child_span: true + clusters: + - name: local_service + connect_timeout: 0.250s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: local_service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + - name: skywalking + connect_timeout: 1s + type: strict_dns + lb_policy: round_robin + http2_protocol_options: {} + load_assignment: + cluster_name: skywalking + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: skywalking-oap + port_value: 11800 diff --git a/examples/skywalking-tracing/verify.sh b/examples/skywalking-tracing/verify.sh new file mode 100755 index 000000000000..3c5c4799ca90 --- /dev/null +++ b/examples/skywalking-tracing/verify.sh @@ -0,0 +1,51 @@ +#!/bin/bash -e + +export NAME=skywalking +export DELAY=200 + +# shellcheck source=examples/verify-common.sh +. "$(dirname "${BASH_SOURCE[0]}")/../verify-common.sh" + +run_log "Test connection" +responds_with \ + "Hello from behind Envoy (service 1)!" \ + http://localhost:8000/trace/1 + +run_log "Test stats" +responds_with \ + "tracing.skywalking.segments_sent: 1" \ + http://localhost:8001/stats + +run_log "Test dashboard" +responds_with \ + "" \ + http://localhost:8080 + +run_log "Test OAP Server" +responds_with \ + "getEndpoints" \ + http://localhost:8080/graphql \ + -X POST \ + -H "Content-Type:application/json" \ + -d "{ \"query\": \"query queryEndpoints(\$serviceId: ID!, \$keyword: String!) { + getEndpoints: searchEndpoint(serviceId: \$serviceId, keyword: \$keyword, limit: 100) { + key: id + label: name + } + }\", + \"variables\": { \"serviceId\": \"\", \"keyword\": \"\" } + }" + +responds_with \ + "currentTimestamp" \ + http://localhost:8080/graphql \ + -X POST \ + -H "Content-Type:application/json" \ + -d "{ \"query\": \"query queryOAPTimeInfo { + getTimeInfo { + timezone + currentTimestamp + } + }\", + \"variables\": {} + }" diff --git a/generated_api_shadow/bazel/repositories.bzl b/generated_api_shadow/bazel/repositories.bzl index a12a0ea98b3a..983f15967b28 100644 --- a/generated_api_shadow/bazel/repositories.bzl +++ b/generated_api_shadow/bazel/repositories.bzl @@ -40,6 +40,10 @@ def api_dependencies(): name = "com_github_openzipkin_zipkinapi", build_file_content = ZIPKINAPI_BUILD_CONTENT, ) + external_http_archive( + name = "com_github_apache_skywalking_data_collect_protocol", + build_file_content = SKYWALKING_DATA_COLLECT_PROTOCOL_BUILD_CONTENT, + ) PROMETHEUSMETRICS_BUILD_CONTENT = """ load("@envoy_api//bazel:api_build_system.bzl", "api_cc_py_proto_library") @@ -101,3 +105,30 @@ go_proto_library( visibility = ["//visibility:public"], ) """ + +SKYWALKING_DATA_COLLECT_PROTOCOL_BUILD_CONTENT = """ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") + +proto_library( + name = "protocol", + srcs = [ + "common/Common.proto", + "language-agent/Tracing.proto", + ], + visibility = ["//visibility:public"], +) + +cc_proto_library( + name = "protocol_cc_proto", + deps = [":protocol"], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "protocol_go_proto", + proto = ":protocol", + visibility = ["//visibility:public"], +) +""" diff --git a/generated_api_shadow/bazel/repository_locations.bzl b/generated_api_shadow/bazel/repository_locations.bzl index e46f7d77f8e5..d72069046b85 100644 --- a/generated_api_shadow/bazel/repository_locations.bzl +++ b/generated_api_shadow/bazel/repository_locations.bzl @@ -88,4 +88,15 @@ REPOSITORY_LOCATIONS_SPEC = dict( release_date = "2020-08-17", use_category = ["api"], ), + com_github_apache_skywalking_data_collect_protocol = dict( + project_name = "SkyWalking API", + project_desc = "SkyWalking's language independent model and gRPC API Definitions", + project_url = "https://github.com/apache/skywalking-data-collect-protocol", + version = "8.1.0", + sha256 = "ebea8a6968722524d1bcc4426fb6a29907ddc2902aac7de1559012d3eee90cf9", + strip_prefix = "skywalking-data-collect-protocol-{version}", + urls = ["https://github.com/apache/skywalking-data-collect-protocol/archive/v{version}.tar.gz"], + release_date = "2020-07-29", + use_category = ["api"], + ), ) diff --git a/generated_api_shadow/envoy/config/trace/v3/skywalking.proto b/generated_api_shadow/envoy/config/trace/v3/skywalking.proto new file mode 100644 index 000000000000..224d474ccf98 --- /dev/null +++ b/generated_api_shadow/envoy/config/trace/v3/skywalking.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package envoy.config.trace.v3; + +import "envoy/config/core/v3/grpc_service.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/migrate.proto"; +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.config.trace.v3"; +option java_outer_classname = "SkywalkingProto"; +option java_multiple_files = true; +option (udpa.annotations.file_migrate).move_to_package = + "envoy.extensions.tracers.skywalking.v4alpha"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: SkyWalking tracer] + +// Configuration for the SkyWalking tracer. Please note that if SkyWalking tracer is used as the +// provider of http tracer, then +// :ref:`start_child_span ` +// in the router must be set to true to get the correct topology and tracing data. Moreover, SkyWalking +// Tracer does not support SkyWalking extension header (``sw8-x``) temporarily. +// [#extension: envoy.tracers.skywalking] +message SkyWalkingConfig { + // SkyWalking collector service. + core.v3.GrpcService grpc_service = 1 [(validate.rules).message = {required: true}]; + + ClientConfig client_config = 2; +} + +// Client config for SkyWalking tracer. +message ClientConfig { + // Service name for SkyWalking tracer. If this field is empty, then local service cluster name + // that configured by :ref:`Bootstrap node ` + // message's :ref:`cluster ` field or command line + // option :option:`--service-cluster` will be used. If both this field and local service cluster + // name are empty, ``EnvoyProxy`` is used as the service name by default. + string service_name = 1; + + // Service instance name for SkyWalking tracer. If this field is empty, then local service node + // that configured by :ref:`Bootstrap node ` + // message's :ref:`id ` field or command line option + // :option:`--service-node` will be used. If both this field and local service node are empty, + // ``EnvoyProxy`` is used as the instance name by default. + string instance_name = 2; + + // Authentication token config for SkyWalking. SkyWalking can use token authentication to secure + // that monitoring application data can be trusted. In current version, Token is considered as a + // simple string. + // [#comment:TODO(wbpcode): Get backend token through the SDS API.] + oneof backend_token_specifier { + // Inline authentication token string. + string backend_token = 3 [(udpa.annotations.sensitive) = true]; + } + + // Envoy caches the segment in memory when the SkyWalking backend service is temporarily unavailable. + // This field specifies the maximum number of segments that can be cached. If not specified, the + // default is 1024. + google.protobuf.UInt32Value max_cache_size = 4; +} diff --git a/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/BUILD b/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/BUILD new file mode 100644 index 000000000000..1d56979cc466 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/BUILD @@ -0,0 +1,13 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/config/core/v4alpha:pkg", + "//envoy/config/trace/v3:pkg", + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto b/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto new file mode 100644 index 000000000000..37936faa6133 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/tracers/skywalking/v4alpha/skywalking.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; + +package envoy.extensions.tracers.skywalking.v4alpha; + +import "envoy/config/core/v4alpha/grpc_service.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/sensitive.proto"; +import "udpa/annotations/status.proto"; +import "udpa/annotations/versioning.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.tracers.skywalking.v4alpha"; +option java_outer_classname = "SkywalkingProto"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSION_CANDIDATE; + +// [#protodoc-title: SkyWalking tracer] + +// Configuration for the SkyWalking tracer. Please note that if SkyWalking tracer is used as the +// provider of http tracer, then +// :ref:`start_child_span ` +// in the router must be set to true to get the correct topology and tracing data. Moreover, SkyWalking +// Tracer does not support SkyWalking extension header (``sw8-x``) temporarily. +// [#extension: envoy.tracers.skywalking] +message SkyWalkingConfig { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.trace.v3.SkyWalkingConfig"; + + // SkyWalking collector service. + config.core.v4alpha.GrpcService grpc_service = 1 [(validate.rules).message = {required: true}]; + + ClientConfig client_config = 2; +} + +// Client config for SkyWalking tracer. +message ClientConfig { + option (udpa.annotations.versioning).previous_message_type = "envoy.config.trace.v3.ClientConfig"; + + // Service name for SkyWalking tracer. If this field is empty, then local service cluster name + // that configured by :ref:`Bootstrap node ` + // message's :ref:`cluster ` field or command line + // option :option:`--service-cluster` will be used. If both this field and local service cluster + // name are empty, ``EnvoyProxy`` is used as the service name by default. + string service_name = 1; + + // Service instance name for SkyWalking tracer. If this field is empty, then local service node + // that configured by :ref:`Bootstrap node ` + // message's :ref:`id ` field or command line option + // :option:`--service-node` will be used. If both this field and local service node are empty, + // ``EnvoyProxy`` is used as the instance name by default. + string instance_name = 2; + + // Authentication token config for SkyWalking. SkyWalking can use token authentication to secure + // that monitoring application data can be trusted. In current version, Token is considered as a + // simple string. + // [#comment:TODO(wbpcode): Get backend token through the SDS API.] + oneof backend_token_specifier { + // Inline authentication token string. + string backend_token = 3 [(udpa.annotations.sensitive) = true]; + } + + // Envoy caches the segment in memory when the SkyWalking backend service is temporarily unavailable. + // This field specifies the maximum number of segments that can be cached. If not specified, the + // default is 1024. + google.protobuf.UInt32Value max_cache_size = 4; +} diff --git a/source/common/http/headers.h b/source/common/http/headers.h index 5a66f8f0ea59..7d89713a3f76 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -59,6 +59,7 @@ class CustomHeaderValues { const LowerCaseString AccessControlExposeHeaders{"access-control-expose-headers"}; const LowerCaseString AccessControlMaxAge{"access-control-max-age"}; const LowerCaseString AccessControlAllowCredentials{"access-control-allow-credentials"}; + const LowerCaseString Authentication{"authentication"}; const LowerCaseString Authorization{"authorization"}; const LowerCaseString CacheControl{"cache-control"}; const LowerCaseString CdnLoop{"cdn-loop"}; diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index e3ec724d9339..664b561fb0d2 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -166,6 +166,7 @@ EXTENSIONS = { "envoy.tracers.opencensus": "//source/extensions/tracers/opencensus:config", # WiP "envoy.tracers.xray": "//source/extensions/tracers/xray:config", + "envoy.tracers.skywalking": "//source/extensions/tracers/skywalking:config", # # Transport sockets diff --git a/source/extensions/tracers/skywalking/BUILD b/source/extensions/tracers/skywalking/BUILD new file mode 100644 index 000000000000..5cf90c3f976f --- /dev/null +++ b/source/extensions/tracers/skywalking/BUILD @@ -0,0 +1,107 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +# Trace driver for Apache SkyWalking. + +envoy_extension_package() + +envoy_cc_library( + name = "trace_segment_reporter_lib", + srcs = ["trace_segment_reporter.cc"], + hdrs = ["trace_segment_reporter.h"], + deps = [ + ":skywalking_client_config_lib", + ":skywalking_stats_lib", + ":skywalking_types_lib", + "//include/envoy/grpc:async_client_manager_interface", + "//source/common/common:backoff_lib", + "//source/common/grpc:async_client_lib", + "@com_github_apache_skywalking_data_collect_protocol//:protocol_cc_proto", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "skywalking_types_lib", + srcs = ["skywalking_types.cc"], + hdrs = ["skywalking_types.h"], + deps = [ + ":skywalking_stats_lib", + "//include/envoy/common:random_generator_interface", + "//include/envoy/common:time_interface", + "//include/envoy/http:header_map_interface", + "//include/envoy/tracing:http_tracer_interface", + "//source/common/common:base64_lib", + "//source/common/common:hex_lib", + "//source/common/common:utility_lib", + "@com_github_apache_skywalking_data_collect_protocol//:protocol_cc_proto", + ], +) + +envoy_cc_library( + name = "skywalking_client_config_lib", + srcs = ["skywalking_client_config.cc"], + hdrs = ["skywalking_client_config.h"], + deps = [ + "//include/envoy/secret:secret_provider_interface", + "//include/envoy/server:factory_context_interface", + "//include/envoy/server:tracer_config_interface", + "//source/common/config:datasource_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "skywalking_tracer_lib", + srcs = [ + "skywalking_tracer_impl.cc", + "tracer.cc", + ], + hdrs = [ + "skywalking_tracer_impl.h", + "tracer.h", + ], + deps = [ + ":skywalking_client_config_lib", + ":skywalking_types_lib", + ":trace_segment_reporter_lib", + "//include/envoy/common:time_interface", + "//include/envoy/server:tracer_config_interface", + "//include/envoy/tracing:http_tracer_interface", + "//source/common/common:macros", + "//source/common/http:header_map_lib", + "//source/common/runtime:runtime_lib", + "//source/common/tracing:http_tracer_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "skywalking_stats_lib", + hdrs = [ + "skywalking_stats.h", + ], + deps = [ + "//include/envoy/stats:stats_macros", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + security_posture = "robust_to_untrusted_downstream", + status = "wip", + deps = [ + ":skywalking_tracer_lib", + "//source/common/config:datasource_lib", + "//source/extensions/tracers/common:factory_base_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/tracers/skywalking/config.cc b/source/extensions/tracers/skywalking/config.cc new file mode 100644 index 000000000000..4f9e15b12f2d --- /dev/null +++ b/source/extensions/tracers/skywalking/config.cc @@ -0,0 +1,36 @@ +#include "extensions/tracers/skywalking/config.h" + +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/config/trace/v3/skywalking.pb.validate.h" +#include "envoy/registry/registry.h" + +#include "common/common/utility.h" +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/skywalking/skywalking_tracer_impl.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +SkyWalkingTracerFactory::SkyWalkingTracerFactory() : FactoryBase("envoy.tracers.skywalking") {} + +Tracing::HttpTracerSharedPtr SkyWalkingTracerFactory::createHttpTracerTyped( + const envoy::config::trace::v3::SkyWalkingConfig& proto_config, + Server::Configuration::TracerFactoryContext& context) { + Tracing::DriverPtr skywalking_driver = + std::make_unique(proto_config, context); + return std::make_shared(std::move(skywalking_driver), + context.serverFactoryContext().localInfo()); +} + +/** + * Static registration for the SkyWalking tracer. @see RegisterFactory. + */ +REGISTER_FACTORY(SkyWalkingTracerFactory, Server::Configuration::TracerFactory); + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/config.h b/source/extensions/tracers/skywalking/config.h new file mode 100644 index 000000000000..abeffe373e5d --- /dev/null +++ b/source/extensions/tracers/skywalking/config.h @@ -0,0 +1,31 @@ +#pragma once + +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/config/trace/v3/skywalking.pb.validate.h" + +#include "extensions/tracers/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +/** + * Config registration for the SkyWalking tracer. @see TracerFactory. + */ +class SkyWalkingTracerFactory + : public Common::FactoryBase { +public: + SkyWalkingTracerFactory(); + +private: + // FactoryBase + Tracing::HttpTracerSharedPtr + createHttpTracerTyped(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, + Server::Configuration::TracerFactoryContext& context) override; +}; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_client_config.cc b/source/extensions/tracers/skywalking/skywalking_client_config.cc new file mode 100644 index 000000000000..ed692b3f72bd --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_client_config.cc @@ -0,0 +1,43 @@ +#include "extensions/tracers/skywalking/skywalking_client_config.h" + +#include "common/config/datasource.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +constexpr uint32_t DEFAULT_DELAYED_SEGMENTS_CACHE_SIZE = 1024; + +// When the user does not provide any available configuration, in order to ensure that the service +// name and instance name are not empty, use this value as the default identifier. In practice, +// user should provide accurate configuration as much as possible to avoid using the default value. +constexpr absl::string_view DEFAULT_SERVICE_AND_INSTANCE = "EnvoyProxy"; + +SkyWalkingClientConfig::SkyWalkingClientConfig(Server::Configuration::TracerFactoryContext& context, + const envoy::config::trace::v3::ClientConfig& config) + : factory_context_(context.serverFactoryContext()), + max_cache_size_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_cache_size, + DEFAULT_DELAYED_SEGMENTS_CACHE_SIZE)), + service_(config.service_name().empty() ? factory_context_.localInfo().clusterName().empty() + ? DEFAULT_SERVICE_AND_INSTANCE + : factory_context_.localInfo().clusterName() + : config.service_name()), + instance_(config.instance_name().empty() ? factory_context_.localInfo().nodeName().empty() + ? DEFAULT_SERVICE_AND_INSTANCE + : factory_context_.localInfo().nodeName() + : config.instance_name()) { + // Since the SDS API to get backend token is not supported yet, we can get the value of token + // from the backend_token field directly. If the user does not provide the configuration, the + // value of token is kept empty. + backend_token_ = config.backend_token(); +} + +// TODO(wbpcode): currently, backend authentication token can only be configured with inline string. +// It will be possible to get authentication through the SDS API later. +const std::string& SkyWalkingClientConfig::backendToken() const { return backend_token_; } + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_client_config.h b/source/extensions/tracers/skywalking/skywalking_client_config.h new file mode 100644 index 000000000000..8983a1dbf758 --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_client_config.h @@ -0,0 +1,43 @@ +#pragma once + +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/secret/secret_provider.h" +#include "envoy/server/factory_context.h" +#include "envoy/server/tracer_config.h" + +#include "absl/synchronization/mutex.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +class SkyWalkingClientConfig { +public: + SkyWalkingClientConfig(Server::Configuration::TracerFactoryContext& context, + const envoy::config::trace::v3::ClientConfig& config); + + uint32_t maxCacheSize() const { return max_cache_size_; } + + const std::string& service() const { return service_; } + const std::string& serviceInstance() const { return instance_; } + + const std::string& backendToken() const; + +private: + Server::Configuration::ServerFactoryContext& factory_context_; + + const uint32_t max_cache_size_{0}; + + const std::string service_; + const std::string instance_; + + std::string backend_token_; +}; + +using SkyWalkingClientConfigPtr = std::unique_ptr; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_stats.h b/source/extensions/tracers/skywalking/skywalking_stats.h new file mode 100644 index 000000000000..0ad8a58f5ab5 --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_stats.h @@ -0,0 +1,21 @@ +#include "envoy/stats/stats_macros.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +#define SKYWALKING_TRACER_STATS(COUNTER) \ + COUNTER(cache_flushed) \ + COUNTER(segments_dropped) \ + COUNTER(segments_flushed) \ + COUNTER(segments_sent) + +struct SkyWalkingTracerStats { + SKYWALKING_TRACER_STATS(GENERATE_COUNTER_STRUCT) +}; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc new file mode 100644 index 000000000000..130cbd6a9801 --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc @@ -0,0 +1,63 @@ +#include "extensions/tracers/skywalking/skywalking_tracer_impl.h" + +#include + +#include "common/common/macros.h" +#include "common/common/utility.h" +#include "common/http/path_utility.h" + +#include "extensions/tracers/skywalking/skywalking_types.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +Driver::Driver(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, + Server::Configuration::TracerFactoryContext& context) + : tracing_stats_{SKYWALKING_TRACER_STATS( + POOL_COUNTER_PREFIX(context.serverFactoryContext().scope(), "tracing.skywalking."))}, + client_config_( + std::make_unique(context, proto_config.client_config())), + random_generator_(context.serverFactoryContext().api().randomGenerator()), + tls_slot_ptr_(context.serverFactoryContext().threadLocal().allocateSlot()) { + + auto& factory_context = context.serverFactoryContext(); + tls_slot_ptr_->set([proto_config, &factory_context, this](Event::Dispatcher& dispatcher) { + TracerPtr tracer = std::make_unique(factory_context.timeSource()); + tracer->setReporter(std::make_unique( + factory_context.clusterManager().grpcAsyncClientManager().factoryForGrpcService( + proto_config.grpc_service(), factory_context.scope(), false), + dispatcher, factory_context.api().randomGenerator(), tracing_stats_, *client_config_)); + return std::make_shared(std::move(tracer)); + }); +} + +Tracing::SpanPtr Driver::startSpan(const Tracing::Config& config, + Http::RequestHeaderMap& request_headers, + const std::string& operation_name, Envoy::SystemTime start_time, + const Tracing::Decision decision) { + auto& tracer = *tls_slot_ptr_->getTyped().tracer_; + + try { + SpanContextPtr previous_span_context = SpanContext::spanContextFromRequest(request_headers); + auto segment_context = std::make_shared(std::move(previous_span_context), + decision, random_generator_); + + // Initialize fields of current span context. + segment_context->setService(client_config_->service()); + segment_context->setServiceInstance(client_config_->serviceInstance()); + + return tracer.startSpan(config, start_time, operation_name, std::move(segment_context), + nullptr); + + } catch (const EnvoyException& e) { + ENVOY_LOG(warn, "New SkyWalking Span/Segment cannot be created for error: {}", e.what()); + return std::make_unique(); + } +} + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.h b/source/extensions/tracers/skywalking/skywalking_tracer_impl.h new file mode 100644 index 000000000000..f073461deb5d --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.h @@ -0,0 +1,48 @@ +#pragma once + +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/server/tracer_config.h" +#include "envoy/thread_local/thread_local.h" +#include "envoy/tracing/http_tracer.h" + +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/skywalking/skywalking_client_config.h" +#include "extensions/tracers/skywalking/tracer.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +class Driver : public Tracing::Driver, public Logger::Loggable { +public: + explicit Driver(const envoy::config::trace::v3::SkyWalkingConfig& config, + Server::Configuration::TracerFactoryContext& context); + + Tracing::SpanPtr startSpan(const Tracing::Config& config, Http::RequestHeaderMap& request_headers, + const std::string& operation, Envoy::SystemTime start_time, + const Tracing::Decision decision) override; + +private: + struct TlsTracer : ThreadLocal::ThreadLocalObject { + TlsTracer(TracerPtr tracer) : tracer_(std::move(tracer)) {} + + TracerPtr tracer_; + }; + + SkyWalkingTracerStats tracing_stats_; + + SkyWalkingClientConfigPtr client_config_; + + // This random_generator_ will be used to create SkyWalking trace id and segment id. + Random::RandomGenerator& random_generator_; + ThreadLocal::SlotPtr tls_slot_ptr_; +}; + +using DriverPtr = std::unique_ptr; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_types.cc b/source/extensions/tracers/skywalking/skywalking_types.cc new file mode 100644 index 000000000000..9c750884bc11 --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_types.cc @@ -0,0 +1,175 @@ +#include "extensions/tracers/skywalking/skywalking_types.h" + +#include "envoy/common/exception.h" + +#include "common/common/base64.h" +#include "common/common/empty_string.h" +#include "common/common/fmt.h" +#include "common/common/hex.h" +#include "common/common/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +namespace { + +// The standard header name is "sw8", as mentioned in: +// https://github.com/apache/skywalking/blob/v8.1.0/docs/en/protocols/Skywalking-Cross-Process-Propagation-Headers-Protocol-v3.md. +const Http::LowerCaseString& propagationHeader() { + CONSTRUCT_ON_FIRST_USE(Http::LowerCaseString, "sw8"); +} + +std::string generateId(Random::RandomGenerator& random_generator) { + return absl::StrCat(Hex::uint64ToHex(random_generator.random()), + Hex::uint64ToHex(random_generator.random())); +} + +std::string base64Encode(const absl::string_view input) { + return Base64::encode(input.data(), input.length()); +} + +// Decode and validate fields of propagation header. +std::string base64Decode(absl::string_view input) { + // The input can be Base64 string with or without padding. + std::string result = Base64::decodeWithoutPadding(input); + if (result.empty()) { + throw EnvoyException("Invalid propagation header for SkyWalking: parse error"); + } + return result; +} + +} // namespace + +SpanContextPtr SpanContext::spanContextFromRequest(Http::RequestHeaderMap& headers) { + auto propagation_header = headers.get(propagationHeader()); + if (propagation_header.empty()) { + // No propagation header then Envoy is first hop. + return nullptr; + } + + auto header_value_string = propagation_header[0]->value().getStringView(); + const auto parts = StringUtil::splitToken(header_value_string, "-", false, true); + // Reference: + // https://github.com/apache/skywalking/blob/v8.1.0/docs/en/protocols/Skywalking-Cross-Process-Propagation-Headers-Protocol-v3.md. + if (parts.size() != 8) { + throw EnvoyException( + fmt::format("Invalid propagation header for SkyWalking: {}", header_value_string)); + } + + SpanContextPtr previous_span_context = std::unique_ptr(new SpanContext()); + + // Parse and validate sampling flag. + if (parts[0] == "0") { + previous_span_context->sampled_ = 0; + } else if (parts[0] == "1") { + previous_span_context->sampled_ = 1; + } else { + throw EnvoyException(fmt::format("Invalid propagation header for SkyWalking: sampling flag can " + "only be '0' or '1' but '{}' was provided", + parts[0])); + } + + // Parse trace id. + previous_span_context->trace_id_ = base64Decode(parts[1]); + // Parse segment id. + previous_span_context->trace_segment_id_ = base64Decode(parts[2]); + + // Parse span id. + if (!absl::SimpleAtoi(parts[3], &previous_span_context->span_id_)) { + throw EnvoyException(fmt::format( + "Invalid propagation header for SkyWalking: connot convert '{}' to valid span id", + parts[3])); + } + + // Parse service. + previous_span_context->service_ = base64Decode(parts[4]); + // Parse service instance. + previous_span_context->service_instance_ = base64Decode(parts[5]); + // Parse endpoint. Operation Name of the first entry span in the previous segment. + previous_span_context->endpoint_ = base64Decode(parts[6]); + // Parse target address used at downstream side of this request. + previous_span_context->target_address_ = base64Decode(parts[7]); + + return previous_span_context; +} + +SegmentContext::SegmentContext(SpanContextPtr&& previous_span_context, Tracing::Decision decision, + Random::RandomGenerator& random_generator) + : previous_span_context_(std::move(previous_span_context)) { + + if (previous_span_context_) { + trace_id_ = previous_span_context_->trace_id_; + sampled_ = previous_span_context_->sampled_; + } else { + trace_id_ = generateId(random_generator); + sampled_ = decision.traced; + } + trace_segment_id_ = generateId(random_generator); + + // Some detailed log for debugging. + ENVOY_LOG(trace, "{} and create new SkyWalking segment:", + previous_span_context_ ? "Has previous span context" : "No previous span context"); + + ENVOY_LOG(trace, " Trace ID: {}", trace_id_); + ENVOY_LOG(trace, " Segment ID: {}", trace_segment_id_); + ENVOY_LOG(trace, " Sampled: {}", sampled_); +} + +SpanStore* SegmentContext::createSpanStore(const SpanStore* parent_span_store) { + ENVOY_LOG(trace, "Create new SpanStore object for current segment: {}", trace_segment_id_); + SpanStorePtr new_span_store = std::make_unique(this); + new_span_store->setSpanId(span_list_.size()); + if (!parent_span_store) { + // The parent SpanStore object does not exist. Create the root SpanStore object in the current + // segment. + new_span_store->setSampled(sampled_); + new_span_store->setParentSpanId(-1); + // First span of current segment for Envoy Proxy must be a Entry Span. It is created for + // downstream HTTP request. + new_span_store->setAsEntrySpan(true); + } else { + // Create child SpanStore object. + new_span_store->setSampled(parent_span_store->sampled()); + new_span_store->setParentSpanId(parent_span_store->spanId()); + new_span_store->setAsEntrySpan(false); + } + SpanStore* ref = new_span_store.get(); + span_list_.emplace_back(std::move(new_span_store)); + return ref; +} + +void SpanStore::injectContext(Http::RequestHeaderMap& request_headers) const { + ASSERT(segment_context_); + + // For SkyWalking Entry Span, Envoy does not need to inject tracing context into the request + // headers. + if (is_entry_span_) { + ENVOY_LOG(debug, "Skip tracing context injection for SkyWalking Entry Span"); + return; + } + + ENVOY_LOG(debug, "Inject or update SkyWalking propagation header in upstream request headers"); + const_cast(this)->setPeerAddress(std::string(request_headers.getHostValue())); + + ENVOY_LOG(trace, "'sw8' header: '({}) - ({}) - ({}) - ({}) - ({}) - ({}) - ({}) - ({})'", + sampled_, segment_context_->traceId(), segment_context_->traceSegmentId(), span_id_, + segment_context_->service(), segment_context_->serviceInstance(), + segment_context_->rootSpanStore()->operation(), peer_address_); + + // Reference: + // https://github.com/apache/skywalking/blob/v8.1.0/docs/en/protocols/Skywalking-Cross-Process-Propagation-Headers-Protocol-v3.md. + const auto value = absl::StrCat(sampled_, "-", base64Encode(segment_context_->traceId()), "-", + base64Encode(segment_context_->traceSegmentId()), "-", span_id_, + "-", base64Encode(segment_context_->service()), "-", + base64Encode(segment_context_->serviceInstance()), "-", + base64Encode(segment_context_->rootSpanStore()->operation()), "-", + base64Encode(peer_address_)); + request_headers.setReferenceKey(propagationHeader(), value); +} + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/skywalking_types.h b/source/extensions/tracers/skywalking/skywalking_types.h new file mode 100644 index 000000000000..eacd9a94a075 --- /dev/null +++ b/source/extensions/tracers/skywalking/skywalking_types.h @@ -0,0 +1,313 @@ +#pragma once + +#include +#include + +#include "envoy/common/random_generator.h" +#include "envoy/common/time.h" +#include "envoy/http/header_map.h" +#include "envoy/tracing/http_tracer.h" + +#include "language-agent/Tracing.pb.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +class SegmentContext; +using SegmentContextSharedPtr = std::shared_ptr; + +class SpanStore; +using SpanStorePtr = std::unique_ptr; + +class SpanContext; +using SpanContextPtr = std::unique_ptr; + +class SpanContext : public Logger::Loggable { +public: + /* + * Parse the context of the previous span from the request and decide whether to sample it or + * not. + * + * @param headers The request headers. + * @return SpanContextPtr The previous span context parsed from request headers. + */ + static SpanContextPtr spanContextFromRequest(Http::RequestHeaderMap& headers); + + // Sampling flag. This field can only be 0 or 1. 1 means this trace need to be sampled and send to + // backend. + int sampled_{0}; + + // This span id points to the parent span in parent trace segment. + int span_id_{0}; + + std::string trace_id_; + + // This trace segment id points to the parent trace segment. + std::string trace_segment_id_; + + std::string service_; + std::string service_instance_; + + // Operation Name of the first entry span in the parent segment. + std::string endpoint_; + + // Target address used at client side of this request. The network address(not must be IP + port) + // used at client side to access this target service. + std::string target_address_; + +private: + // Private default constructor. We can only create SpanContext by 'spanContextFromRequest'. + SpanContext() = default; +}; + +class SegmentContext : public Logger::Loggable { +public: + /* + * Create a new segment context based on the previous span context that parsed from request + * headers. + * + * @param previous_span_context The previous span context. + * @param random_generator The random generator that used to create trace id and segment id. + * @param decision The tracing decision. + */ + SegmentContext(SpanContextPtr&& previous_span_context, Tracing::Decision decision, + Random::RandomGenerator& random_generator); + + /* + * Set service name. + * + * @param service The service name. + */ + void setService(const std::string& service) { service_ = service; } + + /* + * Set service instance name. + * + * @param service_instance The service instance name. + */ + void setServiceInstance(const std::string& service_instance) { + service_instance_ = service_instance; + } + + /* + * Create a new SpanStore object and return its pointer. The ownership of the newly created + * SpanStore object belongs to the current segment context. + * + * @param parent_store The pointer that point to parent SpanStore object. + * @return SpanStore* The pointer that point to newly created SpanStore object. + */ + SpanStore* createSpanStore(const SpanStore* parent_store); + + /* + * Get all SpanStore objects in the current segment. + */ + const std::vector& spanList() const { return span_list_; } + + /* + * Get root SpanStore object in the current segment. + */ + const SpanStore* rootSpanStore() { return span_list_.empty() ? nullptr : span_list_[0].get(); } + + int sampled() const { return sampled_; } + const std::string& traceId() const { return trace_id_; } + const std::string& traceSegmentId() const { return trace_segment_id_; } + + const std::string& service() const { return service_; } + const std::string& serviceInstance() const { return service_instance_; } + + SpanContext* previousSpanContext() const { return previous_span_context_.get(); } + +private: + int sampled_{0}; + // This value is unique in the entire tracing link. If previous_context is null, we will use + // random_generator to create a trace id. + std::string trace_id_; + // Envoy creates a new span when it accepts a new HTTP request. This span and all of its child + // spans belong to the same segment and share the segment id. + std::string trace_segment_id_; + + std::string service_; + std::string service_instance_; + + // The SegmentContext parsed from the request headers. If no propagation headers in request then + // this will be nullptr. + SpanContextPtr previous_span_context_; + + std::vector span_list_; +}; + +using Tag = std::pair; + +/* + * A helper class for the SkyWalking span and is used to store all span-related data, including span + * id, parent span id, tags and so on. Whenever we create a new span, we create a new SpanStore + * object. The new span will hold a pointer to the newly created SpanStore object and write data to + * it or get data from it. + */ +class SpanStore : public Logger::Loggable { +public: + /* + * Construct a SpanStore object using span context and time source. + * + * @param segment_context The pointer that point to current span context. This can not be null. + * @param time_source A time source to get the span end time. + */ + explicit SpanStore(SegmentContext* segment_context) : segment_context_(segment_context) {} + + /* + * Get operation name of span. + */ + const std::string& operation() const { return operation_; } + + /* + * Get peer address. The peer in SkyWalking is different with the tag value of 'peer.address'. The + * tag value of 'peer.address' in Envoy is downstream address and the peer in SkyWalking is + * upstream address. + */ + const std::string& peerAddress() const { return peer_address_; } + + /* + * Get span start time. + */ + uint64_t startTime() const { return start_time_; } + + /* + * Get span end time. + */ + uint64_t endTime() const { return end_time_; } + + /* + * Get span tags. + */ + const std::vector& tags() const { return tags_; } + + /* + * Get span logs. + */ + const std::vector& logs() const { return logs_; } + + /* + * Get span sampling flag. + */ + int sampled() const { return sampled_; } + + /* + * Get span id. + */ + int spanId() const { return span_id_; } + + /* + * Get parent span id. + */ + int parentSpanId() const { return parent_span_id_; } + + /* + * Determines if an error has occurred in the current span. + */ + bool isError() const { return is_error_; } + + /* + * Determines if the current span is an entry span. + * + * Reference: + * https://github.com/apache/skywalking/blob/v8.1.0/docs/en/protocols/Trace-Data-Protocol-v3.md + */ + bool isEntrySpan() const { return is_entry_span_; } + + /* + * Set span start time. This is the time when the HTTP request started, not the time when the span + * was created. + */ + void setStartTime(uint64_t start_time) { start_time_ = start_time; } + + /* + * Set span end time. It is meaningless for now. End time will be set by finish. + */ + void setEndTime(uint64_t end_time) { end_time_ = end_time; } + + /* + * Set operation name. + */ + void setOperation(const std::string& operation) { operation_ = operation; } + + /* + * Set peer address. In SkyWalking, the peer address is only set in Exit Span. And it should the + * upstream address. Since the upstream address cannot be obtained at the request stage, the + * request host is used instead. + */ + void setPeerAddress(const std::string& peer_address) { peer_address_ = peer_address; } + + /* + * Set if the current span has an error. + */ + void setAsError(bool is_error) { is_error_ = is_error; } + + /* + * Set if the current span is a entry span. + */ + void setAsEntrySpan(bool is_entry_span) { is_entry_span_ = is_entry_span; } + + /* + * Add a new tag entry to current span. + */ + void addTag(absl::string_view name, absl::string_view value) { tags_.emplace_back(name, value); } + + /* + * Add a new log entry to current span. Due to different data formats, log is temporarily not + * supported. + */ + void addLog(SystemTime, const std::string&) {} + + /* + * Set span id of current span. The span id in each segment is started from 0. When new span is + * created, its span id is the current max span id plus 1. + */ + void setSpanId(int span_id) { span_id_ = span_id; } + + /* + * Set parent span id. Notice that in SkyWalking, the parent span and the child span belong to the + * same segment. The first span of each segment has a parent span id of -1. + */ + void setParentSpanId(int parent_span_id) { parent_span_id_ = parent_span_id; } + + /* + * Set sampling flag. In general, the sampling flag of span is consistent with the current span + * context. + */ + void setSampled(int sampled) { sampled_ = sampled == 0 ? 0 : 1; } + + /* + * Inject current span context information to request headers. This will update original + * propagation headers. + * + * @param request_headers The request headers. + */ + void injectContext(Http::RequestHeaderMap& request_headers) const; + +private: + SegmentContext* segment_context_{nullptr}; + + int sampled_{0}; + + int span_id_{0}; + int parent_span_id_{-1}; + + uint64_t start_time_{0}; + uint64_t end_time_{0}; + + std::string operation_; + std::string peer_address_; + + bool is_error_{false}; + bool is_entry_span_{true}; + + std::vector tags_; + std::vector logs_; +}; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/trace_segment_reporter.cc b/source/extensions/tracers/skywalking/trace_segment_reporter.cc new file mode 100644 index 000000000000..5ef0046dc800 --- /dev/null +++ b/source/extensions/tracers/skywalking/trace_segment_reporter.cc @@ -0,0 +1,178 @@ +#include "extensions/tracers/skywalking/trace_segment_reporter.h" + +#include "envoy/http/header_map.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +namespace { + +Http::RegisterCustomInlineHeader + authentication_handle(Http::CustomHeaders::get().Authentication); + +// Convert SegmentContext to SegmentObject. +TraceSegmentPtr toSegmentObject(const SegmentContext& segment_context) { + auto new_segment_ptr = std::make_unique(); + SegmentObject& segment_object = *new_segment_ptr; + + segment_object.set_traceid(segment_context.traceId()); + segment_object.set_tracesegmentid(segment_context.traceSegmentId()); + segment_object.set_service(segment_context.service()); + segment_object.set_serviceinstance(segment_context.serviceInstance()); + + for (const auto& span_store : segment_context.spanList()) { + if (!span_store->sampled()) { + continue; + } + auto* span = segment_object.mutable_spans()->Add(); + + span->set_spanlayer(SpanLayer::Http); + span->set_spantype(span_store->isEntrySpan() ? SpanType::Entry : SpanType::Exit); + // Please check + // https://github.com/apache/skywalking/blob/master/oap-server/server-bootstrap/src/main/resources/component-libraries.yml + // get more information. + span->set_componentid(9000); + + if (!span_store->peerAddress().empty() && span_store->isEntrySpan()) { + span->set_peer(span_store->peerAddress()); + } + + span->set_spanid(span_store->spanId()); + span->set_parentspanid(span_store->parentSpanId()); + + span->set_starttime(span_store->startTime()); + span->set_endtime(span_store->endTime()); + + span->set_iserror(span_store->isError()); + + span->set_operationname(span_store->operation()); + + auto& tags = *span->mutable_tags(); + tags.Reserve(span_store->tags().size()); + + for (auto& span_tag : span_store->tags()) { + KeyStringValuePair* new_tag = tags.Add(); + new_tag->set_key(span_tag.first); + new_tag->set_value(span_tag.second); + } + + SpanContext* previous_span_context = segment_context.previousSpanContext(); + + if (!previous_span_context || !span_store->isEntrySpan()) { + continue; + } + + auto* ref = span->mutable_refs()->Add(); + ref->set_traceid(previous_span_context->trace_id_); + ref->set_parenttracesegmentid(previous_span_context->trace_segment_id_); + ref->set_parentspanid(previous_span_context->span_id_); + ref->set_parentservice(previous_span_context->service_); + ref->set_parentserviceinstance(previous_span_context->service_instance_); + ref->set_parentendpoint(previous_span_context->endpoint_); + ref->set_networkaddressusedatpeer(previous_span_context->target_address_); + } + return new_segment_ptr; +} + +} // namespace + +TraceSegmentReporter::TraceSegmentReporter(Grpc::AsyncClientFactoryPtr&& factory, + Event::Dispatcher& dispatcher, + Random::RandomGenerator& random_generator, + SkyWalkingTracerStats& stats, + const SkyWalkingClientConfig& client_config) + : tracing_stats_(stats), client_config_(client_config), client_(factory->create()), + service_method_(*Protobuf::DescriptorPool::generated_pool()->FindMethodByName( + "TraceSegmentReportService.collect")), + random_generator_(random_generator) { + + static constexpr uint32_t RetryInitialDelayMs = 500; + static constexpr uint32_t RetryMaxDelayMs = 30000; + backoff_strategy_ = std::make_unique( + RetryInitialDelayMs, RetryMaxDelayMs, random_generator_); + + retry_timer_ = dispatcher.createTimer([this]() -> void { establishNewStream(); }); + establishNewStream(); +} + +void TraceSegmentReporter::onCreateInitialMetadata(Http::RequestHeaderMap& metadata) { + if (!client_config_.backendToken().empty()) { + metadata.setInline(authentication_handle.handle(), client_config_.backendToken()); + } +} + +void TraceSegmentReporter::report(const SegmentContext& segment_context) { + sendTraceSegment(toSegmentObject(segment_context)); +} + +void TraceSegmentReporter::sendTraceSegment(TraceSegmentPtr request) { + ASSERT(request); + ENVOY_LOG(trace, "Try to report segment to SkyWalking Server:\n{}", request->DebugString()); + + if (stream_ != nullptr) { + tracing_stats_.segments_sent_.inc(); + stream_->sendMessage(*request, false); + return; + } + // Null stream_ and cache segment data temporarily. + delayed_segments_cache_.emplace(std::move(request)); + if (delayed_segments_cache_.size() > client_config_.maxCacheSize()) { + tracing_stats_.segments_dropped_.inc(); + delayed_segments_cache_.pop(); + } +} + +void TraceSegmentReporter::flushTraceSegments() { + ENVOY_LOG(debug, "Flush segments in cache to SkyWalking backend service"); + while (!delayed_segments_cache_.empty() && stream_ != nullptr) { + tracing_stats_.segments_sent_.inc(); + tracing_stats_.segments_flushed_.inc(); + stream_->sendMessage(*delayed_segments_cache_.front(), false); + delayed_segments_cache_.pop(); + } + tracing_stats_.cache_flushed_.inc(); +} + +void TraceSegmentReporter::closeStream() { + if (stream_ != nullptr) { + flushTraceSegments(); + stream_->closeStream(); + } +} + +void TraceSegmentReporter::onRemoteClose(Grpc::Status::GrpcStatus status, + const std::string& message) { + ENVOY_LOG(debug, "{} gRPC stream closed: {}, {}", service_method_.name(), status, message); + stream_ = nullptr; + handleFailure(); +} + +void TraceSegmentReporter::establishNewStream() { + ENVOY_LOG(debug, "Try to create new {} gRPC stream for reporter", service_method_.name()); + stream_ = client_->start(service_method_, *this, Http::AsyncClient::StreamOptions()); + if (stream_ == nullptr) { + ENVOY_LOG(debug, "Failed to create {} gRPC stream", service_method_.name()); + return; + } + // TODO(wbpcode): Even if stream_ is not empty, there is no guarantee that the connection will be + // established correctly. If there is a connection failure, the onRemoteClose method will be + // called. Currently, we lack a way to determine whether the connection is truly available. This + // may cause partial data loss. + if (!delayed_segments_cache_.empty()) { + flushTraceSegments(); + } + backoff_strategy_->reset(); +} + +void TraceSegmentReporter::handleFailure() { setRetryTimer(); } + +void TraceSegmentReporter::setRetryTimer() { + retry_timer_->enableTimer(std::chrono::milliseconds(backoff_strategy_->nextBackOffMs())); +} + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/trace_segment_reporter.h b/source/extensions/tracers/skywalking/trace_segment_reporter.h new file mode 100644 index 000000000000..fd70d819917b --- /dev/null +++ b/source/extensions/tracers/skywalking/trace_segment_reporter.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/grpc/async_client_manager.h" + +#include "common/Common.pb.h" +#include "common/common/backoff_strategy.h" +#include "common/grpc/async_client_impl.h" + +#include "extensions/tracers/skywalking/skywalking_client_config.h" +#include "extensions/tracers/skywalking/skywalking_stats.h" +#include "extensions/tracers/skywalking/skywalking_types.h" + +#include "language-agent/Tracing.pb.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +using TraceSegmentPtr = std::unique_ptr; + +class TraceSegmentReporter : public Logger::Loggable, + public Grpc::AsyncStreamCallbacks { +public: + explicit TraceSegmentReporter(Grpc::AsyncClientFactoryPtr&& factory, + Event::Dispatcher& dispatcher, Random::RandomGenerator& random, + SkyWalkingTracerStats& stats, + const SkyWalkingClientConfig& client_config); + + // Grpc::AsyncStreamCallbacks + void onCreateInitialMetadata(Http::RequestHeaderMap& metadata) override; + void onReceiveInitialMetadata(Http::ResponseHeaderMapPtr&&) override {} + void onReceiveMessage(std::unique_ptr&&) override {} + void onReceiveTrailingMetadata(Http::ResponseTrailerMapPtr&&) override {} + void onRemoteClose(Grpc::Status::GrpcStatus, const std::string&) override; + + /* + * Flush all cached segment objects to the back-end tracing service and close the GRPC stream. + */ + void closeStream(); + + /* + * Convert the current span context into a segment object and report it to the back-end tracing + * service through the GRPC stream. + * + * @param span_context The span context. + */ + void report(const SegmentContext& span_context); + +private: + void flushTraceSegments(); + + void sendTraceSegment(TraceSegmentPtr request); + void establishNewStream(); + void handleFailure(); + void setRetryTimer(); + + SkyWalkingTracerStats& tracing_stats_; + + const SkyWalkingClientConfig& client_config_; + + Grpc::AsyncClient client_; + Grpc::AsyncStream stream_{}; + const Protobuf::MethodDescriptor& service_method_; + + Random::RandomGenerator& random_generator_; + // If the connection is unavailable when reporting data, the created SegmentObject will be cached + // in the queue, and when a new connection is established, the cached data will be reported. + std::queue delayed_segments_cache_; + + Event::TimerPtr retry_timer_; + BackOffStrategyPtr backoff_strategy_; +}; + +using TraceSegmentReporterPtr = std::unique_ptr; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/tracer.cc b/source/extensions/tracers/skywalking/tracer.cc new file mode 100644 index 000000000000..f3845b9c4213 --- /dev/null +++ b/source/extensions/tracers/skywalking/tracer.cc @@ -0,0 +1,82 @@ +#include "extensions/tracers/skywalking/tracer.h" + +#include + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +constexpr absl::string_view StatusCodeTag = "status_code"; +constexpr absl::string_view UrlTag = "url"; + +namespace { + +uint64_t getTimestamp(SystemTime time) { + return std::chrono::duration_cast(time.time_since_epoch()).count(); +} + +} // namespace + +Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, SystemTime start_time, + const std::string& operation, + SegmentContextSharedPtr segment_context, Span* parent) { + SpanStore* span_store = segment_context->createSpanStore(parent ? parent->spanStore() : nullptr); + + span_store->setStartTime(getTimestamp(start_time)); + + span_store->setOperation(operation); + + return std::make_unique(std::move(segment_context), span_store, *this); +} + +void Span::setOperation(absl::string_view operation) { + span_store_->setOperation(std::string(operation)); +} + +void Span::setTag(absl::string_view name, absl::string_view value) { + if (name == Tracing::Tags::get().HttpUrl) { + span_store_->addTag(UrlTag, value); + return; + } + + if (name == Tracing::Tags::get().HttpStatusCode) { + span_store_->addTag(StatusCodeTag, value); + return; + } + + if (name == Tracing::Tags::get().Error) { + span_store_->setAsError(value == Tracing::Tags::get().True); + } + + span_store_->addTag(name, value); +} + +// Logs in the SkyWalking format are temporarily unsupported. +void Span::log(SystemTime, const std::string&) {} + +void Span::finishSpan() { + span_store_->setEndTime(DateUtil::nowToMilliseconds(tracer_.time_source_)); + tryToReportSpan(); +} + +void Span::injectContext(Http::RequestHeaderMap& request_headers) { + span_store_->injectContext(request_headers); +} + +Tracing::SpanPtr Span::spawnChild(const Tracing::Config& config, const std::string& operation_name, + SystemTime start_time) { + // The new child span will share the same context with the parent span. + return tracer_.startSpan(config, start_time, operation_name, segment_context_, this); +} + +void Span::setSampled(bool sampled) { span_store_->setSampled(sampled ? 1 : 0); } + +std::string Span::getBaggage(absl::string_view) { return EMPTY_STRING; } + +void Span::setBaggage(absl::string_view, absl::string_view) {} + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/skywalking/tracer.h b/source/extensions/tracers/skywalking/tracer.h new file mode 100644 index 000000000000..d28276b232cd --- /dev/null +++ b/source/extensions/tracers/skywalking/tracer.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" + +#include "common/tracing/http_tracer_impl.h" + +#include "extensions/tracers/skywalking/skywalking_types.h" +#include "extensions/tracers/skywalking/trace_segment_reporter.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +class Span; + +class Tracer { +public: + explicit Tracer(TimeSource& time_source) : time_source_(time_source) {} + virtual ~Tracer() { reporter_->closeStream(); } + + /* + * Set a trace segment reporter to the current Tracer. Whenever a SkyWalking segment ends, the + * reporter will be used to report segment data. + * + * @param reporter The unique ptr of trace segment reporter. + */ + void setReporter(TraceSegmentReporterPtr&& reporter) { reporter_ = std::move(reporter); } + + /* + * Report trace segment data to backend tracing service. + * + * @param segment_context The segment context. + */ + void report(const SegmentContext& segment_context) { return reporter_->report(segment_context); } + + /* + * Create a new span based on the segment context and parent span. + * + * @param config The tracing config. + * @param start_time Start time of span. + * @param operation Operation name of span. + * @param segment_context The SkyWalking segment context. The newly created span belongs to this + * segment. + * @param parent The parent span pointer. If parent is null, then the newly created span is first + * span of this segment. + * + * @return The unique ptr to the newly created span. + */ + Tracing::SpanPtr startSpan(const Tracing::Config& config, SystemTime start_time, + const std::string& operation, SegmentContextSharedPtr segment_context, + Span* parent); + + TimeSource& time_source_; + +private: + TraceSegmentReporterPtr reporter_; +}; + +using TracerPtr = std::unique_ptr; + +class Span : public Tracing::Span { +public: + /* + * Constructor of span. + * + * @param segment_context The SkyWalking segment context. + * @param span_store Pointer to a SpanStore object. Whenever a new span is created, a new + * SpanStore object is created and stored in the segment context. This parameter can never be + * null. + * @param tracer Reference to tracer. + */ + Span(SegmentContextSharedPtr segment_context, SpanStore* span_store, Tracer& tracer) + : segment_context_(std::move(segment_context)), span_store_(span_store), tracer_(tracer) {} + + // Tracing::Span + void setOperation(absl::string_view operation) override; + void setTag(absl::string_view name, absl::string_view value) override; + void log(SystemTime timestamp, const std::string& event) override; + void finishSpan() override; + void injectContext(Http::RequestHeaderMap& request_headers) override; + Tracing::SpanPtr spawnChild(const Tracing::Config& config, const std::string& name, + SystemTime start_time) override; + void setSampled(bool sampled) override; + std::string getBaggage(absl::string_view key) override; + void setBaggage(absl::string_view key, absl::string_view value) override; + + /* + * Get pointer to corresponding SpanStore object. This method is mainly used in testing. Used to + * check the internal data of the span. + */ + SpanStore* spanStore() const { return span_store_; } + SegmentContext* segmentContext() const { return segment_context_.get(); } + +private: + void tryToReportSpan() { + // If the current span is the root span of the entire segment and its sampling flag is not + // false, the data for the entire segment is reported. Please ensure that the root span is the + // last span to end in the entire segment. + if (span_store_->sampled() && span_store_->spanId() == 0) { + tracer_.report(*segment_context_); + } + } + + SegmentContextSharedPtr segment_context_; + SpanStore* span_store_; + + Tracer& tracer_; +}; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/BUILD b/test/extensions/tracers/skywalking/BUILD new file mode 100644 index 000000000000..b18f8cfe91dc --- /dev/null +++ b/test/extensions/tracers/skywalking/BUILD @@ -0,0 +1,113 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + "//source/extensions/tracers/skywalking:config", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/server:tracer_factory_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/trace/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "skywalking_client_config_test", + srcs = ["skywalking_client_config_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + "//source/extensions/tracers/skywalking:skywalking_client_config_lib", + "//test/mocks:common_lib", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "skywalking_types_test", + srcs = ["skywalking_types_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + ":skywalking_test_helper", + "//source/extensions/tracers/skywalking:skywalking_types_lib", + "//test/mocks:common_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "trace_segment_reporter_test", + srcs = ["trace_segment_reporter_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + ":skywalking_test_helper", + "//source/extensions/tracers/skywalking:trace_segment_reporter_lib", + "//test/mocks:common_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/grpc:grpc_mocks", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/stats:stats_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "skywalking_test_helper", + srcs = ["skywalking_test_helper.h"], + extension_name = "envoy.tracers.skywalking", + deps = [ + "//source/common/common:base64_lib", + "//source/common/common:hex_lib", + "//source/extensions/tracers/skywalking:skywalking_types_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "tracer_test", + srcs = ["tracer_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + ":skywalking_test_helper", + "//source/extensions/tracers/skywalking:skywalking_tracer_lib", + "//test/mocks:common_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/grpc:grpc_mocks", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/stats:stats_mocks", + "//test/mocks/upstream:cluster_manager_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "skywalking_tracer_impl_test", + srcs = ["skywalking_tracer_impl_test.cc"], + extension_name = "envoy.tracers.skywalking", + deps = [ + ":skywalking_test_helper", + "//source/extensions/tracers/skywalking:skywalking_tracer_lib", + "//test/mocks:common_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/grpc:grpc_mocks", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/mocks/stats:stats_mocks", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/tracers/skywalking/config_test.cc b/test/extensions/tracers/skywalking/config_test.cc new file mode 100644 index 000000000000..19c966cf7cb7 --- /dev/null +++ b/test/extensions/tracers/skywalking/config_test.cc @@ -0,0 +1,88 @@ +#include "envoy/config/trace/v3/http_tracer.pb.h" +#include "envoy/config/trace/v3/skywalking.pb.h" +#include "envoy/config/trace/v3/skywalking.pb.validate.h" + +#include "extensions/tracers/skywalking/config.h" + +#include "test/mocks/server/tracer_factory.h" +#include "test/mocks/server/tracer_factory_context.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Eq; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +TEST(SkyWalkingTracerConfigTest, SkyWalkingHttpTracer) { + NiceMock context; + EXPECT_CALL(context.server_factory_context_.cluster_manager_, get(Eq("fake_cluster"))) + .WillRepeatedly( + Return(&context.server_factory_context_.cluster_manager_.thread_local_cluster_)); + ON_CALL(*context.server_factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_, + features()) + .WillByDefault(Return(Upstream::ClusterInfo::Features::HTTP2)); + + const std::string yaml_string = R"EOF( + http: + name: skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + )EOF"; + envoy::config::trace::v3::Tracing configuration; + TestUtility::loadFromYaml(yaml_string, configuration); + + SkyWalkingTracerFactory factory; + auto message = Config::Utility::translateToFactoryConfig( + configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); + Tracing::HttpTracerSharedPtr skywalking_tracer = factory.createHttpTracer(*message, context); + EXPECT_NE(nullptr, skywalking_tracer); +} + +TEST(SkyWalkingTracerConfigTest, SkyWalkingHttpTracerWithClientConfig) { + NiceMock context; + EXPECT_CALL(context.server_factory_context_.cluster_manager_, get(Eq("fake_cluster"))) + .WillRepeatedly( + Return(&context.server_factory_context_.cluster_manager_.thread_local_cluster_)); + ON_CALL(*context.server_factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_, + features()) + .WillByDefault(Return(Upstream::ClusterInfo::Features::HTTP2)); + + const std::string yaml_string = R"EOF( + http: + name: skywalking + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.SkyWalkingConfig + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + client_config: + backend_token: "A fake auth string for SkyWalking test" + service_name: "Test Service" + instance_name: "Test Instance" + max_cache_size: 2333 + )EOF"; + envoy::config::trace::v3::Tracing configuration; + TestUtility::loadFromYaml(yaml_string, configuration); + + SkyWalkingTracerFactory factory; + auto message = Config::Utility::translateToFactoryConfig( + configuration.http(), ProtobufMessage::getStrictValidationVisitor(), factory); + Tracing::HttpTracerSharedPtr skywalking_tracer = factory.createHttpTracer(*message, context); + EXPECT_NE(nullptr, skywalking_tracer); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/skywalking_client_config_test.cc b/test/extensions/tracers/skywalking/skywalking_client_config_test.cc new file mode 100644 index 000000000000..c0d4131a8e9d --- /dev/null +++ b/test/extensions/tracers/skywalking/skywalking_client_config_test.cc @@ -0,0 +1,100 @@ +#include "extensions/tracers/skywalking/skywalking_client_config.h" + +#include "test/mocks/common.h" +#include "test/mocks/server/tracer_factory_context.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +class SkyWalkingClientConfigTest : public testing::Test { +public: + void setupSkyWalkingClientConfig(const std::string& yaml_string) { + auto& local_info = context_.server_factory_context_.local_info_; + + ON_CALL(local_info, clusterName()).WillByDefault(ReturnRef(test_string)); + ON_CALL(local_info, nodeName()).WillByDefault(ReturnRef(test_string)); + + envoy::config::trace::v3::SkyWalkingConfig proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + + client_config_ = + std::make_unique(context_, proto_config.client_config()); + } + +protected: + NiceMock context_; + + std::string test_string = "ABCDEFGHIJKLMN"; + + SkyWalkingClientConfigPtr client_config_; +}; + +// Test whether the default value can be set correctly when there is no proto client config +// provided. +TEST_F(SkyWalkingClientConfigTest, NoProtoClientConfigTest) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + )EOF"; + + setupSkyWalkingClientConfig(yaml_string); + + EXPECT_EQ(client_config_->service(), test_string); + EXPECT_EQ(client_config_->serviceInstance(), test_string); + EXPECT_EQ(client_config_->maxCacheSize(), 1024); + EXPECT_EQ(client_config_->backendToken(), ""); +} + +// Test whether the client config can work correctly when the proto client config is provided. +TEST_F(SkyWalkingClientConfigTest, WithProtoClientConfigTest) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + client_config: + backend_token: "FAKE_FAKE_FAKE_FAKE_FAKE_FAKE" + service_name: "FAKE_FAKE_FAKE" + instance_name: "FAKE_FAKE_FAKE" + max_cache_size: 2333 + )EOF"; + + setupSkyWalkingClientConfig(yaml_string); + + EXPECT_EQ(client_config_->service(), "FAKE_FAKE_FAKE"); + EXPECT_EQ(client_config_->serviceInstance(), "FAKE_FAKE_FAKE"); + EXPECT_EQ(client_config_->maxCacheSize(), 2333); + EXPECT_EQ(client_config_->backendToken(), "FAKE_FAKE_FAKE_FAKE_FAKE_FAKE"); +} + +// Test whether the client config can get default value for service name and instance name. +TEST_F(SkyWalkingClientConfigTest, BothLocalInfoAndClientConfigEmptyTest) { + test_string = ""; + + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + )EOF"; + + setupSkyWalkingClientConfig(yaml_string); + + EXPECT_EQ(client_config_->service(), "EnvoyProxy"); + EXPECT_EQ(client_config_->serviceInstance(), "EnvoyProxy"); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/skywalking_test_helper.h b/test/extensions/tracers/skywalking/skywalking_test_helper.h new file mode 100644 index 000000000000..e158bb535da8 --- /dev/null +++ b/test/extensions/tracers/skywalking/skywalking_test_helper.h @@ -0,0 +1,77 @@ +#pragma once + +#include "common/common/base64.h" +#include "common/common/hex.h" + +#include "extensions/tracers/skywalking/skywalking_types.h" + +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { + +/* + * A simple helper class for auxiliary testing. Contains some simple static functions, such as + * encoding, generating random id, creating SpanContext, etc. + */ +class SkyWalkingTestHelper { +public: + static std::string generateId(Random::RandomGenerator& random) { + return absl::StrCat(Hex::uint64ToHex(random.random()), Hex::uint64ToHex(random.random())); + } + + static std::string base64Encode(absl::string_view input) { + return Base64::encode(input.data(), input.length()); + } + + static SegmentContextSharedPtr createSegmentContext(bool sampled, std::string seed, + std::string prev_seed, + Random::RandomGenerator& random) { + SpanContextPtr previous_span_context; + if (!prev_seed.empty()) { + std::string header_value = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", sampled ? 1 : 0, base64Encode(generateId(random)), + base64Encode(generateId(random)), random.random(), + base64Encode(prev_seed + "#SERVICE"), base64Encode(prev_seed + "#INSTANCE"), + base64Encode(prev_seed + "#ENDPOINT"), base64Encode(prev_seed + "#ADDRESS")); + + Http::TestRequestHeaderMapImpl request_headers{{"sw8", header_value}}; + previous_span_context = SpanContext::spanContextFromRequest(request_headers); + ASSERT(previous_span_context); + } + Tracing::Decision decision; + decision.traced = sampled; + decision.reason = Tracing::Reason::Sampling; + + auto segment_context = + std::make_shared(std::move(previous_span_context), decision, random); + + segment_context->setService(seed + "#SERVICE"); + segment_context->setServiceInstance(seed + "#INSTANCE"); + + return segment_context; + } + + static SpanStore* createSpanStore(SegmentContext* segment_context, SpanStore* parent_span_store, + std::string seed) { + SpanStore* span_store = segment_context->createSpanStore(parent_span_store); + + span_store->setAsError(false); + span_store->setOperation(seed + "#OPERATION"); + span_store->setPeerAddress("0.0.0.0"); + span_store->setStartTime(22222222); + span_store->setEndTime(33333333); + + span_store->addTag(seed + "#TAG_KEY_A", seed + "#TAG_VALUE_A"); + span_store->addTag(seed + "#TAG_KEY_B", seed + "#TAG_VALUE_B"); + span_store->addTag(seed + "#TAG_KEY_C", seed + "#TAG_VALUE_C"); + return span_store; + } +}; + +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc b/test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc new file mode 100644 index 000000000000..cb5075665dcc --- /dev/null +++ b/test/extensions/tracers/skywalking/skywalking_tracer_impl_test.cc @@ -0,0 +1,179 @@ +#include "extensions/tracers/skywalking/skywalking_tracer_impl.h" + +#include "test/extensions/tracers/skywalking/skywalking_test_helper.h" +#include "test/mocks/common.h" +#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/tracing/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +class SkyWalkingDriverTest : public testing::Test { +public: + void setupSkyWalkingDriver(const std::string& yaml_string) { + auto mock_client_factory = std::make_unique>(); + auto mock_client = std::make_unique>(); + mock_stream_ptr_ = std::make_unique>(); + + EXPECT_CALL(*mock_client, startRaw(_, _, _, _)).WillOnce(Return(mock_stream_ptr_.get())); + EXPECT_CALL(*mock_client_factory, create()).WillOnce(Return(ByMove(std::move(mock_client)))); + + auto& factory_context = context_.server_factory_context_; + + EXPECT_CALL(factory_context.cluster_manager_.async_client_manager_, + factoryForGrpcService(_, _, _)) + .WillOnce(Return(ByMove(std::move(mock_client_factory)))); + + EXPECT_CALL(factory_context.thread_local_.dispatcher_, createTimer_(_)) + .WillOnce(Invoke([](Event::TimerCb) { return new NiceMock(); })); + + ON_CALL(factory_context.local_info_, clusterName()).WillByDefault(ReturnRef(test_string)); + ON_CALL(factory_context.local_info_, nodeName()).WillByDefault(ReturnRef(test_string)); + + TestUtility::loadFromYaml(yaml_string, config_); + driver_ = std::make_unique(config_, context_); + } + +protected: + NiceMock context_; + NiceMock mock_tracing_config_; + Event::SimulatedTimeSystem time_system_; + + std::unique_ptr> mock_stream_ptr_{nullptr}; + + envoy::config::trace::v3::SkyWalkingConfig config_; + std::string test_string = "ABCDEFGHIJKLMN"; + + DriverPtr driver_; +}; + +TEST_F(SkyWalkingDriverTest, SkyWalkingDriverStartSpanTestWithClientConfig) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + client_config: + backend_token: "FAKE_FAKE_FAKE_FAKE_FAKE_FAKE" + service_name: "FAKE_FAKE_FAKE" + instance_name: "FAKE_FAKE_FAKE" + max_cache_size: 2333 + )EOF"; + setupSkyWalkingDriver(yaml_string); + + std::string trace_id = + SkyWalkingTestHelper::generateId(context_.server_factory_context_.api_.random_); + std::string segment_id = + SkyWalkingTestHelper::generateId(context_.server_factory_context_.api_.random_); + + // Create new span segment with previous span context. + std::string previous_header_value = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 0, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 233333, + SkyWalkingTestHelper::base64Encode("SERVICE"), + SkyWalkingTestHelper::base64Encode("INSTATNCE"), + SkyWalkingTestHelper::base64Encode("ENDPOINT"), + SkyWalkingTestHelper::base64Encode("ADDRESS")); + + Http::TestRequestHeaderMapImpl request_headers{{"sw8", previous_header_value}, + {":path", "/path"}, + {":method", "GET"}, + {":authority", "test.com"}}; + + ON_CALL(mock_tracing_config_, operationName()) + .WillByDefault(Return(Tracing::OperationName::Ingress)); + + Tracing::Decision decision; + decision.traced = true; + + Tracing::SpanPtr org_span = driver_->startSpan(mock_tracing_config_, request_headers, "TEST_OP", + time_system_.systemTime(), decision); + EXPECT_NE(nullptr, org_span.get()); + + Span* span = dynamic_cast(org_span.get()); + ASSERT(span); + + EXPECT_NE(nullptr, span->segmentContext()->previousSpanContext()); + + EXPECT_EQ("FAKE_FAKE_FAKE", span->segmentContext()->service()); + EXPECT_EQ("FAKE_FAKE_FAKE", span->segmentContext()->serviceInstance()); + + // Tracing decision will be overwrite by sampling flag in propagation headers. + EXPECT_EQ(0, span->segmentContext()->sampled()); + + // Since the sampling flag is false, no segment data is reported. + span->finishSpan(); + + auto& factory_context = context_.server_factory_context_; + EXPECT_EQ(0U, factory_context.scope_.counter("tracing.skywalking.segments_sent").value()); + + // Create new span segment with no previous span context. + Http::TestRequestHeaderMapImpl new_request_headers{ + {":path", "/path"}, {":method", "GET"}, {":authority", "test.com"}}; + + Tracing::SpanPtr org_new_span = driver_->startSpan(mock_tracing_config_, new_request_headers, "", + time_system_.systemTime(), decision); + + Span* new_span = dynamic_cast(org_new_span.get()); + ASSERT(new_span); + + EXPECT_EQ(nullptr, new_span->segmentContext()->previousSpanContext()); + + EXPECT_EQ(true, new_span->segmentContext()->sampled()); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)); + new_span->finishSpan(); + EXPECT_EQ(1U, factory_context.scope_.counter("tracing.skywalking.segments_sent").value()); + + // Create new span segment with error propagation header. + Http::TestRequestHeaderMapImpl error_request_headers{{":path", "/path"}, + {":method", "GET"}, + {":authority", "test.com"}, + {"sw8", "xxxxxx-error-propagation-header"}}; + Tracing::SpanPtr org_null_span = driver_->startSpan( + mock_tracing_config_, error_request_headers, "TEST_OP", time_system_.systemTime(), decision); + + EXPECT_EQ(nullptr, dynamic_cast(org_null_span.get())); + + auto& null_span = *org_null_span; + EXPECT_EQ(typeid(null_span).name(), typeid(Tracing::NullSpan).name()); +} + +TEST_F(SkyWalkingDriverTest, SkyWalkingDriverStartSpanTestNoClientConfig) { + const std::string yaml_string = R"EOF( + grpc_service: + envoy_grpc: + cluster_name: fake_cluster + )EOF"; + + setupSkyWalkingDriver(yaml_string); + + Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/path"}, {":method", "GET"}, {":authority", "test.com"}}; + + Tracing::SpanPtr org_span = driver_->startSpan(mock_tracing_config_, request_headers, "TEST_OP", + time_system_.systemTime(), Tracing::Decision()); + EXPECT_NE(nullptr, org_span.get()); + + Span* span = dynamic_cast(org_span.get()); + ASSERT(span); + + EXPECT_EQ(test_string, span->segmentContext()->service()); + EXPECT_EQ(test_string, span->segmentContext()->serviceInstance()); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/skywalking_types_test.cc b/test/extensions/tracers/skywalking/skywalking_types_test.cc new file mode 100644 index 000000000000..eb1d3147558f --- /dev/null +++ b/test/extensions/tracers/skywalking/skywalking_types_test.cc @@ -0,0 +1,343 @@ +#include "common/common/base64.h" +#include "common/common/hex.h" + +#include "extensions/tracers/skywalking/skywalking_types.h" + +#include "test/extensions/tracers/skywalking/skywalking_test_helper.h" +#include "test/mocks/common.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +// Some constant strings for testing. +constexpr absl::string_view TEST_SERVICE = "EnvoyIngressForTest"; +constexpr absl::string_view TEST_INSTANCE = "node-2.3.4.5~ingress"; +constexpr absl::string_view TEST_ADDRESS = "255.255.255.255"; +constexpr absl::string_view TEST_ENDPOINT = "/POST/path/for/test"; + +// Test whether SpanContext can correctly parse data from propagation headers and throw exceptions +// when errors occur. +TEST(SpanContextTest, SpanContextCommonTest) { + NiceMock mock_random_generator; + ON_CALL(mock_random_generator, random()).WillByDefault(Return(uint64_t(23333))); + + std::string trace_id = SkyWalkingTestHelper::generateId(mock_random_generator); + std::string segment_id = SkyWalkingTestHelper::generateId(mock_random_generator); + + // No propagation header then previous span context will be null. + Http::TestRequestHeaderMapImpl headers_no_propagation; + auto null_span_context = SpanContext::spanContextFromRequest(headers_no_propagation); + EXPECT_EQ(nullptr, null_span_context.get()); + + // Create properly formatted propagation headers and test whether the propagation headers can be + // parsed correctly. + std::string header_value_with_right_format = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 0, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 233333, + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS)); + + Http::TestRequestHeaderMapImpl headers_with_right_format{{"sw8", header_value_with_right_format}}; + + auto previous_span_context = SpanContext::spanContextFromRequest(headers_with_right_format); + EXPECT_NE(nullptr, previous_span_context.get()); + + // Verify that each field parsed from the propagation headers is correct. + EXPECT_EQ(previous_span_context->sampled_, 0); + EXPECT_EQ(previous_span_context->trace_id_, trace_id); + EXPECT_EQ(previous_span_context->trace_segment_id_, segment_id); + EXPECT_EQ(previous_span_context->span_id_, 233333); + EXPECT_EQ(previous_span_context->service_, TEST_SERVICE); + EXPECT_EQ(previous_span_context->service_instance_, TEST_INSTANCE); + EXPECT_EQ(previous_span_context->endpoint_, TEST_ENDPOINT); + EXPECT_EQ(previous_span_context->target_address_, TEST_ADDRESS); + + std::string header_value_with_sampled = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 1, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 233333, + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS)); + + Http::TestRequestHeaderMapImpl headers_with_sampled{{"sw8", header_value_with_sampled}}; + + auto previous_span_context_with_sampled = + SpanContext::spanContextFromRequest(headers_with_sampled); + EXPECT_EQ(previous_span_context_with_sampled->sampled_, 1); + + // Test whether an exception can be correctly thrown when some fields are missing. + std::string header_value_lost_some_parts = + fmt::format("{}-{}-{}-{}-{}-{}", 0, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 3, + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE)); + + Http::TestRequestHeaderMapImpl headers_lost_some_parts{{"sw8", header_value_lost_some_parts}}; + + EXPECT_THROW_WITH_MESSAGE( + SpanContext::spanContextFromRequest(headers_lost_some_parts), EnvoyException, + fmt::format("Invalid propagation header for SkyWalking: {}", header_value_lost_some_parts)); + + // Test whether an exception can be correctly thrown when the sampling flag is wrong. + Http::TestRequestHeaderMapImpl headers_with_error_sampled{ + {"sw8", + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 3, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 3, + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS))}}; + + EXPECT_THROW_WITH_MESSAGE(SpanContext::spanContextFromRequest(headers_with_error_sampled), + EnvoyException, + "Invalid propagation header for SkyWalking: sampling flag can only be " + "'0' or '1' but '3' was provided"); + + // Test whether an exception can be correctly thrown when the span id format is wrong. + Http::TestRequestHeaderMapImpl headers_with_error_span_id{ + {"sw8", + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 1, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), "abc", + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS))}}; + + EXPECT_THROW_WITH_MESSAGE( + SpanContext::spanContextFromRequest(headers_with_error_span_id), EnvoyException, + "Invalid propagation header for SkyWalking: connot convert 'abc' to valid span id"); + + // Test whether an exception can be correctly thrown when a field is empty. + std::string header_value_with_empty_field = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 1, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 4, "", + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS)); + Http::TestRequestHeaderMapImpl headers_with_empty_field{{"sw8", header_value_with_empty_field}}; + + EXPECT_THROW_WITH_MESSAGE( + SpanContext::spanContextFromRequest(headers_with_empty_field), EnvoyException, + fmt::format("Invalid propagation header for SkyWalking: {}", header_value_with_empty_field)); + + // Test whether an exception can be correctly thrown when a string is not properly encoded. + Http::TestRequestHeaderMapImpl headers_with_error_field{ + {"sw8", + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 1, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 4, "hhhhhhh", + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS))}}; + + EXPECT_THROW_WITH_MESSAGE(SpanContext::spanContextFromRequest(headers_with_error_field), + EnvoyException, + "Invalid propagation header for SkyWalking: parse error"); +} + +// Test whether the SegmentContext works normally when Envoy is the root node (Propagation headers +// does not exist). +TEST(SegmentContextTest, SegmentContextTestWithEmptyPreviousSpanContext) { + NiceMock mock_random_generator; + + ON_CALL(mock_random_generator, random()).WillByDefault(Return(233333)); + + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "NEW", "", mock_random_generator); + + // When previous span context is null, the value of the sampling flag depends on the tracing + // decision + EXPECT_EQ(segment_context->sampled(), 1); + // The SegmentContext will use random generator to create new trace id and new trace segment id. + EXPECT_EQ(segment_context->traceId(), SkyWalkingTestHelper::generateId(mock_random_generator)); + EXPECT_EQ(segment_context->traceSegmentId(), + SkyWalkingTestHelper::generateId(mock_random_generator)); + + EXPECT_EQ(segment_context->previousSpanContext(), nullptr); + + // Test whether the value of the fields can be set correctly and the value of the fields can be + // obtained correctly. + EXPECT_EQ(segment_context->service(), "NEW#SERVICE"); + segment_context->setService(std::string(TEST_SERVICE)); + EXPECT_EQ(segment_context->service(), TEST_SERVICE); + + EXPECT_EQ(segment_context->serviceInstance(), "NEW#INSTANCE"); + segment_context->setServiceInstance(std::string(TEST_INSTANCE)); + EXPECT_EQ(segment_context->serviceInstance(), TEST_INSTANCE); + + EXPECT_EQ(segment_context->rootSpanStore(), nullptr); + + // Test whether SegmentContext can correctly create SpanStore object with null parent SpanStore. + SpanStore* root_span = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), nullptr, "PARENT"); + EXPECT_NE(nullptr, root_span); + + // The span id of the first SpanStore in each SegmentContext is 0. Its parent span id is -1. + EXPECT_EQ(root_span->spanId(), 0); + EXPECT_EQ(root_span->parentSpanId(), -1); + + // Root span of current segment should be Entry Span. + EXPECT_EQ(root_span->isEntrySpan(), true); + + // Verify that the SpanStore object is correctly stored in the SegmentContext. + EXPECT_EQ(segment_context->spanList().size(), 1); + EXPECT_EQ(segment_context->spanList()[0].get(), root_span); + + // Test whether SegmentContext can correctly create SpanStore object with a parent SpanStore. + SpanStore* child_span = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), root_span, "CHILD"); + + EXPECT_NE(nullptr, child_span); + + EXPECT_EQ(child_span->spanId(), 1); + EXPECT_EQ(child_span->parentSpanId(), 0); + + // All child spans of current segment should be Exit Span. + EXPECT_EQ(child_span->isEntrySpan(), false); + + EXPECT_EQ(segment_context->spanList().size(), 2); + EXPECT_EQ(segment_context->spanList()[1].get(), child_span); +} + +// Test whether the SegmentContext can work normally when a previous span context exists. +TEST(SegmentContextTest, SegmentContextTestWithPreviousSpanContext) { + NiceMock mock_random_generator; + + ON_CALL(mock_random_generator, random()).WillByDefault(Return(23333)); + + std::string trace_id = SkyWalkingTestHelper::generateId(mock_random_generator); + std::string segment_id = SkyWalkingTestHelper::generateId(mock_random_generator); + + std::string header_value_with_right_format = + fmt::format("{}-{}-{}-{}-{}-{}-{}-{}", 0, SkyWalkingTestHelper::base64Encode(trace_id), + SkyWalkingTestHelper::base64Encode(segment_id), 233333, + SkyWalkingTestHelper::base64Encode(TEST_SERVICE), + SkyWalkingTestHelper::base64Encode(TEST_INSTANCE), + SkyWalkingTestHelper::base64Encode(TEST_ENDPOINT), + SkyWalkingTestHelper::base64Encode(TEST_ADDRESS)); + + Http::TestRequestHeaderMapImpl headers_with_right_format{{"sw8", header_value_with_right_format}}; + + auto previous_span_context = SpanContext::spanContextFromRequest(headers_with_right_format); + SpanContext* previous_span_context_bk = previous_span_context.get(); + + Tracing::Decision decision; + decision.traced = true; + + EXPECT_CALL(mock_random_generator, random()).WillRepeatedly(Return(666666)); + + SegmentContext segment_context(std::move(previous_span_context), decision, mock_random_generator); + + // When a previous span context exists, the sampling flag of the SegmentContext depends on + // previous span context rather than tracing decision. + EXPECT_EQ(segment_context.sampled(), 0); + + // When previous span context exists, the trace id of SegmentContext remains the same as that of + // previous span context. + EXPECT_EQ(segment_context.traceId(), trace_id); + // SegmentContext will always create a new trace segment id. + EXPECT_NE(segment_context.traceSegmentId(), segment_id); + + EXPECT_EQ(segment_context.previousSpanContext(), previous_span_context_bk); +} + +// Test whether SpanStore can work properly. +TEST(SpanStoreTest, SpanStoreCommonTest) { + NiceMock mock_random_generator; + + Event::SimulatedTimeSystem time_system; + Envoy::SystemTime now = time_system.systemTime(); + + ON_CALL(mock_random_generator, random()).WillByDefault(Return(23333)); + + // Create segment context and first span store. + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "CURR", "PREV", mock_random_generator); + SpanStore* root_store = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), nullptr, "ROOT"); + EXPECT_NE(nullptr, root_store); + EXPECT_EQ(3, root_store->tags().size()); + + root_store->addLog(now, "TestLogStringAndNeverBeStored"); + EXPECT_EQ(0, root_store->logs().size()); + + // The span id of the first SpanStore in each SegmentContext is 0. Its parent span id is -1. + EXPECT_EQ(0, root_store->spanId()); + EXPECT_EQ(-1, root_store->parentSpanId()); + + root_store->setSpanId(123); + EXPECT_EQ(123, root_store->spanId()); + root_store->setParentSpanId(234); + EXPECT_EQ(234, root_store->parentSpanId()); + + EXPECT_EQ(1, root_store->sampled()); + root_store->setSampled(0); + EXPECT_EQ(0, root_store->sampled()); + + // Test whether the value of the fields can be set correctly and the value of the fields can be + // obtained correctly. + EXPECT_EQ(true, root_store->isEntrySpan()); + root_store->setAsEntrySpan(false); + EXPECT_EQ(false, root_store->isEntrySpan()); + + EXPECT_EQ(false, root_store->isError()); + root_store->setAsError(true); + EXPECT_EQ(true, root_store->isError()); + + EXPECT_EQ("ROOT#OPERATION", root_store->operation()); + root_store->setOperation(""); + EXPECT_EQ("", root_store->operation()); + root_store->setOperation("oooooop"); + EXPECT_EQ("oooooop", root_store->operation()); + + EXPECT_EQ("0.0.0.0", root_store->peerAddress()); + root_store->setPeerAddress(std::string(TEST_ADDRESS)); + EXPECT_EQ(TEST_ADDRESS, root_store->peerAddress()); + + EXPECT_EQ(22222222, root_store->startTime()); + root_store->setStartTime(23333); + EXPECT_EQ(23333, root_store->startTime()); + + EXPECT_EQ(33333333, root_store->endTime()); + root_store->setEndTime(25555); + EXPECT_EQ(25555, root_store->endTime()); + + SpanStore* child_store = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), root_store, "CHILD"); + + // Test whether SpanStore can correctly inject propagation headers to request headers. + Http::TestRequestHeaderMapImpl request_headers_no_upstream{{":authority", "test.com"}}; + // Only child span (Exit Span) can inject context header to request headers. + child_store->injectContext(request_headers_no_upstream); + std::string expected_header_value = fmt::format( + "{}-{}-{}-{}-{}-{}-{}-{}", child_store->sampled(), + SkyWalkingTestHelper::base64Encode(SkyWalkingTestHelper::generateId(mock_random_generator)), + SkyWalkingTestHelper::base64Encode(SkyWalkingTestHelper::generateId(mock_random_generator)), + child_store->spanId(), SkyWalkingTestHelper::base64Encode("CURR#SERVICE"), + SkyWalkingTestHelper::base64Encode("CURR#INSTANCE"), + SkyWalkingTestHelper::base64Encode("oooooop"), + SkyWalkingTestHelper::base64Encode("test.com")); + + EXPECT_EQ(child_store->peerAddress(), "test.com"); + + EXPECT_EQ(request_headers_no_upstream.get_("sw8"), expected_header_value); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc b/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc new file mode 100644 index 000000000000..fa3f59effdb5 --- /dev/null +++ b/test/extensions/tracers/skywalking/trace_segment_reporter_test.cc @@ -0,0 +1,246 @@ +#include "extensions/tracers/skywalking/trace_segment_reporter.h" + +#include "test/extensions/tracers/skywalking/skywalking_test_helper.h" +#include "test/mocks/common.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/grpc/mocks.h" +#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +class TraceSegmentReporterTest : public testing::Test { +public: + void setupTraceSegmentReporter(const std::string& yaml_string) { + EXPECT_CALL(mock_dispatcher_, createTimer_(_)).WillOnce(Invoke([this](Event::TimerCb timer_cb) { + timer_cb_ = timer_cb; + return timer_; + })); + timer_ = new NiceMock(); + + auto mock_client_factory = std::make_unique>(); + + auto mock_client = std::make_unique>(); + mock_client_ptr_ = mock_client.get(); + + mock_stream_ptr_ = std::make_unique>(); + + EXPECT_CALL(*mock_client_factory, create()).WillOnce(Return(ByMove(std::move(mock_client)))); + EXPECT_CALL(*mock_client_ptr_, startRaw(_, _, _, _)).WillOnce(Return(mock_stream_ptr_.get())); + + auto& local_info = context_.server_factory_context_.local_info_; + + ON_CALL(local_info, clusterName()).WillByDefault(ReturnRef(test_string)); + ON_CALL(local_info, nodeName()).WillByDefault(ReturnRef(test_string)); + + envoy::config::trace::v3::ClientConfig proto_client_config; + TestUtility::loadFromYaml(yaml_string, proto_client_config); + client_config_ = std::make_unique(context_, proto_client_config); + + reporter_ = std::make_unique(std::move(mock_client_factory), + mock_dispatcher_, mock_random_generator_, + tracing_stats_, *client_config_); + } + +protected: + NiceMock context_; + + NiceMock& mock_dispatcher_ = context_.server_factory_context_.dispatcher_; + NiceMock& mock_random_generator_ = + context_.server_factory_context_.api_.random_; + Event::GlobalTimeSystem& mock_time_source_ = context_.server_factory_context_.time_system_; + + NiceMock& mock_scope_ = context_.server_factory_context_.scope_; + + NiceMock* mock_client_ptr_{nullptr}; + + std::unique_ptr> mock_stream_ptr_{nullptr}; + + NiceMock* timer_; + Event::TimerCb timer_cb_; + + std::string test_string = "ABCDEFGHIJKLMN"; + + SkyWalkingClientConfigPtr client_config_; + + SkyWalkingTracerStats tracing_stats_{ + SKYWALKING_TRACER_STATS(POOL_COUNTER_PREFIX(mock_scope_, "tracing.skywalking."))}; + TraceSegmentReporterPtr reporter_; +}; + +// Test whether the reporter can correctly add metadata according to the configuration. +TEST_F(TraceSegmentReporterTest, TraceSegmentReporterInitialMetadata) { + const std::string yaml_string = R"EOF( + backend_token: "FakeStringForAuthenticaion" + )EOF"; + + setupTraceSegmentReporter(yaml_string); + Http::TestRequestHeaderMapImpl metadata; + reporter_->onCreateInitialMetadata(metadata); + + EXPECT_EQ("FakeStringForAuthenticaion", metadata.get_("authentication")); +} + +TEST_F(TraceSegmentReporterTest, TraceSegmentReporterNoMetadata) { + setupTraceSegmentReporter("{}"); + Http::TestRequestHeaderMapImpl metadata; + reporter_->onCreateInitialMetadata(metadata); + + EXPECT_EQ("", metadata.get_("authentication")); +} + +TEST_F(TraceSegmentReporterTest, TraceSegmentReporterReportTraceSegment) { + setupTraceSegmentReporter("{}"); + ON_CALL(mock_random_generator_, random()).WillByDefault(Return(23333)); + + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "NEW", "PRE", mock_random_generator_); + SpanStore* parent_store = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), nullptr, "PARENT"); + // Parent span store has peer address. + parent_store->setPeerAddress("0.0.0.0"); + + SpanStore* first_child_sptore = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), parent_store, "CHILD"); + // Skip reporting the first child span. + first_child_sptore->setSampled(0); + + // Create second child span. + SkyWalkingTestHelper::createSpanStore(segment_context.get(), parent_store, "CHILD"); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)); + + reporter_->report(*segment_context); + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); + + // Create a segment context with no previous span context. + SegmentContextSharedPtr second_segment_context = SkyWalkingTestHelper::createSegmentContext( + true, "SECOND_SEGMENT", "", mock_random_generator_); + SkyWalkingTestHelper::createSpanStore(second_segment_context.get(), nullptr, "PARENT"); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)); + reporter_->report(*second_segment_context); + + EXPECT_EQ(2U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); +} + +TEST_F(TraceSegmentReporterTest, TraceSegmentReporterReportWithDefaultCache) { + setupTraceSegmentReporter("{}"); + ON_CALL(mock_random_generator_, random()).WillByDefault(Return(23333)); + + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "NEW", "PRE", mock_random_generator_); + SpanStore* parent_store = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), nullptr, "PARENT"); + SkyWalkingTestHelper::createSpanStore(segment_context.get(), parent_store, "CHILD"); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)).Times(1025); + + reporter_->report(*segment_context); + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); + + // Simulates a disconnected connection. + EXPECT_CALL(*timer_, enableTimer(_, _)); + reporter_->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Unknown, ""); + + // Try to report 10 segments. Due to the disconnection, the cache size is only 3. So 7 of the + // segments will be discarded. + for (int i = 0; i < 2048; i++) { + reporter_->report(*segment_context); + } + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(1024U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); + + // Simulate the situation where the connection is re-established. The remaining segments in the + // cache will be reported. + EXPECT_CALL(*mock_client_ptr_, startRaw(_, _, _, _)).WillOnce(Return(mock_stream_ptr_.get())); + timer_cb_(); + + EXPECT_EQ(1025U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(1024U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(1024U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); +} + +TEST_F(TraceSegmentReporterTest, TraceSegmentReporterReportWithCacheConfig) { + const std::string yaml_string = R"EOF( + max_cache_size: 3 + )EOF"; + + setupTraceSegmentReporter(yaml_string); + + ON_CALL(mock_random_generator_, random()).WillByDefault(Return(23333)); + + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "NEW", "PRE", mock_random_generator_); + SpanStore* parent_store = + SkyWalkingTestHelper::createSpanStore(segment_context.get(), nullptr, "PARENT"); + SkyWalkingTestHelper::createSpanStore(segment_context.get(), parent_store, "CHILD"); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)).Times(4); + + reporter_->report(*segment_context); + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); + + // Simulates a disconnected connection. + EXPECT_CALL(*timer_, enableTimer(_, _)); + reporter_->onRemoteClose(Grpc::Status::WellKnownGrpcStatus::Unknown, ""); + + // Try to report 10 segments. Due to the disconnection, the cache size is only 3. So 7 of the + // segments will be discarded. + for (int i = 0; i < 10; i++) { + reporter_->report(*segment_context); + } + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(7U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); + + // Simulate the situation where the connection is re-established. The remaining segments in the + // cache will be reported. + EXPECT_CALL(*mock_client_ptr_, startRaw(_, _, _, _)).WillOnce(Return(mock_stream_ptr_.get())); + timer_cb_(); + + EXPECT_EQ(4U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(7U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(3U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/skywalking/tracer_test.cc b/test/extensions/tracers/skywalking/tracer_test.cc new file mode 100644 index 000000000000..c0d9356f1d50 --- /dev/null +++ b/test/extensions/tracers/skywalking/tracer_test.cc @@ -0,0 +1,193 @@ +#include "extensions/tracers/skywalking/skywalking_client_config.h" +#include "extensions/tracers/skywalking/tracer.h" + +#include "test/extensions/tracers/skywalking/skywalking_test_helper.h" +#include "test/mocks/common.h" +#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/stats/mocks.h" +#include "test/mocks/tracing/mocks.h" +#include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace SkyWalking { +namespace { + +class TracerTest : public testing::Test { +public: + void setupTracer(const std::string& yaml_string) { + EXPECT_CALL(mock_dispatcher_, createTimer_(_)).WillOnce(Invoke([](Event::TimerCb) { + return new NiceMock(); + })); + + auto mock_client_factory = std::make_unique>(); + + auto mock_client = std::make_unique>(); + + mock_stream_ptr_ = std::make_unique>(); + + EXPECT_CALL(*mock_client, startRaw(_, _, _, _)).WillOnce(Return(mock_stream_ptr_.get())); + EXPECT_CALL(*mock_client_factory, create()).WillOnce(Return(ByMove(std::move(mock_client)))); + + auto& local_info = context_.server_factory_context_.local_info_; + + ON_CALL(local_info, clusterName()).WillByDefault(ReturnRef(test_string)); + ON_CALL(local_info, nodeName()).WillByDefault(ReturnRef(test_string)); + + envoy::config::trace::v3::ClientConfig proto_client_config; + TestUtility::loadFromYaml(yaml_string, proto_client_config); + client_config_ = std::make_unique(context_, proto_client_config); + + tracer_ = std::make_unique(mock_time_source_); + tracer_->setReporter(std::make_unique( + std::move(mock_client_factory), mock_dispatcher_, mock_random_generator_, tracing_stats_, + *client_config_)); + } + +protected: + NiceMock mock_tracing_config_; + + NiceMock context_; + + NiceMock& mock_dispatcher_ = context_.server_factory_context_.dispatcher_; + NiceMock& mock_random_generator_ = + context_.server_factory_context_.api_.random_; + Event::GlobalTimeSystem& mock_time_source_ = context_.server_factory_context_.time_system_; + + NiceMock& mock_scope_ = context_.server_factory_context_.scope_; + + std::unique_ptr> mock_stream_ptr_{nullptr}; + + std::string test_string = "ABCDEFGHIJKLMN"; + + SkyWalkingClientConfigPtr client_config_; + + SkyWalkingTracerStats tracing_stats_{ + SKYWALKING_TRACER_STATS(POOL_COUNTER_PREFIX(mock_scope_, "tracing.skywalking."))}; + + TracerPtr tracer_; +}; + +// Test that the basic functionality of Tracer is working, including creating Span, using Span to +// create new child Spans. +TEST_F(TracerTest, TracerTestCreateNewSpanWithNoPropagationHeaders) { + setupTracer("{}"); + EXPECT_CALL(mock_random_generator_, random()).WillRepeatedly(Return(666666)); + + // Create a new SegmentContext. + SegmentContextSharedPtr segment_context = + SkyWalkingTestHelper::createSegmentContext(true, "CURR", "", mock_random_generator_); + + Envoy::Tracing::SpanPtr org_span = tracer_->startSpan( + mock_tracing_config_, mock_time_source_.systemTime(), "TEST_OP", segment_context, nullptr); + Span* span = dynamic_cast(org_span.get()); + + EXPECT_EQ(true, span->spanStore()->isEntrySpan()); + + EXPECT_EQ("", span->getBaggage("FakeStringAndNothingToDo")); + span->setBaggage("FakeStringAndNothingToDo", "FakeStringAndNothingToDo"); + + // Test whether the basic functions of Span are normal. + + span->setSampled(false); + EXPECT_EQ(false, span->spanStore()->sampled()); + + // The initial operation name is consistent with the 'operation' parameter in the 'startSpan' + // method call. + EXPECT_EQ("TEST_OP", span->spanStore()->operation()); + span->setOperation("op"); + EXPECT_EQ("op", span->spanStore()->operation()); + + // Test whether the tag can be set correctly. + span->setTag("TestTagKeyA", "TestTagValueA"); + span->setTag("TestTagKeyB", "TestTagValueB"); + EXPECT_EQ("TestTagValueA", span->spanStore()->tags().at(0).second); + EXPECT_EQ("TestTagValueB", span->spanStore()->tags().at(1).second); + + // When setting the status code tag, the corresponding tag name will be rewritten as + // 'status_code'. + span->setTag(Tracing::Tags::get().HttpStatusCode, "200"); + EXPECT_EQ("status_code", span->spanStore()->tags().at(2).first); + EXPECT_EQ("200", span->spanStore()->tags().at(2).second); + + // When setting the error tag, the SpanStore object will also mark itself as an error. + span->setTag(Tracing::Tags::get().Error, Tracing::Tags::get().True); + EXPECT_EQ(Tracing::Tags::get().Error, span->spanStore()->tags().at(3).first); + EXPECT_EQ(Tracing::Tags::get().True, span->spanStore()->tags().at(3).second); + EXPECT_EQ(true, span->spanStore()->isError()); + + // When setting http url tag, the corresponding tag name will be rewritten as 'url'. + span->setTag(Tracing::Tags::get().HttpUrl, "http://test.com/test/path"); + EXPECT_EQ("url", span->spanStore()->tags().at(4).first); + + Envoy::Tracing::SpanPtr org_first_child_span = + span->spawnChild(mock_tracing_config_, "TestChild", mock_time_source_.systemTime()); + Span* first_child_span = dynamic_cast(org_first_child_span.get()); + + EXPECT_EQ(false, first_child_span->spanStore()->isEntrySpan()); + + EXPECT_EQ(0, first_child_span->spanStore()->sampled()); + EXPECT_EQ(1, first_child_span->spanStore()->spanId()); + EXPECT_EQ(0, first_child_span->spanStore()->parentSpanId()); + + EXPECT_EQ("TestChild", first_child_span->spanStore()->operation()); + + Http::TestRequestHeaderMapImpl first_child_headers{{":authority", "test.com"}}; + std::string expected_header_value = fmt::format( + "{}-{}-{}-{}-{}-{}-{}-{}", 0, + SkyWalkingTestHelper::base64Encode(SkyWalkingTestHelper::generateId(mock_random_generator_)), + SkyWalkingTestHelper::base64Encode(SkyWalkingTestHelper::generateId(mock_random_generator_)), + 1, SkyWalkingTestHelper::base64Encode("CURR#SERVICE"), + SkyWalkingTestHelper::base64Encode("CURR#INSTANCE"), SkyWalkingTestHelper::base64Encode("op"), + SkyWalkingTestHelper::base64Encode("test.com")); + + first_child_span->injectContext(first_child_headers); + EXPECT_EQ(expected_header_value, first_child_headers.get_("sw8")); + + // Reset sampling flag to true. + span->setSampled(true); + Envoy::Tracing::SpanPtr org_second_child_span = + span->spawnChild(mock_tracing_config_, "TestChild", mock_time_source_.systemTime()); + Span* second_child_span = dynamic_cast(org_second_child_span.get()); + + EXPECT_EQ(1, second_child_span->spanStore()->sampled()); + EXPECT_EQ(2, second_child_span->spanStore()->spanId()); + EXPECT_EQ(0, second_child_span->spanStore()->parentSpanId()); + + EXPECT_CALL(*mock_stream_ptr_, sendMessageRaw_(_, _)).Times(1); + + // When the child span ends, the data is not reported immediately, but the end time is set. + first_child_span->finishSpan(); + second_child_span->finishSpan(); + EXPECT_NE(0, first_child_span->spanStore()->endTime()); + EXPECT_NE(0, second_child_span->spanStore()->endTime()); + + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); + + // When the first span in the current segment ends, the entire segment is reported. + span->finishSpan(); + + EXPECT_EQ(1U, mock_scope_.counter("tracing.skywalking.segments_sent").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_dropped").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.cache_flushed").value()); + EXPECT_EQ(0U, mock_scope_.counter("tracing.skywalking.segments_flushed").value()); +} + +} // namespace +} // namespace SkyWalking +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 1a0529d16319..c8cc46423a2f 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -33,6 +33,7 @@ HEXDIG HEXDIGIT LTT OWS +SkyWalking TIDs ceil CHACHA From c4e9c4cd85b5e0566441b518f998b2b732108f5d Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 4 Nov 2020 04:39:04 -0500 Subject: [PATCH 026/117] proxy_proto: fixing hashing bug (#13768) Fix a bug where the transport socket options for the first downstream got reused for subsequent upstream connections. Risk Level: low Testing: new integration test Docs Changes: n/a Release Notes: Platform Specific Features: Fixes #13659 Signed-off-by: Alyssa Wilk --- docs/root/version_history/current.rst | 1 + include/envoy/network/proxy_protocol.h | 6 +++- include/envoy/network/transport_socket.h | 16 +++++++--- source/common/network/raw_buffer_socket.h | 1 + .../network/transport_socket_options_impl.cc | 22 ++++++++++--- .../network/transport_socket_options_impl.h | 6 ++-- .../common/upstream/cluster_manager_impl.cc | 4 +-- .../quiche/quic_transport_socket_factory.h | 1 + .../transport_sockets/alts/tsi_socket.h | 1 + .../proxy_protocol/proxy_protocol.h | 3 +- .../extensions/transport_sockets/tap/tap.cc | 4 +++ source/extensions/transport_sockets/tap/tap.h | 1 + .../transport_sockets/tls/ssl_socket.h | 2 ++ test/common/network/BUILD | 9 ++++++ test/common/network/raw_buffer_socket_test.cc | 16 ++++++++++ .../upstream/transport_socket_matcher_test.cc | 2 ++ .../transport_sockets/alts/tsi_socket_test.cc | 6 +++- .../proxy_protocol_integration_test.cc | 32 ++++++++++++++++++- .../tap/tap_config_impl_test.cc | 16 +++++++++- test/integration/base_integration_test.cc | 5 +-- test/integration/base_integration_test.h | 4 ++- test/integration/fake_upstream.h | 6 ++++ test/integration/integration_tcp_client.cc | 5 +-- test/integration/integration_tcp_client.h | 3 +- test/mocks/network/transport_socket.h | 1 + 25 files changed, 149 insertions(+), 24 deletions(-) create mode 100644 test/common/network/raw_buffer_socket_test.cc diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 041ea6adaeac..2310118b6a03 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -27,6 +27,7 @@ Bug Fixes * dns: fix a bug where custom resolvers provided in configuration were not preserved after network issues. * http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. * http: sending CONNECT_ERROR for HTTP/2 where appropriate during CONNECT requests. +* proxy_proto: fixed a bug where the wrong downstream address got sent to upstream connections. * tls: fix read resumption after triggering buffer high-watermark and all remaining request/response bytes are stored in the SSL connection's internal buffers. Removed Config or Runtime diff --git a/include/envoy/network/proxy_protocol.h b/include/envoy/network/proxy_protocol.h index 52c111859b11..12f7323c4ccc 100644 --- a/include/envoy/network/proxy_protocol.h +++ b/include/envoy/network/proxy_protocol.h @@ -8,7 +8,11 @@ namespace Network { struct ProxyProtocolData { const Network::Address::InstanceConstSharedPtr src_addr_; const Network::Address::InstanceConstSharedPtr dst_addr_; + std::string asStringForHash() const { + return std::string(src_addr_ ? src_addr_->asString() : "null") + + (dst_addr_ ? dst_addr_->asString() : "null"); + } }; } // namespace Network -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/include/envoy/network/transport_socket.h b/include/envoy/network/transport_socket.h index fe054ce2f16d..95d07ac2e182 100644 --- a/include/envoy/network/transport_socket.h +++ b/include/envoy/network/transport_socket.h @@ -14,6 +14,7 @@ namespace Envoy { namespace Network { +class TransportSocketFactory; class Connection; enum class ConnectionEvent; @@ -198,11 +199,13 @@ class TransportSocketOptions { virtual absl::optional proxyProtocolOptions() const PURE; /** - * @param vector of bytes to which the option should append hash key data that will be used - * to separate connections based on the option. Any data already in the key vector must - * not be modified. + * @param key supplies a vector of bytes to which the option should append hash key data that will + * be used to separate connections based on the option. Any data already in the key vector + * must not be modified. + * @param factory supplies the factor which will be used for creating the transport socket. */ - virtual void hashKey(std::vector& key) const PURE; + virtual void hashKey(std::vector& key, + const Network::TransportSocketFactory& factory) const PURE; }; // TODO(mattklein123): Rename to TransportSocketOptionsConstSharedPtr in a dedicated follow up. @@ -226,6 +229,11 @@ class TransportSocketFactory { */ virtual TransportSocketPtr createTransportSocket(TransportSocketOptionsSharedPtr options) const PURE; + + /** + * @return bool whether the transport socket will use proxy protocol options. + */ + virtual bool usesProxyProtocolOptions() const PURE; }; using TransportSocketFactoryPtr = std::unique_ptr; diff --git a/source/common/network/raw_buffer_socket.h b/source/common/network/raw_buffer_socket.h index fe87bbeda605..9a17fe35516b 100644 --- a/source/common/network/raw_buffer_socket.h +++ b/source/common/network/raw_buffer_socket.h @@ -32,6 +32,7 @@ class RawBufferSocketFactory : public TransportSocketFactory { // Network::TransportSocketFactory TransportSocketPtr createTransportSocket(TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; + bool usesProxyProtocolOptions() const override { return false; } }; } // namespace Network diff --git a/source/common/network/transport_socket_options_impl.cc b/source/common/network/transport_socket_options_impl.cc index 62358ce48710..66b139bba6e1 100644 --- a/source/common/network/transport_socket_options_impl.cc +++ b/source/common/network/transport_socket_options_impl.cc @@ -16,7 +16,8 @@ namespace Envoy { namespace Network { namespace { -void commonHashKey(const TransportSocketOptions& options, std::vector& key) { +void commonHashKey(const TransportSocketOptions& options, std::vector& key, + const Network::TransportSocketFactory& factory) { const auto& server_name_overide = options.serverNameOverride(); if (server_name_overide.has_value()) { pushScalarToByteVector(StringUtil::CaseInsensitiveHash()(server_name_overide.value()), key); @@ -35,19 +36,30 @@ void commonHashKey(const TransportSocketOptions& options, std::vector& key) const { - commonHashKey(*this, key); +void AlpnDecoratingTransportSocketOptions::hashKey( + std::vector& key, const Network::TransportSocketFactory& factory) const { + commonHashKey(*this, key, factory); } -void TransportSocketOptionsImpl::hashKey(std::vector& key) const { - commonHashKey(*this, key); +void TransportSocketOptionsImpl::hashKey(std::vector& key, + const Network::TransportSocketFactory& factory) const { + commonHashKey(*this, key, factory); } TransportSocketOptionsSharedPtr diff --git a/source/common/network/transport_socket_options_impl.h b/source/common/network/transport_socket_options_impl.h index 3611f117c8e5..4e08da3f292d 100644 --- a/source/common/network/transport_socket_options_impl.h +++ b/source/common/network/transport_socket_options_impl.h @@ -29,7 +29,8 @@ class AlpnDecoratingTransportSocketOptions : public TransportSocketOptions { absl::optional proxyProtocolOptions() const override { return inner_options_->proxyProtocolOptions(); } - void hashKey(std::vector& key) const override; + void hashKey(std::vector& key, + const Network::TransportSocketFactory& factory) const override; private: const absl::optional alpn_fallback_; @@ -67,7 +68,8 @@ class TransportSocketOptionsImpl : public TransportSocketOptions { absl::optional proxyProtocolOptions() const override { return proxy_protocol_options_; } - void hashKey(std::vector& key) const override; + void hashKey(std::vector& key, + const Network::TransportSocketFactory& factory) const override; private: const absl::optional override_server_name_; diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index d467ab417034..ce6ecf509865 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -1361,7 +1361,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::connPool( bool have_transport_socket_options = false; if (context && context->upstreamTransportSocketOptions()) { - context->upstreamTransportSocketOptions()->hashKey(hash_key); + context->upstreamTransportSocketOptions()->hashKey(hash_key, host->transportSocketFactory()); have_transport_socket_options = true; } @@ -1421,7 +1421,7 @@ ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEntry::tcpConnPool( bool have_transport_socket_options = false; if (context != nullptr && context->upstreamTransportSocketOptions() != nullptr) { have_transport_socket_options = true; - context->upstreamTransportSocketOptions()->hashKey(hash_key); + context->upstreamTransportSocketOptions()->hashKey(hash_key, host->transportSocketFactory()); } TcpConnPoolsContainer& container = parent_.host_tcp_conn_pool_map_[host]; diff --git a/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h b/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h index 2ada9e2de17b..142489b39d9b 100644 --- a/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h +++ b/source/extensions/quic_listeners/quiche/quic_transport_socket_factory.h @@ -24,6 +24,7 @@ class QuicTransportSocketFactoryBase : public Network::TransportSocketFactory { NOT_REACHED_GCOVR_EXCL_LINE; } bool implementsSecureTransport() const override { return true; } + bool usesProxyProtocolOptions() const override { return false; } }; // TODO(danzh): when implement ProofSource, examine of it's necessary to diff --git a/source/extensions/transport_sockets/alts/tsi_socket.h b/source/extensions/transport_sockets/alts/tsi_socket.h index 0acba405022d..a4a7423bd927 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.h +++ b/source/extensions/transport_sockets/alts/tsi_socket.h @@ -98,6 +98,7 @@ class TsiSocketFactory : public Network::TransportSocketFactory { TsiSocketFactory(HandshakerFactory handshaker_factory, HandshakeValidator handshake_validator); bool implementsSecureTransport() const override; + bool usesProxyProtocolOptions() const override { return false; } Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; diff --git a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h index 4a191ebf539d..c4c9a80f629e 100644 --- a/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h +++ b/source/extensions/transport_sockets/proxy_protocol/proxy_protocol.h @@ -49,6 +49,7 @@ class UpstreamProxyProtocolSocketFactory : public Network::TransportSocketFactor Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; + bool usesProxyProtocolOptions() const override { return true; } private: Network::TransportSocketFactoryPtr transport_socket_factory_; @@ -58,4 +59,4 @@ class UpstreamProxyProtocolSocketFactory : public Network::TransportSocketFactor } // namespace ProxyProtocol } // namespace TransportSockets } // namespace Extensions -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tap/tap.cc b/source/extensions/transport_sockets/tap/tap.cc index 7674ba6b584d..2f58c23703ce 100644 --- a/source/extensions/transport_sockets/tap/tap.cc +++ b/source/extensions/transport_sockets/tap/tap.cc @@ -66,6 +66,10 @@ bool TapSocketFactory::implementsSecureTransport() const { return transport_socket_factory_->implementsSecureTransport(); } +bool TapSocketFactory::usesProxyProtocolOptions() const { + return transport_socket_factory_->usesProxyProtocolOptions(); +} + } // namespace Tap } // namespace TransportSockets } // namespace Extensions diff --git a/source/extensions/transport_sockets/tap/tap.h b/source/extensions/transport_sockets/tap/tap.h index 33156b705153..2971c3e846ba 100644 --- a/source/extensions/transport_sockets/tap/tap.h +++ b/source/extensions/transport_sockets/tap/tap.h @@ -41,6 +41,7 @@ class TapSocketFactory : public Network::TransportSocketFactory, Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; + bool usesProxyProtocolOptions() const override; private: Network::TransportSocketFactoryPtr transport_socket_factory_; diff --git a/source/extensions/transport_sockets/tls/ssl_socket.h b/source/extensions/transport_sockets/tls/ssl_socket.h index 0e539324f91f..4c5f38e0fb14 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.h +++ b/source/extensions/transport_sockets/tls/ssl_socket.h @@ -109,6 +109,7 @@ class ClientSslSocketFactory : public Network::TransportSocketFactory, Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; + bool usesProxyProtocolOptions() const override { return false; } // Secret::SecretCallbacks void onAddOrUpdateSecret() override; @@ -133,6 +134,7 @@ class ServerSslSocketFactory : public Network::TransportSocketFactory, Network::TransportSocketPtr createTransportSocket(Network::TransportSocketOptionsSharedPtr options) const override; bool implementsSecureTransport() const override; + bool usesProxyProtocolOptions() const override { return false; } // Secret::SecretCallbacks void onAddOrUpdateSecret() override; diff --git a/test/common/network/BUILD b/test/common/network/BUILD index 55ddafdbe198..0d4206d80c7b 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -229,6 +229,15 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "raw_buffer_socket_test", + srcs = ["raw_buffer_socket_test.cc"], + deps = [ + "//source/common/network:raw_buffer_socket_lib", + "//test/test_common:network_utility_lib", + ], +) + envoy_cc_test_library( name = "udp_listener_impl_test_base_lib", hdrs = ["udp_listener_impl_test_base.h"], diff --git a/test/common/network/raw_buffer_socket_test.cc b/test/common/network/raw_buffer_socket_test.cc new file mode 100644 index 000000000000..f28e54f53bc9 --- /dev/null +++ b/test/common/network/raw_buffer_socket_test.cc @@ -0,0 +1,16 @@ +#include "common/network/raw_buffer_socket.h" + +#include "test/test_common/network_utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Network { + +TEST(RawBufferSocketFactory, RawBufferSocketFactory) { + TransportSocketFactoryPtr factory = Envoy::Network::Test::createRawBufferSocketFactory(); + EXPECT_FALSE(factory->usesProxyProtocolOptions()); +} + +} // namespace Network +} // namespace Envoy diff --git a/test/common/upstream/transport_socket_matcher_test.cc b/test/common/upstream/transport_socket_matcher_test.cc index cfde130d1d1f..b5e495c8e592 100644 --- a/test/common/upstream/transport_socket_matcher_test.cc +++ b/test/common/upstream/transport_socket_matcher_test.cc @@ -31,6 +31,7 @@ namespace { class FakeTransportSocketFactory : public Network::TransportSocketFactory { public: MOCK_METHOD(bool, implementsSecureTransport, (), (const)); + MOCK_METHOD(bool, usesProxyProtocolOptions, (), (const)); MOCK_METHOD(Network::TransportSocketPtr, createTransportSocket, (Network::TransportSocketOptionsSharedPtr), (const)); FakeTransportSocketFactory(std::string id) : id_(std::move(id)) {} @@ -46,6 +47,7 @@ class FooTransportSocketFactory Logger::Loggable { public: MOCK_METHOD(bool, implementsSecureTransport, (), (const)); + MOCK_METHOD(bool, usesProxyProtocolOptions, (), (const)); MOCK_METHOD(Network::TransportSocketPtr, createTransportSocket, (Network::TransportSocketOptionsSharedPtr), (const)); diff --git a/test/extensions/transport_sockets/alts/tsi_socket_test.cc b/test/extensions/transport_sockets/alts/tsi_socket_test.cc index 3fd39b520020..85f40ab3fcd8 100644 --- a/test/extensions/transport_sockets/alts/tsi_socket_test.cc +++ b/test/extensions/transport_sockets/alts/tsi_socket_test.cc @@ -55,7 +55,6 @@ class TsiSocketTest : public testing::Test { client_.tsi_socket_ = std::make_unique(client_.handshaker_factory_, client_validator, Network::TransportSocketPtr{client_.raw_socket_}); - ON_CALL(client_.callbacks_.connection_, dispatcher()).WillByDefault(ReturnRef(dispatcher_)); ON_CALL(server_.callbacks_.connection_, dispatcher()).WillByDefault(ReturnRef(dispatcher_)); @@ -194,6 +193,7 @@ static const std::string ClientToServerData = "hello from client"; TEST_F(TsiSocketTest, DoesNotHaveSsl) { initialize(nullptr, nullptr); EXPECT_EQ(nullptr, client_.tsi_socket_->ssl()); + EXPECT_FALSE(client_.tsi_socket_->canFlushClose()); const auto& socket_ = *client_.tsi_socket_; EXPECT_EQ(nullptr, socket_.ssl()); @@ -406,6 +406,10 @@ TEST_F(TsiSocketFactoryTest, ImplementsSecureTransport) { EXPECT_TRUE(socket_factory_->implementsSecureTransport()); } +TEST_F(TsiSocketFactoryTest, UsesProxyProtocolOptions) { + EXPECT_FALSE(socket_factory_->usesProxyProtocolOptions()); +} + } // namespace } // namespace Alts } // namespace TransportSockets diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc index 99e04a348746..5747ec73d125 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_integration_test.cc @@ -104,6 +104,36 @@ TEST_P(ProxyProtocolIntegrationTest, TestV1ProxyProtocol) { ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); } +TEST_P(ProxyProtocolIntegrationTest, TestV1ProxyProtocolMultipleConnections) { + if (GetParam() != Network::Address::IpVersion::v4) { + return; + } + + setup(envoy::config::core::v3::ProxyProtocolConfig::V1, false, + "envoy.transport_sockets.raw_buffer"); + initialize(); + auto listener_port = lookupPort("listener_0"); + + auto loopback2 = Network::Utility::resolveUrl("tcp://127.0.0.2:0"); + auto tcp_client2 = makeTcpConnection(listener_port, nullptr, loopback2); + + auto tcp_client = makeTcpConnection(listener_port); + + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection_)); + FakeRawConnectionPtr conn2; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(conn2)); + + std::string data1, data2; + ASSERT_TRUE( + fake_upstream_connection_->waitForData(FakeRawConnection::waitForAtLeastBytes(32), &data1)); + ASSERT_TRUE(conn2->waitForData(FakeRawConnection::waitForAtLeastBytes(32), &data2)); + + EXPECT_NE(data1, data2); + + tcp_client->close(); + tcp_client2->close(); +} + // Test header is sent unencrypted using a TLS inner socket TEST_P(ProxyProtocolIntegrationTest, TestTLSSocket) { setup(envoy::config::core::v3::ProxyProtocolConfig::V1, false, "envoy.transport_sockets.tls"); @@ -201,4 +231,4 @@ TEST_P(ProxyProtocolIntegrationTest, TestV2ProxyProtocol) { } } // namespace -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tap/tap_config_impl_test.cc b/test/extensions/transport_sockets/tap/tap_config_impl_test.cc index c8bcefbf623d..99f10e518e8d 100644 --- a/test/extensions/transport_sockets/tap/tap_config_impl_test.cc +++ b/test/extensions/transport_sockets/tap/tap_config_impl_test.cc @@ -58,7 +58,11 @@ class PerSocketTapperImplTest : public testing::Test { EXPECT_CALL(matcher_, onNewStream(_)) .WillOnce(Invoke([this](TapCommon::Matcher::MatchStatusVector& statuses) { statuses_ = &statuses; - statuses[0].matches_ = true; + if (fail_match_) { + statuses[0].matches_ = false; + } else { + statuses[0].matches_ = true; + } statuses[0].might_change_status_ = false; })); EXPECT_CALL(*config_, streaming()).WillRepeatedly(Return(streaming)); @@ -79,6 +83,7 @@ class PerSocketTapperImplTest : public testing::Test { TapCommon::Matcher::MatchStatusVector* statuses_; NiceMock connection_; Event::SimulatedTimeSystem time_system_; + bool fail_match_{}; }; // Verify the full streaming flow. @@ -139,6 +144,15 @@ TEST_F(PerSocketTapperImplTest, StreamingFlow) { tapper_->closeSocket(Network::ConnectionEvent::RemoteClose); } +TEST_F(PerSocketTapperImplTest, NonMatchingFlow) { + fail_match_ = true; + setup(true); + + EXPECT_CALL(*sink_manager_, submitTrace_(_)).Times(0); + time_system_.setSystemTime(std::chrono::seconds(2)); + tapper_->closeSocket(Network::ConnectionEvent::RemoteClose); +} + } // namespace } // namespace Tap } // namespace TransportSockets diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index 4a2623f43ae4..ade05762720c 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -190,9 +190,10 @@ void BaseIntegrationTest::setUpstreamProtocol(FakeHttpConnection::Type protocol) IntegrationTcpClientPtr BaseIntegrationTest::makeTcpConnection(uint32_t port, - const Network::ConnectionSocket::OptionsSharedPtr& options) { + const Network::ConnectionSocket::OptionsSharedPtr& options, + Network::Address::InstanceConstSharedPtr source_address) { return std::make_unique(*dispatcher_, *mock_buffer_factory_, port, version_, - enable_half_close_, options); + enable_half_close_, options, source_address); } void BaseIntegrationTest::registerPort(const std::string& key, uint32_t port) { diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index be940cb85fa2..aee365f67a45 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -86,7 +86,9 @@ class BaseIntegrationTest : protected Logger::Loggable { IntegrationTcpClientPtr makeTcpConnection(uint32_t port, - const Network::ConnectionSocket::OptionsSharedPtr& options = nullptr); + const Network::ConnectionSocket::OptionsSharedPtr& options = nullptr, + Network::Address::InstanceConstSharedPtr source_address = + Network::Address::InstanceConstSharedPtr()); // Test-wide port map. void registerPort(const std::string& key, uint32_t port); diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index a4e33d1a7fc8..091edf70478d 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -513,6 +513,12 @@ class FakeRawConnection : public FakeConnectionBase { }; } + // Creates a ValidatorFunction which returns true when data_to_wait_for is + // contains at least bytes_read bytes. + static ValidatorFunction waitForAtLeastBytes(uint32_t bytes) { + return [bytes](const std::string& data) -> bool { return data.size() >= bytes; }; + } + private: struct ReadFilter : public Network::ReadFilterBaseImpl { ReadFilter(FakeRawConnection& parent) : parent_(parent) {} diff --git a/test/integration/integration_tcp_client.cc b/test/integration/integration_tcp_client.cc index 590b5c92fcf4..39c040d4b3b0 100644 --- a/test/integration/integration_tcp_client.cc +++ b/test/integration/integration_tcp_client.cc @@ -37,7 +37,8 @@ using ::testing::NiceMock; IntegrationTcpClient::IntegrationTcpClient( Event::Dispatcher& dispatcher, MockBufferFactory& factory, uint32_t port, Network::Address::IpVersion version, bool enable_half_close, - const Network::ConnectionSocket::OptionsSharedPtr& options) + const Network::ConnectionSocket::OptionsSharedPtr& options, + Network::Address::InstanceConstSharedPtr source_address) : payload_reader_(new WaitForPayloadReader(dispatcher)), callbacks_(new ConnectionCallbacks(*this)) { EXPECT_CALL(factory, create_(_, _, _)) @@ -51,7 +52,7 @@ IntegrationTcpClient::IntegrationTcpClient( connection_ = dispatcher.createClientConnection( Network::Utility::resolveUrl( fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version), port)), - Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), options); + source_address, Network::Test::createRawBufferSocket(), options); ON_CALL(*client_write_buffer_, drain(_)) .WillByDefault(Invoke(client_write_buffer_, &MockWatermarkBuffer::trackDrains)); diff --git a/test/integration/integration_tcp_client.h b/test/integration/integration_tcp_client.h index f353c8a3188c..af75b84de08c 100644 --- a/test/integration/integration_tcp_client.h +++ b/test/integration/integration_tcp_client.h @@ -28,7 +28,8 @@ class IntegrationTcpClient { public: IntegrationTcpClient(Event::Dispatcher& dispatcher, MockBufferFactory& factory, uint32_t port, Network::Address::IpVersion version, bool enable_half_close, - const Network::ConnectionSocket::OptionsSharedPtr& options); + const Network::ConnectionSocket::OptionsSharedPtr& options, + Network::Address::InstanceConstSharedPtr source_address = nullptr); void close(); void waitForData(const std::string& data, bool exact_match = true); diff --git a/test/mocks/network/transport_socket.h b/test/mocks/network/transport_socket.h index ee53570c20ac..ed8fa15b7be3 100644 --- a/test/mocks/network/transport_socket.h +++ b/test/mocks/network/transport_socket.h @@ -36,6 +36,7 @@ class MockTransportSocketFactory : public TransportSocketFactory { ~MockTransportSocketFactory() override; MOCK_METHOD(bool, implementsSecureTransport, (), (const)); + MOCK_METHOD(bool, usesProxyProtocolOptions, (), (const)); MOCK_METHOD(TransportSocketPtr, createTransportSocket, (TransportSocketOptionsSharedPtr), (const)); }; From 67609bc22f68cd3e05f5c01264a33932377955c7 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 4 Nov 2020 16:10:37 +0000 Subject: [PATCH 027/117] ci: Update build hashes (#13895) Signed-off-by: Ryan Northey --- .bazelrc | 2 +- .devcontainer/Dockerfile | 2 +- examples/wasm-cc/docker-compose-wasm.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bazelrc b/.bazelrc index 42c3ab4a0a11..3f2fab533837 100644 --- a/.bazelrc +++ b/.bazelrc @@ -246,7 +246,7 @@ build:remote-clang-cl --config=rbe-toolchain-clang-cl # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/master/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:b480535e8423b5fd7c102fd30c92f4785519e33a +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:19a268cfe3d12625380e7c61d2467c8779b58b56 build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 21f934c44944..3f0cbcc33911 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:b480535e8423b5fd7c102fd30c92f4785519e33a +FROM gcr.io/envoy-ci/envoy-build:19a268cfe3d12625380e7c61d2467c8779b58b56 ARG USERNAME=vscode ARG USER_UID=501 diff --git a/examples/wasm-cc/docker-compose-wasm.yaml b/examples/wasm-cc/docker-compose-wasm.yaml index f0f834ebe801..43c051200fed 100644 --- a/examples/wasm-cc/docker-compose-wasm.yaml +++ b/examples/wasm-cc/docker-compose-wasm.yaml @@ -3,7 +3,7 @@ version: "3.7" services: wasm_compile_update: - image: envoyproxy/envoy-build-ubuntu:b480535e8423b5fd7c102fd30c92f4785519e33a + image: envoyproxy/envoy-build-ubuntu:19a268cfe3d12625380e7c61d2467c8779b58b56 command: | bash -c "bazel build //examples/wasm-cc:envoy_filter_http_wasm_updated_example.wasm \ && cp -a bazel-bin/examples/wasm-cc/* /build" @@ -13,7 +13,7 @@ services: - ./lib:/build wasm_compile: - image: envoyproxy/envoy-build-ubuntu:b480535e8423b5fd7c102fd30c92f4785519e33a + image: envoyproxy/envoy-build-ubuntu:19a268cfe3d12625380e7c61d2467c8779b58b56 command: | bash -c "bazel build //examples/wasm-cc:envoy_filter_http_wasm_example.wasm \ && cp -a bazel-bin/examples/wasm-cc/* /build" From bf55b57614245f81049af9d91106f1b54bacffa8 Mon Sep 17 00:00:00 2001 From: Rama Chavali Date: Wed, 4 Nov 2020 21:45:57 +0530 Subject: [PATCH 028/117] metrics service: use snapshot time for all metrics flushed (#13825) Signed-off-by: Rama Chavali --- include/envoy/stats/BUILD | 1 + include/envoy/stats/sink.h | 6 +++ .../stat_sinks/metrics_service/config.cc | 2 +- .../grpc_metrics_service_impl.cc | 37 +++++++++---------- .../grpc_metrics_service_impl.h | 10 ++--- source/server/server.cc | 14 ++++--- source/server/server.h | 7 +++- .../grpc_metrics_service_impl_test.cc | 9 ++--- .../metrics_service_integration_test.cc | 6 +++ test/mocks/stats/mocks.h | 3 ++ test/server/server_test.cc | 7 ++-- 11 files changed, 61 insertions(+), 41 deletions(-) diff --git a/include/envoy/stats/BUILD b/include/envoy/stats/BUILD index c810ac7ad30c..484115c77691 100644 --- a/include/envoy/stats/BUILD +++ b/include/envoy/stats/BUILD @@ -34,6 +34,7 @@ envoy_cc_library( ":refcount_ptr_interface", ":symbol_table_interface", "//include/envoy/common:interval_set_interface", + "//include/envoy/common:time_interface", ], ) diff --git a/include/envoy/stats/sink.h b/include/envoy/stats/sink.h index 1303c9fd67b8..ff0e607ffaa8 100644 --- a/include/envoy/stats/sink.h +++ b/include/envoy/stats/sink.h @@ -4,6 +4,7 @@ #include #include "envoy/common/pure.h" +#include "envoy/common/time.h" #include "envoy/stats/histogram.h" #include "envoy/stats/stats.h" @@ -40,6 +41,11 @@ class MetricSnapshot { * @return a snapshot of all text readouts. */ virtual const std::vector>& textReadouts() PURE; + + /** + * @return the time in UTC since epoch when the snapshot was created. + */ + virtual SystemTime snapshotTime() const PURE; }; /** diff --git a/source/extensions/stat_sinks/metrics_service/config.cc b/source/extensions/stat_sinks/metrics_service/config.cc index db1998aefe5b..05228e9a67c4 100644 --- a/source/extensions/stat_sinks/metrics_service/config.cc +++ b/source/extensions/stat_sinks/metrics_service/config.cc @@ -36,7 +36,7 @@ MetricsServiceSinkFactory::createStatsSink(const Protobuf::Message& config, server.localInfo(), transport_api_version); return std::make_unique( - grpc_metrics_streamer, server.timeSource(), + grpc_metrics_streamer, PROTOBUF_GET_WRAPPED_OR_DEFAULT(sink_config, report_counters_as_deltas, false)); } diff --git a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc index 092e3fbe6fcf..d18bfbf20c83 100644 --- a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc +++ b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc @@ -1,5 +1,7 @@ #include "extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h" +#include + #include "envoy/common/exception.h" #include "envoy/event/dispatcher.h" #include "envoy/service/metrics/v3/metrics_service.pb.h" @@ -38,20 +40,17 @@ void GrpcMetricsStreamerImpl::send(envoy::service::metrics::v3::StreamMetricsMes } MetricsServiceSink::MetricsServiceSink(const GrpcMetricsStreamerSharedPtr& grpc_metrics_streamer, - TimeSource& time_source, const bool report_counters_as_deltas) - : grpc_metrics_streamer_(grpc_metrics_streamer), time_source_(time_source), + : grpc_metrics_streamer_(grpc_metrics_streamer), report_counters_as_deltas_(report_counters_as_deltas) {} void MetricsServiceSink::flushCounter( - const Stats::MetricSnapshot::CounterSnapshot& counter_snapshot) { + const Stats::MetricSnapshot::CounterSnapshot& counter_snapshot, int64_t snapshot_time_ms) { io::prometheus::client::MetricFamily* metrics_family = message_.add_envoy_metrics(); metrics_family->set_type(io::prometheus::client::MetricType::COUNTER); metrics_family->set_name(counter_snapshot.counter_.get().name()); auto* metric = metrics_family->add_metric(); - metric->set_timestamp_ms(std::chrono::duration_cast( - time_source_.systemTime().time_since_epoch()) - .count()); + metric->set_timestamp_ms(snapshot_time_ms); auto* counter_metric = metric->mutable_counter(); if (report_counters_as_deltas_) { counter_metric->set_value(counter_snapshot.delta_); @@ -60,19 +59,18 @@ void MetricsServiceSink::flushCounter( } } -void MetricsServiceSink::flushGauge(const Stats::Gauge& gauge) { +void MetricsServiceSink::flushGauge(const Stats::Gauge& gauge, int64_t snapshot_time_ms) { io::prometheus::client::MetricFamily* metrics_family = message_.add_envoy_metrics(); metrics_family->set_type(io::prometheus::client::MetricType::GAUGE); metrics_family->set_name(gauge.name()); auto* metric = metrics_family->add_metric(); - metric->set_timestamp_ms(std::chrono::duration_cast( - time_source_.systemTime().time_since_epoch()) - .count()); + metric->set_timestamp_ms(snapshot_time_ms); auto* gauge_metric = metric->mutable_gauge(); gauge_metric->set_value(gauge.value()); } -void MetricsServiceSink::flushHistogram(const Stats::ParentHistogram& envoy_histogram) { +void MetricsServiceSink::flushHistogram(const Stats::ParentHistogram& envoy_histogram, + int64_t snapshot_time_ms) { // TODO(ramaraochavali): Currently we are sending both quantile information and bucket // information. We should make this configurable if it turns out that sending both affects // performance. @@ -82,9 +80,7 @@ void MetricsServiceSink::flushHistogram(const Stats::ParentHistogram& envoy_hist summary_metrics_family->set_type(io::prometheus::client::MetricType::SUMMARY); summary_metrics_family->set_name(envoy_histogram.name()); auto* summary_metric = summary_metrics_family->add_metric(); - summary_metric->set_timestamp_ms(std::chrono::duration_cast( - time_source_.systemTime().time_since_epoch()) - .count()); + summary_metric->set_timestamp_ms(snapshot_time_ms); auto* summary = summary_metric->mutable_summary(); const Stats::HistogramStatistics& hist_stats = envoy_histogram.intervalStatistics(); for (size_t i = 0; i < hist_stats.supportedQuantiles().size(); i++) { @@ -98,9 +94,7 @@ void MetricsServiceSink::flushHistogram(const Stats::ParentHistogram& envoy_hist histogram_metrics_family->set_type(io::prometheus::client::MetricType::HISTOGRAM); histogram_metrics_family->set_name(envoy_histogram.name()); auto* histogram_metric = histogram_metrics_family->add_metric(); - histogram_metric->set_timestamp_ms(std::chrono::duration_cast( - time_source_.systemTime().time_since_epoch()) - .count()); + histogram_metric->set_timestamp_ms(snapshot_time_ms); auto* histogram = histogram_metric->mutable_histogram(); histogram->set_sample_count(hist_stats.sampleCount()); histogram->set_sample_sum(hist_stats.sampleSum()); @@ -119,21 +113,24 @@ void MetricsServiceSink::flush(Stats::MetricSnapshot& snapshot) { // preallocating the pointer array). message_.mutable_envoy_metrics()->Reserve(snapshot.counters().size() + snapshot.gauges().size() + snapshot.histograms().size()); + int64_t snapshot_time_ms = std::chrono::duration_cast( + snapshot.snapshotTime().time_since_epoch()) + .count(); for (const auto& counter : snapshot.counters()) { if (counter.counter_.get().used()) { - flushCounter(counter); + flushCounter(counter, snapshot_time_ms); } } for (const auto& gauge : snapshot.gauges()) { if (gauge.get().used()) { - flushGauge(gauge.get()); + flushGauge(gauge.get(), snapshot_time_ms); } } for (const auto& histogram : snapshot.histograms()) { if (histogram.get().used()) { - flushHistogram(histogram.get()); + flushHistogram(histogram.get(), snapshot_time_ms); } } diff --git a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h index d65bae27f9bb..668c7f3fb567 100644 --- a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h +++ b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h @@ -80,18 +80,18 @@ class MetricsServiceSink : public Stats::Sink { public: // MetricsService::Sink MetricsServiceSink(const GrpcMetricsStreamerSharedPtr& grpc_metrics_streamer, - TimeSource& time_system, const bool report_counters_as_deltas); + const bool report_counters_as_deltas); void flush(Stats::MetricSnapshot& snapshot) override; void onHistogramComplete(const Stats::Histogram&, uint64_t) override {} - void flushCounter(const Stats::MetricSnapshot::CounterSnapshot& counter_snapshot); - void flushGauge(const Stats::Gauge& gauge); - void flushHistogram(const Stats::ParentHistogram& envoy_histogram); + void flushCounter(const Stats::MetricSnapshot::CounterSnapshot& counter_snapshot, + int64_t snapshot_time_ms); + void flushGauge(const Stats::Gauge& gauge, int64_t snapshot_time_ms); + void flushHistogram(const Stats::ParentHistogram& envoy_histogram, int64_t snapshot_time_ms); private: GrpcMetricsStreamerSharedPtr grpc_metrics_streamer_; envoy::service::metrics::v3::StreamMetricsMessage message_; - TimeSource& time_source_; const bool report_counters_as_deltas_; }; diff --git a/source/server/server.cc b/source/server/server.cc index b0a16884d982..f0942407c4e8 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -2,12 +2,14 @@ #include #include +#include #include #include #include #include "envoy/admin/v3/config_dump.pb.h" #include "envoy/common/exception.h" +#include "envoy/common/time.h" #include "envoy/config/bootstrap/v2/bootstrap.pb.h" #include "envoy/config/bootstrap/v2/bootstrap.pb.validate.h" #include "envoy/config/bootstrap/v3/bootstrap.pb.h" @@ -147,7 +149,7 @@ void InstanceImpl::failHealthcheck(bool fail) { server_stats_->live_.set(live_.load()); } -MetricSnapshotImpl::MetricSnapshotImpl(Stats::Store& store) { +MetricSnapshotImpl::MetricSnapshotImpl(Stats::Store& store, TimeSource& time_source) { snapped_counters_ = store.counters(); counters_.reserve(snapped_counters_.size()); for (const auto& counter : snapped_counters_) { @@ -172,15 +174,17 @@ MetricSnapshotImpl::MetricSnapshotImpl(Stats::Store& store) { for (const auto& text_readout : snapped_text_readouts_) { text_readouts_.push_back(*text_readout); } + + snapshot_time_ = time_source.systemTime(); } -void InstanceUtil::flushMetricsToSinks(const std::list& sinks, - Stats::Store& store) { +void InstanceUtil::flushMetricsToSinks(const std::list& sinks, Stats::Store& store, + TimeSource& time_source) { // Create a snapshot and flush to all sinks. // NOTE: Even if there are no sinks, creating the snapshot has the important property that it // latches all counters on a periodic basis. The hot restart code assumes this is being // done so this should not be removed. - MetricSnapshotImpl snapshot(store); + MetricSnapshotImpl snapshot(store, time_source); for (const auto& sink : sinks) { sink->flush(snapshot); } @@ -231,7 +235,7 @@ void InstanceImpl::updateServerStats() { void InstanceImpl::flushStatsInternal() { updateServerStats(); - InstanceUtil::flushMetricsToSinks(config_.statsSinks(), stats_store_); + InstanceUtil::flushMetricsToSinks(config_.statsSinks(), stats_store_, timeSource()); // TODO(ramaraochavali): consider adding different flush interval for histograms. if (stat_flush_timer_ != nullptr) { stat_flush_timer_->enableTimer(config_.statsFlushInterval()); diff --git a/source/server/server.h b/source/server/server.h index f9ce2954d581..1c06562b5a0e 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -113,7 +113,8 @@ class InstanceUtil : Logger::Loggable { * @param sinks supplies the list of sinks. * @param store provides the store being flushed. */ - static void flushMetricsToSinks(const std::list& sinks, Stats::Store& store); + static void flushMetricsToSinks(const std::list& sinks, Stats::Store& store, + TimeSource& time_source); /** * Load a bootstrap config and perform validation. @@ -383,7 +384,7 @@ class InstanceImpl final : Logger::Loggable, // copying and probably be a cleaner API in general. class MetricSnapshotImpl : public Stats::MetricSnapshot { public: - explicit MetricSnapshotImpl(Stats::Store& store); + explicit MetricSnapshotImpl(Stats::Store& store, TimeSource& time_source); // Stats::MetricSnapshot const std::vector& counters() override { return counters_; } @@ -396,6 +397,7 @@ class MetricSnapshotImpl : public Stats::MetricSnapshot { const std::vector>& textReadouts() override { return text_readouts_; } + SystemTime snapshotTime() const override { return snapshot_time_; } private: std::vector snapped_counters_; @@ -406,6 +408,7 @@ class MetricSnapshotImpl : public Stats::MetricSnapshot { std::vector> histograms_; std::vector snapped_text_readouts_; std::vector> text_readouts_; + SystemTime snapshot_time_; }; } // namespace Server diff --git a/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc b/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc index ce940b650136..927b710ac756 100644 --- a/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc +++ b/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc @@ -96,12 +96,11 @@ class MetricsServiceSinkTest : public testing::Test { MetricsServiceSinkTest() = default; NiceMock snapshot_; - Event::SimulatedTimeSystem time_system_; std::shared_ptr streamer_{new MockGrpcMetricsStreamer()}; }; TEST_F(MetricsServiceSinkTest, CheckSendCall) { - MetricsServiceSink sink(streamer_, time_system_, false); + MetricsServiceSink sink(streamer_, false); auto counter = std::make_shared>(); counter->name_ = "test_counter"; @@ -125,7 +124,7 @@ TEST_F(MetricsServiceSinkTest, CheckSendCall) { } TEST_F(MetricsServiceSinkTest, CheckStatsCount) { - MetricsServiceSink sink(streamer_, time_system_, false); + MetricsServiceSink sink(streamer_, false); auto counter = std::make_shared>(); counter->name_ = "test_counter"; @@ -156,7 +155,7 @@ TEST_F(MetricsServiceSinkTest, CheckStatsCount) { // Test that verifies counters are correctly reported as current value when configured to do so. TEST_F(MetricsServiceSinkTest, ReportCountersValues) { - MetricsServiceSink sink(streamer_, time_system_, false); + MetricsServiceSink sink(streamer_, false); auto counter = std::make_shared>(); counter->name_ = "test_counter"; @@ -174,7 +173,7 @@ TEST_F(MetricsServiceSinkTest, ReportCountersValues) { // Test that verifies counters are reported as the delta between flushes when configured to do so. TEST_F(MetricsServiceSinkTest, ReportCountersAsDeltas) { - MetricsServiceSink sink(streamer_, time_system_, true); + MetricsServiceSink sink(streamer_, true); auto counter = std::make_shared>(); counter->name_ = "test_counter"; diff --git a/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc b/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc index dcdf47945e94..ff46886b4cd7 100644 --- a/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc +++ b/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc @@ -87,6 +87,7 @@ class MetricsServiceIntegrationTest : public Grpc::VersionedGrpcClientIntegratio const Protobuf::RepeatedPtrField<::io::prometheus::client::MetricFamily>& envoy_metrics = request_msg.envoy_metrics(); + int64_t previous_time_stamp = 0; for (const ::io::prometheus::client::MetricFamily& metrics_family : envoy_metrics) { if (metrics_family.name() == "cluster.cluster_0.membership_change" && metrics_family.type() == ::io::prometheus::client::MetricType::COUNTER) { @@ -112,6 +113,11 @@ class MetricsServiceIntegrationTest : public Grpc::VersionedGrpcClientIntegratio Stats::HistogramSettingsImpl::defaultBuckets().size()); } ASSERT(metrics_family.metric(0).has_timestamp_ms()); + // Validate that all metrics have the same timestamp. + if (previous_time_stamp > 0) { + EXPECT_EQ(previous_time_stamp, metrics_family.metric(0).timestamp_ms()); + } + previous_time_stamp = metrics_family.metric(0).timestamp_ms(); if (known_counter_exists && known_gauge_exists && known_histogram_exists) { break; } diff --git a/test/mocks/stats/mocks.h b/test/mocks/stats/mocks.h index cc43bd084e10..ba12c326c33d 100644 --- a/test/mocks/stats/mocks.h +++ b/test/mocks/stats/mocks.h @@ -280,10 +280,13 @@ class MockMetricSnapshot : public MetricSnapshot { MOCK_METHOD(const std::vector>&, histograms, ()); MOCK_METHOD(const std::vector>&, textReadouts, ()); + SystemTime snapshotTime() const override { return snapshot_time_; } + std::vector counters_; std::vector> gauges_; std::vector> histograms_; std::vector> text_readouts_; + SystemTime snapshot_time_; }; class MockSink : public Sink { diff --git a/test/server/server_test.cc b/test/server/server_test.cc index a26da0f646e6..5249a38f9828 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -52,6 +52,7 @@ TEST(ServerInstanceUtil, flushHelper) { InSequence s; Stats::TestUtil::TestStore store; + Event::SimulatedTimeSystem time_system; Stats::Counter& c = store.counter("hello"); c.inc(); store.gauge("world", Stats::Gauge::ImportMode::Accumulate).set(5); @@ -59,7 +60,7 @@ TEST(ServerInstanceUtil, flushHelper) { store.textReadout("text").set("is important"); std::list sinks; - InstanceUtil::flushMetricsToSinks(sinks, store); + InstanceUtil::flushMetricsToSinks(sinks, store, time_system); // Make sure that counters have been latched even if there are no sinks. EXPECT_EQ(1UL, c.value()); EXPECT_EQ(0, c.latch()); @@ -80,7 +81,7 @@ TEST(ServerInstanceUtil, flushHelper) { EXPECT_EQ(snapshot.textReadouts()[0].get().value(), "is important"); })); c.inc(); - InstanceUtil::flushMetricsToSinks(sinks, store); + InstanceUtil::flushMetricsToSinks(sinks, store, time_system); // Histograms don't currently work with the isolated store so test those with a mock store. NiceMock mock_store; @@ -93,7 +94,7 @@ TEST(ServerInstanceUtil, flushHelper) { EXPECT_EQ(snapshot.histograms().size(), 1); EXPECT_TRUE(snapshot.textReadouts().empty()); })); - InstanceUtil::flushMetricsToSinks(sinks, mock_store); + InstanceUtil::flushMetricsToSinks(sinks, mock_store, time_system); } class RunHelperTest : public testing::Test { From 7620e7d53c6ede82043eb32e2a7b84fca90c24b1 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Wed, 4 Nov 2020 11:29:28 -0500 Subject: [PATCH 029/117] tls: allow nullptr as slot value (#13883) Commit Message: add support for null data to tls slot typed API, using optional references. To make these less cumbersome at call-sites, add a struct OptRef wrapper for absl::optional that allows directly accessing via -> syntax if the caller can guarantee the optional reference is populated. Additional Description: Risk Level: low Testing: //test/... Docs Changes: n/a Release Notes: n/a Signed-off-by: Joshua Marantz --- include/envoy/common/BUILD | 1 + include/envoy/common/optref.h | 39 ++++++++++ include/envoy/thread_local/thread_local.h | 52 ++++++++----- source/common/config/config_provider_impl.cc | 4 +- .../http/filter_config_discovery_impl.cc | 6 +- source/common/router/rds_impl.cc | 2 +- source/common/stats/thread_local_store.cc | 18 ++--- source/common/stats/thread_local_store.h | 1 + .../common/thread_local/thread_local_impl.cc | 9 --- .../common/thread_local/thread_local_impl.h | 2 - .../common/upstream/cluster_manager_impl.cc | 44 +++++------ .../extensions/clusters/aggregate/cluster.cc | 3 +- .../dynamic_forward_proxy/dns_cache_impl.cc | 6 +- .../common/tap/extension_config_base.cc | 7 +- source/extensions/filters/common/lua/lua.cc | 16 ++-- source/extensions/filters/common/lua/lua.h | 11 ++- .../admission_control/admission_control.h | 2 +- source/server/overload_manager_impl.cc | 6 +- test/common/common/BUILD | 5 ++ test/common/common/optref_test.cc | 37 +++++++++ test/common/stats/thread_local_store_test.cc | 4 +- .../thread_local/thread_local_impl_test.cc | 75 ++++++++++++------- test/mocks/thread_local/mocks.h | 4 - 23 files changed, 229 insertions(+), 125 deletions(-) create mode 100644 include/envoy/common/optref.h create mode 100644 test/common/common/optref_test.cc diff --git a/include/envoy/common/BUILD b/include/envoy/common/BUILD index de79fdf0e689..d120b7c0cc54 100644 --- a/include/envoy/common/BUILD +++ b/include/envoy/common/BUILD @@ -13,6 +13,7 @@ envoy_basic_cc_library( name = "base_includes", hdrs = [ "exception.h", + "optref.h", "platform.h", "pure.h", ], diff --git a/include/envoy/common/optref.h b/include/envoy/common/optref.h new file mode 100644 index 000000000000..cf51cdaa52ea --- /dev/null +++ b/include/envoy/common/optref.h @@ -0,0 +1,39 @@ +#pragma once + +#include "absl/types/optional.h" + +namespace Envoy { + +// Helper class to make it easier to work with optional references, allowing: +// foo(OptRef t) { +// if (t.has_value()) { +// t->method(); +// } +// } +// +// Using absl::optional directly you must write optref.value().method() which is +// a bit more awkward. +template struct OptRef : public absl::optional> { + OptRef(T& t) : absl::optional>(t) {} + OptRef() = default; + + /** + * Helper to call a method on T. The caller is responsible for ensuring + * has_value() is true. + */ + T* operator->() { + T& ref = **this; + return &ref; + } + + /** + * Helper to call a const method on T. The caller is responsible for ensuring + * has_value() is true. + */ + const T* operator->() const { + const T& ref = **this; + return &ref; + } +}; + +} // namespace Envoy diff --git a/include/envoy/thread_local/thread_local.h b/include/envoy/thread_local/thread_local.h index fcf7e54e2091..4993b118d1eb 100644 --- a/include/envoy/thread_local/thread_local.h +++ b/include/envoy/thread_local/thread_local.h @@ -4,6 +4,7 @@ #include #include +#include "envoy/common/optref.h" #include "envoy/common/pure.h" #include "envoy/event/dispatcher.h" @@ -93,8 +94,6 @@ class Slot { // Callers must use the TypedSlot API, below. virtual void runOnAllThreads(const UpdateCb& update_cb) PURE; virtual void runOnAllThreads(const UpdateCb& update_cb, const Event::PostCb& complete_cb) PURE; - virtual void runOnAllThreads(const Event::PostCb& cb) PURE; - virtual void runOnAllThreads(const Event::PostCb& cb, const Event::PostCb& complete_cb) PURE; }; using SlotPtr = std::unique_ptr; @@ -157,39 +156,54 @@ template class TypedSlot { void set(InitializeCb cb) { slot_->set(cb); } /** - * @return a reference to the thread local object. + * @return an optional reference to the thread local object. */ - T& get() { return slot_->getTyped(); } - const T& get() const { return slot_->getTyped(); } + OptRef get() { return getOpt(slot_->get()); } + const OptRef get() const { return getOpt(slot_->get()); } /** + * Helper function to call methods on T. The caller is responsible + * for ensuring that get().has_value() is true. + * * @return a pointer to the thread local object. */ - T* operator->() { return &get(); } - const T* operator->() const { return &get(); } + T* operator->() { return &(slot_->getTyped()); } + const T* operator->() const { return &(slot_->getTyped()); } /** - * UpdateCb is passed a mutable reference to the current stored data. + * Helper function to get access to a T&. The caller is responsible for + * ensuring that get().has_value() is true. * - * NOTE: The update callback is not supposed to capture the TypedSlot, or its owner, as the owner - * may be destructed in main thread before the update_cb gets called in a worker thread. + * @return a reference to the thread local object. */ - using UpdateCb = std::function; + T& operator*() { return slot_->getTyped(); } + const T& operator*() const { return slot_->getTyped(); } + + /** + * UpdateCb is passed a mutable pointer to the current stored data. Callers + * can assume that the passed-in OptRef has a value if they have called set(), + * yielding a non-null shared_ptr, prior to runOnAllThreads(). + * + * NOTE: The update callback is not supposed to capture the TypedSlot, or its + * owner, as the owner may be destructed in main thread before the update_cb + * gets called in a worker thread. + */ + using UpdateCb = std::function obj)>; void runOnAllThreads(const UpdateCb& cb) { slot_->runOnAllThreads(makeSlotUpdateCb(cb)); } void runOnAllThreads(const UpdateCb& cb, const Event::PostCb& complete_cb) { slot_->runOnAllThreads(makeSlotUpdateCb(cb), complete_cb); } - void runOnAllThreads(const Event::PostCb& cb) { slot_->runOnAllThreads(cb); } - void runOnAllThreads(const Event::PostCb& cb, const Event::PostCb& complete_cb) { - slot_->runOnAllThreads(cb, complete_cb); - } private: + static OptRef getOpt(ThreadLocalObjectSharedPtr obj) { + if (obj) { + return OptRef(obj->asType()); + } + return OptRef(); + } + Slot::UpdateCb makeSlotUpdateCb(UpdateCb cb) { - return [cb](ThreadLocalObjectSharedPtr obj) -> ThreadLocalObjectSharedPtr { - cb(obj->asType()); - return obj; - }; + return [cb](ThreadLocalObjectSharedPtr obj) { cb(getOpt(obj)); }; } const SlotPtr slot_; diff --git a/source/common/config/config_provider_impl.cc b/source/common/config/config_provider_impl.cc index b0098883d368..78eddb0ffe10 100644 --- a/source/common/config/config_provider_impl.cc +++ b/source/common/config/config_provider_impl.cc @@ -25,8 +25,8 @@ ConfigSubscriptionCommonBase::~ConfigSubscriptionCommonBase() { } void ConfigSubscriptionCommonBase::applyConfigUpdate(const ConfigUpdateCb& update_fn) { - tls_.runOnAllThreads([update_fn](ThreadLocalConfig& thread_local_config) { - thread_local_config.config_ = update_fn(thread_local_config.config_); + tls_.runOnAllThreads([update_fn](OptRef thread_local_config) { + thread_local_config->config_ = update_fn(thread_local_config->config_); }); } diff --git a/source/common/filter/http/filter_config_discovery_impl.cc b/source/common/filter/http/filter_config_discovery_impl.cc index d6c7e1b6bb62..aef0519b4a88 100644 --- a/source/common/filter/http/filter_config_discovery_impl.cc +++ b/source/common/filter/http/filter_config_discovery_impl.cc @@ -37,7 +37,7 @@ DynamicFilterConfigProviderImpl::~DynamicFilterConfigProviderImpl() { const std::string& DynamicFilterConfigProviderImpl::name() { return subscription_->name(); } absl::optional DynamicFilterConfigProviderImpl::config() { - return tls_.get().config_; + return tls_->config_; } void DynamicFilterConfigProviderImpl::validateConfig( @@ -53,8 +53,8 @@ void DynamicFilterConfigProviderImpl::onConfigUpdate(Envoy::Http::FilterFactoryC const std::string&, Config::ConfigAppliedCb cb) { tls_.runOnAllThreads( - [config, cb](ThreadLocalConfig& tls) { - tls.config_ = config; + [config, cb](OptRef tls) { + tls->config_ = config; if (cb) { cb(); } diff --git a/source/common/router/rds_impl.cc b/source/common/router/rds_impl.cc index 62844e63888a..3886e68e01f6 100644 --- a/source/common/router/rds_impl.cc +++ b/source/common/router/rds_impl.cc @@ -255,7 +255,7 @@ Router::ConfigConstSharedPtr RdsRouteConfigProviderImpl::config() { return tls_- void RdsRouteConfigProviderImpl::onConfigUpdate() { ConfigConstSharedPtr new_config(new ConfigImpl(config_update_info_->routeConfiguration(), factory_context_, validator_, false)); - tls_.runOnAllThreads([new_config](ThreadLocalConfig& tls) { tls.config_ = new_config; }); + tls_.runOnAllThreads([new_config](OptRef tls) { tls->config_ = new_config; }); const auto aliases = config_update_info_->resourceIdsInLastVhdsUpdate(); // Regular (non-VHDS) RDS updates don't populate aliases fields in resources. diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index b0704ff97c19..11b977440837 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -205,8 +205,8 @@ void ThreadLocalStoreImpl::mergeHistograms(PostMergeCb merge_complete_cb) { ASSERT(!merge_in_progress_); merge_in_progress_ = true; tls_cache_->runOnAllThreads( - [](TlsCache& tls_cache) { - for (const auto& id_hist : tls_cache.tls_histogram_cache_) { + [](OptRef tls_cache) { + for (const auto& id_hist : tls_cache->tls_histogram_cache_) { const TlsHistogramSharedPtr& tls_hist = id_hist.second; tls_hist->beginMerge(); } @@ -303,7 +303,7 @@ void ThreadLocalStoreImpl::clearScopeFromCaches(uint64_t scope_id, if (!shutting_down_) { // Perform a cache flush on all threads. tls_cache_->runOnAllThreads( - [scope_id](TlsCache& tls_cache) { tls_cache.eraseScope(scope_id); }, + [scope_id](OptRef tls_cache) { tls_cache->eraseScope(scope_id); }, [central_cache]() { /* Holds onto central_cache until all tls caches are clear */ }); } } @@ -320,7 +320,7 @@ void ThreadLocalStoreImpl::clearHistogramFromCaches(uint64_t histogram_id) { // contains a patch that will implement batching together to clear multiple // histograms. tls_cache_->runOnAllThreads( - [histogram_id](TlsCache& tls_cache) { tls_cache.eraseHistogram(histogram_id); }); + [histogram_id](OptRef tls_cache) { tls_cache->eraseHistogram(histogram_id); }); } } @@ -489,7 +489,7 @@ Counter& ThreadLocalStoreImpl::ScopeImpl::counterFromStatNameWithTags( StatRefMap* tls_cache = nullptr; StatNameHashSet* tls_rejected_stats = nullptr; if (!parent_.shutting_down_ && parent_.tls_cache_) { - TlsCacheEntry& entry = parent_.tls_cache_->get().insertScope(this->scope_id_); + TlsCacheEntry& entry = parent_.tlsCache().insertScope(this->scope_id_); tls_cache = &entry.counters_; tls_rejected_stats = &entry.rejected_stats_; } @@ -541,7 +541,7 @@ Gauge& ThreadLocalStoreImpl::ScopeImpl::gaugeFromStatNameWithTags( StatRefMap* tls_cache = nullptr; StatNameHashSet* tls_rejected_stats = nullptr; if (!parent_.shutting_down_ && parent_.tls_cache_) { - TlsCacheEntry& entry = parent_.tls_cache_->get().scope_cache_[this->scope_id_]; + TlsCacheEntry& entry = parent_.tlsCache().scope_cache_[this->scope_id_]; tls_cache = &entry.gauges_; tls_rejected_stats = &entry.rejected_stats_; } @@ -579,7 +579,7 @@ Histogram& ThreadLocalStoreImpl::ScopeImpl::histogramFromStatNameWithTags( StatNameHashMap* tls_cache = nullptr; StatNameHashSet* tls_rejected_stats = nullptr; if (!parent_.shutting_down_ && parent_.tls_cache_) { - TlsCacheEntry& entry = parent_.tls_cache_->get().scope_cache_[this->scope_id_]; + TlsCacheEntry& entry = parent_.tlsCache().scope_cache_[this->scope_id_]; tls_cache = &entry.parent_histograms_; auto iter = tls_cache->find(final_stat_name); if (iter != tls_cache->end()) { @@ -657,7 +657,7 @@ TextReadout& ThreadLocalStoreImpl::ScopeImpl::textReadoutFromStatNameWithTags( StatRefMap* tls_cache = nullptr; StatNameHashSet* tls_rejected_stats = nullptr; if (!parent_.shutting_down_ && parent_.tls_cache_) { - TlsCacheEntry& entry = parent_.tls_cache_->get().insertScope(this->scope_id_); + TlsCacheEntry& entry = parent_.tlsCache().insertScope(this->scope_id_); tls_cache = &entry.text_readouts_; tls_rejected_stats = &entry.rejected_stats_; } @@ -703,7 +703,7 @@ Histogram& ThreadLocalStoreImpl::tlsHistogram(ParentHistogramImpl& parent, uint6 TlsHistogramSharedPtr* tls_histogram = nullptr; if (!shutting_down_ && tls_cache_) { - tls_histogram = &tls_cache_->get().tls_histogram_cache_[id]; + tls_histogram = &(tlsCache().tls_histogram_cache_[id]); if (*tls_histogram != nullptr) { return **tls_histogram; } diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 410254afeb33..22707accacbd 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -477,6 +477,7 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo void removeRejectedStats(StatMapClass& map, StatListClass& list); bool checkAndRememberRejection(StatName name, StatNameStorageSet& central_rejected_stats, StatNameHashSet* tls_rejected_stats); + TlsCache& tlsCache() { return **tls_cache_; } Allocator& alloc_; Event::Dispatcher* main_thread_dispatcher_{}; diff --git a/source/common/thread_local/thread_local_impl.cc b/source/common/thread_local/thread_local_impl.cc index 6c450449a942..0815236a3195 100644 --- a/source/common/thread_local/thread_local_impl.cc +++ b/source/common/thread_local/thread_local_impl.cc @@ -90,15 +90,6 @@ void InstanceImpl::SlotImpl::runOnAllThreads(const UpdateCb& cb) { parent_.runOnAllThreads(dataCallback(cb)); } -void InstanceImpl::SlotImpl::runOnAllThreads(const Event::PostCb& cb, - const Event::PostCb& complete_cb) { - parent_.runOnAllThreads(wrapCallback(cb), complete_cb); -} - -void InstanceImpl::SlotImpl::runOnAllThreads(const Event::PostCb& cb) { - parent_.runOnAllThreads(wrapCallback(cb)); -} - void InstanceImpl::SlotImpl::set(InitializeCb cb) { ASSERT(std::this_thread::get_id() == parent_.main_thread_id_); ASSERT(!parent_.shutdown_); diff --git a/source/common/thread_local/thread_local_impl.h b/source/common/thread_local/thread_local_impl.h index 4f5bb1b88125..7abed0499166 100644 --- a/source/common/thread_local/thread_local_impl.h +++ b/source/common/thread_local/thread_local_impl.h @@ -45,8 +45,6 @@ class InstanceImpl : Logger::Loggable, public NonCopyable, pub ThreadLocalObjectSharedPtr get() override; void runOnAllThreads(const UpdateCb& cb) override; void runOnAllThreads(const UpdateCb& cb, const Event::PostCb& complete_cb) override; - void runOnAllThreads(const Event::PostCb& cb) override; - void runOnAllThreads(const Event::PostCb& cb, const Event::PostCb& complete_cb) override; bool currentThreadRegistered() override; void set(InitializeCb cb) override; diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index ce6ecf509865..08ca0567025d 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -647,17 +647,17 @@ void ClusterManagerImpl::clusterWarmingToActive(const std::string& cluster_name) void ClusterManagerImpl::createOrUpdateThreadLocalCluster(ClusterData& cluster) { tls_.runOnAllThreads([new_cluster = cluster.cluster_->info(), thread_aware_lb_factory = cluster.loadBalancerFactory()]( - ThreadLocalClusterManagerImpl& cluster_manager) { - if (cluster_manager.thread_local_clusters_.count(new_cluster->name()) > 0) { + OptRef cluster_manager) { + if (cluster_manager->thread_local_clusters_.count(new_cluster->name()) > 0) { ENVOY_LOG(debug, "updating TLS cluster {}", new_cluster->name()); } else { ENVOY_LOG(debug, "adding TLS cluster {}", new_cluster->name()); } auto thread_local_cluster = new ThreadLocalClusterManagerImpl::ClusterEntry( - cluster_manager, new_cluster, thread_aware_lb_factory); - cluster_manager.thread_local_clusters_[new_cluster->name()].reset(thread_local_cluster); - for (auto& cb : cluster_manager.update_callbacks_) { + *cluster_manager, new_cluster, thread_aware_lb_factory); + cluster_manager->thread_local_clusters_[new_cluster->name()].reset(thread_local_cluster); + for (auto& cb : cluster_manager->update_callbacks_) { cb->onClusterAddOrUpdate(*thread_local_cluster); } }); @@ -673,13 +673,13 @@ bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { active_clusters_.erase(existing_active_cluster); ENVOY_LOG(info, "removing cluster {}", cluster_name); - tls_.runOnAllThreads([cluster_name](ThreadLocalClusterManagerImpl& cluster_manager) { - ASSERT(cluster_manager.thread_local_clusters_.count(cluster_name) == 1); + tls_.runOnAllThreads([cluster_name](OptRef cluster_manager) { + ASSERT(cluster_manager->thread_local_clusters_.count(cluster_name) == 1); ENVOY_LOG(debug, "removing TLS cluster {}", cluster_name); - for (auto& cb : cluster_manager.update_callbacks_) { + for (auto& cb : cluster_manager->update_callbacks_) { cb->onClusterRemoval(cluster_name); } - cluster_manager.thread_local_clusters_.erase(cluster_name); + cluster_manager->thread_local_clusters_.erase(cluster_name); }); } @@ -807,7 +807,7 @@ void ClusterManagerImpl::updateClusterCounts() { } ThreadLocalCluster* ClusterManagerImpl::get(absl::string_view cluster) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry != cluster_manager.thread_local_clusters_.end()) { @@ -846,7 +846,7 @@ Http::ConnectionPool::Instance* ClusterManagerImpl::httpConnPoolForCluster(const std::string& cluster, ResourcePriority priority, absl::optional protocol, LoadBalancerContext* context) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry == cluster_manager.thread_local_clusters_.end()) { @@ -872,7 +872,7 @@ ClusterManagerImpl::httpConnPoolForCluster(const std::string& cluster, ResourceP Tcp::ConnectionPool::Instance* ClusterManagerImpl::tcpConnPoolForCluster(const std::string& cluster, ResourcePriority priority, LoadBalancerContext* context) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry == cluster_manager.thread_local_clusters_.end()) { @@ -898,8 +898,8 @@ ClusterManagerImpl::tcpConnPoolForCluster(const std::string& cluster, ResourcePr void ClusterManagerImpl::postThreadLocalDrainConnections(const Cluster& cluster, const HostVector& hosts_removed) { tls_.runOnAllThreads([name = cluster.info()->name(), - hosts_removed](ThreadLocalClusterManagerImpl& cluster_manager) { - cluster_manager.removeHosts(name, hosts_removed); + hosts_removed](OptRef cluster_manager) { + cluster_manager->removeHosts(name, hosts_removed); }); } @@ -912,21 +912,21 @@ void ClusterManagerImpl::postThreadLocalClusterUpdate(const Cluster& cluster, ui update_params = HostSetImpl::updateHostsParams(*host_set), locality_weights = host_set->localityWeights(), hosts_added, hosts_removed, overprovisioning_factor = host_set->overprovisioningFactor()]( - ThreadLocalClusterManagerImpl& cluster_manager) { - cluster_manager.updateClusterMembership(name, priority, update_params, locality_weights, - hosts_added, hosts_removed, overprovisioning_factor); + OptRef cluster_manager) { + cluster_manager->updateClusterMembership(name, priority, update_params, locality_weights, + hosts_added, hosts_removed, overprovisioning_factor); }); } void ClusterManagerImpl::postThreadLocalHealthFailure(const HostSharedPtr& host) { - tls_.runOnAllThreads([host](ThreadLocalClusterManagerImpl& cluster_manager) { - cluster_manager.onHostHealthFailure(host); + tls_.runOnAllThreads([host](OptRef cluster_manager) { + cluster_manager->onHostHealthFailure(host); }); } Host::CreateConnectionData ClusterManagerImpl::tcpConnForCluster(const std::string& cluster, LoadBalancerContext* context) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry == cluster_manager.thread_local_clusters_.end()) { @@ -954,7 +954,7 @@ Host::CreateConnectionData ClusterManagerImpl::tcpConnForCluster(const std::stri } Http::AsyncClient& ClusterManagerImpl::httpAsyncClientForCluster(const std::string& cluster) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; auto entry = cluster_manager.thread_local_clusters_.find(cluster); if (entry != cluster_manager.thread_local_clusters_.end()) { return entry->second->http_async_client_; @@ -965,7 +965,7 @@ Http::AsyncClient& ClusterManagerImpl::httpAsyncClientForCluster(const std::stri ClusterUpdateCallbacksHandlePtr ClusterManagerImpl::addThreadLocalClusterUpdateCallbacks(ClusterUpdateCallbacks& cb) { - ThreadLocalClusterManagerImpl& cluster_manager = tls_.get(); + ThreadLocalClusterManagerImpl& cluster_manager = *tls_; return std::make_unique(cb, cluster_manager.update_callbacks_); } diff --git a/source/extensions/clusters/aggregate/cluster.cc b/source/extensions/clusters/aggregate/cluster.cc index 52d99b036f9a..c630a580a49a 100644 --- a/source/extensions/clusters/aggregate/cluster.cc +++ b/source/extensions/clusters/aggregate/cluster.cc @@ -91,7 +91,8 @@ void Cluster::startPreInit() { void Cluster::refresh(const std::function& skip_predicate) { // Post the priority set to worker threads. // TODO(mattklein123): Remove "this" capture. - tls_.runOnAllThreads([this, skip_predicate, cluster_name = this->info()->name()]() { + tls_.runOnAllThreads([this, skip_predicate, cluster_name = this->info()->name()]( + OptRef) { PriorityContextPtr priority_context = linearizePrioritySet(skip_predicate); Upstream::ThreadLocalCluster* cluster = cluster_manager_.get(cluster_name); ASSERT(cluster != nullptr); diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc index 5d5e39e4b68b..ac09ab220f72 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc @@ -55,7 +55,7 @@ DnsCacheImpl::LoadDnsCacheEntryResult DnsCacheImpl::loadDnsCacheEntry(absl::string_view host, uint16_t default_port, LoadDnsCacheEntryCallbacks& callbacks) { ENVOY_LOG(debug, "thread local lookup for host '{}'", host); - ThreadLocalHostInfo& tls_host_info = tls_slot_.get(); + ThreadLocalHostInfo& tls_host_info = *tls_slot_; auto tls_host = tls_host_info.host_map_->find(host); if (tls_host != tls_host_info.host_map_->end()) { ENVOY_LOG(debug, "thread local hit for host '{}'", host); @@ -275,8 +275,8 @@ void DnsCacheImpl::updateTlsHostsMap() { } } - tls_slot_.runOnAllThreads([new_host_map](ThreadLocalHostInfo& local_host_info) { - local_host_info.updateHostMap(new_host_map); + tls_slot_.runOnAllThreads([new_host_map](OptRef local_host_info) { + local_host_info->updateHostMap(new_host_map); }); } diff --git a/source/extensions/common/tap/extension_config_base.cc b/source/extensions/common/tap/extension_config_base.cc index d5538bff21fb..04a6fb73d20b 100644 --- a/source/extensions/common/tap/extension_config_base.cc +++ b/source/extensions/common/tap/extension_config_base.cc @@ -58,15 +58,16 @@ const absl::string_view ExtensionConfigBase::adminId() { void ExtensionConfigBase::clearTapConfig() { tls_slot_.runOnAllThreads( - [](TlsFilterConfig& tls_filter_config) { tls_filter_config.config_ = nullptr; }); + [](OptRef tls_filter_config) { tls_filter_config->config_ = nullptr; }); } void ExtensionConfigBase::installNewTap(const envoy::config::tap::v3::TapConfig& proto_config, Sink* admin_streamer) { TapConfigSharedPtr new_config = config_factory_->createConfigFromProto(proto_config, admin_streamer); - tls_slot_.runOnAllThreads( - [new_config](TlsFilterConfig& tls_filter_config) { tls_filter_config.config_ = new_config; }); + tls_slot_.runOnAllThreads([new_config](OptRef tls_filter_config) { + tls_filter_config->config_ = new_config; + }); } void ExtensionConfigBase::newTapConfig(const envoy::config::tap::v3::TapConfig& proto_config, diff --git a/source/extensions/filters/common/lua/lua.cc b/source/extensions/filters/common/lua/lua.cc index e1fea1f96d38..f23f968c9e7f 100644 --- a/source/extensions/filters/common/lua/lua.cc +++ b/source/extensions/filters/common/lua/lua.cc @@ -65,20 +65,20 @@ ThreadLocalState::ThreadLocalState(const std::string& code, ThreadLocal::SlotAll } int ThreadLocalState::getGlobalRef(uint64_t slot) { - LuaThreadLocal& tls = tls_slot_->get(); + LuaThreadLocal& tls = **tls_slot_; ASSERT(tls.global_slots_.size() > slot); return tls.global_slots_[slot]; } uint64_t ThreadLocalState::registerGlobal(const std::string& global) { - tls_slot_->runOnAllThreads([global](LuaThreadLocal& tls) { - lua_getglobal(tls.state_.get(), global.c_str()); - if (lua_isfunction(tls.state_.get(), -1)) { - tls.global_slots_.push_back(luaL_ref(tls.state_.get(), LUA_REGISTRYINDEX)); + tls_slot_->runOnAllThreads([global](OptRef tls) { + lua_getglobal(tls->state_.get(), global.c_str()); + if (lua_isfunction(tls->state_.get(), -1)) { + tls->global_slots_.push_back(luaL_ref(tls->state_.get(), LUA_REGISTRYINDEX)); } else { ENVOY_LOG(debug, "definition for '{}' not found in script", global); - lua_pop(tls.state_.get(), 1); - tls.global_slots_.push_back(LUA_REFNIL); + lua_pop(tls->state_.get(), 1); + tls->global_slots_.push_back(LUA_REFNIL); } }); @@ -86,7 +86,7 @@ uint64_t ThreadLocalState::registerGlobal(const std::string& global) { } CoroutinePtr ThreadLocalState::createCoroutine() { - lua_State* state = tls_slot_->get().state_.get(); + lua_State* state = tlsState().get(); return std::make_unique(std::make_pair(lua_newthread(state), state)); } diff --git a/source/extensions/filters/common/lua/lua.h b/source/extensions/filters/common/lua/lua.h index ac84ac14af7e..6112df91b0de 100644 --- a/source/extensions/filters/common/lua/lua.h +++ b/source/extensions/filters/common/lua/lua.h @@ -386,22 +386,23 @@ class ThreadLocalState : Logger::Loggable { * all threaded workers. */ template void registerType() { - tls_slot_->runOnAllThreads([](LuaThreadLocal& tls) { T::registerType(tls.state_.get()); }); + tls_slot_->runOnAllThreads( + [](OptRef tls) { T::registerType(tls->state_.get()); }); } /** * Return the number of bytes used by the runtime. */ uint64_t runtimeBytesUsed() { - uint64_t bytes_used = lua_gc(tls_slot_->get().state_.get(), LUA_GCCOUNT, 0) * 1024; - bytes_used += lua_gc(tls_slot_->get().state_.get(), LUA_GCCOUNTB, 0); + uint64_t bytes_used = lua_gc(tlsState().get(), LUA_GCCOUNT, 0) * 1024; + bytes_used += lua_gc(tlsState().get(), LUA_GCCOUNTB, 0); return bytes_used; } /** * Force a full runtime GC. */ - void runtimeGC() { lua_gc(tls_slot_->get().state_.get(), LUA_GCCOLLECT, 0); } + void runtimeGC() { lua_gc(tlsState().get(), LUA_GCCOLLECT, 0); } private: struct LuaThreadLocal : public ThreadLocal::ThreadLocalObject { @@ -411,6 +412,8 @@ class ThreadLocalState : Logger::Loggable { std::vector global_slots_; }; + CSmartPtr& tlsState() { return (*tls_slot_)->state_; } + ThreadLocal::TypedSlotPtr tls_slot_; uint64_t current_global_slot_{}; }; diff --git a/source/extensions/filters/http/admission_control/admission_control.h b/source/extensions/filters/http/admission_control/admission_control.h index 79a903d361d1..7b4e83de80c7 100644 --- a/source/extensions/filters/http/admission_control/admission_control.h +++ b/source/extensions/filters/http/admission_control/admission_control.h @@ -59,7 +59,7 @@ class AdmissionControlFilterConfig { std::shared_ptr response_evaluator); virtual ~AdmissionControlFilterConfig() = default; - virtual ThreadLocalController& getController() const { return tls_->get(); } + virtual ThreadLocalController& getController() const { return **tls_; } Random::RandomGenerator& random() const { return random_; } bool filterEnabled() const { return admission_control_feature_.enabled(); } diff --git a/source/server/overload_manager_impl.cc b/source/server/overload_manager_impl.cc index c128caf41d12..db5ced97e41d 100644 --- a/source/server/overload_manager_impl.cc +++ b/source/server/overload_manager_impl.cc @@ -384,7 +384,7 @@ bool OverloadManagerImpl::registerForAction(const std::string& action, return true; } -ThreadLocalOverloadState& OverloadManagerImpl::getThreadLocalOverloadState() { return tls_.get(); } +ThreadLocalOverloadState& OverloadManagerImpl::getThreadLocalOverloadState() { return *tls_; } Event::ScaledRangeTimerManagerPtr OverloadManagerImpl::createScaledRangeTimerManager(Event::Dispatcher& dispatcher) const { @@ -442,9 +442,9 @@ void OverloadManagerImpl::flushResourceUpdates() { std::swap(*shared_updates, state_updates_to_flush_); tls_.runOnAllThreads( - [updates = std::move(shared_updates)](ThreadLocalOverloadStateImpl& overload_state) { + [updates = std::move(shared_updates)](OptRef overload_state) { for (const auto& [action, state] : *updates) { - overload_state.setState(action, state); + overload_state->setState(action, state); } }); } diff --git a/test/common/common/BUILD b/test/common/common/BUILD index 20392d66be19..8f9ec5324dc8 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -187,6 +187,11 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "optref_test", + srcs = ["optref_test.cc"], +) + envoy_cc_test( name = "random_generator_test", srcs = ["random_generator_test.cc"], diff --git a/test/common/common/optref_test.cc b/test/common/common/optref_test.cc new file mode 100644 index 000000000000..343d4506bba0 --- /dev/null +++ b/test/common/common/optref_test.cc @@ -0,0 +1,37 @@ +#include + +#include "envoy/common/optref.h" + +#include "gtest/gtest.h" + +namespace Envoy { + +// Helper function for returning the string reference from an OptRef. Calling +// value() inline at the EXPECT_EQ callsites does not compile due to template +// specialization ambiguities, that this wrapper resolves. +static std::string& strref(const OptRef optref) { return optref.value(); } + +TEST(OptRefTest, Empty) { + OptRef optref; + EXPECT_FALSE(optref.has_value()); +} + +TEST(OptRefTest, NonConst) { + std::string str("Hello"); + OptRef optref(str); + EXPECT_TRUE(optref.has_value()); + EXPECT_EQ("Hello", strref(optref)); + EXPECT_EQ(5, optref->size()); + optref->append(", World!"); + EXPECT_EQ("Hello, World!", strref(optref)); +} + +TEST(OptRefTest, Const) { + std::string str("Hello"); + const OptRef optref(str); + EXPECT_TRUE(optref.has_value()); + EXPECT_EQ("Hello", strref(optref)); + EXPECT_EQ(5, optref->size()); +} + +} // namespace Envoy diff --git a/test/common/stats/thread_local_store_test.cc b/test/common/stats/thread_local_store_test.cc index 9e97d323d43d..71bf69be9df7 100644 --- a/test/common/stats/thread_local_store_test.cc +++ b/test/common/stats/thread_local_store_test.cc @@ -55,8 +55,8 @@ class ThreadLocalStoreTestingPeer { const std::function& num_tls_hist_cb) { auto num_tls_histograms = std::make_shared>(0); thread_local_store_impl.tls_cache_->runOnAllThreads( - [num_tls_histograms](ThreadLocalStoreImpl::TlsCache& tls_cache) { - *num_tls_histograms += tls_cache.tls_histogram_cache_.size(); + [num_tls_histograms](OptRef tls_cache) { + *num_tls_histograms += tls_cache->tls_histogram_cache_.size(); }, [num_tls_hist_cb, num_tls_histograms]() { num_tls_hist_cb(*num_tls_histograms); }); } diff --git a/test/common/thread_local/thread_local_impl_test.cc b/test/common/thread_local/thread_local_impl_test.cc index 9aa1d65734be..59bdd6d0080b 100644 --- a/test/common/thread_local/thread_local_impl_test.cc +++ b/test/common/thread_local/thread_local_impl_test.cc @@ -138,32 +138,23 @@ class CallbackNotInvokedAfterDeletionTest : public ThreadLocalInstanceImplTest { ThreadStatus thread_status_; }; -TEST_F(CallbackNotInvokedAfterDeletionTest, WithArg) { +TEST_F(CallbackNotInvokedAfterDeletionTest, WithData) { InSequence s; - slot_->runOnAllThreads([this](ThreadLocalObject&) { + slot_->runOnAllThreads([this](OptRef obj) { + EXPECT_TRUE(obj.has_value()); // Callbacks happen on the main thread but not the workers, so track the total. total_callbacks_++; }); - slot_->runOnAllThreads([this](ThreadLocalObject&) { ++thread_status_.thread_local_calls_; }, - [this]() { - // Callbacks happen on the main thread but not the workers. - EXPECT_EQ(thread_status_.thread_local_calls_, 1); - thread_status_.all_threads_complete_ = true; - }); -} - -TEST_F(CallbackNotInvokedAfterDeletionTest, WithoutArg) { - InSequence s; - slot_->runOnAllThreads([this]() { - // Callbacks happen on the main thread but not the workers, so track the total. - total_callbacks_++; - }); - slot_->runOnAllThreads([this]() { ++thread_status_.thread_local_calls_; }, - [this]() { - // Callbacks happen on the main thread but not the workers. - EXPECT_EQ(thread_status_.thread_local_calls_, 1); - thread_status_.all_threads_complete_ = true; - }); + slot_->runOnAllThreads( + [this](OptRef obj) { + EXPECT_TRUE(obj.has_value()); + ++thread_status_.thread_local_calls_; + }, + [this]() { + // Callbacks happen on the main thread but not the workers. + EXPECT_EQ(thread_status_.thread_local_calls_, 1); + thread_status_.all_threads_complete_ = true; + }); } // Test that the update callback is called as expected, for the worker and main threads. @@ -175,7 +166,7 @@ TEST_F(ThreadLocalInstanceImplTest, UpdateCallback) { uint32_t update_called = 0; TestThreadLocalObject& object_ref = setObject(slot); - auto update_cb = [&update_called](ThreadLocalObject&) { ++update_called; }; + auto update_cb = [&update_called](OptRef) { ++update_called; }; EXPECT_CALL(thread_dispatcher_, post(_)); EXPECT_CALL(object_ref, onDestroy()); slot.runOnAllThreads(update_cb); @@ -192,7 +183,6 @@ struct StringSlotObject : public ThreadLocalObject { TEST_F(ThreadLocalInstanceImplTest, TypedUpdateCallback) { InSequence s; - TypedSlot slot(tls_); uint32_t update_called = 0; @@ -202,16 +192,43 @@ TEST_F(ThreadLocalInstanceImplTest, TypedUpdateCallback) { s->str_ = "hello"; return s; }); - EXPECT_EQ("hello", slot.get().str_); + EXPECT_EQ("hello", slot.get()->str_); + + auto update_cb = [&update_called](OptRef s) { + ++update_called; + EXPECT_TRUE(s.has_value()); + s->str_ = "goodbye"; + }; + EXPECT_CALL(thread_dispatcher_, post(_)); + slot.runOnAllThreads(update_cb); + + // Tests a few different ways of getting at the slot data. + EXPECT_EQ("goodbye", slot.get()->str_); + EXPECT_EQ("goodbye", slot->str_); + EXPECT_EQ("goodbye", (*slot).str_); + EXPECT_EQ(2, update_called); // 1 worker, 1 main thread. + + tls_.shutdownGlobalThreading(); + tls_.shutdownThread(); +} + +TEST_F(ThreadLocalInstanceImplTest, NoDataCallback) { + InSequence s; + TypedSlot slot(tls_); + + uint32_t update_called = 0; + EXPECT_CALL(thread_dispatcher_, post(_)); + slot.set([](Event::Dispatcher&) -> std::shared_ptr { return nullptr; }); + EXPECT_FALSE(slot.get().has_value()); - auto update_cb = [&update_called](StringSlotObject& s) { + auto update_cb = [&update_called](OptRef s) { ++update_called; - s.str_ = "goodbye"; + EXPECT_FALSE(s.has_value()); }; EXPECT_CALL(thread_dispatcher_, post(_)); slot.runOnAllThreads(update_cb); - EXPECT_EQ("goodbye", slot.get().str_); + EXPECT_FALSE(slot.get().has_value()); EXPECT_EQ(2, update_called); // 1 worker, 1 main thread. tls_.shutdownGlobalThreading(); @@ -232,7 +249,7 @@ TEST_F(ThreadLocalInstanceImplTest, RunOnAllThreads) { // Ensure that the thread local call back and all_thread_complete call back are called. ThreadStatus thread_status; tlsptr->runOnAllThreads( - [&thread_status](ThreadLocal::ThreadLocalObject&) { ++thread_status.thread_local_calls_; }, + [&thread_status](OptRef) { ++thread_status.thread_local_calls_; }, [&thread_status]() { EXPECT_EQ(thread_status.thread_local_calls_, 2); thread_status.all_threads_complete_ = true; diff --git a/test/mocks/thread_local/mocks.h b/test/mocks/thread_local/mocks.h index 6c5f7a9a3450..7b3097f5e0fa 100644 --- a/test/mocks/thread_local/mocks.h +++ b/test/mocks/thread_local/mocks.h @@ -66,10 +66,6 @@ class MockInstance : public Instance { void runOnAllThreads(const UpdateCb& cb, const Event::PostCb& main_callback) override { parent_.runOnAllThreads([cb, this]() { cb(parent_.data_[index_]); }, main_callback); } - void runOnAllThreads(const Event::PostCb& cb) override { parent_.runOnAllThreads(cb); } - void runOnAllThreads(const Event::PostCb& cb, const Event::PostCb& main_callback) override { - parent_.runOnAllThreads(cb, main_callback); - } void set(InitializeCb cb) override { if (parent_.defer_data) { From 91638e6a75665262acaa88e8cd3e8a20d6b4f617 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 4 Nov 2020 12:16:31 -0500 Subject: [PATCH 030/117] http: moving functions and member variables out of http1 pool (#13867) As part of #3431 we need to move the logic from HTTP/1 and HTTP/2 connection pools to the active client, so the new mixed connection pool can just create an active client of the right type. This removes member variables from the HTTP/1.1 connection pool in preparation for that move. a follow-up PR will do the same for HTTP/2, then the two classes will be removed in favor of the base http connection pool being created with an instantiate_active_client_ closure which creates the right type of clients. The alpn pool will eventually create an active client of the right type based on initial ALPN. Signed-off-by: Alyssa Wilk --- source/common/conn_pool/conn_pool_base.h | 3 +- source/common/http/conn_pool_base.cc | 5 ++- source/common/http/conn_pool_base.h | 19 ++++++-- source/common/http/http1/conn_pool.cc | 55 +++++++++--------------- source/common/http/http1/conn_pool.h | 14 +----- source/common/http/http2/conn_pool.cc | 3 +- source/common/http/http2/conn_pool.h | 5 --- test/common/http/http1/conn_pool_test.cc | 33 +++++--------- 8 files changed, 55 insertions(+), 82 deletions(-) diff --git a/source/common/conn_pool/conn_pool_base.h b/source/common/conn_pool/conn_pool_base.h index 85d8f304719d..79a7fd78f771 100644 --- a/source/common/conn_pool/conn_pool_base.h +++ b/source/common/conn_pool/conn_pool_base.h @@ -156,7 +156,8 @@ class ConnPoolImplBase : protected Logger::Loggable { virtual ConnectionPool::Cancellable* newPendingStream(AttachContext& context) PURE; - void attachStreamToClient(Envoy::ConnectionPool::ActiveClient& client, AttachContext& context); + virtual void attachStreamToClient(Envoy::ConnectionPool::ActiveClient& client, + AttachContext& context); virtual void onPoolFailure(const Upstream::HostDescriptionConstSharedPtr& host_description, absl::string_view failure_reason, diff --git a/source/common/http/conn_pool_base.cc b/source/common/http/conn_pool_base.cc index b3f56ff434b8..06e61af75ff9 100644 --- a/source/common/http/conn_pool_base.cc +++ b/source/common/http/conn_pool_base.cc @@ -48,10 +48,11 @@ HttpConnPoolImplBase::HttpConnPoolImplBase( Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, - Http::Protocol protocol) + Random::RandomGenerator& random_generator, Http::Protocol protocol) : Envoy::ConnectionPool::ConnPoolImplBase( host, priority, dispatcher, options, - wrapTransportSocketOptions(transport_socket_options, protocol)) {} + wrapTransportSocketOptions(transport_socket_options, protocol)), + random_generator_(random_generator), protocol_(protocol) {} ConnectionPool::Cancellable* HttpConnPoolImplBase::newStream(Http::ResponseDecoder& response_decoder, diff --git a/source/common/http/conn_pool_base.h b/source/common/http/conn_pool_base.h index f24eea263f06..10610ed96cac 100644 --- a/source/common/http/conn_pool_base.h +++ b/source/common/http/conn_pool_base.h @@ -43,7 +43,7 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, - Http::Protocol protocol); + Random::RandomGenerator& random_generator, Http::Protocol protocol); // ConnectionPool::Instance void addDrainedCallback(DrainedCb cb) override { addDrainedCallbackImpl(cb); } @@ -55,6 +55,7 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, return Envoy::ConnectionPool::ConnPoolImplBase::maybePrefetch(ratio); } bool hasActiveConnections() const override; + Http::Protocol protocol() const override { return protocol_; } // Creates a new PendingStream and enqueues it into the queue. ConnectionPool::Cancellable* @@ -69,6 +70,10 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, Envoy::ConnectionPool::AttachContext& context) override; virtual CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) PURE; + +protected: + Random::RandomGenerator& random_generator_; + Http::Protocol protocol_; }; // An implementation of Envoy::ConnectionPool::ActiveClient for HTTP/1.1 and HTTP/2 @@ -78,8 +83,15 @@ class ActiveClient : public Envoy::ConnectionPool::ActiveClient { uint64_t concurrent_stream_limit) : Envoy::ConnectionPool::ActiveClient(parent, lifetime_stream_limit, concurrent_stream_limit) { - Upstream::Host::CreateConnectionData data = parent_.host()->createConnection( - parent_.dispatcher(), parent_.socketOptions(), parent_.transportSocketOptions()); + // The static cast makes sure we call the base class host() and not + // HttpConnPoolImplBase::host which is of a different type. + Upstream::Host::CreateConnectionData data = + static_cast(&parent)->host()->createConnection( + parent.dispatcher(), parent.socketOptions(), parent.transportSocketOptions()); + initialize(data, parent); + } + + void initialize(Upstream::Host::CreateConnectionData& data, HttpConnPoolImplBase& parent) { real_host_description_ = data.host_description_; codec_client_ = parent.createCodecClient(data); codec_client_->addConnectionCallbacks(*this); @@ -90,6 +102,7 @@ class ActiveClient : public Envoy::ConnectionPool::ActiveClient { parent_.host()->cluster().stats().upstream_cx_tx_bytes_buffered_, &parent_.host()->cluster().stats().bind_errors_, nullptr}); } + void close() override { codec_client_->close(); } virtual Http::RequestEncoder& newStreamEncoder(Http::ResponseDecoder& response_decoder) PURE; void onEvent(Network::ConnectionEvent event) override { diff --git a/source/common/http/http1/conn_pool.cc b/source/common/http/http1/conn_pool.cc index 3115e743e88f..ce0c0fce1cd4 100644 --- a/source/common/http/http1/conn_pool.cc +++ b/source/common/http/http1/conn_pool.cc @@ -28,12 +28,7 @@ ConnPoolImpl::ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerato const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options) : HttpConnPoolImplBase(std::move(host), std::move(priority), dispatcher, options, - transport_socket_options, Protocol::Http11), - upstream_ready_cb_(dispatcher_.createSchedulableCallback([this]() { - upstream_ready_enabled_ = false; - onUpstreamReady(); - })), - random_generator_(random_generator) {} + transport_socket_options, random_generator, Protocol::Http11) {} ConnPoolImpl::~ConnPoolImpl() { destructAllConnections(); } @@ -41,32 +36,6 @@ Envoy::ConnectionPool::ActiveClientPtr ConnPoolImpl::instantiateActiveClient() { return std::make_unique(*this); } -void ConnPoolImpl::onDownstreamReset(ActiveClient& client) { - // If we get a downstream reset to an attached client, we just blow it away. - client.codec_client_->close(); -} - -void ConnPoolImpl::onResponseComplete(ActiveClient& client) { - ENVOY_CONN_LOG(debug, "response complete", *client.codec_client_); - - if (!client.stream_wrapper_->encode_complete_) { - ENVOY_CONN_LOG(debug, "response before request complete", *client.codec_client_); - onDownstreamReset(client); - } else if (client.stream_wrapper_->close_connection_ || client.codec_client_->remoteClosed()) { - ENVOY_CONN_LOG(debug, "saw upstream close connection", *client.codec_client_); - onDownstreamReset(client); - } else { - client.stream_wrapper_.reset(); - - if (!pending_streams_.empty() && !upstream_ready_enabled_) { - upstream_ready_enabled_ = true; - upstream_ready_cb_->scheduleCallbackCurrentIteration(); - } - - checkForDrained(); - } -} - ConnPoolImpl::StreamWrapper::StreamWrapper(ResponseDecoder& response_decoder, ActiveClient& parent) : RequestEncoderWrapper(parent.codec_client_->newStream(*this)), ResponseDecoderWrapper(response_decoder), parent_(parent) { @@ -87,7 +56,7 @@ void ConnPoolImpl::StreamWrapper::decodeHeaders(ResponseHeaderMapPtr&& headers, close_connection_ = HeaderUtility::shouldCloseConnection(parent_.codec_client_->protocol(), *headers); if (close_connection_) { - parent_.parent_.host()->cluster().stats().upstream_cx_close_notify_.inc(); + parent_.parent().host()->cluster().stats().upstream_cx_close_notify_.inc(); } } else { // If Connection: close OR @@ -100,7 +69,7 @@ void ConnPoolImpl::StreamWrapper::decodeHeaders(ResponseHeaderMapPtr&& headers, Headers::get().ConnectionValues.KeepAlive)) || (absl::EqualsIgnoreCase(headers->getProxyConnectionValue(), Headers::get().ConnectionValues.Close))) { - parent_.parent_.host()->cluster().stats().upstream_cx_close_notify_.inc(); + parent_.parent().host()->cluster().stats().upstream_cx_close_notify_.inc(); close_connection_ = true; } } @@ -108,8 +77,24 @@ void ConnPoolImpl::StreamWrapper::decodeHeaders(ResponseHeaderMapPtr&& headers, } void ConnPoolImpl::StreamWrapper::onDecodeComplete() { + ASSERT(!decode_complete_); decode_complete_ = encode_complete_; - parent_.parent().onResponseComplete(parent_); + + ENVOY_CONN_LOG(debug, "response complete", *parent_.codec_client_); + + if (!parent_.stream_wrapper_->encode_complete_) { + ENVOY_CONN_LOG(debug, "response before request complete", *parent_.codec_client_); + parent_.codec_client_->close(); + } else if (parent_.stream_wrapper_->close_connection_ || parent_.codec_client_->remoteClosed()) { + ENVOY_CONN_LOG(debug, "saw upstream close connection", *parent_.codec_client_); + parent_.codec_client_->close(); + } else { + auto* pool = &parent_.parent(); + pool->dispatcher_.post([pool]() -> void { pool->onUpstreamReady(); }); + parent_.stream_wrapper_.reset(); + + pool->checkForDrained(); + } } ConnPoolImpl::ActiveClient::ActiveClient(ConnPoolImpl& parent) diff --git a/source/common/http/http1/conn_pool.h b/source/common/http/http1/conn_pool.h index 895e6fba6673..a5034986fdd8 100644 --- a/source/common/http/http1/conn_pool.h +++ b/source/common/http/http1/conn_pool.h @@ -26,9 +26,6 @@ class ConnPoolImpl : public Http::HttpConnPoolImplBase { ~ConnPoolImpl() override; - // ConnectionPool::Instance - Http::Protocol protocol() const override { return Http::Protocol::Http11; } - // ConnPoolImplBase Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override; @@ -51,7 +48,7 @@ class ConnPoolImpl : public Http::HttpConnPoolImplBase { // Http::StreamCallbacks void onResetStream(StreamResetReason, absl::string_view) override { - parent_.parent().onDownstreamReset(parent_); + parent_.codec_client_->close(); } void onAboveWriteBufferHighWatermark() override {} void onBelowWriteBufferLowWatermark() override {} @@ -68,7 +65,7 @@ class ConnPoolImpl : public Http::HttpConnPoolImplBase { public: ActiveClient(ConnPoolImpl& parent); - ConnPoolImpl& parent() { return static_cast(parent_); } + ConnPoolImpl& parent() { return *static_cast(&parent_); } // ConnPoolImplBase::ActiveClient bool closingWithIncompleteStream() const override; @@ -76,13 +73,6 @@ class ConnPoolImpl : public Http::HttpConnPoolImplBase { StreamWrapperPtr stream_wrapper_; }; - - void onDownstreamReset(ActiveClient& client); - void onResponseComplete(ActiveClient& client); - - Event::SchedulableCallbackPtr upstream_ready_cb_; - bool upstream_ready_enabled_{false}; - Random::RandomGenerator& random_generator_; }; /** diff --git a/source/common/http/http2/conn_pool.cc b/source/common/http/http2/conn_pool.cc index 70b9ee71b976..29f89bb60c32 100644 --- a/source/common/http/http2/conn_pool.cc +++ b/source/common/http/http2/conn_pool.cc @@ -21,8 +21,7 @@ ConnPoolImpl::ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerato const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options) : HttpConnPoolImplBase(std::move(host), std::move(priority), dispatcher, options, - transport_socket_options, Protocol::Http2), - random_generator_(random_generator) {} + transport_socket_options, random_generator, Protocol::Http2) {} ConnPoolImpl::~ConnPoolImpl() { destructAllConnections(); } diff --git a/source/common/http/http2/conn_pool.h b/source/common/http/http2/conn_pool.h index 85b5c3949752..833bb09c3702 100644 --- a/source/common/http/http2/conn_pool.h +++ b/source/common/http/http2/conn_pool.h @@ -25,9 +25,6 @@ class ConnPoolImpl : public Envoy::Http::HttpConnPoolImplBase { ~ConnPoolImpl() override; - // Http::ConnectionPool::Instance - Http::Protocol protocol() const override { return Http::Protocol::Http2; } - // ConnPoolImplBase Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override; @@ -55,8 +52,6 @@ class ConnPoolImpl : public Envoy::Http::HttpConnPoolImplBase { bool closed_with_active_rq_{}; }; - - Random::RandomGenerator& random_generator_; }; /** diff --git a/test/common/http/http1/conn_pool_test.cc b/test/common/http/http1/conn_pool_test.cc index c919f9499cea..9e95c4fbc519 100644 --- a/test/common/http/http1/conn_pool_test.cc +++ b/test/common/http/http1/conn_pool_test.cc @@ -51,12 +51,10 @@ namespace { class ConnPoolImplForTest : public ConnPoolImpl { public: ConnPoolImplForTest(Event::MockDispatcher& dispatcher, - Upstream::ClusterInfoConstSharedPtr cluster, - NiceMock* upstream_ready_cb) + Upstream::ClusterInfoConstSharedPtr cluster) : ConnPoolImpl(dispatcher, random_, Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), Upstream::ResourcePriority::Default, nullptr, nullptr), - api_(Api::createApiForTest()), mock_dispatcher_(dispatcher), - mock_upstream_ready_cb_(upstream_ready_cb) {} + api_(Api::createApiForTest()), mock_dispatcher_(dispatcher) {} ~ConnPoolImplForTest() override { EXPECT_EQ(0U, ready_clients_.size()); @@ -111,22 +109,15 @@ class ConnPoolImplForTest : public ConnPoolImpl { } void expectEnableUpstreamReady() { - EXPECT_FALSE(upstream_ready_enabled_); - EXPECT_CALL(*mock_upstream_ready_cb_, scheduleCallbackCurrentIteration()) - .Times(1) - .RetiresOnSaturation(); + EXPECT_CALL(mock_dispatcher_, post(_)).WillOnce(SaveArg<0>(&post_cb_)); } - void expectAndRunUpstreamReady() { - EXPECT_TRUE(upstream_ready_enabled_); - mock_upstream_ready_cb_->invokeCallback(); - EXPECT_FALSE(upstream_ready_enabled_); - } + void expectAndRunUpstreamReady() { post_cb_(); } Api::ApiPtr api_; Event::MockDispatcher& mock_dispatcher_; NiceMock random_; - NiceMock* mock_upstream_ready_cb_; + Event::PostCb post_cb_; std::vector test_clients_; }; @@ -136,9 +127,7 @@ class ConnPoolImplForTest : public ConnPoolImpl { class Http1ConnPoolImplTest : public testing::Test { public: Http1ConnPoolImplTest() - : upstream_ready_cb_(new NiceMock(&dispatcher_)), - conn_pool_( - std::make_unique(dispatcher_, cluster_, upstream_ready_cb_)) {} + : conn_pool_(std::make_unique(dispatcher_, cluster_)) {} ~Http1ConnPoolImplTest() override { EXPECT_EQ("", TestUtility::nonZeroedGauges(cluster_->stats_store_.gauges())); @@ -146,7 +135,6 @@ class Http1ConnPoolImplTest : public testing::Test { NiceMock dispatcher_; std::shared_ptr cluster_{new NiceMock()}; - NiceMock* upstream_ready_cb_; std::unique_ptr conn_pool_; NiceMock runtime_; }; @@ -291,11 +279,9 @@ TEST_F(Http1ConnPoolImplTest, VerifyAlpnFallback) { cluster_->transport_socket_matcher_ = std::make_unique>(std::move(factory)); - new NiceMock(&dispatcher_); - // Recreate the conn pool so that the host re-evaluates the transport socket match, arriving at // our test transport socket factory. - conn_pool_ = std::make_unique(dispatcher_, cluster_, upstream_ready_cb_); + conn_pool_ = std::make_unique(dispatcher_, cluster_); NiceMock outer_decoder; ConnPoolCallbacks callbacks; conn_pool_->expectClientCreate(Protocol::Http11); @@ -646,6 +632,7 @@ TEST_F(Http1ConnPoolImplTest, MaxConnections) { inner_decoder->decodeHeaders(std::move(response_headers), true); conn_pool_->expectAndRunUpstreamReady(); + conn_pool_->expectEnableUpstreamReady(); callbacks2.outer_encoder_->encodeHeaders( TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); // N.B. clang_tidy insists that we use std::make_unique which can not infer std::initialize_list. @@ -693,7 +680,6 @@ TEST_F(Http1ConnPoolImplTest, ConnectionCloseWithoutHeader) { // Finishing request 1 will schedule binding the connection to request 2. conn_pool_->expectEnableUpstreamReady(); - callbacks.outer_encoder_->encodeHeaders( TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); Http::ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); @@ -712,6 +698,7 @@ TEST_F(Http1ConnPoolImplTest, ConnectionCloseWithoutHeader) { EXPECT_CALL(callbacks2.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); + conn_pool_->expectEnableUpstreamReady(); callbacks2.outer_encoder_->encodeHeaders( TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); // N.B. clang_tidy insists that we use std::make_unique which can not infer std::initialize_list. @@ -959,7 +946,9 @@ TEST_F(Http1ConnPoolImplTest, ConcurrentConnections) { r3.startRequest(); EXPECT_EQ(3U, cluster_->stats_.upstream_rq_total_.value()); + conn_pool_->expectEnableUpstreamReady(); r2.completeResponse(false); + conn_pool_->expectEnableUpstreamReady(); r3.completeResponse(false); // Disconnect both clients. From fa590062d2b4a7fa5f17f139231fe8aade17f0cd Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 4 Nov 2020 13:13:43 -0500 Subject: [PATCH 031/117] tcp: make it possible for TCP connections to be creatd by non-TCP pool) (#13889) As part of #3431 making sure the ALPN pool can create raw TCP active clients by allowing them to be created by a generic connection pool. Signed-off-by: Alyssa Wilk --- source/common/conn_pool/conn_pool_base.h | 1 + source/common/tcp/conn_pool.cc | 16 +++++++--- source/common/tcp/conn_pool.h | 26 ++++------------ test/common/tcp/conn_pool_test.cc | 39 +++++++++++++++++++----- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/source/common/conn_pool/conn_pool_base.h b/source/common/conn_pool/conn_pool_base.h index 79a7fd78f771..83265bd23ced 100644 --- a/source/common/conn_pool/conn_pool_base.h +++ b/source/common/conn_pool/conn_pool_base.h @@ -174,6 +174,7 @@ class ConnPoolImplBase : protected Logger::Loggable { const Network::TransportSocketOptionsSharedPtr& transportSocketOptions() { return transport_socket_options_; } + bool hasPendingStreams() const { return !pending_streams_.empty(); } protected: // Creates up to 3 connections, based on the prefetch ratio. diff --git a/source/common/tcp/conn_pool.cc b/source/common/tcp/conn_pool.cc index 7fc370570052..5abc86fc959c 100644 --- a/source/common/tcp/conn_pool.cc +++ b/source/common/tcp/conn_pool.cc @@ -12,7 +12,8 @@ namespace Envoy { namespace Tcp { -ActiveTcpClient::ActiveTcpClient(ConnPoolImpl& parent, const Upstream::HostConstSharedPtr& host, +ActiveTcpClient::ActiveTcpClient(Envoy::ConnectionPool::ConnPoolImplBase& parent, + const Upstream::HostConstSharedPtr& host, uint64_t concurrent_stream_limit) : Envoy::ConnectionPool::ActiveClient(parent, host->cluster().maxRequestsPerConnection(), concurrent_stream_limit), @@ -24,6 +25,12 @@ ActiveTcpClient::ActiveTcpClient(ConnPoolImpl& parent, const Upstream::HostConst connection_->addConnectionCallbacks(*this); connection_->detectEarlyCloseWhenReadDisabled(false); connection_->addReadFilter(std::make_shared(*this)); + connection_->setConnectionStats({host->cluster().stats().upstream_cx_rx_bytes_total_, + host->cluster().stats().upstream_cx_rx_bytes_buffered_, + host->cluster().stats().upstream_cx_tx_bytes_total_, + host->cluster().stats().upstream_cx_tx_bytes_buffered_, + &host->cluster().stats().bind_errors_, nullptr}); + connection_->connect(); } @@ -37,13 +44,12 @@ ActiveTcpClient::~ActiveTcpClient() { parent_.onStreamClosed(*this, true); parent_.checkForDrained(); } - parent_.onConnDestroyed(); } void ActiveTcpClient::clearCallbacks() { - if (state_ == Envoy::ConnectionPool::ActiveClient::State::BUSY || - state_ == Envoy::ConnectionPool::ActiveClient::State::DRAINING) { - parent_.onConnReleased(*this); + if (state_ == Envoy::ConnectionPool::ActiveClient::State::BUSY && parent_.hasPendingStreams()) { + auto* pool = &parent_; + pool->dispatcher().post([pool]() -> void { pool->onUpstreamReady(); }); } callbacks_ = nullptr; tcp_connection_data_ = nullptr; diff --git a/source/common/tcp/conn_pool.h b/source/common/tcp/conn_pool.h index c75b28f59156..a3637b8a43cd 100644 --- a/source/common/tcp/conn_pool.h +++ b/source/common/tcp/conn_pool.h @@ -84,8 +84,8 @@ class ActiveTcpClient : public Envoy::ConnectionPool::ActiveClient { Network::ClientConnection& connection_; }; - ActiveTcpClient(ConnPoolImpl& parent, const Upstream::HostConstSharedPtr& host, - uint64_t concurrent_stream_limit); + ActiveTcpClient(Envoy::ConnectionPool::ConnPoolImplBase& parent, + const Upstream::HostConstSharedPtr& host, uint64_t concurrent_stream_limit); ~ActiveTcpClient() override; // Override the default's of Envoy::ConnectionPool::ActiveClient for class-specific functions. @@ -106,9 +106,9 @@ class ActiveTcpClient : public Envoy::ConnectionPool::ActiveClient { close(); } } - void clearCallbacks(); + virtual void clearCallbacks(); - ConnPoolImpl& parent_; + Envoy::ConnectionPool::ConnPoolImplBase& parent_; ConnectionPool::UpstreamCallbacks* callbacks_{}; Network::ClientConnectionPtr connection_; ConnectionPool::ConnectionStatePtr connection_state_; @@ -123,11 +123,7 @@ class ConnPoolImpl : public Envoy::ConnectionPool::ConnPoolImplBase, const Network::ConnectionSocket::OptionsSharedPtr& options, Network::TransportSocketOptionsSharedPtr transport_socket_options) : Envoy::ConnectionPool::ConnPoolImplBase(host, priority, dispatcher, options, - transport_socket_options), - upstream_ready_cb_(dispatcher.createSchedulableCallback([this]() { - upstream_ready_enabled_ = false; - onUpstreamReady(); - })) {} + transport_socket_options) {} ~ConnPoolImpl() override { destructAllConnections(); } void addDrainedCallback(DrainedCb cb) override { addDrainedCallbackImpl(cb); } @@ -196,18 +192,8 @@ class ConnPoolImpl : public Envoy::ConnectionPool::ConnPoolImplBase, } // These two functions exist for testing parity between old and new Tcp Connection Pools. - virtual void onConnReleased(Envoy::ConnectionPool::ActiveClient& client) { - if (client.state_ == Envoy::ConnectionPool::ActiveClient::State::BUSY) { - if (!pending_streams_.empty() && !upstream_ready_enabled_) { - upstream_ready_cb_->scheduleCallbackCurrentIteration(); - } - } - } + virtual void onConnReleased(Envoy::ConnectionPool::ActiveClient&) {} virtual void onConnDestroyed() {} - -protected: - Event::SchedulableCallbackPtr upstream_ready_cb_; - bool upstream_ready_enabled_{}; }; } // namespace Tcp diff --git a/test/common/tcp/conn_pool_test.cc b/test/common/tcp/conn_pool_test.cc index 838cc9a64035..f81d9ffcd334 100644 --- a/test/common/tcp/conn_pool_test.cc +++ b/test/common/tcp/conn_pool_test.cc @@ -67,6 +67,22 @@ struct ConnPoolCallbacks : public Tcp::ConnectionPool::Callbacks { Upstream::HostDescriptionConstSharedPtr host_; }; +class TestActiveTcpClient : public ActiveTcpClient { +public: + using ActiveTcpClient::ActiveTcpClient; + + ~TestActiveTcpClient() override { parent().onConnDestroyed(); } + void clearCallbacks() override { + if (state_ == Envoy::ConnectionPool::ActiveClient::State::BUSY || + state_ == Envoy::ConnectionPool::ActiveClient::State::DRAINING) { + parent().onConnReleased(*this); + } + ActiveTcpClient::clearCallbacks(); + } + + Envoy::Tcp::ConnPoolImpl& parent() { return *static_cast(&parent_); } +}; + /** * A wrapper around a ConnectionPoolImpl which tracks when the bridge between * the pool and the consumer of the connection is released and destroyed. @@ -143,7 +159,13 @@ class ConnPoolBase : public Tcp::ConnectionPool::Instance { parent_.onConnReleasedForTest(); } + Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override { + return std::make_unique( + *this, Envoy::ConnectionPool::ConnPoolImplBase::host(), 1); + } + void onConnDestroyed() override { parent_.onConnDestroyedForTest(); } + Event::PostCb post_cb_; ConnPoolBase& parent_; }; @@ -202,12 +224,11 @@ void ConnPoolBase::expectEnableUpstreamReady(bool run) { if (!test_new_connection_pool_) { dynamic_cast(conn_pool_.get())->expectEnableUpstreamReady(run); } else { - if (!run) { - EXPECT_CALL(*mock_upstream_ready_cb_, scheduleCallbackCurrentIteration()) - .Times(1) - .RetiresOnSaturation(); + Event::PostCb& post_cb = dynamic_cast(conn_pool_.get())->post_cb_; + if (run) { + post_cb(); } else { - mock_upstream_ready_cb_->invokeCallback(); + EXPECT_CALL(mock_dispatcher_, post(_)).WillOnce(testing::SaveArg<0>(&post_cb)); } } } @@ -219,7 +240,9 @@ class TcpConnPoolImplTest : public testing::TestWithParam { public: TcpConnPoolImplTest() : test_new_connection_pool_(GetParam()), - upstream_ready_cb_(new NiceMock(&dispatcher_)), + upstream_ready_cb_(test_new_connection_pool_ + ? nullptr + : new NiceMock(&dispatcher_)), host_(Upstream::makeTestHost(cluster_, "tcp://127.0.0.1:9000")), conn_pool_(dispatcher_, host_, upstream_ready_cb_, test_new_connection_pool_) {} @@ -244,7 +267,9 @@ class TcpConnPoolImplDestructorTest : public testing::TestWithParam { public: TcpConnPoolImplDestructorTest() : test_new_connection_pool_(GetParam()), - upstream_ready_cb_(new NiceMock(&dispatcher_)) { + upstream_ready_cb_(test_new_connection_pool_ + ? nullptr + : new NiceMock(&dispatcher_)) { host_ = Upstream::makeTestHost(cluster_, "tcp://127.0.0.1:9000"); if (test_new_connection_pool_) { conn_pool_ = std::make_unique( From 7be58c8b90a97accaf631bb869503b77339664b0 Mon Sep 17 00:00:00 2001 From: antonio Date: Wed, 4 Nov 2020 13:28:53 -0500 Subject: [PATCH 032/117] watchdog: Touch Watchdog before executing most event loop callbacks to avoid spurious misses when there are many queued callbacks. (#13104) This change assumes that the primary purposes of the watchdog is to terminate the proxy if worker threads are deadlocked, and aid in the debugging of long-running operations that end up scheduled in worker threads through monitoring for miss/megamiss events and auxiliary watchdog actions like the recently added watchdog profile action. Signed-off-by: Antonio Vicente --- docs/root/version_history/current.rst | 1 + include/envoy/event/BUILD | 1 + include/envoy/event/dispatcher.h | 10 +++ include/envoy/server/BUILD | 3 +- include/envoy/server/guarddog.h | 5 +- include/envoy/server/watchdog.h | 16 +---- source/common/event/dispatcher_impl.cc | 33 +++++++++- source/common/event/dispatcher_impl.h | 30 +++++++++ source/server/BUILD | 3 +- source/server/guarddog_impl.cc | 7 +- source/server/guarddog_impl.h | 4 +- source/server/server.cc | 3 +- source/server/watchdog_impl.cc | 19 ------ source/server/watchdog_impl.h | 11 +--- source/server/worker_impl.cc | 5 +- test/common/event/BUILD | 1 + test/common/event/dispatcher_impl_test.cc | 71 ++++++++++++++++++++ test/mocks/event/mocks.h | 2 + test/mocks/event/wrapped_dispatcher.h | 7 +- test/mocks/server/guard_dog.cc | 2 +- test/mocks/server/guard_dog.h | 3 +- test/mocks/server/watch_dog.h | 1 - test/server/BUILD | 1 + test/server/guarddog_impl_test.cc | 79 +++++++++++++---------- 24 files changed, 221 insertions(+), 97 deletions(-) delete mode 100644 source/server/watchdog_impl.cc diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 2310118b6a03..8dad9a35019e 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -29,6 +29,7 @@ Bug Fixes * http: sending CONNECT_ERROR for HTTP/2 where appropriate during CONNECT requests. * proxy_proto: fixed a bug where the wrong downstream address got sent to upstream connections. * tls: fix read resumption after triggering buffer high-watermark and all remaining request/response bytes are stored in the SSL connection's internal buffers. +* watchdog: touch the watchdog before most event loop operations to avoid misses when handling bursts of callbacks. Removed Config or Runtime ------------------------- diff --git a/include/envoy/event/BUILD b/include/envoy/event/BUILD index 0d22a7747ae1..a51c9345e55f 100644 --- a/include/envoy/event/BUILD +++ b/include/envoy/event/BUILD @@ -31,6 +31,7 @@ envoy_cc_library( "//include/envoy/network:listen_socket_interface", "//include/envoy/network:listener_interface", "//include/envoy/network:transport_socket_interface", + "//include/envoy/server:watchdog_interface", "//include/envoy/thread:thread_interface", ], ) diff --git a/include/envoy/event/dispatcher.h b/include/envoy/event/dispatcher.h index 96b1d12310fb..bd4e9512aa4b 100644 --- a/include/envoy/event/dispatcher.h +++ b/include/envoy/event/dispatcher.h @@ -19,6 +19,7 @@ #include "envoy/network/listen_socket.h" #include "envoy/network/listener.h" #include "envoy/network/transport_socket.h" +#include "envoy/server/watchdog.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" #include "envoy/stream_info/stream_info.h" @@ -63,6 +64,15 @@ class Dispatcher { */ virtual const std::string& name() PURE; + /** + * Register a watchdog for this dispatcher. The dispatcher is responsible for touching the + * watchdog at least once per touch interval. Dispatcher implementations may choose to touch more + * often to avoid spurious miss events when processing long callback queues. + * @param min_touch_interval Touch interval for the watchdog. + */ + virtual void registerWatchdog(const Server::WatchDogSharedPtr& watchdog, + std::chrono::milliseconds min_touch_interval) PURE; + /** * Returns a time-source to use with this dispatcher. */ diff --git a/include/envoy/server/BUILD b/include/envoy/server/BUILD index 2aa968e03874..30b3d46b1c56 100644 --- a/include/envoy/server/BUILD +++ b/include/envoy/server/BUILD @@ -68,6 +68,7 @@ envoy_cc_library( name = "guarddog_interface", hdrs = ["guarddog.h"], deps = [ + "//include/envoy/event:dispatcher_interface", "//include/envoy/server:watchdog_interface", "//include/envoy/stats:stats_interface", "//include/envoy/thread:thread_interface", @@ -159,8 +160,6 @@ envoy_cc_library( name = "watchdog_interface", hdrs = ["watchdog.h"], deps = [ - "//include/envoy/event:dispatcher_interface", - "//include/envoy/network:address_interface", "//include/envoy/thread:thread_interface", ], ) diff --git a/include/envoy/server/guarddog.h b/include/envoy/server/guarddog.h index 08b8f53646a0..4b818f222086 100644 --- a/include/envoy/server/guarddog.h +++ b/include/envoy/server/guarddog.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/common/pure.h" +#include "envoy/event/dispatcher.h" #include "envoy/server/watchdog.h" namespace Envoy { @@ -28,9 +29,11 @@ class GuardDog { * * @param thread_id a Thread::ThreadId containing the system thread id * @param thread_name supplies the name of the thread which is used for per-thread miss stats. + * @param dispatcher dispatcher responsible for petting the watchdog. */ virtual WatchDogSharedPtr createWatchDog(Thread::ThreadId thread_id, - const std::string& thread_name) PURE; + const std::string& thread_name, + Event::Dispatcher& dispatcher) PURE; /** * Tell the GuardDog to forget about this WatchDog. diff --git a/include/envoy/server/watchdog.h b/include/envoy/server/watchdog.h index cd76f552e244..86f9c0c23b31 100644 --- a/include/envoy/server/watchdog.h +++ b/include/envoy/server/watchdog.h @@ -3,7 +3,6 @@ #include #include "envoy/common/pure.h" -#include "envoy/event/dispatcher.h" #include "envoy/thread/thread.h" namespace Envoy { @@ -19,21 +18,12 @@ class WatchDog { public: virtual ~WatchDog() = default; - /** - * Start a recurring touch timer in the dispatcher passed as argument. - * - * This will automatically call the touch() method at the interval specified - * during construction. - * - * The timer object is stored within the WatchDog object. It will go away if - * the object goes out of scope and stop the timer. - */ - virtual void startWatchdog(Event::Dispatcher& dispatcher) PURE; - /** * Manually indicate that you are still alive by calling this. * - * This can be used if this is later used on a thread where there is no dispatcher. + * When the watchdog is registered with a dispatcher, the dispatcher will periodically call this + * method to indicate the thread is still alive. It should be called directly by the application + * code in cases where the watchdog is not registered with a dispatcher. */ virtual void touch() PURE; virtual Thread::ThreadId threadId() const PURE; diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index c0beef89afa6..803ff4558ad5 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -58,6 +58,13 @@ DispatcherImpl::DispatcherImpl(const std::string& name, Buffer::WatermarkFactory DispatcherImpl::~DispatcherImpl() { FatalErrorHandler::removeFatalErrorHandler(*this); } +void DispatcherImpl::registerWatchdog(const Server::WatchDogSharedPtr& watchdog, + std::chrono::milliseconds min_touch_interval) { + ASSERT(!watchdog_registration_, "Each dispatcher can have at most one registered watchdog."); + watchdog_registration_ = + std::make_unique(watchdog, *scheduler_, min_touch_interval, *this); +} + void DispatcherImpl::initializeStats(Stats::Scope& scope, const absl::optional& prefix) { const std::string effective_prefix = prefix.has_value() ? *prefix : absl::StrCat(name_, "."); @@ -150,7 +157,13 @@ Network::DnsResolverSharedPtr DispatcherImpl::createDnsResolver( FileEventPtr DispatcherImpl::createFileEvent(os_fd_t fd, FileReadyCb cb, FileTriggerType trigger, uint32_t events) { ASSERT(isThreadSafe()); - return FileEventPtr{new FileEventImpl(*this, fd, cb, trigger, events)}; + return FileEventPtr{new FileEventImpl( + *this, fd, + [this, cb](uint32_t events) { + touchWatchdog(); + cb(events); + }, + trigger, events)}; } Filesystem::WatcherPtr DispatcherImpl::createFilesystemWatcher() { @@ -179,11 +192,19 @@ TimerPtr DispatcherImpl::createTimer(TimerCb cb) { Event::SchedulableCallbackPtr DispatcherImpl::createSchedulableCallback(std::function cb) { ASSERT(isThreadSafe()); - return base_scheduler_.createSchedulableCallback(cb); + return base_scheduler_.createSchedulableCallback([this, cb]() { + touchWatchdog(); + cb(); + }); } TimerPtr DispatcherImpl::createTimerInternal(TimerCb cb) { - return scheduler_->createTimer(cb, *this); + return scheduler_->createTimer( + [this, cb]() { + touchWatchdog(); + cb(); + }, + *this); } void DispatcherImpl::deferredDelete(DeferredDeletablePtr&& to_delete) { @@ -257,5 +278,11 @@ void DispatcherImpl::runPostCallbacks() { } } +void DispatcherImpl::touchWatchdog() { + if (watchdog_registration_) { + watchdog_registration_->touchWatchdog(); + } +} + } // namespace Event } // namespace Envoy diff --git a/source/common/event/dispatcher_impl.h b/source/common/event/dispatcher_impl.h index c01767f40730..62c10920d7fe 100644 --- a/source/common/event/dispatcher_impl.h +++ b/source/common/event/dispatcher_impl.h @@ -42,6 +42,8 @@ class DispatcherImpl : Logger::Loggable, // Event::Dispatcher const std::string& name() override { return name_; } + void registerWatchdog(const Server::WatchDogSharedPtr& watchdog, + std::chrono::milliseconds min_touch_interval) override; TimeSource& timeSource() override { return api_.timeSource(); } void initializeStats(Stats::Scope& scope, const absl::optional& prefix) override; void clearDeferredDeleteList() override; @@ -93,9 +95,36 @@ class DispatcherImpl : Logger::Loggable, } private: + // Holds a reference to the watchdog registered with this dispatcher and the timer used to ensure + // that the dog is touched periodically. + class WatchdogRegistration { + public: + WatchdogRegistration(const Server::WatchDogSharedPtr& watchdog, Scheduler& scheduler, + std::chrono::milliseconds timer_interval, Dispatcher& dispatcher) + : watchdog_(watchdog), timer_interval_(timer_interval) { + touch_timer_ = scheduler.createTimer( + [this]() -> void { + watchdog_->touch(); + touch_timer_->enableTimer(timer_interval_); + }, + dispatcher); + touch_timer_->enableTimer(timer_interval_); + } + + void touchWatchdog() { watchdog_->touch(); } + + private: + Server::WatchDogSharedPtr watchdog_; + const std::chrono::milliseconds timer_interval_; + TimerPtr touch_timer_; + }; + using WatchdogRegistrationPtr = std::unique_ptr; + TimerPtr createTimerInternal(TimerCb cb); void updateApproximateMonotonicTimeInternal(); void runPostCallbacks(); + // Helper used to touch the watchdog after most schedulable, fd, and timer callbacks. + void touchWatchdog(); // Validate that an operation is thread safe, i.e. it's invoked on the same thread that the // dispatcher run loop is executing on. We allow run_tid_ to be empty for tests where we don't @@ -122,6 +151,7 @@ class DispatcherImpl : Logger::Loggable, const ScopeTrackedObject* current_object_{}; bool deferred_deleting_{}; MonotonicTime approximate_monotonic_time_; + WatchdogRegistrationPtr watchdog_registration_; }; } // namespace Event diff --git a/source/server/BUILD b/source/server/BUILD index 0b0ea13d6e75..5e4ee63d9fae 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -113,6 +113,7 @@ envoy_cc_library( ":watchdog_lib", "//include/envoy/api:api_interface", "//include/envoy/common:time_interface", + "//include/envoy/event:dispatcher_interface", "//include/envoy/event:timer_interface", "//include/envoy/server:configuration_interface", "//include/envoy/server:guarddog_config_interface", @@ -473,11 +474,9 @@ envoy_cc_library( envoy_cc_library( name = "watchdog_lib", - srcs = ["watchdog_impl.cc"], hdrs = ["watchdog_impl.h"], deps = [ "//include/envoy/common:time_interface", - "//include/envoy/event:dispatcher_interface", "//include/envoy/server:watchdog_interface", "//source/common/common:assert_lib", ], diff --git a/source/server/guarddog_impl.cc b/source/server/guarddog_impl.cc index c831b198a1bf..4a49c593166d 100644 --- a/source/server/guarddog_impl.cc +++ b/source/server/guarddog_impl.cc @@ -185,19 +185,22 @@ void GuardDogImpl::step() { } WatchDogSharedPtr GuardDogImpl::createWatchDog(Thread::ThreadId thread_id, - const std::string& thread_name) { + const std::string& thread_name, + Event::Dispatcher& dispatcher) { // Timer started by WatchDog will try to fire at 1/2 of the interval of the // minimum timeout specified. loop_interval_ is const so all shared state // accessed out of the locked section below is const (time_source_ has no // state). const auto wd_interval = loop_interval_ / 2; - auto new_watchdog = std::make_shared(std::move(thread_id), wd_interval); + auto new_watchdog = std::make_shared(std::move(thread_id)); WatchedDogPtr watched_dog = std::make_unique(stats_scope_, thread_name, new_watchdog); new_watchdog->touch(); { Thread::LockGuard guard(wd_lock_); watched_dogs_.push_back(std::move(watched_dog)); } + dispatcher.registerWatchdog(new_watchdog, wd_interval); + new_watchdog->touch(); return new_watchdog; } diff --git a/source/server/guarddog_impl.h b/source/server/guarddog_impl.h index ebc4040e1c81..50e72347441d 100644 --- a/source/server/guarddog_impl.h +++ b/source/server/guarddog_impl.h @@ -92,8 +92,8 @@ class GuardDogImpl : public GuardDog { } // Server::GuardDog - WatchDogSharedPtr createWatchDog(Thread::ThreadId thread_id, - const std::string& thread_name) override; + WatchDogSharedPtr createWatchDog(Thread::ThreadId thread_id, const std::string& thread_name, + Event::Dispatcher& dispatcher) override; void stopWatching(WatchDogSharedPtr wd) override; private: diff --git a/source/server/server.cc b/source/server/server.cc index f0942407c4e8..3af9be2bc127 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -682,8 +682,7 @@ void InstanceImpl::run() { // Run the main dispatch loop waiting to exit. ENVOY_LOG(info, "starting main dispatch loop"); auto watchdog = main_thread_guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), - "main_thread"); - watchdog->startWatchdog(*dispatcher_); + "main_thread", *dispatcher_); dispatcher_->post([this] { notifyCallbacksForStage(Stage::Startup); }); dispatcher_->run(Event::Dispatcher::RunType::Block); ENVOY_LOG(info, "main dispatch loop exited"); diff --git a/source/server/watchdog_impl.cc b/source/server/watchdog_impl.cc deleted file mode 100644 index 8a7e53b7880c..000000000000 --- a/source/server/watchdog_impl.cc +++ /dev/null @@ -1,19 +0,0 @@ -#include "server/watchdog_impl.h" - -#include "envoy/event/dispatcher.h" - -#include "common/common/assert.h" - -namespace Envoy { -namespace Server { - -void WatchDogImpl::startWatchdog(Event::Dispatcher& dispatcher) { - timer_ = dispatcher.createTimer([this]() -> void { - this->touch(); - timer_->enableTimer(timer_interval_); - }); - timer_->enableTimer(timer_interval_); -} - -} // namespace Server -} // namespace Envoy diff --git a/source/server/watchdog_impl.h b/source/server/watchdog_impl.h index a18034745885..4ebe954a9f52 100644 --- a/source/server/watchdog_impl.h +++ b/source/server/watchdog_impl.h @@ -1,10 +1,7 @@ #pragma once #include -#include -#include "envoy/common/time.h" -#include "envoy/event/dispatcher.h" #include "envoy/server/watchdog.h" namespace Envoy { @@ -17,17 +14,15 @@ namespace Server { class WatchDogImpl : public WatchDog { public: /** - * @param interval WatchDog timer interval (used after startWatchdog()) + * @param thread_id ThreadId of the monitored thread */ - WatchDogImpl(Thread::ThreadId thread_id, std::chrono::milliseconds interval) - : thread_id_(thread_id), timer_interval_(interval) {} + WatchDogImpl(Thread::ThreadId thread_id) : thread_id_(thread_id) {} Thread::ThreadId threadId() const override { return thread_id_; } // Used by GuardDogImpl determine if the watchdog was touched recently and reset the touch status. bool getTouchedAndReset() { return touched_.exchange(false, std::memory_order_relaxed); } // Server::WatchDog - void startWatchdog(Event::Dispatcher& dispatcher) override; void touch() override { // Set touched_ if not already set. bool expected = false; @@ -37,8 +32,6 @@ class WatchDogImpl : public WatchDog { private: const Thread::ThreadId thread_id_; std::atomic touched_{false}; - Event::TimerPtr timer_; - const std::chrono::milliseconds timer_interval_; }; } // namespace Server diff --git a/source/server/worker_impl.cc b/source/server/worker_impl.cc index b659ffec6e06..ff02ea9e08b2 100644 --- a/source/server/worker_impl.cc +++ b/source/server/worker_impl.cc @@ -128,9 +128,8 @@ void WorkerImpl::threadRoutine(GuardDog& guard_dog) { // The watch dog must be created after the dispatcher starts running and has post events flushed, // as this is when TLS stat scopes start working. dispatcher_->post([this, &guard_dog]() { - watch_dog_ = - guard_dog.createWatchDog(api_.threadFactory().currentThreadId(), dispatcher_->name()); - watch_dog_->startWatchdog(*dispatcher_); + watch_dog_ = guard_dog.createWatchDog(api_.threadFactory().currentThreadId(), + dispatcher_->name(), *dispatcher_); }); dispatcher_->run(Event::Dispatcher::RunType::Block); ENVOY_LOG(debug, "worker exited dispatch loop"); diff --git a/test/common/event/BUILD b/test/common/event/BUILD index 882d2afa9506..2eaba5a4f461 100644 --- a/test/common/event/BUILD +++ b/test/common/event/BUILD @@ -19,6 +19,7 @@ envoy_cc_test( "//source/common/event:dispatcher_lib", "//source/common/stats:isolated_store_lib", "//test/mocks:common_lib", + "//test/mocks/server:watch_dog_mocks", "//test/mocks/stats:stats_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:test_runtime_lib", diff --git a/test/common/event/dispatcher_impl_test.cc b/test/common/event/dispatcher_impl_test.cc index c6c6a7a96272..cbb0119f0ff2 100644 --- a/test/common/event/dispatcher_impl_test.cc +++ b/test/common/event/dispatcher_impl_test.cc @@ -3,6 +3,7 @@ #include "envoy/thread/thread.h" #include "common/api/api_impl.h" +#include "common/api/os_sys_calls_impl.h" #include "common/common/lock_guard.h" #include "common/event/deferred_task.h" #include "common/event/dispatcher_impl.h" @@ -10,6 +11,7 @@ #include "common/stats/isolated_store_impl.h" #include "test/mocks/common.h" +#include "test/mocks/server/watch_dog.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/test_runtime.h" @@ -1147,6 +1149,75 @@ TEST_F(TimerUtilsTest, TimerValueConversion) { checkConversion(std::chrono::milliseconds(600014), 600, 14000); } +class DispatcherWithWatchdogTest : public testing::Test { +protected: + DispatcherWithWatchdogTest() + : api_(Api::createApiForTest(time_system_)), + dispatcher_(api_->allocateDispatcher("test_thread")), + os_sys_calls_(Api::OsSysCallsSingleton::get()) { + dispatcher_->registerWatchdog(watchdog_, min_touch_interval_); + } + + Event::SimulatedTimeSystem time_system_; + Api::ApiPtr api_; + DispatcherPtr dispatcher_; + Api::OsSysCalls& os_sys_calls_; + std::shared_ptr watchdog_ = std::make_shared(); + std::chrono::milliseconds min_touch_interval_ = std::chrono::seconds(10); +}; + +// The dispatcher creates a periodic touch timer for each registered watchdog. +TEST_F(DispatcherWithWatchdogTest, PeriodicTouchTimer) { + // Advance by min_touch_interval_, verify that watchdog_ is touched. + EXPECT_CALL(*watchdog_, touch()); + time_system_.advanceTimeAndRun(min_touch_interval_, *dispatcher_, Dispatcher::RunType::NonBlock); + + // Advance by min_touch_interval_ again, verify that watchdog_ is touched. + EXPECT_CALL(*watchdog_, touch()); + time_system_.advanceTimeAndRun(min_touch_interval_, *dispatcher_, Dispatcher::RunType::NonBlock); +} + +TEST_F(DispatcherWithWatchdogTest, TouchBeforeSchedulableCallback) { + ReadyWatcher watcher; + + auto cb = dispatcher_->createSchedulableCallback([&]() { watcher.ready(); }); + cb->scheduleCallbackCurrentIteration(); + + InSequence s; + EXPECT_CALL(*watchdog_, touch()); + EXPECT_CALL(watcher, ready()); + dispatcher_->run(Dispatcher::RunType::NonBlock); +} + +TEST_F(DispatcherWithWatchdogTest, TouchBeforeTimer) { + ReadyWatcher watcher; + + auto timer = dispatcher_->createTimer([&]() { watcher.ready(); }); + timer->enableTimer(std::chrono::milliseconds(0)); + + InSequence s; + EXPECT_CALL(*watchdog_, touch()); + EXPECT_CALL(watcher, ready()); + dispatcher_->run(Dispatcher::RunType::NonBlock); +} + +TEST_F(DispatcherWithWatchdogTest, TouchBeforeFdEvent) { + os_fd_t fd = os_sys_calls_.socket(AF_INET6, SOCK_STREAM, 0).rc_; + ASSERT_TRUE(SOCKET_VALID(fd)); + + ReadyWatcher watcher; + + const FileTriggerType trigger = Event::PlatformDefaultTriggerType; + Event::FileEventPtr file_event = dispatcher_->createFileEvent( + fd, [&](uint32_t) -> void { watcher.ready(); }, trigger, FileReadyType::Read); + file_event->activate(FileReadyType::Read); + + InSequence s; + EXPECT_CALL(*watchdog_, touch()); + EXPECT_CALL(watcher, ready()); + dispatcher_->run(Dispatcher::RunType::NonBlock); +} + } // namespace } // namespace Event } // namespace Envoy diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index a89d3147ebf9..861f0a10e340 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -102,6 +102,8 @@ class MockDispatcher : public Dispatcher { } // Event::Dispatcher + MOCK_METHOD(void, registerWatchdog, + (const Server::WatchDogSharedPtr&, std::chrono::milliseconds)); MOCK_METHOD(void, initializeStats, (Stats::Scope&, const absl::optional&)); MOCK_METHOD(void, clearDeferredDeleteList, ()); MOCK_METHOD(Network::ServerConnection*, createServerConnection_, ()); diff --git a/test/mocks/event/wrapped_dispatcher.h b/test/mocks/event/wrapped_dispatcher.h index db9fd1212c14..74e935328984 100644 --- a/test/mocks/event/wrapped_dispatcher.h +++ b/test/mocks/event/wrapped_dispatcher.h @@ -20,6 +20,11 @@ class WrappedDispatcher : public Dispatcher { // Event::Dispatcher const std::string& name() override { return impl_.name(); } + void registerWatchdog(const Server::WatchDogSharedPtr& watchdog, + std::chrono::milliseconds min_touch_interval) override { + impl_.registerWatchdog(watchdog, min_touch_interval); + } + TimeSource& timeSource() override { return impl_.timeSource(); } void initializeStats(Stats::Scope& scope, const absl::optional& prefix) override { @@ -109,4 +114,4 @@ class WrappedDispatcher : public Dispatcher { }; } // namespace Event -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/test/mocks/server/guard_dog.cc b/test/mocks/server/guard_dog.cc index e5e552c234f5..4a9263ffa8ca 100644 --- a/test/mocks/server/guard_dog.cc +++ b/test/mocks/server/guard_dog.cc @@ -11,7 +11,7 @@ using ::testing::NiceMock; using ::testing::Return; MockGuardDog::MockGuardDog() : watch_dog_(new NiceMock()) { - ON_CALL(*this, createWatchDog(_, _)).WillByDefault(Return(watch_dog_)); + ON_CALL(*this, createWatchDog(_, _, _)).WillByDefault(Return(watch_dog_)); } MockGuardDog::~MockGuardDog() = default; diff --git a/test/mocks/server/guard_dog.h b/test/mocks/server/guard_dog.h index fed29041db3e..4dbe70ea36a6 100644 --- a/test/mocks/server/guard_dog.h +++ b/test/mocks/server/guard_dog.h @@ -14,7 +14,8 @@ class MockGuardDog : public GuardDog { // Server::GuardDog MOCK_METHOD(WatchDogSharedPtr, createWatchDog, - (Thread::ThreadId thread_id, const std::string& thread_name)); + (Thread::ThreadId thread_id, const std::string& thread_name, + Event::Dispatcher& dispatcher)); MOCK_METHOD(void, stopWatching, (WatchDogSharedPtr wd)); std::shared_ptr watch_dog_; diff --git a/test/mocks/server/watch_dog.h b/test/mocks/server/watch_dog.h index a658dd33ac94..30b9810927eb 100644 --- a/test/mocks/server/watch_dog.h +++ b/test/mocks/server/watch_dog.h @@ -12,7 +12,6 @@ class MockWatchDog : public WatchDog { ~MockWatchDog() override; // Server::WatchDog - MOCK_METHOD(void, startWatchdog, (Event::Dispatcher & dispatcher)); MOCK_METHOD(void, touch, ()); MOCK_METHOD(Thread::ThreadId, threadId, (), (const)); }; diff --git a/test/server/BUILD b/test/server/BUILD index 36d68688f8d9..1f0c7018deda 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -136,6 +136,7 @@ envoy_cc_test( "//source/common/stats:stats_lib", "//source/server:guarddog_lib", "//test/mocks:common_lib", + "//test/mocks/event:event_mocks", "//test/mocks/server:watchdog_config_mocks", "//test/mocks/stats:stats_mocks", "//test/test_common:registry_lib", diff --git a/test/server/guarddog_impl_test.cc b/test/server/guarddog_impl_test.cc index 4e45ffa83716..9641c5afc2a4 100644 --- a/test/server/guarddog_impl_test.cc +++ b/test/server/guarddog_impl_test.cc @@ -18,6 +18,7 @@ #include "server/guarddog_impl.h" #include "test/mocks/common.h" +#include "test/mocks/event/mocks.h" #include "test/mocks/server/watchdog_config.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/registry.h" @@ -93,6 +94,7 @@ class GuardDogTestBase : public testing::TestWithParam { std::unique_ptr time_system_; Stats::TestUtil::TestStore stats_store_; Api::ApiPtr api_; + NiceMock mock_dispatcher_; std::unique_ptr guard_dog_; }; @@ -117,10 +119,11 @@ class GuardDogDeathTest : public GuardDogTestBase { * This does everything but the final forceCheckForTest() that should cause * death for the single kill case. */ - void SetupForDeath() { + void setupForDeath() { InSequence s; initGuardDog(fakestats_, config_kill_); - unpet_dog_ = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + unpet_dog_ = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread", + mock_dispatcher_); dogs_.emplace_back(unpet_dog_); guard_dog_->forceCheckForTest(); time_system_->advanceTimeWait(std::chrono::milliseconds(99)); // 1 ms shy of death. @@ -130,15 +133,15 @@ class GuardDogDeathTest : public GuardDogTestBase { * This does everything but the final forceCheckForTest() that should cause * death for the multiple kill case. */ - void SetupForMultiDeath() { + void setupForMultiDeath() { InSequence s; initGuardDog(fakestats_, config_multikill_); - auto unpet_dog_ = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto unpet_dog_ = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); dogs_.emplace_back(unpet_dog_); guard_dog_->forceCheckForTest(); - auto second_dog_ = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto second_dog_ = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); dogs_.emplace_back(second_dog_); guard_dog_->forceCheckForTest(); time_system_->advanceTimeWait(std::chrono::milliseconds(499)); // 1 ms shy of multi-death. @@ -154,7 +157,8 @@ class GuardDogDeathTest : public GuardDogTestBase { // Creates 5 watchdogs. for (int i = 0; i < 5; ++i) { - auto dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread", + mock_dispatcher_); dogs_.emplace_back(dog); if (i == 0) { @@ -193,7 +197,7 @@ INSTANTIATE_TEST_SUITE_P( TEST_P(GuardDogDeathTest, KillDeathTest) { // Is it German for "The Function"? Almost... auto die_function = [&]() -> void { - SetupForDeath(); + setupForDeath(); time_system_->advanceTimeWait(std::chrono::milliseconds(401)); // 400 ms past death. guard_dog_->forceCheckForTest(); }; @@ -207,12 +211,12 @@ TEST_P(GuardDogAlmostDeadTest, KillNoFinalCheckTest) { // This does everything the death test does, except allow enough time to // expire to reach the death panic. The death test does not verify that there // was not a crash *before* the expected line, so this test checks that. - SetupForDeath(); + setupForDeath(); } TEST_P(GuardDogDeathTest, MultiKillDeathTest) { auto die_function = [&]() -> void { - SetupForMultiDeath(); + setupForMultiDeath(); time_system_->advanceTimeWait(std::chrono::milliseconds(2)); // 1 ms past multi-death. guard_dog_->forceCheckForTest(); }; @@ -223,7 +227,7 @@ TEST_P(GuardDogAlmostDeadTest, MultiKillNoFinalCheckTest) { // This does everything the death test does not except the final force check that // should actually result in dying. The death test does not verify that there // was not a crash *before* the expected line, so this test checks that. - SetupForMultiDeath(); + setupForMultiDeath(); } TEST_P(GuardDogDeathTest, MultiKillThresholdDeathTest) { @@ -259,9 +263,10 @@ TEST_P(GuardDogAlmostDeadTest, NearDeathTest) { // there is no death. The positive case is covered in MultiKillDeathTest. InSequence s; initGuardDog(fakestats_, config_multikill_); - auto unpet_dog = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); - auto pet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto unpet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); + auto pet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread", + mock_dispatcher_); // This part "waits" 600 milliseconds while one dog is touched every 100, and // the other is not. 600ms is over the threshold of 500ms for multi-kill but // only one is nonresponsive, so there should be no kill (single kill @@ -307,8 +312,8 @@ TEST_P(GuardDogMissTest, MissTest) { // This test checks the actual collected statistics after doing some timer // advances that should and shouldn't increment the counters. initGuardDog(stats_store_, config_miss_); - auto unpet_dog = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto unpet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); // We'd better start at 0: checkMiss(0, "MissTest check 1"); @@ -333,8 +338,8 @@ TEST_P(GuardDogMissTest, MegaMissTest) { // This test checks the actual collected statistics after doing some timer // advances that should and shouldn't increment the counters. initGuardDog(stats_store_, config_mega_); - auto unpet_dog = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto unpet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); // We'd better start at 0: checkMegaMiss(0, "MegaMissTest check 1"); @@ -360,8 +365,8 @@ TEST_P(GuardDogMissTest, MissCountTest) { // spurious condition_variable wakeup causes the counter to get incremented // more than it should be. initGuardDog(stats_store_, config_miss_); - auto sometimes_pet_dog = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto sometimes_pet_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); // These steps are executed once without ever touching the watchdog. // Then the last step is to touch the watchdog and repeat the steps. @@ -424,8 +429,8 @@ TEST_P(GuardDogTestBase, WatchDogThreadIdTest) { NiceMock stats; NiceMock config(100, 90, 1000, 500, 0, std::vector{}); initGuardDog(stats, config); - auto watched_dog = - guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), "test_thread"); + auto watched_dog = guard_dog_->createWatchDog(api_->threadFactory().currentThreadId(), + "test_thread", mock_dispatcher_); EXPECT_EQ(watched_dog->threadId().debugString(), api_->threadFactory().currentThreadId().debugString()); guard_dog_->stopWatching(watched_dog); @@ -590,7 +595,7 @@ class GuardDogActionsTest : public GuardDogTestBase { void setupFirstDog(const NiceMock& config, Thread::ThreadId tid) { initGuardDog(fake_stats_, config); - first_dog_ = guard_dog_->createWatchDog(tid, "test_thread"); + first_dog_ = guard_dog_->createWatchDog(tid, "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); } @@ -612,7 +617,7 @@ TEST_P(GuardDogActionsTest, MissShouldOnlyReportRelevantThreads) { const NiceMock config(100, DISABLE_MEGAMISS, DISABLE_KILL, DISABLE_MULTIKILL, 0, getActionsConfig()); setupFirstDog(config, Thread::ThreadId(10)); - second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); time_system_->advanceTimeWait(std::chrono::milliseconds(50)); second_dog_->touch(); @@ -630,8 +635,8 @@ TEST_P(GuardDogActionsTest, MissShouldBeAbleToReportMultipleThreads) { const NiceMock config(100, DISABLE_MEGAMISS, DISABLE_KILL, DISABLE_MULTIKILL, 0, getActionsConfig()); initGuardDog(fake_stats_, config); - first_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread"); - second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + first_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread", mock_dispatcher_); + second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); first_dog_->touch(); second_dog_->touch(); @@ -674,7 +679,7 @@ TEST_P(GuardDogActionsTest, MegaMissShouldOnlyReportRelevantThreads) { const NiceMock config(DISABLE_MISS, 100, DISABLE_KILL, DISABLE_MULTIKILL, 0, getActionsConfig()); setupFirstDog(config, Thread::ThreadId(10)); - second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); time_system_->advanceTimeWait(std::chrono::milliseconds(50)); second_dog_->touch(); @@ -692,8 +697,8 @@ TEST_P(GuardDogActionsTest, MegaMissShouldBeAbleToReportMultipleThreads) { const NiceMock config(DISABLE_MISS, 100, DISABLE_KILL, DISABLE_MULTIKILL, 0, getActionsConfig()); initGuardDog(fake_stats_, config); - first_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread"); - second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + first_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread", mock_dispatcher_); + second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); first_dog_->touch(); second_dog_->touch(); @@ -740,8 +745,10 @@ TEST_P(GuardDogActionsTest, ShouldRespectEventPriority) { auto kill_function = [&]() -> void { const NiceMock config(100, 100, 100, 100, 0, getActionsConfig()); initGuardDog(fake_stats_, config); - auto first_dog = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread"); - auto second_dog = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + auto first_dog = + guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread", mock_dispatcher_); + auto second_dog = + guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); time_system_->advanceTimeWait(std::chrono::milliseconds(101)); guard_dog_->forceCheckForTest(); @@ -755,8 +762,10 @@ TEST_P(GuardDogActionsTest, ShouldRespectEventPriority) { const NiceMock config(100, 100, DISABLE_KILL, 100, 0, getActionsConfig()); initGuardDog(fake_stats_, config); - auto first_dog = guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread"); - auto second_dog = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + auto first_dog = + guard_dog_->createWatchDog(Thread::ThreadId(10), "test_thread", mock_dispatcher_); + auto second_dog = + guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); time_system_->advanceTimeWait(std::chrono::milliseconds(101)); guard_dog_->forceCheckForTest(); @@ -790,7 +799,7 @@ TEST_P(GuardDogActionsTest, MultikillShouldTriggerGuardDogActions) { const NiceMock config(DISABLE_MISS, DISABLE_MEGAMISS, DISABLE_KILL, 100, 0, getActionsConfig()); setupFirstDog(config, Thread::ThreadId(10)); - second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread"); + second_dog_ = guard_dog_->createWatchDog(Thread::ThreadId(11), "test_thread", mock_dispatcher_); guard_dog_->forceCheckForTest(); time_system_->advanceTimeWait(std::chrono::milliseconds(101)); guard_dog_->forceCheckForTest(); From 6c7bcb6d94cac10469b2bb691d3f759a86fd86e7 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 4 Nov 2020 18:57:38 +0000 Subject: [PATCH 033/117] docs/ci: Update docs artifact publishing in CI (#13826) Signed-off-by: Ryan Northey --- ci/upload_gcs_artifact.sh | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/ci/upload_gcs_artifact.sh b/ci/upload_gcs_artifact.sh index 755abf3a39d5..44e6d685ee15 100755 --- a/ci/upload_gcs_artifact.sh +++ b/ci/upload_gcs_artifact.sh @@ -18,9 +18,28 @@ if [ ! -d "${SOURCE_DIRECTORY}" ]; then exit 1 fi -BRANCH=${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER:-${BUILD_SOURCEBRANCHNAME}} -GCS_LOCATION="${GCS_ARTIFACT_BUCKET}/${BRANCH}/${TARGET_SUFFIX}" +if [[ "$TARGET_SUFFIX" == "docs" ]]; then + # docs upload to the last commit sha (first 7 chars) in the developers branch + UPLOAD_PATH="$(git log --pretty=%P -n 1 | cut -d' ' -f2 | head -c7)" +else + UPLOAD_PATH="${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER:-${BUILD_SOURCEBRANCHNAME}}" +fi + +GCS_LOCATION="${GCS_ARTIFACT_BUCKET}/${UPLOAD_PATH}/${TARGET_SUFFIX}" echo "Uploading to gs://${GCS_LOCATION} ..." gsutil -mq rsync -dr "${SOURCE_DIRECTORY}" "gs://${GCS_LOCATION}" + +# For docs uploads, add a redirect `PR_NUMBER` -> `COMMIT_SHA` +if [[ "$TARGET_SUFFIX" == "docs" ]]; then + REDIRECT_PATH="${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER:-${BUILD_SOURCEBRANCHNAME}}" + TMP_REDIRECT="/tmp/docredirect/${REDIRECT_PATH}/docs" + mkdir -p "$TMP_REDIRECT" + echo "" \ + > "${TMP_REDIRECT}/index.html" + GCS_REDIRECT="${GCS_ARTIFACT_BUCKET}/${REDIRECT_PATH}" + echo "Uploading redirect to gs://${GCS_REDIRECT} ..." + gsutil -h "Cache-Control:no-cache,max-age=0" -mq rsync -dr "/tmp/docredirect/${REDIRECT_PATH}" "gs://${GCS_REDIRECT}" +fi + echo "Artifacts uploaded to: https://storage.googleapis.com/${GCS_LOCATION}/index.html" From 01c45321514224ec1846e0c1e29dc8760e283871 Mon Sep 17 00:00:00 2001 From: Zach Reyes <39203661+zasweq@users.noreply.github.com> Date: Wed, 4 Nov 2020 14:15:55 -0500 Subject: [PATCH 034/117] [fuzz] Added coverage for onGoAway in gRPC Health Checking (#13822) * Added coverage for onGoAway in gRPC Health Checking Signed-off-by: Zach --- .../grpc_end_stream_headers | 49 +++++++++++++ .../health_check_corpus/grpc_raise-go-away | 66 +++++++++++++++++ .../http_headers-with-connection | 51 +++++++++++++ .../http_headers-with-proxy-connection | 51 +++++++++++++ .../health_check_corpus/http_raise-go-away | 69 ++++++++++++++++++ .../health_check_corpus/tcp_raise-go-away | 71 +++++++++++++++++++ test/common/upstream/health_check_fuzz.cc | 4 ++ test/common/upstream/health_check_fuzz.h | 8 ++- test/common/upstream/health_check_fuzz.proto | 6 ++ 9 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 test/common/upstream/health_check_corpus/grpc_end_stream_headers create mode 100644 test/common/upstream/health_check_corpus/grpc_raise-go-away create mode 100644 test/common/upstream/health_check_corpus/http_headers-with-connection create mode 100644 test/common/upstream/health_check_corpus/http_headers-with-proxy-connection create mode 100644 test/common/upstream/health_check_corpus/http_raise-go-away create mode 100644 test/common/upstream/health_check_corpus/tcp_raise-go-away diff --git a/test/common/upstream/health_check_corpus/grpc_end_stream_headers b/test/common/upstream/health_check_corpus/grpc_end_stream_headers new file mode 100644 index 000000000000..17c167a285eb --- /dev/null +++ b/test/common/upstream/health_check_corpus/grpc_end_stream_headers @@ -0,0 +1,49 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + grpc_health_check { + service_name: "service" + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "content-type" + value: "application/grpc" + } + } + status: 200 + } + } + } + } +} diff --git a/test/common/upstream/health_check_corpus/grpc_raise-go-away b/test/common/upstream/health_check_corpus/grpc_raise-go-away new file mode 100644 index 000000000000..f2df96683e38 --- /dev/null +++ b/test/common/upstream/health_check_corpus/grpc_raise-go-away @@ -0,0 +1,66 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + grpc_health_check { + service_name: "service" + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "content-type" + value: "application/grpc" + } + } + status: 200 + } + grpc_respond_bytes { + status: SERVING + chunk_size_for_structured_response: 3 + } + grpc_respond_trailers { + trailers { + headers { + key: "grpc-status" + value: "0" + } + } + } + } + } +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_go_away: ERROR +} diff --git a/test/common/upstream/health_check_corpus/http_headers-with-connection b/test/common/upstream/health_check_corpus/http_headers-with-connection new file mode 100644 index 000000000000..53e7d349ec1c --- /dev/null +++ b/test/common/upstream/health_check_corpus/http_headers-with-connection @@ -0,0 +1,51 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + no_traffic_interval { + seconds: 1 + } + interval_jitter { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + http_health_check { + path: "/healthcheck" + service_name_matcher { + prefix: "locations" + } + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "connection" + value: "close" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + + } + } + } +} diff --git a/test/common/upstream/health_check_corpus/http_headers-with-proxy-connection b/test/common/upstream/health_check_corpus/http_headers-with-proxy-connection new file mode 100644 index 000000000000..d2a35501610b --- /dev/null +++ b/test/common/upstream/health_check_corpus/http_headers-with-proxy-connection @@ -0,0 +1,51 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + no_traffic_interval { + seconds: 1 + } + interval_jitter { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + http_health_check { + path: "/healthcheck" + service_name_matcher { + prefix: "locations" + } + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "proxy-connection" + value: "close" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + + } + } + } +} diff --git a/test/common/upstream/health_check_corpus/http_raise-go-away b/test/common/upstream/health_check_corpus/http_raise-go-away new file mode 100644 index 000000000000..60862f80bf95 --- /dev/null +++ b/test/common/upstream/health_check_corpus/http_raise-go-away @@ -0,0 +1,69 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + http_health_check { + path: "/healthcheck" + service_name_matcher { + prefix: "locations" + } + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "content-type" + value: "application/grpc" + } + } + status: 200 + } + grpc_respond_bytes { + status: SERVING + chunk_size_for_structured_response: 3 + } + grpc_respond_trailers { + trailers { + headers { + key: "grpc-status" + value: "0" + } + } + } + } + } +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_go_away: ERROR +} diff --git a/test/common/upstream/health_check_corpus/tcp_raise-go-away b/test/common/upstream/health_check_corpus/tcp_raise-go-away new file mode 100644 index 000000000000..518c9a716d5f --- /dev/null +++ b/test/common/upstream/health_check_corpus/tcp_raise-go-away @@ -0,0 +1,71 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + tcp_health_check { + send { + text: "01" + } + receive [{ + text: "02" + }] + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + headers { + headers { + key: ":status" + value: "200" + } + headers { + key: "content-type" + value: "application/grpc" + } + } + status: 200 + } + grpc_respond_bytes { + status: SERVING + chunk_size_for_structured_response: 3 + } + grpc_respond_trailers { + trailers { + headers { + key: "grpc-status" + value: "0" + } + } + } + } + } +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_go_away: ERROR +} diff --git a/test/common/upstream/health_check_fuzz.cc b/test/common/upstream/health_check_fuzz.cc index b6cd1171a8f2..17cc1a2074a9 100644 --- a/test/common/upstream/health_check_fuzz.cc +++ b/test/common/upstream/health_check_fuzz.cc @@ -554,6 +554,10 @@ void HealthCheckFuzz::replay(const test::common::upstream::HealthCheckTestCase& raiseEvent(getEventTypeFromProto(event.raise_event()), last_action); break; } + case test::common::upstream::Action::kRaiseGoAway: { + raiseGoAway(event.raise_go_away() == test::common::upstream::RaiseGoAway::NO_ERROR); + break; + } default: break; } diff --git a/test/common/upstream/health_check_fuzz.h b/test/common/upstream/health_check_fuzz.h index 627e44ea1bcc..9749ef492ff5 100644 --- a/test/common/upstream/health_check_fuzz.h +++ b/test/common/upstream/health_check_fuzz.h @@ -28,6 +28,8 @@ class HealthCheckFuzz { virtual void triggerIntervalTimer(bool expect_client_create) PURE; virtual void triggerTimeoutTimer(bool last_action) PURE; virtual void raiseEvent(const Network::ConnectionEvent& event_type, bool last_action) PURE; + // Only implemented by gRPC, otherwise no-op + virtual void raiseGoAway(bool no_error) PURE; virtual ~HealthCheckFuzz() = default; @@ -45,6 +47,8 @@ class HttpHealthCheckFuzz : public HealthCheckFuzz, HttpHealthCheckerImplTestBas void triggerIntervalTimer(bool expect_client_create) override; void triggerTimeoutTimer(bool last_action) override; void raiseEvent(const Network::ConnectionEvent& event_type, bool last_action) override; + // No op + void raiseGoAway(bool) override {} ~HttpHealthCheckFuzz() override = default; // Determines whether the client gets reused or not after response @@ -59,6 +63,8 @@ class TcpHealthCheckFuzz : public HealthCheckFuzz, TcpHealthCheckerImplTestBase void triggerIntervalTimer(bool expect_client_create) override; void triggerTimeoutTimer(bool last_action) override; void raiseEvent(const Network::ConnectionEvent& event_type, bool last_action) override; + // No op + void raiseGoAway(bool) override {} ~TcpHealthCheckFuzz() override = default; // Determines whether the client gets reused or not after response @@ -78,7 +84,7 @@ class GrpcHealthCheckFuzz : public HealthCheckFuzz, public HealthCheckerTestBase void triggerIntervalTimer(bool expect_client_create) override; void triggerTimeoutTimer(bool last_action) override; void raiseEvent(const Network::ConnectionEvent& event_type, bool last_action) override; - void raiseGoAway(bool no_error); + void raiseGoAway(bool no_error) override; ~GrpcHealthCheckFuzz() override = default; // Determines whether the client gets reused or not after response diff --git a/test/common/upstream/health_check_fuzz.proto b/test/common/upstream/health_check_fuzz.proto index f4d0e26dcacb..d4a4dcd2c7bd 100644 --- a/test/common/upstream/health_check_fuzz.proto +++ b/test/common/upstream/health_check_fuzz.proto @@ -72,6 +72,11 @@ enum RaiseEvent { LOCAL_CLOSE = 2; } +enum RaiseGoAway { + NO_ERROR = 0; + ERROR = 1; +} + message Action { oneof action_selector { option (validate.required) = true; @@ -80,6 +85,7 @@ message Action { //TODO: respondBody, respondTrailers google.protobuf.Empty trigger_timeout_timer = 3; RaiseEvent raise_event = 4 [(validate.rules).enum.defined_only = true]; + RaiseGoAway raise_go_away = 5; } } From 359def3dd6c2e96e11d3ab394342475fee7744a1 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 4 Nov 2020 13:20:33 -0800 Subject: [PATCH 035/117] tls: fix detection of the upstream connection close event. (#13858) Fixes #13856. Signed-off-by: Piotr Sikora --- docs/root/version_history/current.rst | 1 + .../transport_sockets/tls/ssl_handshaker.h | 2 +- .../transport_sockets/tls/ssl_socket.cc | 10 +- .../transport_sockets/tls/ssl_socket_test.cc | 179 ++++++++++++++++++ 4 files changed, 190 insertions(+), 2 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 8dad9a35019e..4aa6280e69a3 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -28,6 +28,7 @@ Bug Fixes * http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. * http: sending CONNECT_ERROR for HTTP/2 where appropriate during CONNECT requests. * proxy_proto: fixed a bug where the wrong downstream address got sent to upstream connections. +* tls: fix detection of the upstream connection close event. * tls: fix read resumption after triggering buffer high-watermark and all remaining request/response bytes are stored in the SSL connection's internal buffers. * watchdog: touch the watchdog before most event loop operations to avoid misses when handling bursts of callbacks. diff --git a/source/extensions/transport_sockets/tls/ssl_handshaker.h b/source/extensions/transport_sockets/tls/ssl_handshaker.h index 8eaec861a8f1..50090f6f43a7 100644 --- a/source/extensions/transport_sockets/tls/ssl_handshaker.h +++ b/source/extensions/transport_sockets/tls/ssl_handshaker.h @@ -67,7 +67,7 @@ class SslHandshakerImpl : public Ssl::ConnectionInfo, public Ssl::Handshaker { // Ssl::Handshaker Network::PostIoAction doHandshake() override; - Ssl::SocketState state() { return state_; } + Ssl::SocketState state() const { return state_; } void setState(Ssl::SocketState state) { state_ = state; } SSL* ssl() const { return ssl_.get(); } Ssl::HandshakeCallbacks* handshakeCallbacks() { return handshake_callbacks_; } diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index 65db92e12d4e..50b2d27926e2 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -140,10 +140,18 @@ Network::IoResult SslSocket::doRead(Buffer::Instance& read_buffer) { case SSL_ERROR_WANT_READ: break; case SSL_ERROR_ZERO_RETURN: + // Graceful shutdown using close_notify TLS alert. end_stream = true; break; + case SSL_ERROR_SYSCALL: + if (result.error_.value() == 0) { + // Non-graceful shutdown by closing the underlying socket. + end_stream = true; + break; + } + FALLTHRU; case SSL_ERROR_WANT_WRITE: - // Renegotiation has started. We don't handle renegotiation so just fall through. + // Renegotiation has started. We don't handle renegotiation so just fall through. default: drainErrorQueue(); action = PostIoAction::Close; diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index 89a58bd4e8c6..8aa58b0cdc76 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -2540,6 +2540,185 @@ TEST_P(SslSocketTest, HalfClose) { dispatcher_->run(Event::Dispatcher::RunType::Block); } +TEST_P(SslSocketTest, ShutdownWithCloseNotify) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_certificates.pem" +)EOF"; + + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext server_tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(server_ctx_yaml), server_tls_context); + auto server_cfg = std::make_unique(server_tls_context, factory_context_); + ContextManagerImpl manager(time_system_); + Stats::TestUtil::TestStore server_stats_store; + ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, + server_stats_store, std::vector{}); + + auto socket = std::make_shared( + Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); + Network::MockTcpListenerCallbacks listener_callbacks; + Network::MockConnectionHandler connection_handler; + Network::ListenerPtr listener = + dispatcher_->createListener(socket, listener_callbacks, true, ENVOY_TCP_BACKLOG_SIZE); + std::shared_ptr server_read_filter(new Network::MockReadFilter()); + std::shared_ptr client_read_filter(new Network::MockReadFilter()); + + const std::string client_ctx_yaml = R"EOF( + common_tls_context: + )EOF"; + + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(client_ctx_yaml), tls_context); + auto client_cfg = std::make_unique(tls_context, factory_context_); + Stats::TestUtil::TestStore client_stats_store; + ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, + client_stats_store); + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( + socket->localAddress(), Network::Address::InstanceConstSharedPtr(), + client_ssl_socket_factory.createTransportSocket(nullptr), nullptr); + Network::MockConnectionCallbacks client_connection_callbacks; + client_connection->enableHalfClose(true); + client_connection->addReadFilter(client_read_filter); + client_connection->addConnectionCallbacks(client_connection_callbacks); + client_connection->connect(); + + Network::ConnectionPtr server_connection; + Network::MockConnectionCallbacks server_connection_callbacks; + EXPECT_CALL(listener_callbacks, onAccept_(_)) + .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket) -> void { + server_connection = dispatcher_->createServerConnection( + std::move(socket), server_ssl_socket_factory.createTransportSocket(nullptr), + stream_info_); + server_connection->enableHalfClose(true); + server_connection->addReadFilter(server_read_filter); + server_connection->addConnectionCallbacks(server_connection_callbacks); + })); + EXPECT_CALL(*server_read_filter, onNewConnection()); + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)) + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { + Buffer::OwnedImpl data("hello"); + server_connection->write(data, true); + EXPECT_EQ(data.length(), 0); + })); + + EXPECT_CALL(*client_read_filter, onNewConnection()) + .WillOnce(Return(Network::FilterStatus::Continue)); + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); + EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), true)) + .WillOnce(Invoke([&](Buffer::Instance& read_buffer, bool) -> Network::FilterStatus { + read_buffer.drain(read_buffer.length()); + client_connection->close(Network::ConnectionCloseType::NoFlush); + return Network::FilterStatus::StopIteration; + })); + EXPECT_CALL(*server_read_filter, onData(_, true)); + + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { + server_connection->close(Network::ConnectionCloseType::NoFlush); + dispatcher_->exit(); + })); + + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_P(SslSocketTest, ShutdownWithoutCloseNotify) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/unittest_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_certificates.pem" +)EOF"; + + envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext server_tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(server_ctx_yaml), server_tls_context); + auto server_cfg = std::make_unique(server_tls_context, factory_context_); + ContextManagerImpl manager(time_system_); + Stats::TestUtil::TestStore server_stats_store; + ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, + server_stats_store, std::vector{}); + + auto socket = std::make_shared( + Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); + Network::MockTcpListenerCallbacks listener_callbacks; + Network::MockConnectionHandler connection_handler; + Network::ListenerPtr listener = + dispatcher_->createListener(socket, listener_callbacks, true, ENVOY_TCP_BACKLOG_SIZE); + std::shared_ptr server_read_filter(new Network::MockReadFilter()); + std::shared_ptr client_read_filter(new Network::MockReadFilter()); + + const std::string client_ctx_yaml = R"EOF( + common_tls_context: + )EOF"; + + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; + TestUtility::loadFromYaml(TestEnvironment::substitute(client_ctx_yaml), tls_context); + auto client_cfg = std::make_unique(tls_context, factory_context_); + Stats::TestUtil::TestStore client_stats_store; + ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, + client_stats_store); + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( + socket->localAddress(), Network::Address::InstanceConstSharedPtr(), + client_ssl_socket_factory.createTransportSocket(nullptr), nullptr); + Network::MockConnectionCallbacks client_connection_callbacks; + client_connection->enableHalfClose(true); + client_connection->addReadFilter(client_read_filter); + client_connection->addConnectionCallbacks(client_connection_callbacks); + client_connection->connect(); + + Network::ConnectionPtr server_connection; + Network::MockConnectionCallbacks server_connection_callbacks; + EXPECT_CALL(listener_callbacks, onAccept_(_)) + .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket) -> void { + server_connection = dispatcher_->createServerConnection( + std::move(socket), server_ssl_socket_factory.createTransportSocket(nullptr), + stream_info_); + server_connection->enableHalfClose(true); + server_connection->addReadFilter(server_read_filter); + server_connection->addConnectionCallbacks(server_connection_callbacks); + })); + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)) + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { + Buffer::OwnedImpl data("hello"); + server_connection->write(data, false); + EXPECT_EQ(data.length(), 0); + // Close without sending close_notify alert. + const SslHandshakerImpl* ssl_socket = + dynamic_cast(server_connection->ssl().get()); + EXPECT_EQ(ssl_socket->state(), Ssl::SocketState::HandshakeComplete); + SSL_set_quiet_shutdown(ssl_socket->ssl(), 1); + server_connection->close(Network::ConnectionCloseType::NoFlush); + })); + + EXPECT_CALL(*client_read_filter, onNewConnection()) + .WillOnce(Return(Network::FilterStatus::Continue)); + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); + EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), true)) + .WillOnce(Invoke([&](Buffer::Instance& read_buffer, bool) -> Network::FilterStatus { + read_buffer.drain(read_buffer.length()); + client_connection->close(Network::ConnectionCloseType::NoFlush); + return Network::FilterStatus::StopIteration; + })); + + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)) + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); })); + + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + TEST_P(SslSocketTest, ClientAuthMultipleCAs) { const std::string server_ctx_yaml = R"EOF( common_tls_context: From 9d397e032093e873fc4681da9d66ba16159acf11 Mon Sep 17 00:00:00 2001 From: Zach Reyes <39203661+zasweq@users.noreply.github.com> Date: Wed, 4 Nov 2020 16:26:45 -0500 Subject: [PATCH 036/117] [fuzz] Fixed load balancer fuzz test bug (#13887) * Fixed load balancer fuzz test bug Signed-off-by: Zach --- .../upstream/load_balancer_fuzz_base.cc | 16 ---------- .../common/upstream/load_balancer_fuzz_base.h | 19 ++++++++++-- ...n_load_balancer_fuzz_test-5193127549468672 | 31 +++++++++++++++++++ .../zone_aware_load_balancer_fuzz_base.cc | 12 ------- .../zone_aware_load_balancer_fuzz_base.h | 10 ++++-- 5 files changed, 55 insertions(+), 33 deletions(-) create mode 100644 test/common/upstream/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 diff --git a/test/common/upstream/load_balancer_fuzz_base.cc b/test/common/upstream/load_balancer_fuzz_base.cc index 9ad2d538fbc4..8fce55d4f7cc 100644 --- a/test/common/upstream/load_balancer_fuzz_base.cc +++ b/test/common/upstream/load_balancer_fuzz_base.cc @@ -248,22 +248,6 @@ void LoadBalancerFuzzBase::replay( break; } } - clearStaticHostsState(); -} - -void LoadBalancerFuzzBase::clearStaticHostsState() { - // The only outstanding health flags set are those that are set from hosts being placed in - // degraded and excluded. Thus, use the priority set pointer to know which flags to clear. - for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); - ++priority_level) { - MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); - for (auto& host : host_set.degraded_hosts_) { - host->healthFlagClear(Host::HealthFlag::DEGRADED_ACTIVE_HC); - } - for (auto& host : host_set.excluded_hosts_) { - host->healthFlagClear(Host::HealthFlag::FAILED_ACTIVE_HC); - } - } } } // namespace Upstream diff --git a/test/common/upstream/load_balancer_fuzz_base.h b/test/common/upstream/load_balancer_fuzz_base.h index 4e2fc3f30b52..210eae065008 100644 --- a/test/common/upstream/load_balancer_fuzz_base.h +++ b/test/common/upstream/load_balancer_fuzz_base.h @@ -35,8 +35,6 @@ class LoadBalancerFuzzBase { void chooseHost(); void replay(const Protobuf::RepeatedPtrField& actions); - virtual void clearStaticHostsState(); - // These public objects shared amongst all types of load balancers will be used to construct load // balancers in specific load balancer fuzz classes Stats::IsolatedStoreImpl stats_store_; @@ -46,7 +44,22 @@ class LoadBalancerFuzzBase { NiceMock priority_set_; std::unique_ptr lb_; - virtual ~LoadBalancerFuzzBase() = default; + virtual ~LoadBalancerFuzzBase() { + // In an iteration, after this class is destructed, whether through an exception throw or + // finishing an action stream, must clear any state that could persist in static hosts. + // The only outstanding health flags set are those that are set from hosts being placed in + // degraded and excluded. Thus, use the priority set pointer to know which flags to clear. + for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); + for (auto& host : host_set.degraded_hosts_) { + host->healthFlagClear(Host::HealthFlag::DEGRADED_ACTIVE_HC); + } + for (auto& host : host_set.excluded_hosts_) { + host->healthFlagClear(Host::HealthFlag::FAILED_ACTIVE_HC); + } + } + }; protected: // Untrusted upstreams don't have the ability to change the host set size, so keep it constant diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 new file mode 100644 index 000000000000..159fa40953f2 --- /dev/null +++ b/test/common/upstream/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 @@ -0,0 +1,31 @@ +zone_aware_load_balancer_test_case { + load_balancer_test_case { + common_lb_config { + healthy_panic_threshold { + value: nan + } + locality_weighted_lb_config { + } + update_merge_window { + seconds: -1 + nanos: 292 + } + ignore_new_hosts_until_first_hc: true + close_connections_on_host_set_change: true + } + actions { + update_health_flags { + random_bytestring: 67108864 + } + } + setup_priority_levels { + num_hosts_in_priority_level: 46080 + num_hosts_locality_a: 46080 + random_bytestring: 46080 + random_bytestring: 46080 + } + seed_for_prng: 2816 + } + need_local_priority_set: true + random_bytestring_for_weights: "qk\274\337\204\263!\246" +} diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc b/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc index 0f95e3d8c2f6..73b3890089f8 100644 --- a/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc +++ b/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc @@ -68,17 +68,5 @@ void ZoneAwareLoadBalancerFuzzBase::addWeightsToHosts() { } } -void ZoneAwareLoadBalancerFuzzBase::clearStaticHostsState() { - LoadBalancerFuzzBase::clearStaticHostsState(); - // Clear out any set weights - for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); - ++priority_level) { - MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); - for (auto& host : host_set.hosts_) { - host->weight(1); - } - } -} - } // namespace Upstream } // namespace Envoy diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz_base.h b/test/common/upstream/zone_aware_load_balancer_fuzz_base.h index ecb373fd123b..cc0359943c09 100644 --- a/test/common/upstream/zone_aware_load_balancer_fuzz_base.h +++ b/test/common/upstream/zone_aware_load_balancer_fuzz_base.h @@ -15,6 +15,14 @@ class ZoneAwareLoadBalancerFuzzBase : public LoadBalancerFuzzBase { } ~ZoneAwareLoadBalancerFuzzBase() override { + // Clear out any set weights + for (uint32_t priority_level = 0; priority_level < priority_set_.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set_.getMockHostSet(priority_level); + for (auto& host : host_set.hosts_) { + host->weight(1); + } + } // This deletes the load balancer first. If constructed with a local priority set, load balancer // with reference local priority set on destruction. Since local priority set is in a base // class, it will be initialized second and thus destructed first. Thus, in order to avoid a use @@ -42,8 +50,6 @@ class ZoneAwareLoadBalancerFuzzBase : public LoadBalancerFuzzBase { // constructed, a local_priority_set_.get() call will return a nullptr. std::shared_ptr local_priority_set_; - void clearStaticHostsState() override; - private: // This bytestring will be iterated through representing randomness in order to choose // weights for hosts. From 751d4d0cef642a88445285322deb69f9ba6c7abc Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Thu, 5 Nov 2020 04:05:35 +0530 Subject: [PATCH 037/117] Remove spelling terms. (#13902) Cleaning tools/spelling/spelling_dictionary.txt which are single-use keywords. Includes a partial fix to #12807 Signed-off-by: Manish Kumar --- .../addr_family_aware_socket_option_impl.h | 2 +- source/common/network/apple_dns_impl.cc | 2 +- source/common/network/connection_impl.cc | 4 ++-- .../common/upstream/cluster_manager_impl.cc | 2 +- .../filters/http/grpc_web/grpc_web_filter.cc | 2 +- .../quiche/platform/flags_list.h | 2 +- .../transport_sockets/tls/context_impl.h | 2 +- source/server/worker_impl.cc | 4 ++-- test/common/config/version_converter_test.cc | 2 +- test/common/network/dns_impl_test.cc | 2 +- test/extensions/common/tap/common.h | 2 +- .../tls/integration/ssl_integration_test.cc | 2 +- test/integration/filters/common.h | 2 +- test/server/listener_manager_impl_test.cc | 2 +- tools/spelling/spelling_dictionary.txt | 19 ------------------- 15 files changed, 16 insertions(+), 35 deletions(-) diff --git a/source/common/network/addr_family_aware_socket_option_impl.h b/source/common/network/addr_family_aware_socket_option_impl.h index 37d4b93c338c..5f06b13e9cd8 100644 --- a/source/common/network/addr_family_aware_socket_option_impl.h +++ b/source/common/network/addr_family_aware_socket_option_impl.h @@ -42,7 +42,7 @@ class AddrFamilyAwareSocketOptionImpl : public Socket::Option, * platform. * @param optval as per setsockopt(2). * @param optlen as per setsockopt(2). - * @return int as per setsockopt(2). ENOTSUP is returned if the option is not supported on the + * @return int as per setsockopt(2). `ENOTSUP` is returned if the option is not supported on the * platform for fd after the above option level fallback semantics are taken into account or the * socket is non-IP. */ diff --git a/source/common/network/apple_dns_impl.cc b/source/common/network/apple_dns_impl.cc index dd2a75e4c0af..4ef8c495e2f2 100644 --- a/source/common/network/apple_dns_impl.cc +++ b/source/common/network/apple_dns_impl.cc @@ -321,7 +321,7 @@ AppleDnsResolverImpl::PendingResolution::dnsServiceGetAddrInfo(DnsLookupFamily d // TODO: explore caching: there are caching flags in the dns_sd.h flags, allow expired answers // from the cache? - // TODO: explore validation via DNSSEC? + // TODO: explore validation via `DNSSEC`? return DnsServiceSingleton::get().dnsServiceGetAddrInfo( &individual_sd_ref_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, protocol, dns_name_.c_str(), diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index 8c44fd9ff8d6..1a265cf10287 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -318,7 +318,7 @@ void ConnectionImpl::readDisable(bool disable) { disable, read_disable_count_, static_cast(state()), read_buffer_.length()); // When we disable reads, we still allow for early close notifications (the equivalent of - // EPOLLRDHUP for an epoll backend). For backends that support it, this allows us to apply + // `EPOLLRDHUP` for an epoll backend). For backends that support it, this allows us to apply // back pressure at the kernel layer, but still get timely notification of a FIN. Note that // we are not guaranteed to get notified, so even if the remote has closed, we may not know // until we try to write. Further note that currently we optionally don't correctly handle half @@ -818,7 +818,7 @@ void ClientConnectionImpl::connect() { ASSERT(SOCKET_FAILURE(result.rc_)); #ifdef WIN32 // winsock2 connect returns EWOULDBLOCK if the socket is non-blocking and the connection - // cannot be completed immediately. We do not check for EINPROGRESS as that error is for + // cannot be completed immediately. We do not check for `EINPROGRESS` as that error is for // blocking operations. if (result.errno_ == SOCKET_ERROR_AGAIN) { #else diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 08ca0567025d..5a2fb1328c36 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -1192,7 +1192,7 @@ void ClusterManagerImpl::ThreadLocalClusterManagerImpl::onHostHealthFailure( const HostSharedPtr& host) { // Drain all HTTP connection pool connections in the case of a host health failure. If outlier/ - // health is due to ECMP flow hashing issues for example, a new set of connections might do + // health is due to `ECMP` flow hashing issues for example, a new set of connections might do // better. // TODO(mattklein123): This function is currently very specific, but in the future when we do // more granular host set changes, we should be able to capture single host changes and make them diff --git a/source/extensions/filters/http/grpc_web/grpc_web_filter.cc b/source/extensions/filters/http/grpc_web/grpc_web_filter.cc index 6a7fd9b74c32..62d7eda354f9 100644 --- a/source/extensions/filters/http/grpc_web/grpc_web_filter.cc +++ b/source/extensions/filters/http/grpc_web/grpc_web_filter.cc @@ -203,7 +203,7 @@ Http::FilterTrailersStatus GrpcWebFilter::encodeTrailers(Http::ResponseTrailerMa } // Trailers are expected to come all in once, and will be encoded into one single trailers frame. - // Trailers in the trailers frame are separated by CRLFs. + // Trailers in the trailers frame are separated by `CRLFs`. Buffer::OwnedImpl temp; trailers.iterate([&temp](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { temp.add(header.key().getStringView().data(), header.key().size()); diff --git a/source/extensions/quic_listeners/quiche/platform/flags_list.h b/source/extensions/quic_listeners/quiche/platform/flags_list.h index 7e9e20a7c192..52454caec0a2 100644 --- a/source/extensions/quic_listeners/quiche/platform/flags_list.h +++ b/source/extensions/quic_listeners/quiche/platform/flags_list.h @@ -216,7 +216,7 @@ QUICHE_FLAG(bool, quic_reloadable_flag_quic_ip_based_cwnd_exp, true, QUICHE_FLAG(bool, quic_reloadable_flag_quic_listener_never_fake_epollout, false, "If true, QuicListener::OnSocketIsWritable will always return false, which means there " - "will never be a fake EPOLLOUT event in the next epoll iteration.") + "will never be a fake `EPOLLOUT` event in the next epoll iteration.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_neuter_initial_packet_in_coalescer_with_initial_key_discarded, diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index 772ab6fa2090..c533e0cc9c4f 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -169,7 +169,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context { const Ocsp::OcspResponseWrapper* ocsp_response) const; struct TlsContext { - // Each certificate specified for the context has its own SSL_CTX. SSL_CTXs + // Each certificate specified for the context has its own SSL_CTX. `SSL_CTXs` // are identical with the exception of certificate material, and can be // safely substituted via SSL_set_SSL_CTX() during the // SSL_CTX_set_select_certificate_cb() callback following ClientHello. diff --git a/source/server/worker_impl.cc b/source/server/worker_impl.cc index ff02ea9e08b2..760b7ca630bc 100644 --- a/source/server/worker_impl.cc +++ b/source/server/worker_impl.cc @@ -40,8 +40,8 @@ void WorkerImpl::addListener(absl::optional overridden_listener, Network::ListenerConfig& listener, AddListenerCompletion completion) { // All listener additions happen via post. However, we must deal with the case where the listener // can not be created on the worker. There is a race condition where 2 processes can successfully - // bind to an address, but then fail to listen() with EADDRINUSE. During initial startup, we want - // to surface this. + // bind to an address, but then fail to listen() with `EADDRINUSE`. During initial startup, we + // want to surface this. dispatcher_->post([this, overridden_listener, &listener, completion]() -> void { try { handler_->addListener(overridden_listener, listener); diff --git a/test/common/config/version_converter_test.cc b/test/common/config/version_converter_test.cc index d3cc29c5f475..65bb66145fc7 100644 --- a/test/common/config/version_converter_test.cc +++ b/test/common/config/version_converter_test.cc @@ -64,7 +64,7 @@ TEST(VersionConverterTest, Upgrade) { // Empty upgrade between version_converter.proto entities. TODO(htuch): consider migrating all the // upgrades in this test to version_converter.proto to reduce dependence on APIs that will be -// removed at EOY. +// removed at `EOY`. TEST(VersionConverterProto, UpgradeNextVersion) { test::common::config::PreviousVersion source; test::common::config::NextVersion dst; diff --git a/test/common/network/dns_impl_test.cc b/test/common/network/dns_impl_test.cc index 0489bc067d39..e6282f2e6405 100644 --- a/test/common/network/dns_impl_test.cc +++ b/test/common/network/dns_impl_test.cc @@ -705,7 +705,7 @@ TEST_P(DnsImplTest, DestroyChannelOnRefused) { DnsResolver::ResolutionStatus::Failure, {}, {}, absl::nullopt)); dispatcher_->run(Event::Dispatcher::RunType::Block); // However, the fresh channel initialized by production code does not point to the TestDnsServer. - // This means that resolution will return ARES_ENOTFOUND. This should not dirty the channel. + // This means that resolution will return `ARES_ENOTFOUND`. This should not dirty the channel. EXPECT_FALSE(peer_->isChannelDirty()); // Reset the channel to point to the TestDnsServer, and make sure resolution is healthy. diff --git a/test/extensions/common/tap/common.h b/test/extensions/common/tap/common.h index 023e61a82444..772af3c6f546 100644 --- a/test/extensions/common/tap/common.h +++ b/test/extensions/common/tap/common.h @@ -13,7 +13,7 @@ namespace data { namespace tap { namespace v3 { -// TODO(mattklein123): AFAICT gtest has built in printing for proto messages but it doesn't seem +// TODO(mattklein123): `AFAICT` gtest has built in printing for proto messages but it doesn't seem // to work unless this is here. std::ostream& operator<<(std::ostream& os, const TraceWrapper& trace); diff --git a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc index f9e76ee3024b..bdfb6b5504ea 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc @@ -363,7 +363,7 @@ TEST_P(SslCertficateIntegrationTest, ServerEcdsa) { checkStats(); } -// Server with RSA/ECDSAs certificates and a client with RSA/ECDSA cipher suites works. +// Server with RSA/`ECDSAs` certificates and a client with RSA/ECDSA cipher suites works. TEST_P(SslCertficateIntegrationTest, ServerRsaEcdsa) { server_rsa_cert_ = true; server_ecdsa_cert_ = true; diff --git a/test/integration/filters/common.h b/test/integration/filters/common.h index 7021ad09139b..9c33c034ec6b 100644 --- a/test/integration/filters/common.h +++ b/test/integration/filters/common.h @@ -9,7 +9,7 @@ namespace Envoy { -// DRYs up the creation of a simple filter config for a filter that requires no config. +// `DRYs` up the creation of a simple filter config for a filter that requires no config. template class SimpleFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig { public: diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 38934ce66feb..742602c958aa 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -1517,7 +1517,7 @@ reuse_port: true ASSERT_TRUE(SOCKET_VALID(syscall_result.rc_)); // On Windows if the socket has not been bound to an address with bind - // the call to getsockname fails with WSAEINVAL. To avoid that we make sure + // the call to getsockname fails with `WSAEINVAL`. To avoid that we make sure // that the bind system actually happens and it does not get mocked. ON_CALL(os_sys_calls_, bind(_, _, _)) .WillByDefault(Invoke( diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index c8cc46423a2f..53d6dc677a7d 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -6,7 +6,6 @@ ABI ACK ACL AES -AFAICT ALPN ALS AMZ @@ -19,7 +18,6 @@ ASM ASSERTs AST AWS -Allowlisted BACKTRACE BSON BPF @@ -31,7 +29,6 @@ CEL DSR HEXDIG HEXDIGIT -LTT OWS SkyWalking TIDs @@ -54,14 +51,12 @@ CPU CQ CRC CRL -CRLFs CRT CSDS CSRF CSS CSV CTX -CTXs CVC CVE CX @@ -73,38 +68,25 @@ DFATAL DGRAM DLOG DNS -DNSSEC DQUOTE -DRYs DS DST DW DWORD -EADDRINUSE -EADDRNOTAVAIL EAGAIN ECDH ECDHE ECDS ECDSA -ECDSAs -ECMP ECONNREFUSED EDESTRUCTION EDF -EINPROGRESS EINVAL ELB -EMSGSIZE ENOENT -ENOTFOUND -ENOTSUP ENV EOF EOS -EOY -EPOLLOUT -EPOLLRDHUP EQ ERANGE EV @@ -367,7 +349,6 @@ WRR WS WSA WSABUF -WSAEINVAL WSS Welford's Wi From 65a1c356a27af5ef4406929705f79e663e82363a Mon Sep 17 00:00:00 2001 From: Yuchen Dai Date: Wed, 4 Nov 2020 19:50:22 -0800 Subject: [PATCH 038/117] ci: allow apt-get update failure in azure pipeline (#13908) Signed-off-by: Yuchen Dai --- .azure-pipelines/cleanup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure-pipelines/cleanup.sh b/.azure-pipelines/cleanup.sh index 5611a7b212fc..3714f24cac1e 100755 --- a/.azure-pipelines/cleanup.sh +++ b/.azure-pipelines/cleanup.sh @@ -3,7 +3,7 @@ set -e # Temporary script to remove tools from Azure pipelines agent to create more disk space room. -sudo apt-get update -y +sudo apt-get update -y || true sudo apt-get purge -y --no-upgrade 'ghc-*' 'zulu-*-azure-jdk' 'libllvm*' 'mysql-*' 'dotnet-*' 'libgl1' \ 'adoptopenjdk-*' 'azure-cli' 'google-chrome-stable' 'firefox' 'hhvm' From 54391ee236cf599333049ffbb5d0ca821c53f95f Mon Sep 17 00:00:00 2001 From: asraa Date: Wed, 4 Nov 2020 22:56:12 -0500 Subject: [PATCH 039/117] [http] Handle faulty filters from removing required request headers (#13470) Signed-off-by: Asra Ali --- .../http_conn_man/response_code_details.rst | 1 + docs/root/version_history/current.rst | 1 + include/envoy/http/codec.h | 6 +- include/envoy/router/router.h | 4 +- include/envoy/stream_info/stream_info.h | 2 + source/common/http/BUILD | 1 + source/common/http/codec_wrappers.h | 5 +- source/common/http/header_utility.cc | 22 ++ source/common/http/header_utility.h | 9 + source/common/http/http1/codec_impl.cc | 15 +- source/common/http/http1/codec_impl.h | 2 +- source/common/http/http1/codec_impl_legacy.cc | 3 +- source/common/http/http1/codec_impl_legacy.h | 2 +- source/common/http/http2/codec_impl.cc | 8 +- source/common/http/http2/codec_impl.h | 2 +- source/common/http/http2/codec_impl_legacy.cc | 5 +- source/common/http/http2/codec_impl_legacy.h | 2 +- source/common/router/router.cc | 5 - source/common/router/upstream_request.cc | 16 +- source/common/tcp_proxy/upstream.cc | 4 +- source/common/upstream/health_checker_impl.cc | 8 +- .../quiche/envoy_quic_client_stream.cc | 4 +- .../quiche/envoy_quic_client_stream.h | 2 +- .../upstreams/http/http/upstream_request.h | 5 +- .../upstreams/http/tcp/upstream_request.cc | 4 +- .../upstreams/http/tcp/upstream_request.h | 2 +- test/common/http/codec_impl_fuzz_test.cc | 10 +- test/common/http/codec_wrappers_test.cc | 24 ++- test/common/http/http1/codec_impl_test.cc | 107 +++++----- test/common/http/http1/conn_pool_test.cc | 72 ++++--- test/common/http/http2/codec_impl_test.cc | 171 +++++++-------- test/common/http/http2/conn_pool_test.cc | 199 +++++++++++------- test/common/http/http2/frame_replay_test.cc | 2 +- .../http/http2/response_header_fuzz_test.cc | 2 +- test/common/router/router_test.cc | 39 +++- .../upstream/health_checker_impl_test.cc | 39 ++-- .../quiche/envoy_quic_client_session_test.cc | 3 +- .../quiche/envoy_quic_client_stream_test.cc | 12 +- .../http/tcp/upstream_request_test.cc | 10 +- test/integration/BUILD | 2 + test/integration/filters/BUILD | 16 ++ .../filters/invalid_header_filter.cc | 46 ++++ test/integration/http_integration.cc | 6 +- test/integration/protocol_integration_test.cc | 82 ++++++++ test/integration/utility.cc | 3 +- test/mocks/http/BUILD | 1 + test/mocks/http/stream_encoder.cc | 7 +- test/mocks/http/stream_encoder.h | 4 +- 48 files changed, 676 insertions(+), 321 deletions(-) create mode 100644 test/integration/filters/invalid_header_filter.cc diff --git a/docs/root/configuration/http/http_conn_man/response_code_details.rst b/docs/root/configuration/http/http_conn_man/response_code_details.rst index 350c0767f93f..898b8523f185 100644 --- a/docs/root/configuration/http/http_conn_man/response_code_details.rst +++ b/docs/root/configuration/http/http_conn_man/response_code_details.rst @@ -26,6 +26,7 @@ Below are the list of reasons the HttpConnectionManager or Router filter may sen duration_timeout, The max connection duration was exceeded. direct_response, A direct response was generated by the router filter. filter_chain_not_found, The request was rejected due to no matching filter chain. + filter_removed_required_headers, The request was rejected in the filter manager because a configured filter removed required headers. internal_redirect, The original stream was replaced with an internal redirect. low_version, The HTTP/1.0 or HTTP/0.9 request was rejected due to HTTP/1.0 support not being configured. maintenance_mode, The request was rejected by the router filter because the cluster was in maintenance mode. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 4aa6280e69a3..12fabb48e182 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -26,6 +26,7 @@ Bug Fixes * dns: fix a bug where custom resolvers provided in configuration were not preserved after network issues. * http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. +* http: reject requests with missing required headers after filter chain processing. * http: sending CONNECT_ERROR for HTTP/2 where appropriate during CONNECT requests. * proxy_proto: fixed a bug where the wrong downstream address got sent to upstream connections. * tls: fix detection of the upstream connection close event. diff --git a/include/envoy/http/codec.h b/include/envoy/http/codec.h index 5e977c8a7d09..9ffa360bd72e 100644 --- a/include/envoy/http/codec.h +++ b/include/envoy/http/codec.h @@ -105,10 +105,12 @@ class RequestEncoder : public virtual StreamEncoder { public: /** * Encode headers, optionally indicating end of stream. - * @param headers supplies the header map to encode. + * @param headers supplies the header map to encode. Must have required HTTP headers. * @param end_stream supplies whether this is a header only request. + * @return Status indicating whether encoding succeeded. Encoding will fail if request + * headers are missing required HTTP headers (method, path for non-CONNECT, host for CONNECT). */ - virtual void encodeHeaders(const RequestHeaderMap& headers, bool end_stream) PURE; + virtual Status encodeHeaders(const RequestHeaderMap& headers, bool end_stream) PURE; /** * Encode trailers. This implicitly ends the stream. diff --git a/include/envoy/router/router.h b/include/envoy/router/router.h index b4b3be9f3c37..f9454eee452d 100644 --- a/include/envoy/router/router.h +++ b/include/envoy/router/router.h @@ -1274,8 +1274,10 @@ class GenericUpstream { * Encode headers, optionally indicating end of stream. * @param headers supplies the header map to encode. * @param end_stream supplies whether this is a header only request. + * @return status indicating success. Encoding will fail if headers do not have required HTTP + * headers. */ - virtual void encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) PURE; + virtual Http::Status encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) PURE; /** * Encode trailers. This implicitly ends the stream. * @param trailers supplies the trailers to encode. diff --git a/include/envoy/stream_info/stream_info.h b/include/envoy/stream_info/stream_info.h index 858ff0f0952f..821053465367 100644 --- a/include/envoy/stream_info/stream_info.h +++ b/include/envoy/stream_info/stream_info.h @@ -172,6 +172,8 @@ struct ResponseCodeDetailValues { const std::string AdminFilterResponse = "admin_filter_response"; // The original stream was replaced with an internal redirect. const std::string InternalRedirect = "internal_redirect"; + // The request was rejected because configured filters erroneously removed required headers. + const std::string FilterRemovedRequiredHeaders = "filter_removed_required_headers"; // Changes or additions to details should be reflected in // docs/root/configuration/http/http_conn_man/response_code_details_details.rst }; diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 726c33322614..e5a2d719d369 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -405,6 +405,7 @@ envoy_cc_library( ], deps = [ ":header_map_lib", + ":status_lib", ":utility_lib", "//include/envoy/common:regex_interface", "//include/envoy/http:header_map_interface", diff --git a/source/common/http/codec_wrappers.h b/source/common/http/codec_wrappers.h index 6a4503e53451..21390cd5647f 100644 --- a/source/common/http/codec_wrappers.h +++ b/source/common/http/codec_wrappers.h @@ -68,11 +68,12 @@ class ResponseDecoderWrapper : public ResponseDecoder { class RequestEncoderWrapper : public RequestEncoder { public: // RequestEncoder - void encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override { - inner_.encodeHeaders(headers, end_stream); + Status encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override { + RETURN_IF_ERROR(inner_.encodeHeaders(headers, end_stream)); if (end_stream) { onEncodeComplete(); } + return okStatus(); } void encodeData(Buffer::Instance& data, bool end_stream) override { diff --git a/source/common/http/header_utility.cc b/source/common/http/header_utility.cc index 3e030010de4f..c59efda824db 100644 --- a/source/common/http/header_utility.cc +++ b/source/common/http/header_utility.cc @@ -276,5 +276,27 @@ bool HeaderUtility::shouldCloseConnection(Http::Protocol protocol, return false; } +Http::Status HeaderUtility::checkRequiredHeaders(const Http::RequestHeaderMap& headers) { + if (!headers.Method()) { + return absl::InvalidArgumentError( + absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Method.get())); + } + bool is_connect = Http::HeaderUtility::isConnect(headers); + if (is_connect) { + if (!headers.Host()) { + // Host header must be present for CONNECT request. + return absl::InvalidArgumentError( + absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Host.get())); + } + } else { + if (!headers.Path()) { + // :path header must be present for non-CONNECT requests. + return absl::InvalidArgumentError( + absl::StrCat("missing required header: ", Envoy::Http::Headers::get().Path.get())); + } + } + return Http::okStatus(); +} + } // namespace Http } // namespace Envoy diff --git a/source/common/http/header_utility.h b/source/common/http/header_utility.h index 0a1e23c46717..864b1d942ad8 100644 --- a/source/common/http/header_utility.h +++ b/source/common/http/header_utility.h @@ -8,6 +8,7 @@ #include "envoy/http/protocol.h" #include "envoy/type/v3/range.pb.h" +#include "common/http/status.h" #include "common/protobuf/protobuf.h" namespace Envoy { @@ -174,6 +175,14 @@ class HeaderUtility { * @brief Remove the port part from host/authority header if it is equal to provided port */ static void stripPortFromHost(RequestHeaderMap& headers, uint32_t listener_port); + + /* Does a common header check ensuring required headers are present. + * Required request headers include :method header, :path for non-CONNECT requests, and + * host/authority for HTTP/1.1 or CONNECT requests. + * @return Status containing the result. If failed, message includes details on which header was + * missing. + */ + static Http::Status checkRequiredHeaders(const Http::RequestHeaderMap& headers); }; } // namespace Http } // namespace Envoy diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index 42b7f4dfce6a..6a803cca1e6c 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -366,20 +366,16 @@ void ResponseEncoderImpl::encodeHeaders(const ResponseHeaderMap& headers, bool e static const char REQUEST_POSTFIX[] = " HTTP/1.1\r\n"; -void RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end_stream) { +Status RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end_stream) { + // Required headers must be present. This can only happen by some erroneous processing after the + // downstream codecs decode. + RETURN_IF_ERROR(HeaderUtility::checkRequiredHeaders(headers)); + const HeaderEntry* method = headers.Method(); const HeaderEntry* path = headers.Path(); const HeaderEntry* host = headers.Host(); bool is_connect = HeaderUtility::isConnect(headers); - // TODO(#10878): Include missing host header for CONNECT. - // The RELEASE_ASSERT below does not change the existing behavior of `encodeHeaders`. - // The `encodeHeaders` used to throw on errors. Callers of `encodeHeaders()` do not catch - // exceptions and this would cause abnormal process termination in error cases. This change - // replaces abnormal process termination from unhandled exception with the RELEASE_ASSERT. Further - // work will replace this RELEASE_ASSERT with proper error handling. - RELEASE_ASSERT(method && (path || is_connect), ":method and :path must be specified"); - if (method->value() == Headers::get().MethodValues.Head) { head_request_ = true; } else if (method->value() == Headers::get().MethodValues.Connect) { @@ -400,6 +396,7 @@ void RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end connection_.copyToBuffer(REQUEST_POSTFIX, sizeof(REQUEST_POSTFIX) - 1); encodeHeadersBase(headers, absl::nullopt, end_stream); + return okStatus(); } int ConnectionImpl::setAndCheckCallbackStatus(Status&& status) { diff --git a/source/common/http/http1/codec_impl.h b/source/common/http/http1/codec_impl.h index 2ae00fb03400..867cd603f57a 100644 --- a/source/common/http/http1/codec_impl.h +++ b/source/common/http/http1/codec_impl.h @@ -156,7 +156,7 @@ class RequestEncoderImpl : public StreamEncoderImpl, public RequestEncoder { bool connectRequest() const { return connect_request_; } // Http::RequestEncoder - void encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; + Status encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; void encodeTrailers(const RequestTrailerMap& trailers) override { encodeTrailersBase(trailers); } private: diff --git a/source/common/http/http1/codec_impl_legacy.cc b/source/common/http/http1/codec_impl_legacy.cc index 332f1241eb7d..474fd7ce1b0c 100644 --- a/source/common/http/http1/codec_impl_legacy.cc +++ b/source/common/http/http1/codec_impl_legacy.cc @@ -365,7 +365,7 @@ void ResponseEncoderImpl::encodeHeaders(const ResponseHeaderMap& headers, bool e static const char REQUEST_POSTFIX[] = " HTTP/1.1\r\n"; -void RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end_stream) { +Status RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end_stream) { const HeaderEntry* method = headers.Method(); const HeaderEntry* path = headers.Path(); const HeaderEntry* host = headers.Host(); @@ -397,6 +397,7 @@ void RequestEncoderImpl::encodeHeaders(const RequestHeaderMap& headers, bool end connection_.copyToBuffer(REQUEST_POSTFIX, sizeof(REQUEST_POSTFIX) - 1); encodeHeadersBase(headers, absl::nullopt, end_stream); + return okStatus(); } http_parser_settings ConnectionImpl::settings_{ diff --git a/source/common/http/http1/codec_impl_legacy.h b/source/common/http/http1/codec_impl_legacy.h index 8a1b68b0fad4..48382473f0bd 100644 --- a/source/common/http/http1/codec_impl_legacy.h +++ b/source/common/http/http1/codec_impl_legacy.h @@ -160,7 +160,7 @@ class RequestEncoderImpl : public StreamEncoderImpl, public RequestEncoder { bool connectRequest() const { return connect_request_; } // Http::RequestEncoder - void encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; + Status encodeHeaders(const RequestHeaderMap& headers, bool end_stream) override; void encodeTrailers(const RequestTrailerMap& trailers) override { encodeTrailersBase(trailers); } private: diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index c3be00073dba..fc5cc2349171 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -183,8 +183,11 @@ void ConnectionImpl::StreamImpl::encodeHeadersBase(const std::vector } } -void ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& headers, - bool end_stream) { +Status ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& headers, + bool end_stream) { + // Required headers must be present. This can only happen by some erroneous processing after the + // downstream codecs decode. + RETURN_IF_ERROR(HeaderUtility::checkRequiredHeaders(headers)); // This must exist outside of the scope of isUpgrade as the underlying memory is // needed until encodeHeadersBase has been called. std::vector final_headers; @@ -211,6 +214,7 @@ void ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& hea buildHeaders(final_headers, headers); } encodeHeadersBase(final_headers, end_stream); + return okStatus(); } void ConnectionImpl::ServerStreamImpl::encodeHeaders(const ResponseHeaderMap& headers, diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 83f5d504e80b..7fed071a8063 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -351,7 +351,7 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable parent_.checkProtocolConstraintViolation(); } -void ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& headers, - bool end_stream) { +Status ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& headers, + bool end_stream) { // This must exist outside of the scope of isUpgrade as the underlying memory is // needed until encodeHeadersBase has been called. std::vector final_headers; @@ -211,6 +211,7 @@ void ConnectionImpl::ClientStreamImpl::encodeHeaders(const RequestHeaderMap& hea buildHeaders(final_headers, headers); } encodeHeadersBase(final_headers, end_stream); + return okStatus(); } void ConnectionImpl::ServerStreamImpl::encodeHeaders(const ResponseHeaderMap& headers, diff --git a/source/common/http/http2/codec_impl_legacy.h b/source/common/http/http2/codec_impl_legacy.h index 238762c45df3..766724a36d53 100644 --- a/source/common/http/http2/codec_impl_legacy.h +++ b/source/common/http/http2/codec_impl_legacy.h @@ -351,7 +351,7 @@ class ConnectionImpl : public virtual Connection, protected Logger::LoggableencodeHeaders(*parent_.downstreamHeaders(), shouldSendEndStream()); - + const Http::Status status = + upstream_->encodeHeaders(*parent_.downstreamHeaders(), shouldSendEndStream()); calling_encode_headers_ = false; + if (!status.ok()) { + // It is possible that encodeHeaders() fails. This can happen if filters or other extensions + // erroneously remove required headers. + stream_info_.setResponseFlag(StreamInfo::ResponseFlag::DownstreamProtocolError); + const std::string details = + absl::StrCat(StreamInfo::ResponseCodeDetails::get().FilterRemovedRequiredHeaders, "{", + status.message(), "}"); + parent_.callbacks()->sendLocalReply(Http::Code::ServiceUnavailable, status.message(), nullptr, + absl::nullopt, details); + return; + } + if (!paused_for_connect_) { encodeBodyAndTrailers(); } diff --git a/source/common/tcp_proxy/upstream.cc b/source/common/tcp_proxy/upstream.cc index 1da6eb915797..8c82b07852eb 100644 --- a/source/common/tcp_proxy/upstream.cc +++ b/source/common/tcp_proxy/upstream.cc @@ -123,7 +123,9 @@ void HttpUpstream::setRequestEncoder(Http::RequestEncoder& request_encoder, bool {Http::Headers::get().Scheme, scheme}, {Http::Headers::get().Path, "/"}, {Http::Headers::get().Host, hostname_}}); - request_encoder_->encodeHeaders(*headers, false); + const auto status = request_encoder_->encodeHeaders(*headers, false); + // Encoding can only fail on missing required request headers. + ASSERT(status.ok()); } void HttpUpstream::resetEncoder(Network::ConnectionEvent event, bool inform_downstream) { diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index 475f2c377e1f..f9dfb221c9ce 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -274,7 +274,9 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onInterval() { stream_info.setDownstreamRemoteAddress(local_address_); stream_info.onUpstreamHostSelected(host_); parent_.request_headers_parser_->evaluateHeaders(*request_headers, stream_info); - request_encoder->encodeHeaders(*request_headers, true); + auto status = request_encoder->encodeHeaders(*request_headers, true); + // Encoding will only fail if required request headers are missing. + ASSERT(status.ok()); } void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onResetStream(Http::StreamResetReason, @@ -691,7 +693,9 @@ void GrpcHealthCheckerImpl::GrpcActiveHealthCheckSession::onInterval() { Router::FilterUtility::setUpstreamScheme( headers_message->headers(), host_->transportSocketFactory().implementsSecureTransport()); - request_encoder_->encodeHeaders(headers_message->headers(), false); + auto status = request_encoder_->encodeHeaders(headers_message->headers(), false); + // Encoding will only fail if required headers are missing. + ASSERT(status.ok()); grpc::health::v1::HealthCheckRequest request; if (parent_.service_name_.has_value()) { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index 866e35416b0b..b52b411df7e7 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -46,7 +46,8 @@ EnvoyQuicClientStream::EnvoyQuicClientStream(quic::PendingStream* pending, 16 * 1024, [this]() { runLowWatermarkCallbacks(); }, [this]() { runHighWatermarkCallbacks(); }) {} -void EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) { +Http::Status EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& headers, + bool end_stream) { ENVOY_STREAM_LOG(debug, "encodeHeaders: (end_stream={}) {}.", *this, end_stream, headers); quic::QuicStream* writing_stream = quic::VersionUsesHttp3(transport_version()) @@ -60,6 +61,7 @@ void EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& headers, // IETF QUIC sends HEADER frame on current stream. After writing headers, the // buffer may increase. maybeCheckWatermark(bytes_to_send_old, bytes_to_send_new, *filterManagerConnection()); + return Http::okStatus(); } void EnvoyQuicClientStream::encodeData(Buffer::Instance& data, bool end_stream) { diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h index 79003e4621f4..2446fbb0c3e9 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h @@ -37,7 +37,7 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, } // Http::RequestEncoder - void encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) override; + Http::Status encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) override; void encodeTrailers(const Http::RequestTrailerMap& trailers) override; // Http::Stream diff --git a/source/extensions/upstreams/http/http/upstream_request.h b/source/extensions/upstreams/http/http/upstream_request.h index 588efa93c7f6..ff0650a3b231 100644 --- a/source/extensions/upstreams/http/http/upstream_request.h +++ b/source/extensions/upstreams/http/http/upstream_request.h @@ -66,8 +66,9 @@ class HttpUpstream : public Router::GenericUpstream, public Envoy::Http::StreamC void encodeMetadata(const Envoy::Http::MetadataMapVector& metadata_map_vector) override { request_encoder_->encodeMetadata(metadata_map_vector); } - void encodeHeaders(const Envoy::Http::RequestHeaderMap& headers, bool end_stream) override { - request_encoder_->encodeHeaders(headers, end_stream); + Envoy::Http::Status encodeHeaders(const Envoy::Http::RequestHeaderMap& headers, + bool end_stream) override { + return request_encoder_->encodeHeaders(headers, end_stream); } void encodeTrailers(const Envoy::Http::RequestTrailerMap& trailers) override { request_encoder_->encodeTrailers(trailers); diff --git a/source/extensions/upstreams/http/tcp/upstream_request.cc b/source/extensions/upstreams/http/tcp/upstream_request.cc index 4284a2e5a13d..0c4dd46ece64 100644 --- a/source/extensions/upstreams/http/tcp/upstream_request.cc +++ b/source/extensions/upstreams/http/tcp/upstream_request.cc @@ -43,7 +43,8 @@ void TcpUpstream::encodeData(Buffer::Instance& data, bool end_stream) { upstream_conn_data_->connection().write(data, end_stream); } -void TcpUpstream::encodeHeaders(const Envoy::Http::RequestHeaderMap&, bool end_stream) { +Envoy::Http::Status TcpUpstream::encodeHeaders(const Envoy::Http::RequestHeaderMap&, + bool end_stream) { // Headers should only happen once, so use this opportunity to add the proxy // proto header, if configured. ASSERT(upstream_request_->routeEntry().connectConfig().has_value()); @@ -64,6 +65,7 @@ void TcpUpstream::encodeHeaders(const Envoy::Http::RequestHeaderMap&, bool end_s Envoy::Http::createHeaderMap( {{Envoy::Http::Headers::get().Status, "200"}})}; upstream_request_->decodeHeaders(std::move(headers), false); + return Envoy::Http::okStatus(); } void TcpUpstream::encodeTrailers(const Envoy::Http::RequestTrailerMap&) { diff --git a/source/extensions/upstreams/http/tcp/upstream_request.h b/source/extensions/upstreams/http/tcp/upstream_request.h index 4f7cc1c212de..3829fdead139 100644 --- a/source/extensions/upstreams/http/tcp/upstream_request.h +++ b/source/extensions/upstreams/http/tcp/upstream_request.h @@ -70,7 +70,7 @@ class TcpUpstream : public Router::GenericUpstream, // GenericUpstream void encodeData(Buffer::Instance& data, bool end_stream) override; void encodeMetadata(const Envoy::Http::MetadataMapVector&) override {} - void encodeHeaders(const Envoy::Http::RequestHeaderMap&, bool end_stream) override; + Envoy::Http::Status encodeHeaders(const Envoy::Http::RequestHeaderMap&, bool end_stream) override; void encodeTrailers(const Envoy::Http::RequestTrailerMap&) override; void readDisable(bool disable) override; void resetStream() override; diff --git a/test/common/http/codec_impl_fuzz_test.cc b/test/common/http/codec_impl_fuzz_test.cc index 3829d69b5dc4..6f9ee8c57a4b 100644 --- a/test/common/http/codec_impl_fuzz_test.cc +++ b/test/common/http/codec_impl_fuzz_test.cc @@ -182,7 +182,7 @@ class HttpStream : public LinkedObject { request_.request_encoder_->getStream().addCallbacks(request_.stream_callbacks_); } - request_.request_encoder_->encodeHeaders(request_headers, end_stream); + request_.request_encoder_->encodeHeaders(request_headers, end_stream).IgnoreError(); request_.stream_state_ = end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; response_.stream_state_ = StreamState::PendingHeaders; } @@ -219,9 +219,11 @@ class HttpStream : public LinkedObject { } state.response_encoder_->encodeHeaders(headers, end_stream); } else { - state.request_encoder_->encodeHeaders( - fromSanitizedHeaders(directional_action.headers()), - end_stream); + state.request_encoder_ + ->encodeHeaders( + fromSanitizedHeaders(directional_action.headers()), + end_stream) + .IgnoreError(); } if (end_stream) { state.closeLocal(); diff --git a/test/common/http/codec_wrappers_test.cc b/test/common/http/codec_wrappers_test.cc index ea481bfa2247..02e47262da65 100644 --- a/test/common/http/codec_wrappers_test.cc +++ b/test/common/http/codec_wrappers_test.cc @@ -25,8 +25,12 @@ TEST(RequestEncoderWrapper, HeaderOnlyEncode) { MockRequestEncoderWrapper wrapper; EXPECT_CALL(wrapper.innerEncoder(), encodeHeaders(_, true)); - wrapper.encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, true); + EXPECT_TRUE( + wrapper + .encodeHeaders( + TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, + true) + .ok()); EXPECT_TRUE(wrapper.encodeComplete()); } @@ -34,8 +38,12 @@ TEST(RequestEncoderWrapper, HeaderAndBodyEncode) { MockRequestEncoderWrapper wrapper; EXPECT_CALL(wrapper.innerEncoder(), encodeHeaders(_, false)); - wrapper.encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, false); + EXPECT_TRUE( + wrapper + .encodeHeaders( + TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, + false) + .ok()); EXPECT_FALSE(wrapper.encodeComplete()); Buffer::OwnedImpl data; @@ -48,8 +56,12 @@ TEST(RequestEncoderWrapper, HeaderAndBodyAndTrailersEncode) { MockRequestEncoderWrapper wrapper; EXPECT_CALL(wrapper.innerEncoder(), encodeHeaders(_, false)); - wrapper.encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, false); + EXPECT_TRUE( + wrapper + .encodeHeaders( + TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}, {":authority", "foo"}}, + false) + .ok()); EXPECT_FALSE(wrapper.encodeComplete()); Buffer::OwnedImpl data; diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 200ba2423191..3b3138173c95 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -2010,7 +2010,7 @@ void Http1ClientConnectionImplTest::testClientAllowChunkedContentLength(uint32_t Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); TestResponseHeaderMapImpl expected_headers{{":status", "200"}, {"transfer-encoding", "chunked"}}; Buffer::OwnedImpl expected_data("Hello World"); @@ -2051,7 +2051,7 @@ TEST_P(Http1ClientConnectionImplTest, SimpleGet) { ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ("GET / HTTP/1.1\r\ncontent-length: 0\r\n\r\n", output); } @@ -2067,7 +2067,7 @@ TEST_P(Http1ClientConnectionImplTest, SimpleGetWithHeaderCasing) { ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {"my-custom-header", "hey"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ("GET / HTTP/1.1\r\nMy-Custom-Header: hey\r\nContent-Length: 0\r\n\r\n", output); } @@ -2081,7 +2081,7 @@ TEST_P(Http1ClientConnectionImplTest, HostHeaderTranslate) { ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ("GET / HTTP/1.1\r\nhost: host\r\ncontent-length: 0\r\n\r\n", output); } @@ -2114,7 +2114,7 @@ TEST_P(Http1ClientConnectionImplTest, FlowControlReadDisabledReenable) { // Request. TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder->encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder->encodeHeaders(headers, true).ok()); EXPECT_EQ("GET / HTTP/1.1\r\nhost: host\r\ncontent-length: 0\r\n\r\n", output); output.clear(); @@ -2142,7 +2142,7 @@ TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse503) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\n\r\n"); @@ -2156,7 +2156,7 @@ TEST_P(Http1ClientConnectionImplTest, EmptyBodyResponse200) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"); @@ -2170,7 +2170,7 @@ TEST_P(Http1ClientConnectionImplTest, HeadRequest) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "HEAD"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\nContent-Length: 20\r\n\r\n"); @@ -2184,7 +2184,7 @@ TEST_P(Http1ClientConnectionImplTest, 204Response) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\n\r\n"); @@ -2201,7 +2201,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseContentLengthNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nContent-Length: 20\r\n\r\n"); auto status = codec_->dispatch(response); @@ -2219,7 +2219,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseContentLengthNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nContent-Length: 20\r\n\r\n"); auto status = codec_->dispatch(response); @@ -2236,7 +2236,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseWithContentLength0) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nContent-Length: 0\r\n\r\n"); @@ -2253,7 +2253,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseWithContentLength0) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, true)); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nContent-Length: 0\r\n\r\n"); @@ -2271,7 +2271,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseTransferEncodingNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); auto status = codec_->dispatch(response); @@ -2289,7 +2289,7 @@ TEST_P(Http1ClientConnectionImplTest, 204ResponseTransferEncodingNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response("HTTP/1.1 204 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); auto status = codec_->dispatch(response); @@ -2304,7 +2304,7 @@ TEST_P(Http1ClientConnectionImplTest, ContinueHeaders) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decode100ContinueHeaders_(_)); EXPECT_CALL(response_decoder, decodeData(_, _)).Times(0); @@ -2326,7 +2326,7 @@ TEST_P(Http1ClientConnectionImplTest, MultipleContinueHeaders) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decode100ContinueHeaders_(_)); EXPECT_CALL(response_decoder, decodeData(_, _)).Times(0); @@ -2355,7 +2355,7 @@ TEST_P(Http1ClientConnectionImplTest, 1xxNonContinueHeaders) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); Buffer::OwnedImpl response("HTTP/1.1 102 Processing\r\n\r\n"); @@ -2372,7 +2372,7 @@ TEST_P(Http1ClientConnectionImplTest, 101ResponseTransferEncodingNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response( "HTTP/1.1 101 Switching Protocols\r\nTransfer-Encoding: chunked\r\n\r\n"); @@ -2391,7 +2391,7 @@ TEST_P(Http1ClientConnectionImplTest, 101ResponseTransferEncodingNotAllowed) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response( "HTTP/1.1 101 Switching Protocols\r\nTransfer-Encoding: chunked\r\n\r\n"); @@ -2405,21 +2405,26 @@ TEST_P(Http1ClientConnectionImplTest, BadEncodeParams) { NiceMock response_decoder; - // Need to set :method and :path. - // New and legacy codecs will behave differently on errors from processing outbound data. The - // legacy codecs will throw an exception (that presently will be uncaught in contexts like - // sendLocalReply), while the new codecs temporarily RELEASE_ASSERT until Envoy handles errors on - // outgoing data. + // Invalid outbound data errors are impossible to trigger in normal processing, since bad + // downstream data would have been rejected by the codec, and erroneous filter processing would + // cause a direct response by the filter manager. An invalid status is returned from new codecs + // which protects against future extensions or header modifications after the filter chain. The + // old codecs will still throw an exception (that presently will be uncaught in contexts like + // sendLocalReply). Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); if (testingNewCodec()) { - EXPECT_DEATH(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true), - ":method and :path must be specified"); - EXPECT_DEATH(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true), - ":method and :path must be specified"); + EXPECT_THAT( + request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true).message(), + testing::HasSubstr("missing required")); + EXPECT_THAT( + request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true).message(), + testing::HasSubstr("missing required")); } else { - EXPECT_THROW(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true), - CodecClientException); - EXPECT_THROW(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true), + EXPECT_THROW( + request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}}, true).IgnoreError(), + CodecClientException); + EXPECT_THROW(request_encoder.encodeHeaders(TestRequestHeaderMapImpl{{":method", "GET"}}, true) + .IgnoreError(), CodecClientException); } } @@ -2430,7 +2435,7 @@ TEST_P(Http1ClientConnectionImplTest, NoContentLengthResponse) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl expected_data1("Hello World"); EXPECT_CALL(response_decoder, decodeData(BufferEqual(&expected_data1), false)); @@ -2452,7 +2457,7 @@ TEST_P(Http1ClientConnectionImplTest, ResponseWithTrailers) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\n\r\ntransfer-encoding: chunked\r\n\r\nb\r\nHello " "World\r\n0\r\nhello: world\r\nsecond: header\r\n\r\n"); @@ -2468,7 +2473,7 @@ TEST_P(Http1ClientConnectionImplTest, GiantPath) { Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{ {":method", "GET"}, {":path", "/" + std::string(16384, 'a')}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); Buffer::OwnedImpl response("HTTP/1.1 200 OK\r\nContent-Length: 20\r\n\r\n"); @@ -2498,7 +2503,7 @@ TEST_P(Http1ClientConnectionImplTest, UpgradeResponse) { {":authority", "host"}, {"connection", "upgrade"}, {"upgrade", "websocket"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Send upgrade headers EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); @@ -2534,7 +2539,7 @@ TEST_P(Http1ClientConnectionImplTest, UpgradeResponseWithEarlyData) { {":authority", "host"}, {"connection", "upgrade"}, {"upgrade", "websocket"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Send upgrade headers EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); @@ -2554,7 +2559,7 @@ TEST_P(Http1ClientConnectionImplTest, ConnectResponse) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "CONNECT"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Send response headers EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); @@ -2585,7 +2590,7 @@ TEST_P(Http1ClientConnectionImplTest, ConnectResponseWithEarlyData) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "CONNECT"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Send response headers and payload EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); @@ -2604,7 +2609,7 @@ TEST_P(Http1ClientConnectionImplTest, ConnectRejected) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "CONNECT"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_CALL(response_decoder, decodeHeaders_(_, false)); Buffer::OwnedImpl expected_data("12345abcd"); @@ -2635,7 +2640,7 @@ TEST_P(Http1ClientConnectionImplTest, WatermarkTest) { EXPECT_CALL(stream_callbacks, onAboveWriteBufferHighWatermark()); EXPECT_CALL(stream_callbacks, onBelowWriteBufferLowWatermark()); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Fake out the underlying Network::Connection buffer being drained. EXPECT_CALL(stream_callbacks, onBelowWriteBufferLowWatermark()); @@ -2660,7 +2665,7 @@ TEST_P(Http1ClientConnectionImplTest, HighwatermarkMultipleResponses) { request_encoder.getStream().addCallbacks(stream_callbacks); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Fake a call from the underlying Network::Connection and verify the stream is notified. EXPECT_CALL(stream_callbacks, onAboveWriteBufferHighWatermark()); @@ -2694,7 +2699,7 @@ TEST_P(Http1ClientConnectionImplTest, LowWatermarkDuringClose) { request_encoder.getStream().addCallbacks(stream_callbacks); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); // Fake a call from the underlying Network::Connection and verify the stream is notified. EXPECT_CALL(stream_callbacks, onAboveWriteBufferHighWatermark()); @@ -2867,7 +2872,7 @@ TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeValueRejected) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"); auto status = codec_->dispatch(buffer); @@ -2887,7 +2892,7 @@ TEST_P(Http1ClientConnectionImplTest, ResponseHeadersWithLargeFieldRejected) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"); auto status = codec_->dispatch(buffer); @@ -2906,7 +2911,7 @@ TEST_P(Http1ClientConnectionImplTest, LargeResponseHeadersAccepted) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"); auto status = codec_->dispatch(buffer); @@ -2928,7 +2933,7 @@ TEST_P(Http1ClientConnectionImplTest, LargeMethodRequestEncode) { {":method", long_method}, {":path", "/"}, {":authority", "host"}}; std::string output; ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ(long_method + " / HTTP/1.1\r\nhost: host\r\ncontent-length: 0\r\n\r\n", output); } @@ -2946,7 +2951,7 @@ TEST_P(Http1ClientConnectionImplTest, LargePathRequestEncode) { {":method", "GET"}, {":path", long_path}, {":authority", "host"}}; std::string output; ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ("GET " + long_path + " HTTP/1.1\r\nhost: host\r\ncontent-length: 0\r\n\r\n", output); } @@ -2962,7 +2967,7 @@ TEST_P(Http1ClientConnectionImplTest, LargeHeaderRequestEncode) { {":method", "GET"}, {"foo", long_header_value}, {":path", "/"}, {":authority", "host"}}; std::string output; ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); EXPECT_EQ("GET / HTTP/1.1\r\nhost: host\r\nfoo: " + long_header_value + "\r\ncontent-length: 0\r\n\r\n", output); @@ -2975,7 +2980,7 @@ TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersRejected) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"); auto status = codec_->dispatch(buffer); @@ -2995,7 +3000,7 @@ TEST_P(Http1ClientConnectionImplTest, ManyResponseHeadersAccepted) { NiceMock response_decoder; Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; - request_encoder.encodeHeaders(headers, true); + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); Buffer::OwnedImpl buffer("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n"); auto status = codec_->dispatch(buffer); diff --git a/test/common/http/http1/conn_pool_test.cc b/test/common/http/http1/conn_pool_test.cc index 9e95c4fbc519..072a1b807d1b 100644 --- a/test/common/http/http1/conn_pool_test.cc +++ b/test/common/http/http1/conn_pool_test.cc @@ -200,8 +200,10 @@ struct ActiveTestRequest { } void startRequest() { - callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); } Http1ConnPoolImplTest& parent_; @@ -626,15 +628,19 @@ TEST_F(Http1ConnPoolImplTest, MaxConnections) { .WillOnce(DoAll(SaveArgAddress(&inner_decoder), ReturnRef(request_encoder))); EXPECT_CALL(callbacks2.pool_ready_, ready()); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); Http::ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); inner_decoder->decodeHeaders(std::move(response_headers), true); conn_pool_->expectAndRunUpstreamReady(); conn_pool_->expectEnableUpstreamReady(); - callbacks2.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks2.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // N.B. clang_tidy insists that we use std::make_unique which can not infer std::initialize_list. response_headers = std::make_unique( std::initializer_list>{{":status", "200"}}); @@ -680,8 +686,10 @@ TEST_F(Http1ConnPoolImplTest, ConnectionCloseWithoutHeader) { // Finishing request 1 will schedule binding the connection to request 2. conn_pool_->expectEnableUpstreamReady(); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); Http::ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); inner_decoder->decodeHeaders(std::move(response_headers), true); @@ -699,8 +707,10 @@ TEST_F(Http1ConnPoolImplTest, ConnectionCloseWithoutHeader) { conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); conn_pool_->expectEnableUpstreamReady(); - callbacks2.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks2.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // N.B. clang_tidy insists that we use std::make_unique which can not infer std::initialize_list. response_headers = std::make_unique( std::initializer_list>{{":status", "200"}}); @@ -732,8 +742,10 @@ TEST_F(Http1ConnPoolImplTest, ConnectionCloseHeader) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Response with 'connection: close' which should cause the connection to go away. EXPECT_CALL(*conn_pool_, onClientDestroy()); @@ -766,8 +778,10 @@ TEST_F(Http1ConnPoolImplTest, ProxyConnectionCloseHeader) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(*conn_pool_, onClientDestroy()); // Response with 'proxy-connection: close' which should cause the connection to go away, even if @@ -804,8 +818,10 @@ TEST_F(Http1ConnPoolImplTest, ProxyConnectionCloseHeaderLegacy) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Response with 'proxy-connection: close' which should cause the connection to go away, even if // there are other tokens in that header. @@ -839,8 +855,10 @@ TEST_F(Http1ConnPoolImplTest, Http10NoConnectionKeepAlive) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Response without 'connection: keep-alive' which should cause the connection to go away. EXPECT_CALL(*conn_pool_, onClientDestroy()); @@ -876,8 +894,10 @@ TEST_F(Http1ConnPoolImplTest, Http10NoConnectionKeepAliveLegacy) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Response without 'connection: keep-alive' which should cause the connection to go away. EXPECT_CALL(*conn_pool_, onClientDestroy()); @@ -912,8 +932,10 @@ TEST_F(Http1ConnPoolImplTest, MaxRequestsPerConnection) { EXPECT_CALL(callbacks.pool_ready_, ready()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Response with 'connection: close' which should cause the connection to go away. EXPECT_CALL(*conn_pool_, onClientDestroy()); @@ -1019,8 +1041,10 @@ TEST_F(Http1ConnPoolImplTest, RemoteCloseToCompleteResponse) { EXPECT_CALL(*conn_pool_->test_clients_[0].connect_timer_, disableTimer()); conn_pool_->test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::Connected); - callbacks.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + callbacks.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); inner_decoder->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, false); diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index d8ec1e0adb55..e2c749c55940 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -285,7 +285,7 @@ class Http2CodecImplTest : public ::testing::TestWithParamencodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); nghttp2_priority_spec spec = {0, 10, 0}; // HTTP/2 codec adds 1 to the number of active streams when computing PRIORITY frames limit @@ -302,7 +302,7 @@ class Http2CodecImplTest : public ::testing::TestWithParamencodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); // Send one DATA frame back EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); @@ -329,7 +329,7 @@ class Http2CodecImplTest : public ::testing::TestWithParamencodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // HTTP/2 codec does not send empty DATA frames with no END_STREAM flag. // To make this work, send raw bytes representing empty DATA frames bypassing client codec. @@ -349,7 +349,7 @@ TEST_P(Http2CodecImplTest, ShutdownNotice) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); EXPECT_CALL(client_callbacks_, onGoAway(_)); server_->shutdownNotice(); @@ -367,7 +367,7 @@ TEST_P(Http2CodecImplTest, ContinueHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); @@ -385,7 +385,7 @@ TEST_P(Http2CodecImplTest, TrailerStatus) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); @@ -407,7 +407,7 @@ TEST_P(Http2CodecImplTest, MultipleContinueHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); @@ -428,7 +428,7 @@ TEST_P(Http2CodecImplTest, 1xxNonContinueHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl other_headers{{":status", "102"}}; EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); @@ -442,7 +442,7 @@ TEST_P(Http2CodecImplTest, Invalid101SwitchingProtocols) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl upgrade_headers{{":status", "101"}}; EXPECT_CALL(response_decoder_, decodeHeaders_(_, _)).Times(0); @@ -456,7 +456,7 @@ TEST_P(Http2CodecImplTest, InvalidContinueWithFin) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_THROW(response_encoder_->encodeHeaders(continue_headers, true), ClientCodecError); @@ -473,7 +473,7 @@ TEST_P(Http2CodecImplTest, InvalidContinueWithFinAllowed) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); // Buffer client data to avoid mock recursion causing lifetime issues. ON_CALL(server_connection_, write(_, _)) @@ -499,7 +499,7 @@ TEST_P(Http2CodecImplTest, CodecHasCorrectStreamErrorIfFalse) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); EXPECT_FALSE(response_encoder_->streamErrorOnInvalidHttpMessage()); } @@ -511,7 +511,7 @@ TEST_P(Http2CodecImplTest, CodecHasCorrectStreamErrorIfTrue) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); EXPECT_TRUE(response_encoder_->streamErrorOnInvalidHttpMessage()); } @@ -522,7 +522,7 @@ TEST_P(Http2CodecImplTest, InvalidRepeatContinue) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); @@ -542,7 +542,7 @@ TEST_P(Http2CodecImplTest, InvalidRepeatContinueAllowed) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl continue_headers{{":status", "100"}}; EXPECT_CALL(response_decoder_, decode100ContinueHeaders_(_)); @@ -571,7 +571,7 @@ TEST_P(Http2CodecImplTest, Invalid204WithContentLength) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl response_headers{{":status", "204"}, {"content-length", "3"}}; // What follows is a hack to get headers that should span into continuation frames. The default @@ -600,7 +600,7 @@ TEST_P(Http2CodecImplTest, Invalid204WithContentLengthAllowed) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); // Buffer client data to avoid mock recursion causing lifetime issues. ON_CALL(server_connection_, write(_, _)) @@ -635,7 +635,7 @@ TEST_P(Http2CodecImplTest, RefusedStreamReset) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); MockStreamCallbacks callbacks; request_encoder_->getStream().addCallbacks(callbacks); @@ -648,27 +648,16 @@ TEST_P(Http2CodecImplTest, RefusedStreamReset) { TEST_P(Http2CodecImplTest, InvalidHeadersFrame) { initialize(); - EXPECT_THROW(request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true), ServerCodecError); - EXPECT_EQ(1, server_stats_store_.counter("http2.rx_messaging_error").value()); -} - -TEST_P(Http2CodecImplTest, InvalidHeadersFrameAllowed) { - stream_error_on_invalid_http_messaging_ = true; - initialize(); - - MockStreamCallbacks request_callbacks; - request_encoder_->getStream().addCallbacks(request_callbacks); - - ON_CALL(client_connection_, write(_, _)) - .WillByDefault( - Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.new_codec_behavior")) { + const auto status = request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true); - request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true); - EXPECT_CALL(server_stream_callbacks_, onResetStream(StreamResetReason::LocalReset, _)); - EXPECT_CALL(request_callbacks, onResetStream(StreamResetReason::RemoteReset, _)); - auto status = server_wrapper_.dispatch(Buffer::OwnedImpl(), *server_); - EXPECT_TRUE(status.ok()); - expectDetailsResponse("http2.violation.of.messaging.rule"); + EXPECT_FALSE(status.ok()); + EXPECT_THAT(status.message(), testing::HasSubstr("missing required")); + } else { + EXPECT_THROW(request_encoder_->encodeHeaders(TestRequestHeaderMapImpl{}, true).IgnoreError(), + ServerCodecError); + EXPECT_EQ(1, server_stats_store_.counter("http2.rx_messaging_error").value()); + } } TEST_P(Http2CodecImplTest, TrailingHeaders) { @@ -677,7 +666,7 @@ TEST_P(Http2CodecImplTest, TrailingHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_CALL(request_decoder_, decodeData(_, false)); Buffer::OwnedImpl hello("hello"); request_encoder_->encodeData(hello, false); @@ -707,7 +696,7 @@ TEST_P(Http2CodecImplTest, IgnoreTrailingEmptyHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_CALL(request_decoder_, decodeData(_, false)); Buffer::OwnedImpl hello("hello"); request_encoder_->encodeData(hello, false); @@ -736,7 +725,7 @@ TEST_P(Http2CodecImplTest, SubmitTrailingEmptyHeaders) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_CALL(request_decoder_, decodeData(_, false)); Buffer::OwnedImpl hello("hello"); request_encoder_->encodeData(hello, false); @@ -764,7 +753,7 @@ TEST_P(Http2CodecImplTest, TrailingHeadersLargeClientBody) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_CALL(request_decoder_, decodeData(_, false)).Times(AtLeast(1)); Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); request_encoder_->encodeData(body, false); @@ -794,7 +783,7 @@ TEST_P(Http2CodecImplTest, SmallMetadataVecTest) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); MetadataMapVector metadata_map_vector; const int size = 10; @@ -824,7 +813,7 @@ TEST_P(Http2CodecImplTest, LargeMetadataVecTest) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); MetadataMapVector metadata_map_vector; const int size = 10; @@ -851,7 +840,7 @@ TEST_P(Http2CodecImplTest, BadMetadataVecReceivedTest) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); MetadataMap metadata_map = { {"header_key1", "header_value1"}, @@ -894,7 +883,7 @@ TEST_P(Http2CodecImplTest, EncodeMetadataWhileDispatchingTest) { response_encoder_->encodeMetadata(metadata_map_vector); })); EXPECT_CALL(response_decoder_, decodeMetadata_(_)).Times(size); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } // Validate the keepalive PINGs are sent and received correctly. @@ -972,7 +961,7 @@ TEST_P(Http2CodecImplDeferredResetTest, DeferredResetClient) { Invoke([&](Buffer::Instance& data, bool) -> void { server_wrapper_.buffer_.add(data); })); TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); EXPECT_CALL(client_stream_callbacks, onAboveWriteBufferHighWatermark()).Times(AnyNumber()); request_encoder_->encodeData(body, true); @@ -1004,7 +993,7 @@ TEST_P(Http2CodecImplDeferredResetTest, DeferredResetServer) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // In this case we do the same thing as DeferredResetClient but on the server side. ON_CALL(server_connection_, write(_, _)) @@ -1048,7 +1037,7 @@ TEST_P(Http2CodecImplFlowControlTest, TestFlowControlInPendingSendData) { TestRequestHeaderMapImpl expected_headers; HttpTestUtility::addDefaultHeaders(expected_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // Force the server stream to be read disabled. This will cause it to stop sending window // updates to the client. @@ -1109,7 +1098,7 @@ TEST_P(Http2CodecImplFlowControlTest, TestFlowControlInPendingSendData) { return request_decoder2; })); EXPECT_CALL(request_decoder2, decodeHeaders_(_, false)); - request_encoder2->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder2->encodeHeaders(request_headers, false).ok()); // Add the stream callbacks belatedly. On creation the stream should have // been noticed that the connection was backed up. Any new subscriber to @@ -1155,7 +1144,7 @@ TEST_P(Http2CodecImplFlowControlTest, EarlyResetRestoresWindow) { TestRequestHeaderMapImpl expected_headers; HttpTestUtility::addDefaultHeaders(expected_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // Force the server stream to be read disabled. This will cause it to stop sending window // updates to the client. @@ -1214,7 +1203,7 @@ TEST_P(Http2CodecImplFlowControlTest, FlowControlPendingRecvData) { TestRequestHeaderMapImpl expected_headers; HttpTestUtility::addDefaultHeaders(expected_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // Set artificially small watermarks to make the recv buffer easy to overrun. In production, // the recv buffer can be overrun by a client which negotiates a larger @@ -1236,7 +1225,7 @@ TEST_P(Http2CodecImplFlowControlTest, TrailingHeadersLargeServerBody) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); ON_CALL(client_connection_, write(_, _)) .WillByDefault( @@ -1273,7 +1262,7 @@ TEST_P(Http2CodecImplFlowControlTest, TrailingHeadersLargeServerBodyFlushTimeout TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); ON_CALL(client_connection_, write(_, _)) .WillByDefault( @@ -1308,7 +1297,7 @@ TEST_P(Http2CodecImplFlowControlTest, LargeServerBodyFlushTimeout) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); ON_CALL(client_connection_, write(_, _)) .WillByDefault( @@ -1341,7 +1330,7 @@ TEST_P(Http2CodecImplFlowControlTest, LargeServerBodyFlushTimeoutAfterGoaway) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); ON_CALL(client_connection_, write(_, _)) .WillByDefault( @@ -1375,7 +1364,7 @@ TEST_P(Http2CodecImplFlowControlTest, WindowUpdateOnReadResumingFlood) { TestRequestHeaderMapImpl expected_headers; HttpTestUtility::addDefaultHeaders(expected_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -1440,7 +1429,7 @@ TEST_P(Http2CodecImplFlowControlTest, RstStreamOnPendingFlushTimeoutFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -1492,7 +1481,7 @@ TEST_P(Http2CodecImplTest, WatermarkUnderEndStream) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // The 'true' on encodeData will set local_end_stream_ on the client but not // the server. Verify that client watermark callbacks will not be called, but @@ -1567,7 +1556,7 @@ TEST_P(Http2CodecImplStreamLimitTest, MaxClientStreams) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } } @@ -1745,7 +1734,7 @@ TEST_P(Http2CustomSettingsTest, UserDefinedSettings) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); uint32_t hpack_table_size = ::testing::get(getSettingsTuple()); if (hpack_table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { @@ -1784,7 +1773,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersInvokeResetStream) { std::string long_string = std::string(63 * 1024, 'q'); request_headers.addCopy("big", long_string); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(1); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); } // Large request headers are accepted when max limit configured. @@ -1799,7 +1788,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersAccepted) { EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); } // Tests request headers with name containing underscore are dropped when the option is set to drop @@ -1813,7 +1802,7 @@ TEST_P(Http2CodecImplTest, HeaderNameWithUnderscoreAreDropped) { TestRequestHeaderMapImpl expected_headers(request_headers); request_headers.addCopy("bad_header", "something"); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), _)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_EQ(1, server_stats_store_.counter("http2.dropped_headers_with_underscores").value()); } @@ -1827,7 +1816,7 @@ TEST_P(Http2CodecImplTest, HeaderNameWithUnderscoreAreRejectedByDefault) { HttpTestUtility::addDefaultHeaders(request_headers); request_headers.addCopy("bad_header", "something"); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(1); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_EQ( 1, server_stats_store_.counter("http2.requests_rejected_with_underscores_in_headers").value()); @@ -1845,7 +1834,7 @@ TEST_P(Http2CodecImplTest, HeaderNameWithUnderscoreAllowed) { TestRequestHeaderMapImpl expected_headers(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), _)); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_EQ(0, server_stats_store_.counter("http2.dropped_headers_with_underscores").value()); } @@ -1865,7 +1854,7 @@ TEST_P(Http2CodecImplTest, LargeMethodRequestEncode) { request_headers.setReferenceKey(Headers::get().Method, long_method); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&request_headers), false)); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); } // Tests stream reset when the number of request headers exceeds the default maximum of 100. @@ -1878,7 +1867,7 @@ TEST_P(Http2CodecImplTest, ManyRequestHeadersInvokeResetStream) { request_headers.addCopy(std::to_string(i), ""); } EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(1); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); } // Tests that max number of request headers is configurable. @@ -1893,7 +1882,7 @@ TEST_P(Http2CodecImplTest, ManyRequestHeadersAccepted) { } EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); } // Tests that max number of response headers is configurable. @@ -1904,7 +1893,7 @@ TEST_P(Http2CodecImplTest, ManyResponseHeadersAccepted) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"compression", "test"}}; for (int i = 0; i < 105; i++) { @@ -1934,7 +1923,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersAtLimitAccepted) { ASSERT_EQ(request_headers.byteSize() + head_room, codec_limit_kb * 1024); EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } TEST_P(Http2CodecImplTest, LargeRequestHeadersOverDefaultCodecLibraryLimit) { @@ -1948,7 +1937,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersOverDefaultCodecLibraryLimit) { EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(1); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } TEST_P(Http2CodecImplTest, LargeRequestHeadersExceedPerHeaderLimit) { @@ -1968,7 +1957,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersExceedPerHeaderLimit) { EXPECT_CALL(client_callbacks_, onGoAway(_)); server_->shutdownNotice(); server_->goAway(); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } TEST_P(Http2CodecImplTest, ManyLargeRequestHeadersUnderPerHeaderLimit) { @@ -1984,7 +1973,7 @@ TEST_P(Http2CodecImplTest, ManyLargeRequestHeadersUnderPerHeaderLimit) { EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(1); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } TEST_P(Http2CodecImplTest, LargeRequestHeadersAtMaxConfigurable) { @@ -2002,7 +1991,7 @@ TEST_P(Http2CodecImplTest, LargeRequestHeadersAtMaxConfigurable) { EXPECT_CALL(request_decoder_, decodeHeaders_(_, _)).Times(1); EXPECT_CALL(server_stream_callbacks_, onResetStream(_, _)).Times(0); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); } // Note this is Http2CodecImplTestAll not Http2CodecImplTest, to test @@ -2013,7 +2002,7 @@ TEST_P(Http2CodecImplTestAll, TestCodecHeaderCompression) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); - request_encoder_->encodeHeaders(request_headers, true); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, true).ok()); TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"compression", "test"}}; EXPECT_CALL(response_decoder_, decodeHeaders_(_, true)); @@ -2044,7 +2033,7 @@ TEST_P(Http2CodecImplTest, PingFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // Send one frame above the outbound control queue size limit for (uint32_t i = 0; i < CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES + 1; @@ -2077,7 +2066,7 @@ TEST_P(Http2CodecImplTest, PingFloodMitigationDisabled) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // Send one frame above the outbound control queue size limit for (uint32_t i = 0; i < CommonUtility::OptionsLimits::DEFAULT_MAX_OUTBOUND_CONTROL_FRAMES + 1; @@ -2102,7 +2091,7 @@ TEST_P(Http2CodecImplTest, PingFloodCounterReset) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); for (int i = 0; i < kMaxOutboundControlFrames; ++i) { EXPECT_EQ(0, nghttp2_submit_ping(client_->session(), NGHTTP2_FLAG_NONE, nullptr)); @@ -2149,7 +2138,7 @@ TEST_P(Http2CodecImplTest, ResponseHeadersFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2181,7 +2170,7 @@ TEST_P(Http2CodecImplTest, ResponseDataFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2218,7 +2207,7 @@ TEST_P(Http2CodecImplTest, ResponseDataFloodMitigationDisabled) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); // +2 is to account for HEADERS and PING ACK, that is used to trigger mitigation EXPECT_CALL(server_connection_, write(_, _)) @@ -2248,7 +2237,7 @@ TEST_P(Http2CodecImplTest, ResponseDataFloodCounterReset) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2290,7 +2279,7 @@ TEST_P(Http2CodecImplTest, PingStacksWithDataFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2322,7 +2311,7 @@ TEST_P(Http2CodecImplTest, ResponseTrailersFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2362,7 +2351,7 @@ TEST_P(Http2CodecImplTest, MetadataFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2472,7 +2461,7 @@ TEST_P(Http2CodecImplTest, GoAwayCausesOutboundFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2512,7 +2501,7 @@ TEST_P(Http2CodecImplTest, ShudowNoticeCausesOutboundFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2565,7 +2554,7 @@ TEST_P(Http2CodecImplTest, KeepAliveCausesOutboundFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2611,7 +2600,7 @@ TEST_P(Http2CodecImplTest, ResetStreamCausesOutboundFlood) { TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); int frame_count = 0; Buffer::OwnedImpl buffer; @@ -2662,7 +2651,7 @@ TEST_P(Http2CodecImplTest, ConnectTest) { Http::Headers::get().MethodValues.Connect); expected_headers.setReferenceKey(Headers::get().Protocol, "bytestream"); EXPECT_CALL(request_decoder_, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); - request_encoder_->encodeHeaders(request_headers, false); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); EXPECT_CALL(callbacks, onResetStream(StreamResetReason::ConnectError, _)); EXPECT_CALL(server_stream_callbacks_, onResetStream(StreamResetReason::ConnectError, _)); diff --git a/test/common/http/http2/conn_pool_test.cc b/test/common/http/http2/conn_pool_test.cc index c8ea9f0c89c7..dd5952b2a227 100644 --- a/test/common/http/http2/conn_pool_test.cc +++ b/test/common/http/http2/conn_pool_test.cc @@ -269,8 +269,10 @@ void Http2ConnPoolImplTest::closeAllClients() { void Http2ConnPoolImplTest::completeRequest(ActiveTestRequest& r) { EXPECT_CALL(r.inner_encoder_, encodeHeaders(_, true)); - r.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r.decoder_, decodeHeaders_(_, true)); r.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -335,8 +337,10 @@ TEST_F(Http2ConnPoolImplTest, VerifyAlpnFallback) { ActiveTestRequest r(*this, 0, false); expectClientConnect(0, r); EXPECT_CALL(r.inner_encoder_, encodeHeaders(_, true)); - r.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r.decoder_, decodeHeaders_(_, true)); EXPECT_CALL(*this, onClientDestroy()); @@ -359,8 +363,10 @@ TEST_F(Http2ConnPoolImplTest, DrainConnectionReadyWithRequest) { ActiveTestRequest r(*this, 0, false); expectClientConnect(0, r); EXPECT_CALL(r.inner_encoder_, encodeHeaders(_, true)); - r.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); pool_->drainConnections(); @@ -382,8 +388,10 @@ TEST_F(Http2ConnPoolImplTest, DrainConnectionBusy) { ActiveTestRequest r(*this, 0, false); expectClientConnect(0, r); EXPECT_CALL(r.inner_encoder_, encodeHeaders(_, true)); - r.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); pool_->drainConnections(); @@ -570,8 +578,10 @@ TEST_F(Http2ConnPoolImplTest, DrainConnections) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // With max_streams == 1, the second request moves the first connection // to draining. @@ -579,8 +589,10 @@ TEST_F(Http2ConnPoolImplTest, DrainConnections) { ActiveTestRequest r2(*this, 1, false); expectClientConnect(1, r2); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // This will move the second connection to draining. pool_->drainConnections(); @@ -665,16 +677,21 @@ TEST_F(Http2ConnPoolImplTest, PendingStreams) { // Send a request through each stream. EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r3.inner_encoder_, encodeHeaders(_, true)); - r3.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r3.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Since we now have an active connection, subsequent requests should connect immediately. ActiveTestRequest r4(*this, 0, true); @@ -709,16 +726,21 @@ TEST_F(Http2ConnPoolImplTest, PendingStreamsNumberConnectingTotalRequestsPerConn // Send a request through each stream. EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r3.inner_encoder_, encodeHeaders(_, true)); - r3.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r3.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Clean up everything. test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -748,16 +770,21 @@ TEST_F(Http2ConnPoolImplTest, PendingStreamsNumberConnectingConcurrentRequestsPe // Send a request through each stream. EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r3.inner_encoder_, encodeHeaders(_, true)); - r3.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r3.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); // Clean up everything. test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -899,8 +926,10 @@ TEST_F(Http2ConnPoolImplTest, VerifyConnectionTimingStats) { deliverHistogramToSinks(Property(&Stats::Metric::name, "upstream_cx_connect_ms"), _)); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); r1.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -925,8 +954,10 @@ TEST_F(Http2ConnPoolImplTest, VerifyBufferLimits) { expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); r1.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -946,8 +977,10 @@ TEST_F(Http2ConnPoolImplTest, RequestAndResponse) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_EQ(1U, cluster_->stats_.upstream_cx_active_.value()); EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); r1.inner_decoder_->decodeHeaders( @@ -955,8 +988,10 @@ TEST_F(Http2ConnPoolImplTest, RequestAndResponse) { ActiveTestRequest r2(*this, 0, true); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.decoder_, decodeHeaders_(_, true)); r2.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -977,8 +1012,10 @@ TEST_F(Http2ConnPoolImplTest, LocalReset) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, false)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, false); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, false) + .ok()); r1.callbacks_.outer_encoder_->getStream().resetStream(Http::StreamResetReason::LocalReset); test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -998,8 +1035,10 @@ TEST_F(Http2ConnPoolImplTest, RemoteReset) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, false)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, false); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, false) + .ok()); r1.inner_encoder_.stream_.resetStream(Http::StreamResetReason::RemoteReset); test_clients_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1020,9 +1059,10 @@ TEST_F(Http2ConnPoolImplTest, DrainDisconnectWithActiveRequest) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); ReadyWatcher drained; pool_->addDrainedCallback([&]() -> void { drained.ready(); }); @@ -1046,15 +1086,18 @@ TEST_F(Http2ConnPoolImplTest, DrainDisconnectDrainingWithActiveRequest) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); expectClientCreate(); ActiveTestRequest r2(*this, 1, false); expectClientConnect(1, r2); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); ReadyWatcher drained; pool_->addDrainedCallback([&]() -> void { drained.ready(); }); @@ -1086,15 +1129,18 @@ TEST_F(Http2ConnPoolImplTest, DrainPrimary) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); expectClientCreate(); ActiveTestRequest r2(*this, 1, false); expectClientConnect(1, r2); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); ReadyWatcher drained; pool_->addDrainedCallback([&]() -> void { drained.ready(); }); @@ -1126,8 +1172,10 @@ TEST_F(Http2ConnPoolImplTest, DrainPrimaryNoActiveRequest) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(dispatcher_, deferredDelete_(_)); EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); r1.inner_decoder_->decodeHeaders( @@ -1139,8 +1187,10 @@ TEST_F(Http2ConnPoolImplTest, DrainPrimaryNoActiveRequest) { EXPECT_CALL(*this, onClientDestroy()); dispatcher_.clearDeferredDeleteList(); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(dispatcher_, deferredDelete_(_)); EXPECT_CALL(r2.decoder_, decodeHeaders_(_, true)); r2.inner_decoder_->decodeHeaders( @@ -1174,8 +1224,10 @@ TEST_F(Http2ConnPoolImplTest, ConnectTimeout) { ActiveTestRequest r2(*this, 1, false); expectClientConnect(1, r2); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.decoder_, decodeHeaders_(_, true)); r2.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -1201,9 +1253,10 @@ TEST_F(Http2ConnPoolImplTest, MaxGlobalRequests) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); - + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); ConnPoolCallbacks callbacks; MockResponseDecoder decoder; EXPECT_CALL(callbacks.pool_failure_, ready()); @@ -1224,8 +1277,10 @@ TEST_F(Http2ConnPoolImplTest, GoAway) { ActiveTestRequest r1(*this, 0, false); expectClientConnect(0, r1); EXPECT_CALL(r1.inner_encoder_, encodeHeaders(_, true)); - r1.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r1.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r1.decoder_, decodeHeaders_(_, true)); r1.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); @@ -1236,8 +1291,10 @@ TEST_F(Http2ConnPoolImplTest, GoAway) { ActiveTestRequest r2(*this, 1, false); expectClientConnect(1, r2); EXPECT_CALL(r2.inner_encoder_, encodeHeaders(_, true)); - r2.callbacks_.outer_encoder_->encodeHeaders( - TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true); + EXPECT_TRUE( + r2.callbacks_.outer_encoder_ + ->encodeHeaders(TestRequestHeaderMapImpl{{":path", "/"}, {":method", "GET"}}, true) + .ok()); EXPECT_CALL(r2.decoder_, decodeHeaders_(_, true)); r2.inner_decoder_->decodeHeaders( ResponseHeaderMapPtr{new TestResponseHeaderMapImpl{{":status", "200"}}}, true); diff --git a/test/common/http/http2/frame_replay_test.cc b/test/common/http/http2/frame_replay_test.cc index b1931d350bb8..510002cf3fde 100644 --- a/test/common/http/http2/frame_replay_test.cc +++ b/test/common/http/http2/frame_replay_test.cc @@ -33,7 +33,7 @@ void setupStream(ClientCodecFrameInjector& codec, TestClientConnectionImplNew& c // Setup a single stream to inject frames as a reply to. TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); - codec.request_encoder_->encodeHeaders(request_headers, true); + codec.request_encoder_->encodeHeaders(request_headers, true).IgnoreError(); } // Validate that a simple Huffman encoded request HEADERS frame can be decoded. diff --git a/test/common/http/http2/response_header_fuzz_test.cc b/test/common/http/http2/response_header_fuzz_test.cc index 4559aa06419b..112f1392921a 100644 --- a/test/common/http/http2/response_header_fuzz_test.cc +++ b/test/common/http/http2/response_header_fuzz_test.cc @@ -27,7 +27,7 @@ void Replay(const Frame& frame, ClientCodecFrameInjector& codec) { // Setup a single stream to inject frames as a reply to. TestRequestHeaderMapImpl request_headers; HttpTestUtility::addDefaultHeaders(request_headers); - codec.request_encoder_->encodeHeaders(request_headers, true); + codec.request_encoder_->encodeHeaders(request_headers, true).IgnoreError(); // Send frames. status = codec.write(WellKnownFrames::defaultSettingsFrame(), connection); diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 00fdd783afc9..11dd4f5ee2f5 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -462,6 +462,36 @@ TEST_F(RouterTest, RouteNotFound) { EXPECT_EQ(callbacks_.details(), "route_not_found"); } +TEST_F(RouterTest, MissingRequiredHeaders) { + NiceMock encoder; + Http::ResponseDecoder* response_decoder = nullptr; + EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke( + [&](Http::ResponseDecoder& decoder, + Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { + response_decoder = &decoder; + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); + return nullptr; + })); + expectResponseTimerCreate(); + + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + headers.removeMethod(); + + EXPECT_CALL(encoder, encodeHeaders(_, _)) + .WillOnce(Invoke([](const Http::RequestHeaderMap& headers, bool) -> Http::Status { + return Http::HeaderUtility::checkRequiredHeaders(headers); + })); + EXPECT_CALL(callbacks_, + sendLocalReply(Http::Code::ServiceUnavailable, + testing::Eq("missing required header: :method"), _, _, + "filter_removed_required_headers{missing required header: :method}")) + .WillOnce(testing::InvokeWithoutArgs([] {})); + router_.decodeHeaders(headers, true); + router_.onDestroy(); +} + TEST_F(RouterTest, ClusterNotFound) { EXPECT_CALL(callbacks_.stream_info_, setResponseFlag(StreamInfo::ResponseFlag::NoRouteFound)); @@ -1507,8 +1537,9 @@ TEST_F(RouterTest, ResetDuringEncodeHeaders) { EXPECT_CALL(callbacks_, removeDownstreamWatermarkCallbacks(_)); EXPECT_CALL(callbacks_, addDownstreamWatermarkCallbacks(_)); EXPECT_CALL(encoder, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> void { + .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> Http::Status { encoder.stream_.resetStream(Http::StreamResetReason::RemoteReset); + return Http::okStatus(); })); Http::TestRequestHeaderMapImpl headers; @@ -5837,8 +5868,9 @@ TEST_F(RouterTest, AutoHostRewriteEnabled) { // :authority header in the outgoing request should match the DNS name of // the selected upstream host EXPECT_CALL(encoder, encodeHeaders(HeaderMapEqualRef(&outgoing_headers), true)) - .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> void { + .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> Http::Status { encoder.stream_.resetStream(Http::StreamResetReason::RemoteReset); + return Http::okStatus(); })); EXPECT_CALL(callbacks_.stream_info_, onUpstreamHostSelected(_)) @@ -5874,8 +5906,9 @@ TEST_F(RouterTest, AutoHostRewriteDisabled) { // :authority header in the outgoing request should match the :authority header of // the incoming request EXPECT_CALL(encoder, encodeHeaders(HeaderMapEqualRef(&incoming_headers), true)) - .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> void { + .WillOnce(Invoke([&](const Http::HeaderMap&, bool) -> Http::Status { encoder.stream_.resetStream(Http::StreamResetReason::RemoteReset); + return Http::okStatus(); })); EXPECT_CALL(callbacks_.stream_info_, onUpstreamHostSelected(_)) diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 4a1ed5a7ae35..586b2a4e19b6 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -895,10 +895,11 @@ TEST_F(HttpHealthCheckerImplTest, ZeroRetryInterval) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -970,10 +971,11 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheck) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -1004,10 +1006,11 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServicePrefixPatternCheck) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -1038,10 +1041,11 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceExactPatternCheck) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -1072,10 +1076,11 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceRegexPatternCheck) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -1114,9 +1119,10 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValueOnTheHos expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); + return Http::okStatus(); })); health_checker_->start(); @@ -1158,9 +1164,10 @@ TEST_F(HttpHealthCheckerImplTest, expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); + return Http::okStatus(); })); health_checker_->start(); @@ -1192,9 +1199,10 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValue) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); + return Http::okStatus(); })); health_checker_->start(); @@ -1255,7 +1263,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillRepeatedly(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillRepeatedly(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.get(header_ok)[0]->value().getStringView(), value_ok); EXPECT_EQ(headers.get(header_cool)[0]->value().getStringView(), value_cool); EXPECT_EQ(headers.get(header_awesome)[0]->value().getStringView(), value_awesome); @@ -1278,6 +1286,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { std::string current_start_time = date_formatter.fromTime(dispatcher_.timeSource().systemTime()); EXPECT_EQ(headers.get(start_time)[0]->value().getStringView(), current_start_time); + return Http::okStatus(); })); health_checker_->start(); @@ -1318,8 +1327,9 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithoutUserAgent) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillRepeatedly(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillRepeatedly(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.UserAgent(), nullptr); + return Http::okStatus(); })); health_checker_->start(); @@ -2325,9 +2335,10 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAltPort) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); + return Http::okStatus(); })); health_checker_->start(); @@ -2627,10 +2638,11 @@ TEST_F(HttpHealthCheckerImplTest, DEPRECATED_FEATURE_TEST(ServiceNameMatch)) { expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(headers.getHostValue(), host); EXPECT_EQ(headers.getPathValue(), path); EXPECT_EQ(headers.getSchemeValue(), Http::Headers::get().SchemeValues.Http); + return Http::okStatus(); })); health_checker_->start(); @@ -3678,7 +3690,7 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils expectHealthcheckStart(0); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, false)) - .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) { + .WillOnce(Invoke([&](const Http::RequestHeaderMap& headers, bool) -> Http::Status { EXPECT_EQ(Http::Headers::get().ContentTypeValues.Grpc, headers.getContentTypeValue()); EXPECT_EQ(std::string("/grpc.health.v1.Health/Check"), headers.getPathValue()); EXPECT_EQ(Http::Headers::get().SchemeValues.Http, headers.getSchemeValue()); @@ -3686,6 +3698,7 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils EXPECT_EQ(expected_host, headers.getHostValue()); EXPECT_EQ(std::chrono::milliseconds(1000).count(), Envoy::Grpc::Common::getGrpcTimeout(headers).value().count()); + return Http::okStatus(); })); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeData(_, true)) .WillOnce(Invoke([&](Buffer::Instance& data, bool) { diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc index e2d90d916469..c95a61e5ace8 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc @@ -152,7 +152,8 @@ class EnvoyQuicClientSessionTest : public testing::TestWithParam { std::string host("www.abc.com"); Http::TestRequestHeaderMapImpl request_headers{ {":authority", host}, {":method", "GET"}, {":path", "/"}}; - stream.encodeHeaders(request_headers, true); + const auto result = stream.encodeHeaders(request_headers, true); + ASSERT(result.ok()); return stream; } diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index ac82239db0bb..7711b7c88bf0 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -117,7 +117,8 @@ INSTANTIATE_TEST_SUITE_P(EnvoyQuicClientStreamTests, EnvoyQuicClientStreamTest, TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { EXPECT_EQ(absl::nullopt, quic_stream_->http1StreamEncoderOptions()); - quic_stream_->encodeHeaders(request_headers_, false); + const auto result = quic_stream_->encodeHeaders(request_headers_, false); + EXPECT_TRUE(result.ok()); quic_stream_->encodeData(request_body_, false); quic_stream_->encodeTrailers(request_trailers_); @@ -167,7 +168,8 @@ TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); return; } - quic_stream_->encodeHeaders(request_headers_, true); + const auto result = quic_stream_->encodeHeaders(request_headers_, true); + EXPECT_TRUE(result.ok()); EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([](const Http::ResponseHeaderMapPtr& headers, bool) { EXPECT_EQ("200", headers->getStatusValue()); @@ -220,7 +222,8 @@ TEST_P(EnvoyQuicClientStreamTest, WatermarkSendBuffer) { quic_session_.OnWindowUpdateFrame(window_update); request_headers_.addCopy(":content-length", "32770"); // 32KB + 2 byte - quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); + const auto result = quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); + EXPECT_TRUE(result.ok()); // Encode 32kB request body. first 16KB should be written out right away. The // rest should be buffered. The high watermark is 16KB, so this call should // make the send buffer reach its high watermark. @@ -288,7 +291,8 @@ TEST_P(EnvoyQuicClientStreamTest, HeadersContributeToWatermarkIquic) { quiche::QuicheOptional) { return quic::QuicConsumedData{0u, state != quic::NO_FIN}; })); - quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); + const auto result = quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); + EXPECT_TRUE(result.ok()); // Encode 16kB -10 bytes request body. Because the high watermark is 16KB, with previously // buffered headers, this call should make the send buffers reach their high watermark. diff --git a/test/extensions/upstreams/http/tcp/upstream_request_test.cc b/test/extensions/upstreams/http/tcp/upstream_request_test.cc index ef3d08ca908a..d835151eab82 100644 --- a/test/extensions/upstreams/http/tcp/upstream_request_test.cc +++ b/test/extensions/upstreams/http/tcp/upstream_request_test.cc @@ -117,7 +117,7 @@ TEST_F(TcpUpstreamTest, Basic) { // Swallow the request headers and generate response headers. EXPECT_CALL(connection_, write(_, false)).Times(0); EXPECT_CALL(mock_router_filter_, onUpstreamHeaders(200, _, _, false)); - tcp_upstream_->encodeHeaders(request_, false); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); // Proxy the data. EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); @@ -154,7 +154,7 @@ TEST_F(TcpUpstreamTest, V1Header) { // encodeHeaders now results in the proxy proto header being sent. EXPECT_CALL(connection_, write(BufferEqual(&expected_data), false)); - tcp_upstream_->encodeHeaders(request_, false); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); // Data is proxied as usual. EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); @@ -177,7 +177,7 @@ TEST_F(TcpUpstreamTest, V2Header) { // encodeHeaders now results in the proxy proto header being sent. EXPECT_CALL(connection_, write(BufferEqual(&expected_data), false)); - tcp_upstream_->encodeHeaders(request_, false); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); // Data is proxied as usual. EXPECT_CALL(connection_, write(BufferStringEqual("foo"), false)); @@ -187,7 +187,7 @@ TEST_F(TcpUpstreamTest, V2Header) { TEST_F(TcpUpstreamTest, TrailersEndStream) { // Swallow the headers. - tcp_upstream_->encodeHeaders(request_, false); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, false).ok()); EXPECT_CALL(connection_, write(BufferStringEqual(""), true)); Envoy::Http::TestRequestTrailerMapImpl trailers{{"foo", "bar"}}; @@ -196,7 +196,7 @@ TEST_F(TcpUpstreamTest, TrailersEndStream) { TEST_F(TcpUpstreamTest, HeaderEndStreamHalfClose) { EXPECT_CALL(connection_, write(BufferStringEqual(""), true)); - tcp_upstream_->encodeHeaders(request_, true); + EXPECT_TRUE(tcp_upstream_->encodeHeaders(request_, true).ok()); } TEST_F(TcpUpstreamTest, ReadDisable) { diff --git a/test/integration/BUILD b/test/integration/BUILD index 81a632d40f79..8cf3bfcda25e 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -447,6 +447,7 @@ envoy_cc_test( "//test/common/http/http2:http2_frame", "//test/integration/filters:continue_headers_only_inject_body", "//test/integration/filters:encoder_decoder_buffer_filter_lib", + "//test/integration/filters:invalid_header_filter_lib", "//test/integration/filters:local_reply_during_encoding_filter_lib", "//test/integration/filters:random_pause_filter_lib", "//test/test_common:utility_lib", @@ -891,6 +892,7 @@ envoy_cc_test( "//source/extensions/filters/http/health_check:config", "//test/integration/filters:clear_route_cache_filter_lib", "//test/integration/filters:encoder_decoder_buffer_filter_lib", + "//test/integration/filters:invalid_header_filter_lib", "//test/integration/filters:process_context_lib", "//test/integration/filters:stop_iteration_and_continue", "//test/mocks/http:http_mocks", diff --git a/test/integration/filters/BUILD b/test/integration/filters/BUILD index 372a4c0c359e..8e25cc2c1622 100644 --- a/test/integration/filters/BUILD +++ b/test/integration/filters/BUILD @@ -373,3 +373,19 @@ envoy_cc_test_library( "@envoy_api//envoy/extensions/network/socket_interface/v3:pkg_cc_proto", ], ) + +envoy_cc_test_library( + name = "invalid_header_filter_lib", + srcs = [ + "invalid_header_filter.cc", + ], + deps = [ + ":common_lib", + "//include/envoy/http:filter_interface", + "//include/envoy/registry", + "//include/envoy/server:filter_config_interface", + "//source/common/http:header_utility_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", + "//test/extensions/filters/http/common:empty_http_filter_config_lib", + ], +) diff --git a/test/integration/filters/invalid_header_filter.cc b/test/integration/filters/invalid_header_filter.cc new file mode 100644 index 000000000000..7c1120356662 --- /dev/null +++ b/test/integration/filters/invalid_header_filter.cc @@ -0,0 +1,46 @@ +#include + +#include "envoy/http/filter.h" +#include "envoy/registry/registry.h" +#include "envoy/server/filter_config.h" + +#include "common/http/header_utility.h" + +#include "extensions/filters/http/common/pass_through_filter.h" + +#include "test/extensions/filters/http/common/empty_http_filter_config.h" +#include "test/integration/filters/common.h" + +namespace Envoy { + +// Faulty filter that may remove critical headers. +class InvalidHeaderFilter : public Http::PassThroughFilter { +public: + constexpr static char name[] = "invalid-header-filter"; + + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override { + // Remove method when there is a "remove-method" header. + if (!headers.get(Http::LowerCaseString("remove-method")).empty()) { + headers.removeMethod(); + } + if (!headers.get(Http::LowerCaseString("remove-path")).empty()) { + headers.removePath(); + } + if (Http::HeaderUtility::isConnect(headers)) { + headers.removeHost(); + } + if (!headers.get(Http::LowerCaseString("send-reply")).empty()) { + decoder_callbacks_->sendLocalReply(Envoy::Http::Code::OK, "", nullptr, absl::nullopt, + "InvalidHeaderFilter ready"); + return Http::FilterHeadersStatus::StopIteration; + } + return Http::FilterHeadersStatus::Continue; + } +}; + +constexpr char InvalidHeaderFilter::name[]; +static Registry::RegisterFactory, + Server::Configuration::NamedHttpFilterConfigFactory> + decoder_register_; + +} // namespace Envoy diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 6ba9e29461bf..bae6f3ba9acb 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -87,7 +87,7 @@ IntegrationCodecClient::makeHeaderOnlyRequest(const Http::RequestHeaderMap& head auto response = std::make_unique(dispatcher_); Http::RequestEncoder& encoder = newStream(*response); encoder.getStream().addCallbacks(*response); - encoder.encodeHeaders(headers, true); + encoder.encodeHeaders(headers, true).IgnoreError(); flushWrite(); return response; } @@ -104,7 +104,7 @@ IntegrationCodecClient::makeRequestWithBody(const Http::RequestHeaderMap& header auto response = std::make_unique(dispatcher_); Http::RequestEncoder& encoder = newStream(*response); encoder.getStream().addCallbacks(*response); - encoder.encodeHeaders(headers, false); + encoder.encodeHeaders(headers, false).IgnoreError(); Buffer::OwnedImpl data(body); encoder.encodeData(data, true); flushWrite(); @@ -155,7 +155,7 @@ IntegrationCodecClient::startRequest(const Http::RequestHeaderMap& headers) { auto response = std::make_unique(dispatcher_); Http::RequestEncoder& encoder = newStream(*response); encoder.getStream().addCallbacks(*response); - encoder.encodeHeaders(headers, false); + encoder.encodeHeaders(headers, false).IgnoreError(); flushWrite(); return {encoder, std::move(response)}; } diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index cb72353e1ad2..480b5b2e7cf5 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -334,6 +334,88 @@ TEST_P(ProtocolIntegrationTest, ResponseWithHostHeader) { response->headers().get(Http::LowerCaseString("host"))[0]->value().getStringView()); } +// Tests missing headers needed for H/1 codec first line. +TEST_P(ProtocolIntegrationTest, DownstreamRequestWithFaultyFilter) { + useAccessLog("%RESPONSE_CODE_DETAILS%"); + config_helper_.addFilter("{ name: invalid-header-filter, typed_config: { \"@type\": " + "type.googleapis.com/google.protobuf.Empty } }"); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Missing method + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"remove-method", "yes"}}); + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("503", response->headers().getStatusValue()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("missing_required_header")); + + // Missing path for non-CONNECT + response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"remove-path", "yes"}}); + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("503", response->headers().getStatusValue()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("missing_required_header")); +} + +TEST_P(ProtocolIntegrationTest, FaultyFilterWithConnect) { + // Faulty filter that removed host in a CONNECT request. + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { ConfigHelper::setConnectConfig(hcm, false); }); + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + // Clone the whole listener. + auto static_resources = bootstrap.mutable_static_resources(); + auto* old_listener = static_resources->mutable_listeners(0); + auto* cloned_listener = static_resources->add_listeners(); + cloned_listener->CopyFrom(*old_listener); + old_listener->set_name("http_forward"); + }); + useAccessLog("%RESPONSE_CODE_DETAILS%"); + config_helper_.addFilter("{ name: invalid-header-filter, typed_config: { \"@type\": " + "type.googleapis.com/google.protobuf.Empty } }"); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Missing host for CONNECT + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ + {":method", "CONNECT"}, {":scheme", "http"}, {":authority", "www.host.com:80"}}); + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("503", response->headers().getStatusValue()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("missing_required_header")); +} + +TEST_P(ProtocolIntegrationTest, MissingHeadersLocalReply) { + useAccessLog("%RESPONSE_CODE_DETAILS%"); + config_helper_.addFilter("{ name: invalid-header-filter, typed_config: { \"@type\": " + "type.googleapis.com/google.protobuf.Empty } }"); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Missing method + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestRequestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"remove-method", "yes"}, + {"send-reply", "yes"}}); + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("InvalidHeaderFilter_ready\n")); +} + // Regression test for https://github.com/envoyproxy/envoy/issues/10270 TEST_P(ProtocolIntegrationTest, LongHeaderValueWithSpaces) { // Header with at least 20kb of spaces surrounded by non-whitespace characters to ensure that diff --git a/test/integration/utility.cc b/test/integration/utility.cc index ddbd0816f53e..bbcc7a3b33fd 100644 --- a/test/integration/utility.cc +++ b/test/integration/utility.cc @@ -110,7 +110,8 @@ IntegrationUtil::makeSingleRequest(const Network::Address::InstanceConstSharedPt if (!content_type.empty()) { headers.setContentType(content_type); } - encoder.encodeHeaders(headers, body.empty()); + const auto status = encoder.encodeHeaders(headers, body.empty()); + ASSERT(status.ok()); if (!body.empty()) { Buffer::OwnedImpl body_buffer(body); encoder.encodeData(body_buffer, true); diff --git a/test/mocks/http/BUILD b/test/mocks/http/BUILD index ca7705d8881f..e5593a908c55 100644 --- a/test/mocks/http/BUILD +++ b/test/mocks/http/BUILD @@ -85,6 +85,7 @@ envoy_cc_mock( deps = [ ":stream_mock", "//include/envoy/http:codec_interface", + "//source/common/http:header_utility_lib", ], ) diff --git a/test/mocks/http/stream_encoder.cc b/test/mocks/http/stream_encoder.cc index ad9b646af7d8..5d872c64db15 100644 --- a/test/mocks/http/stream_encoder.cc +++ b/test/mocks/http/stream_encoder.cc @@ -1,5 +1,7 @@ #include "test/mocks/http/stream_encoder.h" +#include "common/http/header_utility.h" + using testing::_; using testing::Invoke; @@ -12,10 +14,11 @@ MockHttp1StreamEncoderOptions::~MockHttp1StreamEncoderOptions() = default; MockRequestEncoder::MockRequestEncoder() { ON_CALL(*this, getStream()).WillByDefault(ReturnRef(stream_)); ON_CALL(*this, encodeHeaders(_, _)) - .WillByDefault(Invoke([](const RequestHeaderMap& headers, bool) { + .WillByDefault(Invoke([](const RequestHeaderMap& headers, bool) -> Status { // Check to see that method is not-null. Path can be null for CONNECT and authority can be // null at the codec level. - ASSERT_NE(nullptr, headers.Method()); + ASSERT(HeaderUtility::checkRequiredHeaders(headers).ok()); + return okStatus(); })); } MockRequestEncoder::~MockRequestEncoder() = default; diff --git a/test/mocks/http/stream_encoder.h b/test/mocks/http/stream_encoder.h index d2c682889ea3..df3d62c00e71 100644 --- a/test/mocks/http/stream_encoder.h +++ b/test/mocks/http/stream_encoder.h @@ -2,6 +2,8 @@ #include "envoy/http/codec.h" +#include "common/http/status.h" + #include "test/mocks/http/stream.h" #include "gmock/gmock.h" @@ -23,7 +25,7 @@ class MockRequestEncoder : public RequestEncoder { ~MockRequestEncoder() override; // Http::RequestEncoder - MOCK_METHOD(void, encodeHeaders, (const RequestHeaderMap& headers, bool end_stream)); + MOCK_METHOD(Status, encodeHeaders, (const RequestHeaderMap& headers, bool end_stream)); MOCK_METHOD(void, encodeTrailers, (const RequestTrailerMap& trailers)); // Http::StreamEncoder From 2f72126295005cc191216715655e012a943c5be8 Mon Sep 17 00:00:00 2001 From: Alvin Baptiste <11775386+abaptiste@users.noreply.github.com> Date: Wed, 4 Nov 2020 19:56:50 -0800 Subject: [PATCH 040/117] [dnsfilter] Fix query ID mismatch in response generation (#13767) Signed-off-by: Alvin Baptiste --- docs/root/version_history/current.rst | 1 + .../udp/dns_filter/dns_filter_resolver.cc | 3 +- .../filters/udp/dns_filter/dns_parser.cc | 80 ++++++++-------- .../filters/udp/dns_filter/dns_parser.h | 92 +++++++++--------- .../dns_filter/dns_filter_integration_test.cc | 14 +-- .../filters/udp/dns_filter/dns_filter_test.cc | 95 ++++++++++--------- .../udp/dns_filter/dns_filter_test_utils.cc | 7 +- .../udp/dns_filter/dns_filter_test_utils.h | 3 +- 8 files changed, 155 insertions(+), 140 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 12fabb48e182..8d45e31362d5 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -25,6 +25,7 @@ Bug Fixes *Changes expected to improve the state of the world and are unlikely to have negative effects* * dns: fix a bug where custom resolvers provided in configuration were not preserved after network issues. +* dns_filter: correctly associate DNS response IDs when multiple queries are received. * http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. * http: reject requests with missing required headers after filter chain processing. * http: sending CONNECT_ERROR for HTTP/2 where appropriate during CONNECT requests. diff --git a/source/extensions/filters/udp/dns_filter/dns_filter_resolver.cc b/source/extensions/filters/udp/dns_filter/dns_filter_resolver.cc index 5d2cbb3cb4b6..d6de809ef35d 100644 --- a/source/extensions/filters/udp/dns_filter/dns_filter_resolver.cc +++ b/source/extensions/filters/udp/dns_filter/dns_filter_resolver.cc @@ -80,7 +80,8 @@ void DnsFilterResolver::resolveExternalQuery(DnsQueryContextPtr context, // timer ctx.timeout_timer->disableTimer(); - ENVOY_LOG(trace, "async query status returned. Entries {}", response.size()); + ENVOY_LOG(trace, "async query status returned for query [{}]. Entries {}", + ctx.query_context->id_, response.size()); ASSERT(ctx.resolver_status == DnsFilterResolverStatus::Pending); ctx.query_context->in_callback_ = true; diff --git a/source/extensions/filters/udp/dns_filter/dns_parser.cc b/source/extensions/filters/udp/dns_filter/dns_parser.cc index 763424a63324..b779120c942b 100644 --- a/source/extensions/filters/udp/dns_filter/dns_parser.cc +++ b/source/extensions/filters/udp/dns_filter/dns_parser.cc @@ -150,7 +150,7 @@ bool DnsMessageParser::parseDnsObject(DnsQueryContextPtr& context, bool done = false; DnsQueryParseState state{DnsQueryParseState::Init}; - header_ = {}; + context->header_ = {}; do { // Ensure that we have enough data remaining in the buffer to parse the query if (available_bytes < field_size) { @@ -167,53 +167,54 @@ bool DnsMessageParser::parseDnsObject(DnsQueryContextPtr& context, switch (state) { case DnsQueryParseState::Init: - header_.id = data; + context->header_.id = data; state = DnsQueryParseState::Flags; break; case DnsQueryParseState::Flags: - ::memcpy(static_cast(&header_.flags), &data, field_size); + ::memcpy(static_cast(&context->header_.flags), &data, field_size); state = DnsQueryParseState::Questions; break; case DnsQueryParseState::Questions: - header_.questions = data; + context->header_.questions = data; state = DnsQueryParseState::Answers; break; case DnsQueryParseState::Answers: - header_.answers = data; + context->header_.answers = data; state = DnsQueryParseState::Authority; break; case DnsQueryParseState::Authority: - header_.authority_rrs = data; + context->header_.authority_rrs = data; state = DnsQueryParseState::Authority2; break; case DnsQueryParseState::Authority2: - header_.additional_rrs = data; + context->header_.additional_rrs = data; done = true; break; } } while (!done); - if (!header_.flags.qr && header_.answers) { + if (!context->header_.flags.qr && context->header_.answers) { ENVOY_LOG(debug, "Answer records present in query"); return false; } - if (header_.questions != 1) { + if (context->header_.questions != 1) { context->response_code_ = DNS_RESPONSE_CODE_FORMAT_ERROR; - ENVOY_LOG(debug, "Unexpected number [{}] of questions in DNS query", header_.questions); + ENVOY_LOG(debug, "Unexpected number [{}] of questions in DNS query", + context->header_.questions); return false; } - context->id_ = static_cast(header_.id); + context->id_ = static_cast(context->header_.id); if (context->id_ == 0) { ENVOY_LOG(debug, "No ID in DNS query"); return false; } // Almost always, we will have only one query here. Per the RFC, QDCOUNT is usually 1 - context->queries_.reserve(header_.questions); - for (auto index = 0; index < header_.questions; index++) { - ENVOY_LOG(trace, "Parsing [{}/{}] questions", index, header_.questions); + context->queries_.reserve(context->header_.questions); + for (auto index = 0; index < context->header_.questions; index++) { + ENVOY_LOG(trace, "Parsing [{}/{}] questions", index, context->header_.questions); auto rec = parseDnsQueryRecord(buffer, offset); if (rec == nullptr) { context->counters_.query_parsing_failure.inc(); @@ -232,11 +233,12 @@ bool DnsMessageParser::parseDnsObject(DnsQueryContextPtr& context, // Parse Answer Records and Additional Resource Records. This is primarily used for tests // to validate the response generated by the filter - if (header_.answers && !parseAnswerRecords(context->answers_, header_.answers, buffer, offset)) { + if (context->header_.answers && + !parseAnswerRecords(context->answers_, context->header_.answers, buffer, offset)) { return false; } - if (header_.authority_rrs) { + if (context->header_.authority_rrs) { // We are not generating these in the filter and don't have a use for them at the moment. // If they exist, we will not parse them and return an error to the client since they appear // between the answers and additional resource records in the buffer. We return true so that @@ -245,11 +247,11 @@ bool DnsMessageParser::parseDnsObject(DnsQueryContextPtr& context, return true; } - if (header_.additional_rrs) { + if (context->header_.additional_rrs) { // We may encounter additional resource records that we do not support. Since the filter // operates on queries, we can skip any additional records that we cannot parse since // they will not affect responses. - parseAnswerRecords(context->additional_, header_.additional_rrs, buffer, offset); + parseAnswerRecords(context->additional_, context->header_.additional_rrs, buffer, offset); } return true; @@ -503,36 +505,36 @@ void DnsMessageParser::setDnsResponseFlags(DnsQueryContextPtr& query_context, const uint16_t authority_rrs, const uint16_t additional_rrs) { // Copy the transaction ID - response_header_.id = header_.id; + query_context->response_header_.id = query_context->header_.id; // Signify that this is a response to a query - response_header_.flags.qr = 1; + query_context->response_header_.flags.qr = 1; - response_header_.flags.opcode = header_.flags.opcode; - response_header_.flags.aa = 0; - response_header_.flags.tc = 0; + query_context->response_header_.flags.opcode = query_context->header_.flags.opcode; + query_context->response_header_.flags.aa = 0; + query_context->response_header_.flags.tc = 0; // Copy Recursion flags - response_header_.flags.rd = header_.flags.rd; + query_context->response_header_.flags.rd = query_context->header_.flags.rd; // Set the recursion flag based on whether Envoy is configured to forward queries - response_header_.flags.ra = recursion_available_; + query_context->response_header_.flags.ra = recursion_available_; // reserved flag is not set - response_header_.flags.z = 0; + query_context->response_header_.flags.z = 0; // Set the authenticated flags to zero - response_header_.flags.ad = 0; + query_context->response_header_.flags.ad = 0; - response_header_.flags.cd = 0; - response_header_.answers = answers; - response_header_.flags.rcode = query_context->response_code_; + query_context->response_header_.flags.cd = 0; + query_context->response_header_.answers = answers; + query_context->response_header_.flags.rcode = query_context->response_code_; // Set the number of questions from the incoming query - response_header_.questions = questions; + query_context->response_header_.questions = questions; - response_header_.authority_rrs = authority_rrs; - response_header_.additional_rrs = additional_rrs; + query_context->response_header_.authority_rrs = authority_rrs; + query_context->response_header_.additional_rrs = additional_rrs; } bool DnsMessageParser::createAndStoreDnsAnswerRecord( @@ -738,16 +740,16 @@ void DnsMessageParser::buildResponseBuffer(DnsQueryContextPtr& query_context, serialized_authority_rrs, serialized_additional_rrs); // Build the response buffer for transmission to the client - buffer.writeBEInt(response_header_.id); + buffer.writeBEInt(query_context->response_header_.id); uint16_t flags; - ::memcpy(&flags, static_cast(&response_header_.flags), sizeof(uint16_t)); + ::memcpy(&flags, static_cast(&query_context->response_header_.flags), sizeof(uint16_t)); buffer.writeBEInt(flags); - buffer.writeBEInt(response_header_.questions); - buffer.writeBEInt(response_header_.answers); - buffer.writeBEInt(response_header_.authority_rrs); - buffer.writeBEInt(response_header_.additional_rrs); + buffer.writeBEInt(query_context->response_header_.questions); + buffer.writeBEInt(query_context->response_header_.answers); + buffer.writeBEInt(query_context->response_header_.authority_rrs); + buffer.writeBEInt(query_context->response_header_.additional_rrs); // write the queries and answers buffer.move(query_buffer); diff --git a/source/extensions/filters/udp/dns_filter/dns_parser.h b/source/extensions/filters/udp/dns_filter/dns_parser.h index 8dbd07afc02f..d604f0784ba7 100644 --- a/source/extensions/filters/udp/dns_filter/dns_parser.h +++ b/source/extensions/filters/udp/dns_filter/dns_parser.h @@ -133,6 +133,35 @@ struct DnsParserCounters { query_parsing_failure(query_parsing) {} }; +// The flags have been verified with dig and this structure should not be modified. The flag +// order here does not match the RFC, but takes byte ordering into account so that serialization +// does not bitwise operations. +PACKED_STRUCT(struct DnsHeaderFlags { + unsigned rcode : 4; // return code + unsigned cd : 1; // checking disabled + unsigned ad : 1; // authenticated data + unsigned z : 1; // z - bit (must be zero in queries per RFC1035) + unsigned ra : 1; // recursion available + unsigned rd : 1; // recursion desired + unsigned tc : 1; // truncated response + unsigned aa : 1; // authoritative answer + unsigned opcode : 4; // operation code + unsigned qr : 1; // query or response +}); + +/** + * Structure representing the DNS header as it appears in a packet + * See https://www.ietf.org/rfc/rfc1035.txt for more details + */ +PACKED_STRUCT(struct DnsHeader { + uint16_t id; + struct DnsHeaderFlags flags; + uint16_t questions; + uint16_t answers; + uint16_t authority_rrs; + uint16_t additional_rrs; +}); + /** * DnsQueryContext contains all the data necessary for responding to a query from a given client. */ @@ -152,10 +181,18 @@ class DnsQueryContext { uint64_t retry_; uint16_t id_; Network::DnsResolver::ResolutionStatus resolution_status_; + DnsHeader header_; + DnsHeader response_header_; DnsQueryPtrVec queries_; DnsAnswerMap answers_; DnsAnswerMap additional_; bool in_callback_; + + /** + * @param context the query context for which we are querying the response code + * @return uint16_t the response code flag value from a parsed dns object + */ + uint16_t getQueryResponseCode() { return static_cast(header_.flags.rcode); } }; using DnsQueryContextPtr = std::unique_ptr; @@ -167,44 +204,6 @@ using DnsFilterResolverCallback = std::function { public: - enum class DnsQueryParseState { - Init, - Flags, // 2 bytes - Questions, // 2 bytes - Answers, // 2 bytes - Authority, // 2 bytes - Authority2 // 2 bytes - }; - - // The flags have been verified with dig and this structure should not be modified. The flag - // order here does not match the RFC, but takes byte ordering into account so that serialization - // does not bitwise operations. - PACKED_STRUCT(struct DnsHeaderFlags { - unsigned rcode : 4; // return code - unsigned cd : 1; // checking disabled - unsigned ad : 1; // authenticated data - unsigned z : 1; // z - bit (must be zero in queries per RFC1035) - unsigned ra : 1; // recursion available - unsigned rd : 1; // recursion desired - unsigned tc : 1; // truncated response - unsigned aa : 1; // authoritative answer - unsigned opcode : 4; // operation code - unsigned qr : 1; // query or response - }); - - /** - * Structure representing the DNS header as it appears in a packet - * See https://www.ietf.org/rfc/rfc1035.txt for more details - */ - PACKED_STRUCT(struct DnsHeader { - uint16_t id; - struct DnsHeaderFlags flags; - uint16_t questions; - uint16_t answers; - uint16_t authority_rrs; - uint16_t additional_rrs; - }); - DnsMessageParser(bool recurse, TimeSource& timesource, uint64_t retry_count, Random::RandomGenerator& random, Stats::Histogram& latency_histogram) : recursion_available_(recurse), timesource_(timesource), retry_count_(retry_count), @@ -351,11 +350,6 @@ class DnsMessageParser : public Logger::Loggable { const std::chrono::seconds ttl, Network::Address::InstanceConstSharedPtr ipaddr); - /** - * @return uint16_t the response code flag value from a parsed dns object - */ - uint16_t getQueryResponseCode() { return static_cast(header_.flags.rcode); } - /** * @brief Parse the incoming query and create a context object for the filter * @@ -371,6 +365,15 @@ class DnsMessageParser : public Logger::Loggable { bool parseDnsObject(DnsQueryContextPtr& context, const Buffer::InstancePtr& buffer); private: + enum class DnsQueryParseState { + Init, + Flags, // 2 bytes + Questions, // 2 bytes + Answers, // 2 bytes + Authority, // 2 bytes + Authority2 // 2 bytes + }; + /** * @brief Adds a new DNS SRV Answer record for a given service to the service map * @@ -420,11 +423,8 @@ class DnsMessageParser : public Logger::Loggable { TimeSource& timesource_; uint64_t retry_count_; Stats::Histogram& query_latency_histogram_; - DnsHeader header_; - DnsHeader response_header_; Random::RandomGenerator& rng_; }; - } // namespace DnsFilter } // namespace UdpFilters } // namespace Extensions diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc index 905b464d977f..0c018a3c4353 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_integration_test.cc @@ -232,7 +232,7 @@ TEST_P(DnsFilterIntegrationTest, ExternalLookupTest) { EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(1, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); } TEST_P(DnsFilterIntegrationTest, ExternalLookupTestIPv6) { @@ -250,7 +250,7 @@ TEST_P(DnsFilterIntegrationTest, ExternalLookupTestIPv6) { EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(1, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); } TEST_P(DnsFilterIntegrationTest, LocalLookupTest) { @@ -268,7 +268,7 @@ TEST_P(DnsFilterIntegrationTest, LocalLookupTest) { EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(4, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); } TEST_P(DnsFilterIntegrationTest, ClusterLookupTest) { @@ -292,7 +292,7 @@ TEST_P(DnsFilterIntegrationTest, ClusterLookupTest) { EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(2, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); } TEST_P(DnsFilterIntegrationTest, ClusterEndpointLookupTest) { @@ -317,7 +317,7 @@ TEST_P(DnsFilterIntegrationTest, ClusterEndpointLookupTest) { EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(2, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); } TEST_P(DnsFilterIntegrationTest, ClusterEndpointWithPortServiceRecordLookupTest) { @@ -335,7 +335,7 @@ TEST_P(DnsFilterIntegrationTest, ClusterEndpointWithPortServiceRecordLookupTest) EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(2, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); for (const auto& answer : query_ctx_->answers_) { EXPECT_EQ(answer.second->type_, DNS_RECORD_TYPE_SRV); @@ -370,7 +370,7 @@ TEST_P(DnsFilterIntegrationTest, ClusterEndpointWithoutPortServiceRecordLookupTe EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(endpoints, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); std::set ports; for (const auto& answer : query_ctx_->answers_) { diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc index d07e2e955a11..dbb385a2eeb4 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_test.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_test.cc @@ -400,7 +400,7 @@ TEST_F(DnsFilterTest, InvalidQuery) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -408,9 +408,6 @@ TEST_F(DnsFilterTest, InvalidQuery) { EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); EXPECT_TRUE(config_->stats().downstream_rx_bytes_.used()); EXPECT_TRUE(config_->stats().downstream_tx_bytes_.used()); - - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); - EXPECT_EQ(0, query_ctx_->answers_.size()); } TEST_F(DnsFilterTest, MaxQueryAndResponseSizeTest) { @@ -429,7 +426,7 @@ TEST_F(DnsFilterTest, MaxQueryAndResponseSizeTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); // There are 8 addresses, however, since the domain is part of the answer record, each // serialized answer is over 100 bytes in size, there is room for 3 before the next // serialized answer puts the buffer over the 512 byte limit. The query itself is also @@ -460,7 +457,7 @@ TEST_F(DnsFilterTest, InvalidQueryNameTooLongTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -469,7 +466,7 @@ TEST_F(DnsFilterTest, InvalidQueryNameTooLongTest) { EXPECT_TRUE(config_->stats().downstream_rx_bytes_.used()); EXPECT_TRUE(config_->stats().downstream_tx_bytes_.used()); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); } @@ -488,7 +485,7 @@ TEST_F(DnsFilterTest, InvalidLabelNameTooLongTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -497,7 +494,7 @@ TEST_F(DnsFilterTest, InvalidLabelNameTooLongTest) { EXPECT_TRUE(config_->stats().downstream_rx_bytes_.used()); EXPECT_TRUE(config_->stats().downstream_tx_bytes_.used()); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); } @@ -516,7 +513,7 @@ TEST_F(DnsFilterTest, SingleTypeAQuery) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); // Verify that we have an answer record for the queried domain @@ -543,24 +540,36 @@ TEST_F(DnsFilterTest, RepeatedTypeAQuerySuccess) { setup(forward_query_off_config); constexpr size_t loopCount = 5; const std::string domain("www.foo3.com"); - size_t total_query_bytes = 0; + std::list query_id_list{}; + + query_id_list.resize(loopCount); for (size_t i = 0; i < loopCount; i++) { + + // Generate a changing, non-zero query ID for each lookup + const uint16_t query_id = (random_.random() + i) & 0xFFFF; const std::string query = - Utils::buildQueryForDomain(domain, DNS_RECORD_TYPE_A, DNS_RECORD_CLASS_IN); - total_query_bytes += query.size(); + Utils::buildQueryForDomain(domain, DNS_RECORD_TYPE_A, DNS_RECORD_CLASS_IN, query_id); ASSERT_FALSE(query.empty()); sendQueryFromClient("10.0.0.1:1000", query); query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); + + const uint16_t response_id = query_ctx_->header_.id; + auto iter = std::find(query_id_list.begin(), query_id_list.end(), query_id); + EXPECT_EQ(iter, query_id_list.end()); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); // Verify that we have an answer record for the queried domain const DnsAnswerRecordPtr& answer = query_ctx_->answers_.find(domain)->second; + // Verify that the Query ID matches the Response ID + EXPECT_EQ(query_id, response_id); + query_id_list.emplace_back(query_id); + // Verify the address returned std::list expected{"10.0.3.1"}; Utils::verifyAddress(expected, answer); @@ -585,7 +594,7 @@ TEST_F(DnsFilterTest, LocalTypeAQueryFail) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -610,7 +619,7 @@ TEST_F(DnsFilterTest, LocalTypeAAAAQuerySuccess) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(expected.size(), query_ctx_->answers_.size()); // Verify the address returned @@ -658,7 +667,7 @@ TEST_F(DnsFilterTest, ExternalResolutionReturnSingleAddress) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); std::list expected{expected_address}; @@ -711,7 +720,7 @@ TEST_F(DnsFilterTest, ExternalResolutionIpv6SingleAddress) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); std::list expected{expected_address}; @@ -764,7 +773,7 @@ TEST_F(DnsFilterTest, ExternalResolutionReturnMultipleAddresses) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(expected_address.size(), query_ctx_->answers_.size()); EXPECT_LT(udp_response_.buffer_->length(), Utils::MAX_UDP_DNS_SIZE); @@ -814,7 +823,7 @@ TEST_F(DnsFilterTest, ExternalResolutionReturnNoAddresses) { // parse the result query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -853,7 +862,7 @@ TEST_F(DnsFilterTest, ExternalResolutionTimeout) { // parse the result query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -901,7 +910,7 @@ TEST_F(DnsFilterTest, ExternalResolutionTimeout2) { // parse the result query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -952,7 +961,7 @@ TEST_F(DnsFilterTest, ExternalResolutionExceedMaxPendingLookups) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); EXPECT_EQ(0, query_ctx_->answers_.size()); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); // Validate stats EXPECT_EQ(3, config_->stats().downstream_rx_queries_.value()); @@ -980,7 +989,7 @@ TEST_F(DnsFilterTest, ConsumeExternalJsonTableTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(2, query_ctx_->answers_.size()); // Verify the address returned @@ -1014,7 +1023,7 @@ TEST_F(DnsFilterTest, ConsumeExternalJsonTableTestNoIpv6Answer) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -1043,7 +1052,7 @@ TEST_F(DnsFilterTest, ConsumeExternalYamlTableTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(2, query_ctx_->answers_.size()); // Verify the address returned @@ -1086,7 +1095,7 @@ TEST_F(DnsFilterTest, RawBufferTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); // Verify that we have an answer record for the queried domain @@ -1124,7 +1133,7 @@ TEST_F(DnsFilterTest, InvalidAnswersInQueryTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); } @@ -1155,7 +1164,7 @@ TEST_F(DnsFilterTest, InvalidQueryNameTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); } @@ -1525,7 +1534,7 @@ TEST_F(DnsFilterTest, InvalidQueryNameTest2) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); // TODO(abaptiste): underflow/overflow stats EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); @@ -1563,7 +1572,7 @@ TEST_F(DnsFilterTest, MultipleQueryCountTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); EXPECT_EQ(0, config_->stats().a_record_queries_.value()); @@ -1595,7 +1604,7 @@ TEST_F(DnsFilterTest, InvalidQueryCountTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, config_->stats().a_record_queries_.value()); EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); @@ -1628,7 +1637,7 @@ TEST_F(DnsFilterTest, InvalidNameLabelTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, config_->stats().a_record_queries_.value()); EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); @@ -1661,7 +1670,7 @@ TEST_F(DnsFilterTest, NotImplementedQueryTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NOT_IMPLEMENTED, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NOT_IMPLEMENTED, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, config_->stats().a_record_queries_.value()); EXPECT_EQ(0, config_->stats().downstream_rx_invalid_queries_.value()); @@ -1693,7 +1702,7 @@ TEST_F(DnsFilterTest, NotImplementedAuthorityRRTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NOT_IMPLEMENTED, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NOT_IMPLEMENTED, query_ctx_->getQueryResponseCode()); } TEST_F(DnsFilterTest, NoTransactionIdTest) { @@ -1722,7 +1731,7 @@ TEST_F(DnsFilterTest, NoTransactionIdTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); } TEST_F(DnsFilterTest, InvalidShortBufferTest) { @@ -1736,7 +1745,7 @@ TEST_F(DnsFilterTest, InvalidShortBufferTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_FALSE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_FORMAT_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, config_->stats().a_record_queries_.value()); EXPECT_EQ(1, config_->stats().downstream_rx_invalid_queries_.value()); @@ -1755,7 +1764,7 @@ TEST_F(DnsFilterTest, RandomizeFirstAnswerTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); // Although 16 addresses are defined, only 8 are returned EXPECT_EQ(8, query_ctx_->answers_.size()); @@ -1790,7 +1799,7 @@ TEST_F(DnsFilterTest, ConsumeExternalTableWithServicesTest) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); std::map validation_weight_map = { {10, "backup.voip.subzero.com"}, @@ -1888,7 +1897,7 @@ TEST_F(DnsFilterTest, SrvTargetResolution) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->answers_.size()); const DnsAnswerRecordPtr& answer = query_ctx_->answers_.find(domain)->second; @@ -1921,7 +1930,7 @@ TEST_F(DnsFilterTest, NonExistentClusterServiceLookup) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(0, query_ctx_->answers_.size()); // Validate stats @@ -1959,7 +1968,7 @@ TEST_F(DnsFilterTest, SrvRecordQuery) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NAME_ERROR, query_ctx_->getQueryResponseCode()); EXPECT_EQ(1, query_ctx_->queries_.size()); const auto& parsed_query = query_ctx_->queries_.front(); @@ -1990,7 +1999,7 @@ TEST_F(DnsFilterTest, SrvQueryMaxRecords) { query_ctx_ = response_parser_->createQueryContext(udp_response_, counters_); EXPECT_TRUE(query_ctx_->parse_status_); - EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, response_parser_->getQueryResponseCode()); + EXPECT_EQ(DNS_RESPONSE_CODE_NO_ERROR, query_ctx_->getQueryResponseCode()); // We can only serialize 7 records before reaching the 512 byte limit EXPECT_LT(query_ctx_->answers_.size(), MAX_RETURNED_RECORDS); diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.cc b/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.cc index 364ee8b3094c..b6a50496a7f3 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.cc +++ b/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.cc @@ -18,10 +18,11 @@ std::string buildQueryFromBytes(const char* bytes, const size_t count) { return query; } -std::string buildQueryForDomain(const std::string& name, uint16_t rec_type, uint16_t rec_class) { +std::string buildQueryForDomain(const std::string& name, uint16_t rec_type, uint16_t rec_class, + const uint16_t query_id) { Random::RandomGeneratorImpl random_; - struct DnsMessageParser::DnsHeader query {}; - uint16_t id = random_.random() & 0xFFFF; + struct DnsHeader query {}; + uint16_t id = (query_id ? query_id : random_.random() & 0xFFFF); // Generate a random query ID query.id = id; diff --git a/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.h b/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.h index 4c500091ea29..378abbe43186 100644 --- a/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.h +++ b/test/extensions/filters/udp/dns_filter/dns_filter_test_utils.h @@ -12,7 +12,8 @@ namespace Utils { static constexpr uint64_t MAX_UDP_DNS_SIZE{512}; std::string buildQueryFromBytes(const char* bytes, const size_t count); -std::string buildQueryForDomain(const std::string& name, uint16_t rec_type, uint16_t rec_class); +std::string buildQueryForDomain(const std::string& name, uint16_t rec_type, uint16_t rec_class, + const uint16_t query_id = 0); void verifyAddress(const std::list& addresses, const DnsAnswerRecordPtr& answer); size_t getResponseQueryCount(DnsMessageParser& parser); From 9a205dab7ad565ca416dff5bbccc8838c4627485 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 5 Nov 2020 09:05:57 -0500 Subject: [PATCH 041/117] conn_pool: allowing multiple protocols in ALPN (#13901) Extending the TransportSocketOptions to allow for multiple fallback ALPN for the upcoming connection pool which supports both HTTP/1 and HTTP/2 Risk Level: Low (data plane refactor, but a pretty minor one) Testing: new unit tests Docs Changes: n/a Release Notes: n/a Part of #3431 Signed-off-by: Alyssa Wilk --- include/envoy/network/transport_socket.h | 8 +-- source/common/http/conn_pool_base.cc | 53 ++++++++------- source/common/http/conn_pool_base.h | 3 +- source/common/http/http1/conn_pool.cc | 2 +- source/common/http/http2/conn_pool.cc | 2 +- .../network/transport_socket_options_impl.cc | 11 ++-- .../network/transport_socket_options_impl.h | 13 ++-- .../transport_sockets/tls/context_impl.cc | 4 +- test/common/http/BUILD | 16 +++++ test/common/http/http1/conn_pool_test.cc | 2 +- test/common/http/http2/conn_pool_test.cc | 2 +- test/common/http/mixed_conn_pool_test.cc | 65 +++++++++++++++++++ .../proxy_protocol/proxy_protocol_test.cc | 12 ++-- .../transport_sockets/tls/ssl_socket_test.cc | 17 ++++- 14 files changed, 159 insertions(+), 51 deletions(-) create mode 100644 test/common/http/mixed_conn_pool_test.cc diff --git a/include/envoy/network/transport_socket.h b/include/envoy/network/transport_socket.h index 95d07ac2e182..4244e6247767 100644 --- a/include/envoy/network/transport_socket.h +++ b/include/envoy/network/transport_socket.h @@ -180,7 +180,7 @@ class TransportSocketOptions { virtual const std::vector& applicationProtocolListOverride() const PURE; /** - * The application protocol to use when negotiating an upstream connection and no other + * The application protocol(s) to use when negotiating an upstream connection and no other * application protocol has been configured. Both * TransportSocketOptions::applicationProtocolListOverride and application protocols configured * in the CommonTlsContext on the Cluster will take precedence. @@ -188,10 +188,10 @@ class TransportSocketOptions { * Note that this option is intended for intermediate code (e.g. the HTTP connection pools) to * specify a default ALPN when no specific values are specified elsewhere. As such, providing a * value here might not make sense prior to load balancing. - * @return the optional fallback for application protocols, for when they are not specified in the - * TLS configuration. + * @return the optional fallback(s) for application protocols, for when they are not specified in + * the TLS configuration. */ - virtual const absl::optional& applicationProtocolFallback() const PURE; + virtual const std::vector& applicationProtocolFallback() const PURE; /** * @return optional PROXY protocol address information. diff --git a/source/common/http/conn_pool_base.cc b/source/common/http/conn_pool_base.cc index 06e61af75ff9..e785f87a7b0e 100644 --- a/source/common/http/conn_pool_base.cc +++ b/source/common/http/conn_pool_base.cc @@ -12,35 +12,37 @@ namespace Http { Network::TransportSocketOptionsSharedPtr wrapTransportSocketOptions(Network::TransportSocketOptionsSharedPtr transport_socket_options, - Protocol protocol) { + std::vector protocols) { if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http_default_alpn")) { return transport_socket_options; } - // If configured to do so, we override the ALPN to use for the upstream connection to match the - // selected protocol. - std::string alpn; - switch (protocol) { - case Http::Protocol::Http10: - NOT_REACHED_GCOVR_EXCL_LINE; - case Http::Protocol::Http11: - alpn = Http::Utility::AlpnNames::get().Http11; - break; - case Http::Protocol::Http2: - alpn = Http::Utility::AlpnNames::get().Http2; - break; - case Http::Protocol::Http3: - // TODO(snowp): Add once HTTP/3 upstream support is added. - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; - break; + std::vector fallbacks; + for (auto protocol : protocols) { + // If configured to do so, we override the ALPN to use for the upstream connection to match the + // selected protocol. + switch (protocol) { + case Http::Protocol::Http10: + NOT_REACHED_GCOVR_EXCL_LINE; + case Http::Protocol::Http11: + fallbacks.push_back(Http::Utility::AlpnNames::get().Http11); + break; + case Http::Protocol::Http2: + fallbacks.push_back(Http::Utility::AlpnNames::get().Http2); + break; + case Http::Protocol::Http3: + // TODO(snowp): Add once HTTP/3 upstream support is added. + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + break; + } } if (transport_socket_options) { return std::make_shared( - std::move(alpn), transport_socket_options); + std::move(fallbacks), transport_socket_options); } else { return std::make_shared( - "", std::vector{}, std::vector{}, std::move(alpn)); + "", std::vector{}, std::vector{}, std::move(fallbacks)); } } @@ -48,11 +50,18 @@ HttpConnPoolImplBase::HttpConnPoolImplBase( Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, - Random::RandomGenerator& random_generator, Http::Protocol protocol) + Random::RandomGenerator& random_generator, std::vector protocols) : Envoy::ConnectionPool::ConnPoolImplBase( host, priority, dispatcher, options, - wrapTransportSocketOptions(transport_socket_options, protocol)), - random_generator_(random_generator), protocol_(protocol) {} + wrapTransportSocketOptions(transport_socket_options, protocols)), + random_generator_(random_generator) { + ASSERT(!protocols.empty()); + // TODO(alyssawilk) the protocol function should probably be an optional and + // simply not set if there's more than one and ALPN has not been negotiated. + if (!protocols.empty()) { + protocol_ = protocols[0]; + } +} ConnectionPool::Cancellable* HttpConnPoolImplBase::newStream(Http::ResponseDecoder& response_decoder, diff --git a/source/common/http/conn_pool_base.h b/source/common/http/conn_pool_base.h index 10610ed96cac..16e4f39ac103 100644 --- a/source/common/http/conn_pool_base.h +++ b/source/common/http/conn_pool_base.h @@ -43,7 +43,8 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, - Random::RandomGenerator& random_generator, Http::Protocol protocol); + Random::RandomGenerator& random_generator, + std::vector protocol); // ConnectionPool::Instance void addDrainedCallback(DrainedCb cb) override { addDrainedCallbackImpl(cb); } diff --git a/source/common/http/http1/conn_pool.cc b/source/common/http/http1/conn_pool.cc index ce0c0fce1cd4..3aaf5aa2b572 100644 --- a/source/common/http/http1/conn_pool.cc +++ b/source/common/http/http1/conn_pool.cc @@ -28,7 +28,7 @@ ConnPoolImpl::ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerato const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options) : HttpConnPoolImplBase(std::move(host), std::move(priority), dispatcher, options, - transport_socket_options, random_generator, Protocol::Http11) {} + transport_socket_options, random_generator, {Protocol::Http11}) {} ConnPoolImpl::~ConnPoolImpl() { destructAllConnections(); } diff --git a/source/common/http/http2/conn_pool.cc b/source/common/http/http2/conn_pool.cc index 29f89bb60c32..b4e3be74a46f 100644 --- a/source/common/http/http2/conn_pool.cc +++ b/source/common/http/http2/conn_pool.cc @@ -21,7 +21,7 @@ ConnPoolImpl::ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerato const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options) : HttpConnPoolImplBase(std::move(host), std::move(priority), dispatcher, options, - transport_socket_options, random_generator, Protocol::Http2) {} + transport_socket_options, random_generator, {Protocol::Http2}) {} ConnPoolImpl::~ConnPoolImpl() { destructAllConnections(); } diff --git a/source/common/network/transport_socket_options_impl.cc b/source/common/network/transport_socket_options_impl.cc index 66b139bba6e1..9f158f416bf0 100644 --- a/source/common/network/transport_socket_options_impl.cc +++ b/source/common/network/transport_socket_options_impl.cc @@ -38,8 +38,10 @@ void commonHashKey(const TransportSocketOptions& options, std::vector application_protocols; std::vector subject_alt_names; + std::vector alpn_fallback; absl::optional proxy_protocol_options; bool needs_transport_socket_options = false; @@ -100,8 +103,8 @@ TransportSocketOptionsUtility::fromFilterState(const StreamInfo::FilterState& fi if (needs_transport_socket_options) { return std::make_shared( - server_name, std::move(subject_alt_names), std::move(application_protocols), absl::nullopt, - proxy_protocol_options); + server_name, std::move(subject_alt_names), std::move(application_protocols), + std::move(alpn_fallback), proxy_protocol_options); } else { return nullptr; } diff --git a/source/common/network/transport_socket_options_impl.h b/source/common/network/transport_socket_options_impl.h index 4e08da3f292d..8a961e4fa33d 100644 --- a/source/common/network/transport_socket_options_impl.h +++ b/source/common/network/transport_socket_options_impl.h @@ -10,7 +10,7 @@ namespace Network { // A wrapper around another TransportSocketOptions that overrides the ALPN fallback. class AlpnDecoratingTransportSocketOptions : public TransportSocketOptions { public: - AlpnDecoratingTransportSocketOptions(std::string&& alpn, + AlpnDecoratingTransportSocketOptions(std::vector&& alpn, TransportSocketOptionsSharedPtr inner_options) : alpn_fallback_(std::move(alpn)), inner_options_(std::move(inner_options)) {} // Network::TransportSocketOptions @@ -23,7 +23,7 @@ class AlpnDecoratingTransportSocketOptions : public TransportSocketOptions { const std::vector& applicationProtocolListOverride() const override { return inner_options_->applicationProtocolListOverride(); } - const absl::optional& applicationProtocolFallback() const override { + const std::vector& applicationProtocolFallback() const override { return alpn_fallback_; } absl::optional proxyProtocolOptions() const override { @@ -33,7 +33,7 @@ class AlpnDecoratingTransportSocketOptions : public TransportSocketOptions { const Network::TransportSocketFactory& factory) const override; private: - const absl::optional alpn_fallback_; + const std::vector alpn_fallback_; const TransportSocketOptionsSharedPtr inner_options_; }; @@ -42,8 +42,7 @@ class TransportSocketOptionsImpl : public TransportSocketOptions { TransportSocketOptionsImpl( absl::string_view override_server_name = "", std::vector&& override_verify_san_list = {}, - std::vector&& override_alpn = {}, - absl::optional&& fallback_alpn = {}, + std::vector&& override_alpn = {}, std::vector&& fallback_alpn = {}, absl::optional proxy_proto_options = absl::nullopt) : override_server_name_(override_server_name.empty() ? absl::nullopt @@ -62,7 +61,7 @@ class TransportSocketOptionsImpl : public TransportSocketOptions { const std::vector& applicationProtocolListOverride() const override { return override_alpn_list_; } - const absl::optional& applicationProtocolFallback() const override { + const std::vector& applicationProtocolFallback() const override { return alpn_fallback_; } absl::optional proxyProtocolOptions() const override { @@ -75,7 +74,7 @@ class TransportSocketOptionsImpl : public TransportSocketOptions { const absl::optional override_server_name_; const std::vector override_verify_san_list_; const std::vector override_alpn_list_; - const absl::optional alpn_fallback_; + const std::vector alpn_fallback_; const absl::optional proxy_protocol_options_; }; diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index cfcf1d6ce16a..e163eddfc8b4 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -963,9 +963,9 @@ bssl::UniquePtr ClientContextImpl::newSsl(const Network::TransportSocketOpt has_alpn_defined |= parseAndSetAlpn(options->applicationProtocolListOverride(), *ssl_con); } - if (options && !has_alpn_defined && options->applicationProtocolFallback().has_value()) { + if (options && !has_alpn_defined && !options->applicationProtocolFallback().empty()) { // If ALPN hasn't already been set (either through TLS context or override), use the fallback. - parseAndSetAlpn({*options->applicationProtocolFallback()}, *ssl_con); + parseAndSetAlpn(options->applicationProtocolFallback(), *ssl_con); } if (allow_renegotiation_) { diff --git a/test/common/http/BUILD b/test/common/http/BUILD index 7b19f269bbdd..ea256d23386d 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -397,6 +397,22 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "mixed_conn_pool_test", + srcs = ["mixed_conn_pool_test.cc"], + deps = [ + "//source/common/http:conn_pool_base_lib", + "//test/common/upstream:utility_lib", + "//test/mocks:common_lib", + "//test/mocks/buffer:buffer_mocks", + "//test/mocks/http:http_mocks", + "//test/mocks/local_info:local_info_mocks", + "//test/mocks/router:router_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/stats:stats_mocks", + ], +) + envoy_proto_library( name = "path_utility_fuzz_proto", srcs = ["path_utility_fuzz.proto"], diff --git a/test/common/http/http1/conn_pool_test.cc b/test/common/http/http1/conn_pool_test.cc index 072a1b807d1b..0d02cc51e36b 100644 --- a/test/common/http/http1/conn_pool_test.cc +++ b/test/common/http/http1/conn_pool_test.cc @@ -274,7 +274,7 @@ TEST_F(Http1ConnPoolImplTest, VerifyAlpnFallback) { .WillOnce(Invoke( [](Network::TransportSocketOptionsSharedPtr options) -> Network::TransportSocketPtr { EXPECT_TRUE(options != nullptr); - EXPECT_EQ(options->applicationProtocolFallback(), + EXPECT_EQ(options->applicationProtocolFallback()[0], Http::Utility::AlpnNames::get().Http11); return std::make_unique(); })); diff --git a/test/common/http/http2/conn_pool_test.cc b/test/common/http/http2/conn_pool_test.cc index dd5952b2a227..569991842e89 100644 --- a/test/common/http/http2/conn_pool_test.cc +++ b/test/common/http/http2/conn_pool_test.cc @@ -329,7 +329,7 @@ TEST_F(Http2ConnPoolImplTest, VerifyAlpnFallback) { .WillOnce(Invoke( [](Network::TransportSocketOptionsSharedPtr options) -> Network::TransportSocketPtr { EXPECT_TRUE(options != nullptr); - EXPECT_EQ(options->applicationProtocolFallback(), + EXPECT_EQ(options->applicationProtocolFallback()[0], Http::Utility::AlpnNames::get().Http2); return std::make_unique(); })); diff --git a/test/common/http/mixed_conn_pool_test.cc b/test/common/http/mixed_conn_pool_test.cc new file mode 100644 index 000000000000..f48b37afdcf7 --- /dev/null +++ b/test/common/http/mixed_conn_pool_test.cc @@ -0,0 +1,65 @@ +#include + +#include "common/http/conn_pool_base.h" +#include "common/http/utility.h" + +#include "test/common/upstream/utility.h" +#include "test/mocks/common.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Http { +namespace { + +// TODO(alyssawilk) replace this with the MixedConnectionPool once it lands. +class ConnPoolImplForTest : public HttpConnPoolImplBase { +public: + ConnPoolImplForTest(Event::MockDispatcher& dispatcher, Random::RandomGenerator& random, + Upstream::ClusterInfoConstSharedPtr cluster) + : HttpConnPoolImplBase(Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), + Upstream::ResourcePriority::Default, dispatcher, nullptr, nullptr, + random, {Http::Protocol::Http2, Http::Protocol::Http11}) {} + + Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override { return nullptr; } + Http::Protocol protocol() const override { return Http::Protocol::Http2; } + CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData&) override { + return nullptr; + } +}; + +/** + * Test fixture for a connection pool which can have HTTP/2 or HTTP/1.1 connections. + */ +class MixedConnPoolImplTest : public testing::Test { +public: + MixedConnPoolImplTest() + : conn_pool_(std::make_unique(dispatcher_, random_, cluster_)) {} + + ~MixedConnPoolImplTest() override { + EXPECT_EQ("", TestUtility::nonZeroedGauges(cluster_->stats_store_.gauges())); + } + + NiceMock dispatcher_; + std::shared_ptr cluster_{new NiceMock()}; + std::unique_ptr conn_pool_; + NiceMock runtime_; + NiceMock random_; +}; + +TEST_F(MixedConnPoolImplTest, AlpnTest) { + auto& fallback = conn_pool_->transportSocketOptions()->applicationProtocolFallback(); + ASSERT_EQ(2, fallback.size()); + EXPECT_EQ(fallback[0], Http::Utility::AlpnNames::get().Http2); + EXPECT_EQ(fallback[1], Http::Utility::AlpnNames::get().Http11); +} + +} // namespace +} // namespace Http +} // namespace Envoy diff --git a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc index 953e5999a5fb..16f56b4f9489 100644 --- a/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/transport_sockets/proxy_protocol/proxy_protocol_test.cc @@ -256,7 +256,7 @@ TEST_F(ProxyProtocolTest, V1IPV4DownstreamAddresses) { new Network::Address::Ipv4Instance("174.2.2.222", 80)); Network::TransportSocketOptionsSharedPtr socket_options = std::make_shared( - "", std::vector{}, std::vector{}, absl::nullopt, + "", std::vector{}, std::vector{}, std::vector{}, absl::optional( Network::ProxyProtocolData{src_addr, dst_addr})); transport_callbacks_.connection_.local_address_ = @@ -288,7 +288,7 @@ TEST_F(ProxyProtocolTest, V1IPV6DownstreamAddresses) { Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv6Instance("a:b:c:d::", 80)); Network::TransportSocketOptionsSharedPtr socket_options = std::make_shared( - "", std::vector{}, std::vector{}, absl::nullopt, + "", std::vector{}, std::vector{}, std::vector{}, absl::optional( Network::ProxyProtocolData{src_addr, dst_addr})); transport_callbacks_.connection_.local_address_ = @@ -365,7 +365,7 @@ TEST_F(ProxyProtocolTest, V2IPV4DownstreamAddresses) { Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("0.1.1.2", 513)); Network::TransportSocketOptionsSharedPtr socket_options = std::make_shared( - "", std::vector{}, std::vector{}, absl::nullopt, + "", std::vector{}, std::vector{}, std::vector{}, absl::optional( Network::ProxyProtocolData{src_addr, dst_addr})); transport_callbacks_.connection_.local_address_ = @@ -397,7 +397,7 @@ TEST_F(ProxyProtocolTest, V2IPV6DownstreamAddresses) { new Network::Address::Ipv6Instance("1:100:200:3::", 2)); Network::TransportSocketOptionsSharedPtr socket_options = std::make_shared( - "", std::vector{}, std::vector{}, absl::nullopt, + "", std::vector{}, std::vector{}, std::vector{}, absl::optional( Network::ProxyProtocolData{src_addr, dst_addr})); transport_callbacks_.connection_.local_address_ = @@ -429,7 +429,7 @@ TEST_F(ProxyProtocolTest, OnConnectedCallsInnerOnConnected) { new Network::Address::Ipv6Instance("1:100:200:3::", 2)); Network::TransportSocketOptionsSharedPtr socket_options = std::make_shared( - "", std::vector{}, std::vector{}, absl::nullopt, + "", std::vector{}, std::vector{}, std::vector{}, absl::optional( Network::ProxyProtocolData{src_addr, dst_addr})); transport_callbacks_.connection_.local_address_ = @@ -473,4 +473,4 @@ TEST_F(ProxyProtocolSocketFactoryTest, ImplementsSecureTransportCallInnerFactory } // namespace ProxyProtocol } // namespace TransportSockets } // namespace Extensions -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index 8aa58b0cdc76..b96fc6366330 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -4648,7 +4648,22 @@ TEST_P(SslSocketTest, OverrideApplicationProtocols) { // in the config. server_ctx->add_alpn_protocols("test"); transport_socket_options = std::make_shared( - "", std::vector{}, std::vector{}, "test"); + "", std::vector{}, std::vector{}, std::vector{"test"}); + testUtilV2(test_options.setExpectedALPNProtocol("test").setTransportSocketOptions( + transport_socket_options)); + + // With multiple fallbacks specified, a single match will match. + transport_socket_options = std::make_shared( + "", std::vector{}, std::vector{}, + std::vector{"foo", "test"}); + testUtilV2(test_options.setExpectedALPNProtocol("test").setTransportSocketOptions( + transport_socket_options)); + + // With multiple matching fallbacks specified, a single match will match. + server_ctx->add_alpn_protocols("foo"); + transport_socket_options = std::make_shared( + "", std::vector{}, std::vector{}, + std::vector{"foo", "test"}); testUtilV2(test_options.setExpectedALPNProtocol("test").setTransportSocketOptions( transport_socket_options)); From 6a044a38b762214950aea40fcd0709cd7dccdc44 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 5 Nov 2020 11:34:53 -0500 Subject: [PATCH 042/117] conn_pool: making TCP upstreams pluggable (#13548) Risk Level: Medium (some refactory) Testing: existing tests pass Docs Changes: n/a Release Notes: n/a Fixes #13185 Signed-off-by: Alyssa Wilk --- CODEOWNERS | 4 +- api/BUILD | 1 + .../extensions/upstreams/tcp/generic/v3/BUILD | 9 ++ .../generic/v3/generic_connection_pool.proto | 18 +++ api/versioning/BUILD | 1 + docs/generate_extension_db.py | 2 + docs/root/api-v3/config/upstream/upstream.rst | 1 + .../extensions/upstreams/tcp/generic/v3/BUILD | 9 ++ .../generic/v3/generic_connection_pool.proto | 18 +++ include/envoy/tcp/BUILD | 10 ++ include/envoy/tcp/upstream.h | 136 ++++++++++++++++++ source/common/config/utility.h | 30 +++- source/common/tcp_proxy/BUILD | 25 +++- source/common/tcp_proxy/tcp_proxy.cc | 60 +++----- source/common/tcp_proxy/tcp_proxy.h | 2 +- source/common/tcp_proxy/upstream.cc | 16 +-- source/common/tcp_proxy/upstream.h | 67 ++------- source/extensions/upstreams/tcp/generic/BUILD | 25 ++++ .../upstreams/tcp/generic/config.cc | 45 ++++++ .../extensions/upstreams/tcp/generic/config.h | 37 +++++ test/common/tcp_proxy/BUILD | 3 + test/common/tcp_proxy/tcp_proxy_test.cc | 133 ++++++++++++----- test/extensions/upstreams/tcp/generic/BUILD | 18 +++ .../upstreams/tcp/generic/config_test.cc | 39 +++++ 24 files changed, 564 insertions(+), 145 deletions(-) create mode 100644 api/envoy/extensions/upstreams/tcp/generic/v3/BUILD create mode 100644 api/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto create mode 100644 generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/BUILD create mode 100644 generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto create mode 100644 include/envoy/tcp/upstream.h create mode 100644 source/extensions/upstreams/tcp/generic/BUILD create mode 100644 source/extensions/upstreams/tcp/generic/config.cc create mode 100644 source/extensions/upstreams/tcp/generic/config.h create mode 100644 test/extensions/upstreams/tcp/generic/BUILD create mode 100644 test/extensions/upstreams/tcp/generic/config_test.cc diff --git a/CODEOWNERS b/CODEOWNERS index 99cda541b1cc..ab8af77e3401 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -146,9 +146,7 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/watchdog/profile_action @kbaichoo @antoniovicente # Core upstream code extensions/upstreams/http @alyssawilk @snowp @mattklein123 -extensions/upstreams/http/http @alyssawilk @snowp @mattklein123 -extensions/upstreams/http/tcp @alyssawilk @mattklein123 -extensions/upstreams/http/default @alyssawilk @snowp @mattklein123 +extensions/upstreams/tcp @alyssawilk @ggreenway @mattklein123 # OAuth2 extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp # HTTP Local Rate Limit diff --git a/api/BUILD b/api/BUILD index 345732128a0d..3df8b906b006 100644 --- a/api/BUILD +++ b/api/BUILD @@ -247,6 +247,7 @@ proto_library( "//envoy/extensions/upstreams/http/generic/v3:pkg", "//envoy/extensions/upstreams/http/http/v3:pkg", "//envoy/extensions/upstreams/http/tcp/v3:pkg", + "//envoy/extensions/upstreams/tcp/generic/v3:pkg", "//envoy/extensions/wasm/v3:pkg", "//envoy/extensions/watchdog/profile_action/v3alpha:pkg", "//envoy/service/accesslog/v3:pkg", diff --git a/api/envoy/extensions/upstreams/tcp/generic/v3/BUILD b/api/envoy/extensions/upstreams/tcp/generic/v3/BUILD new file mode 100644 index 000000000000..ee92fb652582 --- /dev/null +++ b/api/envoy/extensions/upstreams/tcp/generic/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto b/api/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto new file mode 100644 index 000000000000..5754491b91d1 --- /dev/null +++ b/api/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package envoy.extensions.upstreams.tcp.generic.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.upstreams.tcp.generic.v3"; +option java_outer_classname = "GenericConnectionPoolProtoOuterClass"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Generic Connection Pool] + +// A connection pool which forwards downstream TCP as TCP or HTTP to upstream, +// based on CONNECT configuration. +// [#extension: envoy.upstreams.tcp.generic] +message GenericConnectionPoolProto { +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 2e0a1cd4997d..dc1162bb93c7 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -130,6 +130,7 @@ proto_library( "//envoy/extensions/upstreams/http/generic/v3:pkg", "//envoy/extensions/upstreams/http/http/v3:pkg", "//envoy/extensions/upstreams/http/tcp/v3:pkg", + "//envoy/extensions/upstreams/tcp/generic/v3:pkg", "//envoy/extensions/wasm/v3:pkg", "//envoy/extensions/watchdog/profile_action/v3alpha:pkg", "//envoy/service/accesslog/v3:pkg", diff --git a/docs/generate_extension_db.py b/docs/generate_extension_db.py index 3a04aac76705..c6261977696e 100755 --- a/docs/generate_extension_db.py +++ b/docs/generate_extension_db.py @@ -63,5 +63,7 @@ def GetExtensionMetadata(target): '//source/extensions/transport_sockets/tls:config') extension_db['envoy.upstreams.http.generic'] = GetExtensionMetadata( '//source/extensions/upstreams/http/generic:config') + extension_db['envoy.upstreams.tcp.generic'] = GetExtensionMetadata( + '//source/extensions/upstreams/tcp/generic:config') pathlib.Path(output_path).write_text(json.dumps(extension_db)) diff --git a/docs/root/api-v3/config/upstream/upstream.rst b/docs/root/api-v3/config/upstream/upstream.rst index 5047eaa92b28..49f3cf9a6db9 100644 --- a/docs/root/api-v3/config/upstream/upstream.rst +++ b/docs/root/api-v3/config/upstream/upstream.rst @@ -6,3 +6,4 @@ Upstream Configuration :maxdepth: 3 ../../extensions/upstreams/http/*/v3/** + ../../extensions/upstreams/tcp/*/v3/** diff --git a/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/BUILD b/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/BUILD new file mode 100644 index 000000000000..ee92fb652582 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto b/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto new file mode 100644 index 000000000000..5754491b91d1 --- /dev/null +++ b/generated_api_shadow/envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package envoy.extensions.upstreams.tcp.generic.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.upstreams.tcp.generic.v3"; +option java_outer_classname = "GenericConnectionPoolProtoOuterClass"; +option java_multiple_files = true; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Generic Connection Pool] + +// A connection pool which forwards downstream TCP as TCP or HTTP to upstream, +// based on CONNECT configuration. +// [#extension: envoy.upstreams.tcp.generic] +message GenericConnectionPoolProto { +} diff --git a/include/envoy/tcp/BUILD b/include/envoy/tcp/BUILD index bbf990581003..2ab93df03996 100644 --- a/include/envoy/tcp/BUILD +++ b/include/envoy/tcp/BUILD @@ -18,3 +18,13 @@ envoy_cc_library( "//include/envoy/upstream:upstream_interface", ], ) + +envoy_cc_library( + name = "upstream_interface", + hdrs = ["upstream.h"], + deps = [ + "//include/envoy/tcp:conn_pool_interface", + "//include/envoy/upstream:upstream_interface", + "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", + ], +) diff --git a/include/envoy/tcp/upstream.h b/include/envoy/tcp/upstream.h new file mode 100644 index 000000000000..71d266b9f1fc --- /dev/null +++ b/include/envoy/tcp/upstream.h @@ -0,0 +1,136 @@ +#pragma once + +#include "envoy/buffer/buffer.h" +#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" +#include "envoy/stream_info/stream_info.h" +#include "envoy/tcp/conn_pool.h" +#include "envoy/upstream/upstream.h" + +namespace Envoy { + +namespace Upstream { +class LoadBalancerContext; +} // namespace Upstream + +namespace TcpProxy { + +class GenericConnectionPoolCallbacks; +class GenericUpstream; + +// An API for wrapping either a TCP or an HTTP connection pool. +class GenericConnPool : public Logger::Loggable { +public: + virtual ~GenericConnPool() = default; + + /** + * Called to create a TCP connection or HTTP stream for "CONNECT" streams. + * + * The implementation is then responsible for calling either onGenericPoolReady or + * onGenericPoolFailure on the supplied GenericConnectionPoolCallbacks. + * + * @param callbacks callbacks to communicate stream failure or creation on. + */ + virtual void newStream(GenericConnectionPoolCallbacks& callbacks) PURE; +}; + +// An API for the UpstreamRequest to get callbacks from either an HTTP or TCP +// connection pool. +class GenericConnectionPoolCallbacks { +public: + virtual ~GenericConnectionPoolCallbacks() = default; + + /** + * Called when GenericConnPool::newStream has established a new stream. + * + * @param info supplies the stream info object associated with the upstream connection. + * @param upstream supplies the generic upstream for the stream. + * @param host supplies the description of the host that will carry the request. + * @param upstream_local_address supplies the local address of the upstream connection. + * @param ssl_info supplies the ssl information of the upstream connection. + */ + virtual void + onGenericPoolReady(StreamInfo::StreamInfo* info, std::unique_ptr&& upstream, + Upstream::HostDescriptionConstSharedPtr& host, + const Network::Address::InstanceConstSharedPtr& upstream_local_address, + Ssl::ConnectionInfoConstSharedPtr ssl_info) PURE; + + /** + * Called to indicate a failure for GenericConnPool::newStream to establish a stream. + * + * @param reason supplies the failure reason. + * @param host supplies the description of the host that caused the failure. This may be nullptr + * if no host was involved in the failure (for example overflow). + */ + virtual void onGenericPoolFailure(ConnectionPool::PoolFailureReason reason, + Upstream::HostDescriptionConstSharedPtr host) PURE; +}; + +// Interface for a generic Upstream, which can communicate with a TCP or HTTP +// upstream. +class GenericUpstream { +public: + virtual ~GenericUpstream() = default; + + /** + * Enable/disable further data from this stream. + * + * @param disable true if the stream should be read disabled, false otherwise. + * @return returns true if the disable is performed, false otherwise + * (e.g. if the connection is closed) + */ + virtual bool readDisable(bool disable) PURE; + + /** + * Encodes data upstream. + * @param data supplies the data to encode. The data may be moved by the encoder. + * @param end_stream supplies whether this is the last data to encode. + */ + virtual void encodeData(Buffer::Instance& data, bool end_stream) PURE; + + /** + * Adds a callback to be called when the data is sent to the kernel. + * @param cb supplies the callback to be called + */ + virtual void addBytesSentCallback(Network::Connection::BytesSentCb cb) PURE; + + /** + * Called when an event is received on the downstream connection + * @param event supplies the event which occurred. + * @return the underlying ConnectionData if the event is not "Connected" and draining + is supported for this upstream. + */ + virtual Tcp::ConnectionPool::ConnectionData* + onDownstreamEvent(Network::ConnectionEvent event) PURE; +}; + +using GenericConnPoolPtr = std::unique_ptr; + +/* + * A factory for creating generic connection pools. + */ +class GenericConnPoolFactory : public Envoy::Config::TypedFactory { +public: + ~GenericConnPoolFactory() override = default; + + using TunnelingConfig = + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig; + + /* + * @param cluster_name the name of the cluster to use + * @param cm the cluster manager to get the connection pool from + * @param config the tunneling config, if doing connect tunneling. + * @param context the load balancing context for this connection. + * @param upstream_callbacks the callbacks to provide to the connection if successfully created. + * @return may be null if there is no cluster with the given name. + */ + virtual GenericConnPoolPtr + createGenericConnPool(const std::string& cluster_name, Upstream::ClusterManager& cm, + const absl::optional& config, + Upstream::LoadBalancerContext* context, + Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks) const PURE; +}; + +using GenericConnPoolFactoryPtr = std::unique_ptr; + +} // namespace TcpProxy +} // namespace Envoy diff --git a/source/common/config/utility.h b/source/common/config/utility.h index 5ffbe637d3d7..e9f206842849 100644 --- a/source/common/config/utility.h +++ b/source/common/config/utility.h @@ -212,8 +212,8 @@ class Utility { /** * Get a Factory from the registry with a particular name (and templated type) with error checking * to ensure the name and factory are valid. - * @param name string identifier for the particular implementation. Note: this is a proto string - * because it is assumed that this value will be pulled directly from the configuration proto. + * @param name string identifier for the particular implementation. + * @return factory the factory requested or nullptr if it does not exist. */ template static Factory& getAndCheckFactoryByName(const std::string& name) { if (name.empty()) { @@ -230,6 +230,32 @@ class Utility { return *factory; } + /** + * Get a Factory from the registry with a particular name or return nullptr. + * @param name string identifier for the particular implementation. + */ + template static Factory* getFactoryByName(const std::string& name) { + if (name.empty()) { + return nullptr; + } + + return Registry::FactoryRegistry::getFactory(name); + } + + /** + * Get a Factory from the registry or return nullptr. + * @param message proto that contains fields 'name' and 'typed_config'. + */ + template + static Factory* getFactory(const ProtoMessage& message) { + Factory* factory = Utility::getFactoryByType(message.typed_config()); + if (factory != nullptr) { + return factory; + } + + return Utility::getFactoryByName(message.name()); + } + /** * Get a Factory from the registry with error checking to ensure the name and the factory are * valid. diff --git a/source/common/tcp_proxy/BUILD b/source/common/tcp_proxy/BUILD index f75138d14134..401b8c3e8794 100644 --- a/source/common/tcp_proxy/BUILD +++ b/source/common/tcp_proxy/BUILD @@ -8,17 +8,35 @@ licenses(["notice"]) # Apache 2 envoy_package() +envoy_cc_library( + name = "upstream_lib", + srcs = [ + "upstream.cc", + ], + hdrs = [ + "upstream.h", + ], + deps = [ + "//include/envoy/tcp:conn_pool_interface", + "//include/envoy/tcp:upstream_interface", + "//include/envoy/upstream:cluster_manager_interface", + "//include/envoy/upstream:load_balancer_interface", + "//source/common/http:header_map_lib", + "//source/common/http:headers_lib", + "//source/common/http:utility_lib", + ], +) + envoy_cc_library( name = "tcp_proxy", srcs = [ "tcp_proxy.cc", - "upstream.cc", ], hdrs = [ "tcp_proxy.h", - "upstream.h", ], deps = [ + ":upstream_lib", "//include/envoy/access_log:access_log_interface", "//include/envoy/buffer:buffer_interface", "//include/envoy/common:time_interface", @@ -32,6 +50,7 @@ envoy_cc_library( "//include/envoy/stats:timespan_interface", "//include/envoy/stream_info:filter_state_interface", "//include/envoy/tcp:conn_pool_interface", + "//include/envoy/tcp:upstream_interface", "//include/envoy/upstream:cluster_manager_interface", "//include/envoy/upstream:upstream_interface", "//source/common/access_log:access_log_lib", @@ -39,7 +58,6 @@ envoy_cc_library( "//source/common/common:empty_string", "//source/common/common:macros", "//source/common/common:minimal_logger_lib", - "//source/common/http:headers_lib", "//source/common/network:application_protocol_lib", "//source/common/network:cidr_range_lib", "//source/common/network:filter_lib", @@ -51,6 +69,7 @@ envoy_cc_library( "//source/common/router:metadatamatchcriteria_lib", "//source/common/stream_info:stream_info_lib", "//source/common/upstream:load_balancer_lib", + "//source/extensions/upstreams/tcp/generic:config", "@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index 3688634f5305..736435a7b803 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -20,6 +20,7 @@ #include "common/common/fmt.h" #include "common/common/macros.h" #include "common/common/utility.h" +#include "common/config/utility.h" #include "common/config/well_known_names.h" #include "common/network/application_protocol.h" #include "common/network/proxy_protocol_filter_state.h" @@ -431,7 +432,7 @@ Network::FilterStatus Filter::initializeUpstreamConnection() { downstreamConnection()->streamInfo().filterState()); } - if (!maybeTunnel(cluster_name)) { + if (!maybeTunnel(*thread_local_cluster, cluster_name)) { // Either cluster is unknown or there are no healthy hosts. tcpConnPoolForCluster() increments // cluster->stats().upstream_cx_none_healthy in the latter case. getStreamInfo().setResponseFlag(StreamInfo::ResponseFlag::NoHealthyUpstream); @@ -440,47 +441,32 @@ Network::FilterStatus Filter::initializeUpstreamConnection() { return Network::FilterStatus::StopIteration; } -bool Filter::maybeTunnel(const std::string& cluster_name) { - if (!config_->tunnelingConfig()) { - generic_conn_pool_ = - std::make_unique(cluster_name, cluster_manager_, this, *upstream_callbacks_); - if (generic_conn_pool_->valid()) { - connecting_ = true; - connect_attempts_++; - generic_conn_pool_->newStream(this); - // Because we never return open connections to the pool, this either has a handle waiting on - // connection completion, or onPoolFailure has been invoked. Either way, stop iteration. - return true; - } else { - generic_conn_pool_.reset(); - } +bool Filter::maybeTunnel(Upstream::ThreadLocalCluster& cluster, const std::string& cluster_name) { + GenericConnPoolFactory* factory = nullptr; + if (cluster.info()->upstreamConfig().has_value()) { + factory = Envoy::Config::Utility::getFactory( + cluster.info()->upstreamConfig().value()); } else { - auto* cluster = cluster_manager_.get(cluster_name); - if (!cluster) { - return false; - } - // TODO(snowp): Ideally we should prevent this from being configured, but that's tricky to get - // right since whether a cluster is invalid depends on both the tcp_proxy config + cluster - // config. - if ((cluster->info()->features() & Upstream::ClusterInfo::Features::HTTP2) == 0) { - ENVOY_LOG(error, "Attempted to tunnel over HTTP/1.1, this is not supported. Set " - "http2_protocol_options on the cluster."); - return false; - } - - generic_conn_pool_ = std::make_unique(cluster_name, cluster_manager_, this, - config_->tunnelingConfig()->hostname(), - *upstream_callbacks_); - if (generic_conn_pool_->valid()) { - generic_conn_pool_->newStream(this); - return true; - } else { - generic_conn_pool_.reset(); - } + factory = Envoy::Config::Utility::getFactoryByName( + "envoy.filters.connection_pools.tcp.generic"); + } + if (!factory) { + return false; } + generic_conn_pool_ = factory->createGenericConnPool( + cluster_name, cluster_manager_, config_->tunnelingConfig(), this, *upstream_callbacks_); + if (generic_conn_pool_) { + connecting_ = true; + connect_attempts_++; + generic_conn_pool_->newStream(*this); + // Because we never return open connections to the pool, this either has a handle waiting on + // connection completion, or onPoolFailure has been invoked. Either way, stop iteration. + return true; + } return false; } + void Filter::onGenericPoolFailure(ConnectionPool::PoolFailureReason reason, Upstream::HostDescriptionConstSharedPtr host) { generic_conn_pool_.reset(); diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index 12b482e01690..de61eb665139 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -345,7 +345,7 @@ class Filter : public Network::ReadFilter, void initialize(Network::ReadFilterCallbacks& callbacks, bool set_connection_stats); Network::FilterStatus initializeUpstreamConnection(); - bool maybeTunnel(const std::string& cluster_name); + bool maybeTunnel(Upstream::ThreadLocalCluster& cluster, const std::string& cluster_name); void onConnectTimeout(); void onDownstreamEvent(Network::ConnectionEvent event); void onUpstreamData(Buffer::Instance& data, bool end_stream); diff --git a/source/common/tcp_proxy/upstream.cc b/source/common/tcp_proxy/upstream.cc index 8c82b07852eb..3f3d73c6add4 100644 --- a/source/common/tcp_proxy/upstream.cc +++ b/source/common/tcp_proxy/upstream.cc @@ -170,10 +170,8 @@ TcpConnPool::~TcpConnPool() { } } -bool TcpConnPool::valid() const { return conn_pool_ != nullptr; } - -void TcpConnPool::newStream(GenericConnectionPoolCallbacks* callbacks) { - callbacks_ = callbacks; +void TcpConnPool::newStream(GenericConnectionPoolCallbacks& callbacks) { + callbacks_ = &callbacks; // Given this function is reentrant, make sure we only reset the upstream_handle_ if given a // valid connection handle. If newConnection fails inline it may result in attempting to // select a new host, and a recursive call to initializeUpstreamConnection. In this case the @@ -205,9 +203,9 @@ void TcpConnPool::onPoolReady(Tcp::ConnectionPool::ConnectionDataPtr&& conn_data HttpConnPool::HttpConnPool(const std::string& cluster_name, Upstream::ClusterManager& cluster_manager, - Upstream::LoadBalancerContext* context, std::string hostname, + Upstream::LoadBalancerContext* context, const TunnelingConfig& config, Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks) - : hostname_(hostname), upstream_callbacks_(upstream_callbacks) { + : hostname_(config.hostname()), upstream_callbacks_(upstream_callbacks) { conn_pool_ = cluster_manager.httpConnPoolForCluster( cluster_name, Upstream::ResourcePriority::Default, absl::nullopt, context); } @@ -220,10 +218,8 @@ HttpConnPool::~HttpConnPool() { } } -bool HttpConnPool::valid() const { return conn_pool_ != nullptr; } - -void HttpConnPool::newStream(GenericConnectionPoolCallbacks* callbacks) { - callbacks_ = callbacks; +void HttpConnPool::newStream(GenericConnectionPoolCallbacks& callbacks) { + callbacks_ = &callbacks; upstream_ = std::make_unique(upstream_callbacks_, hostname_); Tcp::ConnectionPool::Cancellable* handle = conn_pool_->newStream(upstream_->responseDecoder(), *this); diff --git a/source/common/tcp_proxy/upstream.h b/source/common/tcp_proxy/upstream.h index 33943e70b982..e132b667e2ca 100644 --- a/source/common/tcp_proxy/upstream.h +++ b/source/common/tcp_proxy/upstream.h @@ -3,28 +3,13 @@ #include "envoy/http/conn_pool.h" #include "envoy/network/connection.h" #include "envoy/tcp/conn_pool.h" +#include "envoy/tcp/upstream.h" #include "envoy/upstream/load_balancer.h" #include "envoy/upstream/upstream.h" namespace Envoy { namespace TcpProxy { -class GenericConnectionPoolCallbacks; -class GenericUpstream; - -// An API for wrapping either an HTTP or a TCP connection pool. -class GenericConnPool : public Logger::Loggable { -public: - virtual ~GenericConnPool() = default; - - // Called to create a new HTTP stream or TCP connection. The implementation - // is then responsible for calling either onPoolReady or onPoolFailure on the - // supplied GenericConnectionPoolCallbacks. - virtual void newStream(GenericConnectionPoolCallbacks* callbacks) PURE; - // Returns true if there was a valid connection pool, false otherwise. - virtual bool valid() const PURE; -}; - class TcpConnPool : public GenericConnPool, public Tcp::ConnectionPool::Callbacks { public: TcpConnPool(const std::string& cluster_name, Upstream::ClusterManager& cluster_manager, @@ -32,9 +17,10 @@ class TcpConnPool : public GenericConnPool, public Tcp::ConnectionPool::Callback Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks); ~TcpConnPool() override; + bool valid() const { return conn_pool_ != nullptr; } + // GenericConnPool - bool valid() const override; - void newStream(GenericConnectionPoolCallbacks* callbacks) override; + void newStream(GenericConnectionPoolCallbacks& callbacks) override; // Tcp::ConnectionPool::Callbacks void onPoolFailure(ConnectionPool::PoolFailureReason reason, @@ -53,14 +39,18 @@ class HttpUpstream; class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callbacks { public: + using TunnelingConfig = + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig; + HttpConnPool(const std::string& cluster_name, Upstream::ClusterManager& cluster_manager, - Upstream::LoadBalancerContext* context, std::string hostname, + Upstream::LoadBalancerContext* context, const TunnelingConfig& config, Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks); ~HttpConnPool() override; + bool valid() const { return conn_pool_ != nullptr; } + // GenericConnPool - bool valid() const override; - void newStream(GenericConnectionPoolCallbacks* callbacks) override; + void newStream(GenericConnectionPoolCallbacks& callbacks) override; // Http::ConnectionPool::Callbacks, void onPoolFailure(ConnectionPool::PoolFailureReason reason, @@ -79,39 +69,6 @@ class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callba std::unique_ptr upstream_; }; -// An API for the UpstreamRequest to get callbacks from either an HTTP or TCP -// connection pool. -class GenericConnectionPoolCallbacks { -public: - virtual ~GenericConnectionPoolCallbacks() = default; - - virtual void onGenericPoolReady(StreamInfo::StreamInfo* info, - std::unique_ptr&& upstream, - Upstream::HostDescriptionConstSharedPtr& host, - const Network::Address::InstanceConstSharedPtr& local_address, - Ssl::ConnectionInfoConstSharedPtr ssl_info) PURE; - virtual void onGenericPoolFailure(ConnectionPool::PoolFailureReason reason, - Upstream::HostDescriptionConstSharedPtr host) PURE; -}; - -// Interface for a generic Upstream, which can communicate with a TCP or HTTP -// upstream. -class GenericUpstream { -public: - virtual ~GenericUpstream() = default; - // Calls readDisable on the upstream connection. Returns false if readDisable could not be - // performed (e.g. if the connection is closed) - virtual bool readDisable(bool disable) PURE; - // Encodes data upstream. - virtual void encodeData(Buffer::Instance& data, bool end_stream) PURE; - // Adds a callback to be called when the data is sent to the kernel. - virtual void addBytesSentCallback(Network::Connection::BytesSentCb cb) PURE; - // Called when a Network::ConnectionEvent is received on the downstream connection, to allow the - // upstream to do any cleanup. - virtual Tcp::ConnectionPool::ConnectionData* - onDownstreamEvent(Network::ConnectionEvent event) PURE; -}; - class TcpUpstream : public GenericUpstream { public: TcpUpstream(Tcp::ConnectionPool::ConnectionDataPtr&& data, @@ -149,7 +106,7 @@ class HttpUpstream : public GenericUpstream, Http::StreamCallbacks { void onAboveWriteBufferHighWatermark() override; void onBelowWriteBufferLowWatermark() override; - void setRequestEncoder(Http::RequestEncoder& request_encoder, bool is_ssl); + virtual void setRequestEncoder(Http::RequestEncoder& request_encoder, bool is_ssl); Http::ResponseDecoder& responseDecoder() { return response_decoder_; } diff --git a/source/extensions/upstreams/tcp/generic/BUILD b/source/extensions/upstreams/tcp/generic/BUILD new file mode 100644 index 000000000000..d6e2f2f3cae2 --- /dev/null +++ b/source/extensions/upstreams/tcp/generic/BUILD @@ -0,0 +1,25 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = [ + "config.cc", + ], + hdrs = [ + "config.h", + ], + security_posture = "robust_to_untrusted_downstream", + visibility = ["//visibility:public"], + deps = [ + "//source/common/tcp_proxy:upstream_lib", + "@envoy_api//envoy/extensions/upstreams/tcp/generic/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/upstreams/tcp/generic/config.cc b/source/extensions/upstreams/tcp/generic/config.cc new file mode 100644 index 000000000000..634862885f5c --- /dev/null +++ b/source/extensions/upstreams/tcp/generic/config.cc @@ -0,0 +1,45 @@ +#include "extensions/upstreams/tcp/generic/config.h" + +#include "envoy/upstream/cluster_manager.h" + +#include "common/tcp_proxy/upstream.h" + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Tcp { +namespace Generic { + +TcpProxy::GenericConnPoolPtr GenericConnPoolFactory::createGenericConnPool( + const std::string& cluster_name, Upstream::ClusterManager& cluster_manager, + const absl::optional& config, Upstream::LoadBalancerContext* context, + Envoy::Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks) const { + if (config.has_value()) { + auto* cluster = cluster_manager.get(cluster_name); + if (!cluster) { + return nullptr; + } + // TODO(snowp): Ideally we should prevent this from being configured, but that's tricky to get + // right since whether a cluster is invalid depends on both the tcp_proxy config + cluster + // config. + if ((cluster->info()->features() & Upstream::ClusterInfo::Features::HTTP2) == 0) { + ENVOY_LOG_MISC(error, "Attempted to tunnel over HTTP/1.1, this is not supported. Set " + "http2_protocol_options on the cluster."); + return nullptr; + } + auto ret = std::make_unique(cluster_name, cluster_manager, context, + config.value(), upstream_callbacks); + return (ret->valid() ? std::move(ret) : nullptr); + } + auto ret = std::make_unique(cluster_name, cluster_manager, context, + upstream_callbacks); + return (ret->valid() ? std::move(ret) : nullptr); +} + +REGISTER_FACTORY(GenericConnPoolFactory, TcpProxy::GenericConnPoolFactory); + +} // namespace Generic +} // namespace Tcp +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/upstreams/tcp/generic/config.h b/source/extensions/upstreams/tcp/generic/config.h new file mode 100644 index 000000000000..5ba6171ac691 --- /dev/null +++ b/source/extensions/upstreams/tcp/generic/config.h @@ -0,0 +1,37 @@ +#pragma once + +#include "envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.pb.h" +#include "envoy/registry/registry.h" +#include "envoy/tcp/upstream.h" + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Tcp { +namespace Generic { + +/** + * Config registration for the GenericConnPool. * @see TcpProxy::GenericConnPoolFactory + */ +class GenericConnPoolFactory : public TcpProxy::GenericConnPoolFactory { +public: + std::string name() const override { return "envoy.filters.connection_pools.tcp.generic"; } + std::string category() const override { return "envoy.upstreams"; } + TcpProxy::GenericConnPoolPtr createGenericConnPool( + const std::string& cluster_name, Upstream::ClusterManager& cm, + const absl::optional& config, Upstream::LoadBalancerContext* context, + Envoy::Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks) const override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique< + envoy::extensions::upstreams::tcp::generic::v3::GenericConnectionPoolProto>(); + } +}; + +DECLARE_FACTORY(GenericConnPoolFactory); + +} // namespace Generic +} // namespace Tcp +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy diff --git a/test/common/tcp_proxy/BUILD b/test/common/tcp_proxy/BUILD index f9db96cbaf13..9f8b01a83e27 100644 --- a/test/common/tcp_proxy/BUILD +++ b/test/common/tcp_proxy/BUILD @@ -24,6 +24,7 @@ envoy_cc_test( "//source/common/upstream:upstream_lib", "//source/extensions/access_loggers:well_known_names", "//source/extensions/access_loggers/file:config", + "//source/extensions/upstreams/http/generic:config", "//test/common/upstream:utility_lib", "//test/mocks/buffer:buffer_mocks", "//test/mocks/network:network_mocks", @@ -36,6 +37,8 @@ envoy_cc_test( "@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/upstreams/http/generic/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/upstreams/tcp/generic/v3:pkg_cc_proto", ], ) diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 803381c4192c..f19c3bac4d45 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -9,6 +9,8 @@ #include "envoy/extensions/access_loggers/file/v3/file.pb.h" #include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" #include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.validate.h" +#include "envoy/extensions/upstreams/http/generic/v3/generic_connection_pool.pb.h" +#include "envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.pb.h" #include "common/buffer/buffer_impl.h" #include "common/network/address_impl.h" @@ -994,7 +996,7 @@ class TcpProxyTest : public testing::Test { Upstream::HostDescriptionConstSharedPtr upstream_host_{}; }; -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DefaultRoutes)) { +TEST_F(TcpProxyTest, DefaultRoutes) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy::WeightedCluster::ClusterWeight* @@ -1009,7 +1011,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DefaultRoutes)) { } // Tests that half-closes are proxied and don't themselves cause any connection to be closed. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(HalfCloseProxy)) { +TEST_F(TcpProxyTest, HalfCloseProxy) { setup(1); EXPECT_CALL(filter_callbacks_.connection_, close(_)).Times(0); @@ -1029,8 +1031,71 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(HalfCloseProxy)) { upstream_callbacks_->onEvent(Network::ConnectionEvent::RemoteClose); } +// Test with an explicitly configured upstream. +TEST_F(TcpProxyTest, ExplicitFactory) { + // Explicitly configure an HTTP upstream, to test factory creation. + auto& info = factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_; + info->upstream_config_ = absl::make_optional(); + envoy::extensions::upstreams::tcp::generic::v3::GenericConnectionPoolProto generic_config; + info->upstream_config_.value().mutable_typed_config()->PackFrom(generic_config); + setup(1); + + raiseEventUpstreamConnected(0); + + Buffer::OwnedImpl buffer("hello"); + EXPECT_CALL(*upstream_connections_.at(0), write(BufferEqual(&buffer), false)); + filter_->onData(buffer, false); + + Buffer::OwnedImpl response("world"); + EXPECT_CALL(filter_callbacks_.connection_, write(BufferEqual(&response), _)); + upstream_callbacks_->onUpstreamData(response, false); + + EXPECT_CALL(filter_callbacks_.connection_, close(_)); + upstream_callbacks_->onEvent(Network::ConnectionEvent::LocalClose); +} + +// Test nothing bad happens if an invalid factory is configured. +TEST_F(TcpProxyTest, BadFactory) { + auto& info = factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_; + info->upstream_config_ = absl::make_optional(); + // The HTTP Generic connection pool is not a valid type for TCP upstreams. + envoy::extensions::upstreams::http::generic::v3::GenericConnectionPoolProto generic_config; + info->upstream_config_.value().mutable_typed_config()->PackFrom(generic_config); + + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); + + configure(config); + + upstream_connections_.push_back(std::make_unique>()); + upstream_connection_data_.push_back( + std::make_unique>()); + ON_CALL(*upstream_connection_data_.back(), connection()) + .WillByDefault(ReturnRef(*upstream_connections_.back())); + upstream_hosts_.push_back(std::make_shared>()); + conn_pool_handles_.push_back( + std::make_unique>()); + + ON_CALL(*upstream_hosts_.at(0), cluster()) + .WillByDefault( + ReturnPointee(factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_)); + EXPECT_CALL(*upstream_connections_.at(0), dispatcher()) + .WillRepeatedly(ReturnRef(filter_callbacks_.connection_.dispatcher_)); + + filter_ = std::make_unique(config_, factory_context_.cluster_manager_); + EXPECT_CALL(filter_callbacks_.connection_, enableHalfClose(true)); + EXPECT_CALL(filter_callbacks_.connection_, readDisable(true)); + filter_->initializeReadFilterCallbacks(filter_callbacks_); + filter_callbacks_.connection_.streamInfo().setDownstreamSslConnection( + filter_callbacks_.connection_.ssl()); + filter_callbacks_.connection_.streamInfo().setDownstreamLocalAddress( + filter_callbacks_.connection_.localAddress()); + filter_callbacks_.connection_.streamInfo().setDownstreamRemoteAddress( + filter_callbacks_.connection_.remoteAddress()); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onNewConnection()); +} + // Test that downstream is closed after an upstream LocalClose. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamLocalDisconnect)) { +TEST_F(TcpProxyTest, UpstreamLocalDisconnect) { setup(1); raiseEventUpstreamConnected(0); @@ -1048,7 +1113,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamLocalDisconnect)) { } // Test that downstream is closed after an upstream RemoteClose. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamRemoteDisconnect)) { +TEST_F(TcpProxyTest, UpstreamRemoteDisconnect) { setup(1); raiseEventUpstreamConnected(0); @@ -1066,7 +1131,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamRemoteDisconnect)) { } // Test that reconnect is attempted after a local connect failure -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamLocalFail)) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamLocalFail) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); @@ -1081,7 +1146,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamLocalFail)) } // Make sure that the tcp proxy code handles reentrant calls to onPoolFailure. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamLocalFailReentrant)) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamLocalFailReentrant) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); @@ -1106,7 +1171,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamLocalFailRee } // Test that reconnect is attempted after a remote connect failure -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamRemoteFail)) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamRemoteFail) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); setup(2, config); @@ -1120,7 +1185,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamRemoteFail)) } // Test that reconnect is attempted after a connect timeout -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamTimeout)) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamTimeout) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); setup(2, config); @@ -1134,7 +1199,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsUpstreamTimeout)) { } // Test that only the configured number of connect attempts occur -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(ConnectAttemptsLimit)) { +TEST_F(TcpProxyTest, ConnectAttemptsLimit) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = accessLogConfig("%RESPONSE_FLAGS%"); config.mutable_max_connect_attempts()->set_value(3); @@ -1186,7 +1251,7 @@ TEST_F(TcpProxyTest, OutlierDetection) { raiseEventUpstreamConnected(2); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamDisconnectDownstreamFlowControl)) { +TEST_F(TcpProxyTest, UpstreamDisconnectDownstreamFlowControl) { setup(1); raiseEventUpstreamConnected(0); @@ -1208,7 +1273,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamDisconnectDownstreamFlowCon filter_callbacks_.connection_.runLowWatermarkCallbacks(); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DownstreamDisconnectRemote)) { +TEST_F(TcpProxyTest, DownstreamDisconnectRemote) { setup(1); raiseEventUpstreamConnected(0); @@ -1225,7 +1290,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DownstreamDisconnectRemote)) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DownstreamDisconnectLocal)) { +TEST_F(TcpProxyTest, DownstreamDisconnectLocal) { setup(1); raiseEventUpstreamConnected(0); @@ -1242,7 +1307,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DownstreamDisconnectLocal)) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::LocalClose); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectTimeout)) { +TEST_F(TcpProxyTest, UpstreamConnectTimeout) { setup(1, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush)); @@ -1252,14 +1317,14 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectTimeout)) { EXPECT_EQ(access_log_data_, "UF,URX"); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(NoHost)) { +TEST_F(TcpProxyTest, NoHost) { EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush)); setup(0, accessLogConfig("%RESPONSE_FLAGS%")); filter_.reset(); EXPECT_EQ(access_log_data_, "UH"); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(RouteWithMetadataMatch)) { +TEST_F(TcpProxyTest, RouteWithMetadataMatch) { auto v1 = ProtobufWkt::Value(); v1.set_string_value("v1"); auto v2 = ProtobufWkt::Value(); @@ -1475,7 +1540,7 @@ TEST_F(TcpProxyTest, StreamInfoDynamicMetadataAndConfigMerged) { EXPECT_EQ(hv2, effective_criterions[2]->value()); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DisconnectBeforeData)) { +TEST_F(TcpProxyTest, DisconnectBeforeData) { configure(defaultConfig()); filter_ = std::make_unique(config_, factory_context_.cluster_manager_); filter_->initializeReadFilterCallbacks(filter_callbacks_); @@ -1485,7 +1550,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(DisconnectBeforeData)) { // Test that if the downstream connection is closed before the upstream connection // is established, the upstream connection is cancelled. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(RemoteClosedBeforeUpstreamConnected)) { +TEST_F(TcpProxyTest, RemoteClosedBeforeUpstreamConnected) { setup(1); EXPECT_CALL(*conn_pool_handles_.at(0), cancel(Tcp::ConnectionPool::CancelPolicy::CloseExcess)); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1493,13 +1558,13 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(RemoteClosedBeforeUpstreamConnected // Test that if the downstream connection is closed before the upstream connection // is established, the upstream connection is cancelled. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(LocalClosetBeforeUpstreamConnected)) { +TEST_F(TcpProxyTest, LocalClosetBeforeUpstreamConnected) { setup(1); EXPECT_CALL(*conn_pool_handles_.at(0), cancel(Tcp::ConnectionPool::CancelPolicy::CloseExcess)); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::LocalClose); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectFailure)) { +TEST_F(TcpProxyTest, UpstreamConnectFailure) { setup(1, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush)); @@ -1509,7 +1574,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectFailure)) { EXPECT_EQ(access_log_data_, "UF,URX"); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectionLimit)) { +TEST_F(TcpProxyTest, UpstreamConnectionLimit) { configure(accessLogConfig("%RESPONSE_FLAGS%")); factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_->resetResourceManager( 0, 0, 0, 0, 0); @@ -1527,7 +1592,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamConnectionLimit)) { // Tests that the idle timer closes both connections, and gets updated when either // connection has activity. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimeout)) { +TEST_F(TcpProxyTest, IdleTimeout) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1557,7 +1622,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimeout)) { } // Tests that the idle timer is disabled when the downstream connection is closed. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimerDisabledDownstreamClose)) { +TEST_F(TcpProxyTest, IdleTimerDisabledDownstreamClose) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1571,7 +1636,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimerDisabledDownstreamClose)) } // Tests that the idle timer is disabled when the upstream connection is closed. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimerDisabledUpstreamClose)) { +TEST_F(TcpProxyTest, IdleTimerDisabledUpstreamClose) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1585,7 +1650,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimerDisabledUpstreamClose)) { } // Tests that flushing data during an idle timeout doesn't cause problems. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimeoutWithOutstandingDataFlushed)) { +TEST_F(TcpProxyTest, IdleTimeoutWithOutstandingDataFlushed) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1634,7 +1699,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(IdleTimeoutWithOutstandingDataFlush } // Test that access log fields %UPSTREAM_HOST% and %UPSTREAM_CLUSTER% are correctly logged. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamHost)) { +TEST_F(TcpProxyTest, AccessLogUpstreamHost) { setup(1, accessLogConfig("%UPSTREAM_HOST% %UPSTREAM_CLUSTER%")); raiseEventUpstreamConnected(0); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1643,7 +1708,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamHost)) { } // Test that access log field %UPSTREAM_LOCAL_ADDRESS% is correctly logged. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamLocalAddress)) { +TEST_F(TcpProxyTest, AccessLogUpstreamLocalAddress) { setup(1, accessLogConfig("%UPSTREAM_LOCAL_ADDRESS%")); raiseEventUpstreamConnected(0); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1652,7 +1717,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamLocalAddress)) { } // Test that access log fields %DOWNSTREAM_PEER_URI_SAN% is correctly logged. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogPeerUriSan)) { +TEST_F(TcpProxyTest, AccessLogPeerUriSan) { filter_callbacks_.connection_.local_address_ = Network::Utility::resolveUrl("tcp://1.1.1.2:20000"); filter_callbacks_.connection_.remote_address_ = @@ -1670,7 +1735,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogPeerUriSan)) { } // Test that access log fields %DOWNSTREAM_TLS_SESSION_ID% is correctly logged. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogTlsSessionId)) { +TEST_F(TcpProxyTest, AccessLogTlsSessionId) { filter_callbacks_.connection_.local_address_ = Network::Utility::resolveUrl("tcp://1.1.1.2:20000"); filter_callbacks_.connection_.remote_address_ = @@ -1690,7 +1755,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogTlsSessionId)) { // Test that access log fields %DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT% and // %DOWNSTREAM_LOCAL_ADDRESS% are correctly logged. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogDownstreamAddress)) { +TEST_F(TcpProxyTest, AccessLogDownstreamAddress) { filter_callbacks_.connection_.local_address_ = Network::Utility::resolveUrl("tcp://1.1.1.2:20000"); filter_callbacks_.connection_.remote_address_ = @@ -1701,7 +1766,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogDownstreamAddress)) { EXPECT_EQ(access_log_data_, "1.1.1.1 1.1.1.2:20000"); } -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamSSLConnection)) { +TEST_F(TcpProxyTest, AccessLogUpstreamSSLConnection) { setup(1); NiceMock stream_info; @@ -1717,7 +1782,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(AccessLogUpstreamSSLConnection)) { } // Tests that upstream flush works properly with no idle timeout configured. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushNoTimeout)) { +TEST_F(TcpProxyTest, UpstreamFlushNoTimeout) { setup(1); raiseEventUpstreamConnected(0); @@ -1742,7 +1807,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushNoTimeout)) { // Tests that upstream flush works with an idle timeout configured, but the connection // finishes draining before the timer expires. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushTimeoutConfigured)) { +TEST_F(TcpProxyTest, UpstreamFlushTimeoutConfigured) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1774,7 +1839,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushTimeoutConfigured)) { } // Tests that upstream flush closes the connection when the idle timeout fires. -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushTimeoutExpired)) { +TEST_F(TcpProxyTest, UpstreamFlushTimeoutExpired) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1803,7 +1868,7 @@ TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushTimeoutExpired)) { // Tests that upstream flush will close a connection if it reads data from the upstream // connection after the downstream connection is closed (nowhere to send it). -TEST_F(TcpProxyTest, DEPRECATED_FEATURE_TEST(UpstreamFlushReceiveUpstreamData)) { +TEST_F(TcpProxyTest, UpstreamFlushReceiveUpstreamData) { setup(1); raiseEventUpstreamConnected(0); diff --git a/test/extensions/upstreams/tcp/generic/BUILD b/test/extensions/upstreams/tcp/generic/BUILD new file mode 100644 index 000000000000..e14b000d6056 --- /dev/null +++ b/test/extensions/upstreams/tcp/generic/BUILD @@ -0,0 +1,18 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + deps = [ + "//source/extensions/upstreams/tcp/generic:config", + "//test/mocks/upstream:upstream_mocks", + ], +) diff --git a/test/extensions/upstreams/tcp/generic/config_test.cc b/test/extensions/upstreams/tcp/generic/config_test.cc new file mode 100644 index 000000000000..9136d888ea72 --- /dev/null +++ b/test/extensions/upstreams/tcp/generic/config_test.cc @@ -0,0 +1,39 @@ +#include "extensions/upstreams/tcp/generic/config.h" + +#include "test/mocks/tcp/mocks.h" +#include "test/mocks/upstream/cluster_manager.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace Upstreams { +namespace Tcp { +namespace Generic { + +class TcpConnPoolTest : public ::testing::Test { +public: + NiceMock cluster_manager_; + const std::string cluster_name_{"cluster_name"}; + GenericConnPoolFactory factory_; + NiceMock callbacks_; +}; + +TEST_F(TcpConnPoolTest, TestNoMatchingClusterName) { + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config; + config.set_hostname("host"); + EXPECT_CALL(cluster_manager_, get(_)).WillOnce(Return(nullptr)); + EXPECT_EQ(nullptr, factory_.createGenericConnPool(cluster_name_, cluster_manager_, config, + nullptr, callbacks_)); +} + +} // namespace Generic +} // namespace Tcp +} // namespace Upstreams +} // namespace Extensions +} // namespace Envoy From a35164db575749fa7b80f5a7e7eceeae390e2e5d Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Thu, 5 Nov 2020 09:02:39 -0800 Subject: [PATCH 043/117] tls: make test more representative of the production code. (#13912) Fixes test from #13858. Signed-off-by: Piotr Sikora --- .../transport_sockets/tls/ssl_socket_test.cc | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index b96fc6366330..b265710464f1 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -2694,26 +2694,41 @@ TEST_P(SslSocketTest, ShutdownWithoutCloseNotify) { Buffer::OwnedImpl data("hello"); server_connection->write(data, false); EXPECT_EQ(data.length(), 0); - // Close without sending close_notify alert. - const SslHandshakerImpl* ssl_socket = - dynamic_cast(server_connection->ssl().get()); - EXPECT_EQ(ssl_socket->state(), Ssl::SocketState::HandshakeComplete); - SSL_set_quiet_shutdown(ssl_socket->ssl(), 1); - server_connection->close(Network::ConnectionCloseType::NoFlush); + // Calling close(FlushWrite) in onEvent() callback results in PostIoAction::Close, + // after which the connection is closed without write ready event being delivered, + // and with all outstanding data (here, "hello") being lost. })); EXPECT_CALL(*client_read_filter, onNewConnection()) .WillOnce(Return(Network::FilterStatus::Continue)); EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); - EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), true)) + EXPECT_CALL(*client_read_filter, onData(BufferStringEqual("hello"), false)) .WillOnce(Invoke([&](Buffer::Instance& read_buffer, bool) -> Network::FilterStatus { read_buffer.drain(read_buffer.length()); - client_connection->close(Network::ConnectionCloseType::NoFlush); + // Close without sending close_notify alert. + const SslHandshakerImpl* ssl_socket = + dynamic_cast(client_connection->ssl().get()); + EXPECT_EQ(ssl_socket->state(), Ssl::SocketState::HandshakeComplete); + SSL_set_quiet_shutdown(ssl_socket->ssl(), 1); + client_connection->close(Network::ConnectionCloseType::FlushWrite); return Network::FilterStatus::StopIteration; })); - EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); - EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)) + EXPECT_CALL(*server_read_filter, onNewConnection()) + .WillOnce(Return(Network::FilterStatus::Continue)); + EXPECT_CALL(*server_read_filter, onData(BufferStringEqual(""), true)) + .WillOnce(Invoke([&](Buffer::Instance&, bool) -> Network::FilterStatus { + // Close without sending close_notify alert. + const SslHandshakerImpl* ssl_socket = + dynamic_cast(server_connection->ssl().get()); + EXPECT_EQ(ssl_socket->state(), Ssl::SocketState::HandshakeComplete); + SSL_set_quiet_shutdown(ssl_socket->ssl(), 1); + server_connection->close(Network::ConnectionCloseType::NoFlush); + return Network::FilterStatus::StopIteration; + })); + + EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)) .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); })); dispatcher_->run(Event::Dispatcher::RunType::Block); From a491dc5736e636093e68b77d8ed59e9cddf20b93 Mon Sep 17 00:00:00 2001 From: Zach Reyes <39203661+zasweq@users.noreply.github.com> Date: Thu, 5 Nov 2020 13:39:06 -0500 Subject: [PATCH 044/117] Fixed grpc health check bug (#13870) * Fixed grpc health check bug caused by a faulty upstream response Signed-off-by: Zach --- source/common/upstream/health_checker_impl.cc | 2 +- test/common/grpc/codec_test.cc | 51 +++++++++++++++++ .../health_check_corpus/grpc_double_reset | 46 ++++++++++++++++ .../upstream/health_checker_impl_test.cc | 55 +++++++++++++++++++ 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 test/common/upstream/health_check_corpus/grpc_double_reset diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index f9dfb221c9ce..47b3c551f006 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -618,12 +618,12 @@ void GrpcHealthCheckerImpl::GrpcActiveHealthCheckSession::decodeData(Buffer::Ins "gRPC protocol violation: unexpected stream end", true); return; } - // We should end up with only one frame here. std::vector decoded_frames; if (!decoder_.decode(data, decoded_frames)) { onRpcComplete(Grpc::Status::WellKnownGrpcStatus::Internal, "gRPC wire protocol decode error", false); + return; } for (auto& frame : decoded_frames) { if (frame.length_ > 0) { diff --git a/test/common/grpc/codec_test.cc b/test/common/grpc/codec_test.cc index 2093ce37261e..e85b973be4a9 100644 --- a/test/common/grpc/codec_test.cc +++ b/test/common/grpc/codec_test.cc @@ -106,6 +106,57 @@ TEST(GrpcCodecTest, decodeInvalidFrame) { EXPECT_EQ(size, buffer.length()); } +// This test shows that null bytes in the bytestring successfully decode into a frame with length 0. +// Should this test really pass? +TEST(GrpcCodecTest, DecodeMultipleFramesInvalid) { + // A frame constructed from null bytes followed by an invalid frame + const std::string data("\000\000\000\000\0000000", 9); + Buffer::OwnedImpl buffer(data.data(), data.size()); + + size_t size = buffer.length(); + + std::vector frames; + Decoder decoder; + EXPECT_FALSE(decoder.decode(buffer, frames)); + // When the decoder doesn't successfully decode, it puts decoded frames up until + // an invalid frame into output frame vector. + EXPECT_EQ(1, frames.size()); + // Buffer does not get drained due to it returning false. + EXPECT_EQ(size, buffer.length()); + // Only part of the buffer represented a frame. Thus, the frame length should not equal the buffer + // length. The frame put into the output vector has no length. + EXPECT_EQ(0, frames[0].length_); +} + +// If there is a valid frame followed by an invalid frame, the decoder will successfully put the +// valid frame in the output and return false due to the invalid frame +TEST(GrpcCodecTest, DecodeValidFrameWithInvalidFrameAfterward) { + // Decode a valid encoded structured request plus invalid data afterward + helloworld::HelloRequest request; + request.set_name("hello"); + + Buffer::OwnedImpl buffer; + std::array header; + Encoder encoder; + encoder.newFrame(GRPC_FH_DEFAULT, request.ByteSize(), header); + buffer.add(header.data(), 5); + buffer.add(request.SerializeAsString()); + buffer.add("000000", 6); + size_t size = buffer.length(); + + std::vector frames; + Decoder decoder; + EXPECT_FALSE(decoder.decode(buffer, frames)); + // When the decoder doesn't successfully decode, it puts valid frames up until + // an invalid frame into output frame vector. + EXPECT_EQ(1, frames.size()); + // Buffer does not get drained due to it returning false. + EXPECT_EQ(size, buffer.length()); + // Only part of the buffer represented a valid frame. Thus, the frame length should not equal the + // buffer length. + EXPECT_NE(size, frames[0].length_); +} + TEST(GrpcCodecTest, decodeEmptyFrame) { Buffer::OwnedImpl buffer("\0\0\0\0", 5); diff --git a/test/common/upstream/health_check_corpus/grpc_double_reset b/test/common/upstream/health_check_corpus/grpc_double_reset new file mode 100644 index 000000000000..aaeca5b1d548 --- /dev/null +++ b/test/common/upstream/health_check_corpus/grpc_double_reset @@ -0,0 +1,46 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold { + value: 2 + } + grpc_health_check { + service_name: "service" + authority: "wwnvoyproxy.io" + } + event_log_path: "200" +} +actions { + respond { + http_respond { + status: 200 + } + tcp_respond { + } + grpc_respond { + grpc_respond_headers { + headers { + headers { + key: "content-type" + value: "application/grpc" + } + } + status: 200 + } + grpc_respond_bytes { + grpc_respond_unstructured_bytes { + data: "\000\000\000\000\000\000\000\000\000\000\000\000\000\00000000000000000000000000000000000\000\000\000\000\000\000\000\000\000\000\000\000\000c_r000\000\000\000\000\000\000\000" + data: "200" + } + } + } + } +} +http_verify_cluster: true diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 586b2a4e19b6..e12d543bfb16 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -3488,6 +3488,30 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils } return spec; } + // Null dereference from health check fuzzer + static ChunkSpec badData() { + std::string data("\000\000\000\000\0000000", 9); + std::vector chunk(data.begin(), data.end()); + ChunkSpec spec; + spec.valid = true; + spec.data = chunk; + return spec; + } + static ChunkSpec validFramesThenInvalidFrames() { + grpc::health::v1::HealthCheckResponse response; + response.set_status(grpc::health::v1::HealthCheckResponse::SERVING); + const auto data = Grpc::Common::serializeToGrpcFrame(response); + std::vector buffer_vector = std::vector(data->length(), 0); + data->copyOut(0, data->length(), &buffer_vector[0]); + // Invalid frame here + for (size_t i = 0; i < 6; i++) { + buffer_vector.push_back(48); // Represents ASCII Character of 0 + } + ChunkSpec spec; + spec.valid = true; + spec.data = buffer_vector; + return spec; + } static ChunkSpec validChunk(grpc::health::v1::HealthCheckResponse::ServingStatus status) { ChunkSpec spec; spec.valid = true; @@ -4427,6 +4451,37 @@ TEST_F(GrpcHealthCheckerImplTest, GrpcFailUnknown) { cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); } +// This used to cause a null dereference +TEST_F(GrpcHealthCheckerImplTest, GrpcFailNullBytes) { + setupHC(); + expectSingleHealthcheck(HealthTransition::Changed); + EXPECT_CALL(event_logger_, logEjectUnhealthy(_, _, _)); + EXPECT_CALL(event_logger_, logUnhealthy(_, _, _, true)); + respondResponseSpec(0, ResponseSpec{{{":status", "200"}, {"content-type", "application/grpc"}}, + {GrpcHealthCheckerImplTest::ResponseSpec::badData()}, + {}}); + EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( + Host::HealthFlag::FAILED_ACTIVE_HC)); + EXPECT_EQ(Host::Health::Unhealthy, + cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); +} + +// This used to cause a null dereference +TEST_F(GrpcHealthCheckerImplTest, GrpcValidFramesThenInvalidFrames) { + setupHC(); + expectSingleHealthcheck(HealthTransition::Changed); + EXPECT_CALL(event_logger_, logEjectUnhealthy(_, _, _)); + EXPECT_CALL(event_logger_, logUnhealthy(_, _, _, true)); + respondResponseSpec( + 0, ResponseSpec{{{":status", "200"}, {"content-type", "application/grpc"}}, + {GrpcHealthCheckerImplTest::ResponseSpec::validFramesThenInvalidFrames()}, + {}}); + EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( + Host::HealthFlag::FAILED_ACTIVE_HC)); + EXPECT_EQ(Host::Health::Unhealthy, + cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); +} + // Test SERVICE_UNKNOWN health status is considered unhealthy. TEST_F(GrpcHealthCheckerImplTest, GrpcFailServiceUnknown) { setupHC(); From d5e8634edf26dab65218e42f2acfe7c3dc95b72d Mon Sep 17 00:00:00 2001 From: Jared Tan Date: Fri, 6 Nov 2020 02:40:58 +0800 Subject: [PATCH 045/117] lua: update config examples to use typed_pre_filter_config (#13909) Signed-off-by: JaredTan95 --- docs/root/configuration/http/http_filters/lua_filter.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/root/configuration/http/http_filters/lua_filter.rst b/docs/root/configuration/http/http_filters/lua_filter.rst index 3f067fdb7ea0..84dc87a8b63f 100644 --- a/docs/root/configuration/http/http_filters/lua_filter.rst +++ b/docs/root/configuration/http/http_filters/lua_filter.rst @@ -124,8 +124,9 @@ follow: .. code-block:: yaml - per_filter_config: + typed_per_filter_config: envoy.filters.http.lua: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute disabled: true We can also refer to a Lua script in the filter configuration by specifying a name in LuaPerRoute. @@ -133,8 +134,9 @@ The ``GLOBAL`` Lua script will be overridden by the referenced script: .. code-block:: yaml - per_filter_config: + typed_per_filter_config: envoy.filters.http.lua: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute name: hello.lua .. attention:: @@ -148,8 +150,9 @@ Lua script as follows: .. code-block:: yaml - per_filter_config: + typed_per_filter_config: envoy.filters.http.lua: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute source_code: inline_string: | function envoy_on_response(response_handle) From 49cbae46d7d4bad644adcc4f809e3499e44406da Mon Sep 17 00:00:00 2001 From: Zach Reyes <39203661+zasweq@users.noreply.github.com> Date: Fri, 6 Nov 2020 16:07:12 -0500 Subject: [PATCH 046/117] [fuzz] Fixed fuzz bug in health check fuzzing (#13923) * Fixed false positive fuzz bugs in health checking Signed-off-by: Zach --- ...se-health_check_fuzz_test-5704648171978752 | 84 +++++++++++++++++++ ...se-health_check_fuzz_test-5713695386370048 | 40 +++++++++ .../grpc_generalized-crash | 39 +++++++++ .../health_check_corpus/grpc_trigger-interval | 33 ++++++++ test/common/upstream/health_check_fuzz.cc | 2 + 5 files changed, 198 insertions(+) create mode 100644 test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5704648171978752 create mode 100644 test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5713695386370048 create mode 100644 test/common/upstream/health_check_corpus/grpc_generalized-crash create mode 100644 test/common/upstream/health_check_corpus/grpc_trigger-interval diff --git a/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5704648171978752 b/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5704648171978752 new file mode 100644 index 000000000000..31117664f39d --- /dev/null +++ b/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5704648171978752 @@ -0,0 +1,84 @@ +health_check_config { + timeout { + nanos: 5 + } + interval { + nanos: 65 + } + unhealthy_threshold { + value: 538968064 + } + healthy_threshold { + value: 538968064 + } + reuse_connection { + value: true + } + grpc_health_check { + service_name: "\004" + } + unhealthy_interval { + seconds: 4227858432 + } + event_log_path: "?(" + tls_options { + } +} +actions { + trigger_timeout_timer { + } +} +actions { + trigger_timeout_timer { + } +} +actions { + raise_event: REMOTE_CLOSE +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + trigger_timeout_timer { + } +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + trigger_timeout_timer { + } +} +actions { + raise_event: CONNECTED +} +actions { + trigger_timeout_timer { + } +} +actions { + trigger_interval_timer { + } +} +actions { + raise_event: LOCAL_CLOSE +} +http_verify_cluster: true diff --git a/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5713695386370048 b/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5713695386370048 new file mode 100644 index 000000000000..020ca3fc0c7c --- /dev/null +++ b/test/common/upstream/health_check_corpus/clusterfuzz-testcase-health_check_fuzz_test-5713695386370048 @@ -0,0 +1,40 @@ +health_check_config { + timeout { + seconds: 512 + nanos: 8 + } + interval { + nanos: 8 + } + unhealthy_threshold { + value: 36 + } + healthy_threshold { + } + grpc_health_check { + } + no_traffic_interval { + seconds: 512 + nanos: 8 + } + initial_jitter { + seconds: 512 + nanos: 262152 + } +} +actions { + trigger_timeout_timer { + } +} +actions { + raise_go_away: NO_ERROR +} +actions { + trigger_interval_timer { + } +} +actions { + trigger_timeout_timer { + } +} +http_verify_cluster: true diff --git a/test/common/upstream/health_check_corpus/grpc_generalized-crash b/test/common/upstream/health_check_corpus/grpc_generalized-crash new file mode 100644 index 000000000000..0336f7d7e42e --- /dev/null +++ b/test/common/upstream/health_check_corpus/grpc_generalized-crash @@ -0,0 +1,39 @@ +health_check_config { + timeout { + nanos: 5 + } + interval { + nanos: 65 + } + unhealthy_threshold { + value: 538968064 + } + healthy_threshold { + value: 538968064 + } + reuse_connection { + value: true + } + grpc_health_check { + service_name: "\004" + } + unhealthy_interval { + seconds: 4227858432 + } + event_log_path: "?(" + tls_options { + } +} +actions { + raise_go_away: NO_ERROR +} +actions { + raise_event: LOCAL_CLOSE +} +actions { + trigger_timeout_timer { + } +} +actions { + raise_event: LOCAL_CLOSE +} diff --git a/test/common/upstream/health_check_corpus/grpc_trigger-interval b/test/common/upstream/health_check_corpus/grpc_trigger-interval new file mode 100644 index 000000000000..e17472a9767b --- /dev/null +++ b/test/common/upstream/health_check_corpus/grpc_trigger-interval @@ -0,0 +1,33 @@ +health_check_config { + timeout { + seconds: 512 + nanos: 8 + } + interval { + nanos: 8 + } + unhealthy_threshold { + value: 36 + } + healthy_threshold { + } + grpc_health_check { + } + no_traffic_interval { + seconds: 512 + nanos: 8 + } + initial_jitter { + seconds: 512 + nanos: 262152 + } +} +actions { + trigger_timeout_timer { + } +} + +actions { + trigger_interval_timer { + } +} diff --git a/test/common/upstream/health_check_fuzz.cc b/test/common/upstream/health_check_fuzz.cc index 17cc1a2074a9..f981aba20cba 100644 --- a/test/common/upstream/health_check_fuzz.cc +++ b/test/common/upstream/health_check_fuzz.cc @@ -462,6 +462,7 @@ void GrpcHealthCheckFuzz::triggerTimeoutTimer(bool last_action) { void GrpcHealthCheckFuzz::raiseEvent(const Network::ConnectionEvent& event_type, bool last_action) { test_session_->client_connection_->raiseEvent(event_type); if (!last_action && event_type != Network::ConnectionEvent::Connected) { + received_no_error_goaway_ = false; // from resetState() // Close events will always blow away the client ENVOY_LOG_MISC(trace, "Triggering interval timer after close event"); // Interval timer is guaranteed to be enabled from a close event - calls @@ -475,6 +476,7 @@ void GrpcHealthCheckFuzz::raiseGoAway(bool no_error) { test_session_->codec_client_->raiseGoAway(Http::GoAwayErrorCode::NoError); // Will cause other events to blow away client, because this is a "graceful" go away received_no_error_goaway_ = true; + triggerIntervalTimer(true); } else { // go away events without no error flag explicitly blow away client test_session_->codec_client_->raiseGoAway(Http::GoAwayErrorCode::Other); From 5330d1d2bea34724bdc20df0ae1a8c67f2b9b22c Mon Sep 17 00:00:00 2001 From: Zach Reyes <39203661+zasweq@users.noreply.github.com> Date: Mon, 9 Nov 2020 08:08:40 -0500 Subject: [PATCH 047/117] [fuzz] Added Least request load balancer fuzz test (#13841) * Added Least request load balancer fuzz test Signed-off-by: Zach --- test/common/upstream/BUILD | 20 +++++ .../least_request-high-number-of-hosts | 66 +++++++++++++++ .../least_request-no-config | 49 +++++++++++ .../least_request-no-hosts | 32 ++++++++ .../least_request-normal | 52 ++++++++++++ ...request-with-locality-high-number-of-hosts | 66 +++++++++++++++ .../least_request_load_balancer_fuzz.proto | 16 ++++ .../least_request_load_balancer_fuzz_test.cc | 82 +++++++++++++++++++ 8 files changed, 383 insertions(+) create mode 100644 test/common/upstream/least_request_load_balancer_corpus/least_request-high-number-of-hosts create mode 100644 test/common/upstream/least_request_load_balancer_corpus/least_request-no-config create mode 100644 test/common/upstream/least_request_load_balancer_corpus/least_request-no-hosts create mode 100644 test/common/upstream/least_request_load_balancer_corpus/least_request-normal create mode 100644 test/common/upstream/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts create mode 100644 test/common/upstream/least_request_load_balancer_fuzz.proto create mode 100644 test/common/upstream/least_request_load_balancer_fuzz_test.cc diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 2260de1e447a..a793dbe3ab4a 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -775,3 +775,23 @@ envoy_cc_fuzz_test( "//test/fuzz:utility_lib", ], ) + +envoy_proto_library( + name = "least_request_load_balancer_fuzz_proto", + srcs = ["least_request_load_balancer_fuzz.proto"], + deps = [ + "//test/common/upstream:zone_aware_load_balancer_fuzz_proto", + "@envoy_api//envoy/config/cluster/v3:pkg", + ], +) + +envoy_cc_fuzz_test( + name = "least_request_load_balancer_fuzz_test", + srcs = ["least_request_load_balancer_fuzz_test.cc"], + corpus = "//test/common/upstream:least_request_load_balancer_corpus", + deps = [ + ":least_request_load_balancer_fuzz_proto_cc_proto", + ":utility_lib", + ":zone_aware_load_balancer_fuzz_lib", + ], +) diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-high-number-of-hosts b/test/common/upstream/least_request_load_balancer_corpus/least_request-high-number-of-hosts new file mode 100644 index 000000000000..3c3f884753fa --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_corpus/least_request-high-number-of-hosts @@ -0,0 +1,66 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + num_degraded_hosts: 3 + num_excluded_hosts: 4 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 1000 + num_hosts_locality_b: 500 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 300 + num_hosts_locality_b: 1200 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +seed_for_prng: 1 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01\x02" +} +least_request_lb_config { + +} +random_bytestring_for_requests: "\x01\x02" diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-no-config b/test/common/upstream/least_request_load_balancer_corpus/least_request-no-config new file mode 100644 index 000000000000..470a160224c8 --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_corpus/least_request-no-config @@ -0,0 +1,49 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + random_bytestring: 1 + random_bytestring: 2 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 2 + random_bytestring: 1 + random_bytestring: 2 +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: 1 + random_bytestring: 2 +} +seed_for_prng: 1 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01" +} +random_bytestring_for_requests: "\x01\x02" diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-no-hosts b/test/common/upstream/least_request_load_balancer_corpus/least_request-no-hosts new file mode 100644 index 000000000000..f27f2002d8a7 --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_corpus/least_request-no-hosts @@ -0,0 +1,32 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: "\x01\x02" +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: "\x01\x02" +} +seed_for_prng: 2 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01" +} +least_request_lb_config { + +} +random_bytestring_for_requests: "\x01\x02" diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-normal b/test/common/upstream/least_request_load_balancer_corpus/least_request-normal new file mode 100644 index 000000000000..75417693b31b --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_corpus/least_request-normal @@ -0,0 +1,52 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + random_bytestring: 1 + random_bytestring: 2 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 2 + random_bytestring: 1 + random_bytestring: 2 +} +setup_priority_levels { + num_hosts_in_priority_level: 0 + random_bytestring: 1 + random_bytestring: 2 +} +seed_for_prng: 1 +} +need_local_priority_set: false +random_bytestring_for_weights: "\x01" +} +least_request_lb_config { + +} +random_bytestring_for_requests: "\x01\x02" diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts b/test/common/upstream/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts new file mode 100644 index 000000000000..99ab1d0edc0e --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts @@ -0,0 +1,66 @@ +zone_aware_load_balancer_test_case { +load_balancer_test_case { +common_lb_config { + +} +actions { + update_health_flags { + host_priority: 0 + num_healthy_hosts: 2 + num_degraded_hosts: 3 + num_excluded_hosts: 4 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 + } +} +actions { + prefetch { + + } +} +actions { + prefetch { + + } +} +actions { + choose_host { + + } +} +actions { + choose_host { + + } +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 1000 + num_hosts_locality_b: 500 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +setup_priority_levels { + num_hosts_in_priority_level: 3000 + num_hosts_locality_a: 300 + num_hosts_locality_b: 1200 + num_hosts_locality_c: 1500 + random_bytestring: 100000 + random_bytestring: 1000000 + random_bytestring: 1500000 + random_bytestring: 2000000 +} +seed_for_prng: 1 +} +need_local_priority_set: true +random_bytestring_for_weights: "\x01\x02" +} +least_request_lb_config { + +} +random_bytestring_for_requests: "\x01\x02" diff --git a/test/common/upstream/least_request_load_balancer_fuzz.proto b/test/common/upstream/least_request_load_balancer_fuzz.proto new file mode 100644 index 000000000000..9e699b964b74 --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_fuzz.proto @@ -0,0 +1,16 @@ + +syntax = "proto3"; + +package test.common.upstream; + +import "validate/validate.proto"; +import "envoy/config/cluster/v3/cluster.proto"; +import "test/common/upstream/zone_aware_load_balancer_fuzz.proto"; + +message LeastRequestLoadBalancerTestCase { + test.common.upstream.ZoneAwareLoadBalancerTestCase zone_aware_load_balancer_test_case = 1 + [(validate.rules).message.required = true]; + envoy.config.cluster.v3.Cluster.LeastRequestLbConfig least_request_lb_config = 2; + // This is used to determine the requests for each host - will wrap around if runs out of space + bytes random_bytestring_for_requests = 3 [(validate.rules).bytes = {min_len: 1, max_len: 2048}]; +} diff --git a/test/common/upstream/least_request_load_balancer_fuzz_test.cc b/test/common/upstream/least_request_load_balancer_fuzz_test.cc new file mode 100644 index 000000000000..85b0689f4d1e --- /dev/null +++ b/test/common/upstream/least_request_load_balancer_fuzz_test.cc @@ -0,0 +1,82 @@ +#include + +#include "test/common/upstream/least_request_load_balancer_fuzz.pb.validate.h" +#include "test/common/upstream/zone_aware_load_balancer_fuzz_base.h" +#include "test/fuzz/fuzz_runner.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Upstream { + +// Least Request takes into account both weights (handled in ZoneAwareLoadBalancerFuzzBase), and +// requests active as well +void setRequestsActiveForStaticHosts(NiceMock& priority_set, + const std::string& random_bytestring) { + uint32_t index_of_random_bytestring = 0; + // Iterate through all the current host sets and set requests for each + for (uint32_t priority_level = 0; priority_level < priority_set.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set.getMockHostSet(priority_level); + for (auto& host : host_set.hosts_) { + // Make sure no weights persisted from previous fuzz iterations + ASSERT(host->stats().rq_active_.value() == 0); + host->stats().rq_active_.set( + random_bytestring[index_of_random_bytestring % random_bytestring.length()] % 3); + ++index_of_random_bytestring; + } + } +} + +void removeRequestsActiveForStaticHosts(NiceMock& priority_set) { + // Clear out any set requests active + for (uint32_t priority_level = 0; priority_level < priority_set.hostSetsPerPriority().size(); + ++priority_level) { + MockHostSet& host_set = *priority_set.getMockHostSet(priority_level); + for (auto& host : host_set.hosts_) { + host->stats().rq_active_.set(0); + } + } +} + +DEFINE_PROTO_FUZZER(const test::common::upstream::LeastRequestLoadBalancerTestCase& input) { + try { + TestUtility::validate(input); + } catch (const ProtoValidationException& e) { + ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what()); + return; + } + + const test::common::upstream::ZoneAwareLoadBalancerTestCase& zone_aware_load_balancer_test_case = + input.zone_aware_load_balancer_test_case(); + + ZoneAwareLoadBalancerFuzzBase zone_aware_load_balancer_fuzz = ZoneAwareLoadBalancerFuzzBase( + zone_aware_load_balancer_test_case.need_local_priority_set(), + zone_aware_load_balancer_test_case.random_bytestring_for_weights()); + zone_aware_load_balancer_fuzz.initializeLbComponents( + zone_aware_load_balancer_test_case.load_balancer_test_case()); + + setRequestsActiveForStaticHosts(zone_aware_load_balancer_fuzz.priority_set_, + input.random_bytestring_for_requests()); + + try { + zone_aware_load_balancer_fuzz.lb_ = std::make_unique( + zone_aware_load_balancer_fuzz.priority_set_, + zone_aware_load_balancer_fuzz.local_priority_set_.get(), + zone_aware_load_balancer_fuzz.stats_, zone_aware_load_balancer_fuzz.runtime_, + zone_aware_load_balancer_fuzz.random_, + zone_aware_load_balancer_test_case.load_balancer_test_case().common_lb_config(), + input.least_request_lb_config()); + } catch (EnvoyException& e) { + ENVOY_LOG_MISC(debug, "EnvoyException; {}", e.what()); + removeRequestsActiveForStaticHosts(zone_aware_load_balancer_fuzz.priority_set_); + return; + } + + zone_aware_load_balancer_fuzz.replay( + zone_aware_load_balancer_test_case.load_balancer_test_case().actions()); + + removeRequestsActiveForStaticHosts(zone_aware_load_balancer_fuzz.priority_set_); +} + +} // namespace Upstream +} // namespace Envoy From 7a15e5d200f9e4e5f5cfe0347fe1c337e534be4b Mon Sep 17 00:00:00 2001 From: Yuchen Dai Date: Mon, 9 Nov 2020 09:10:17 -0800 Subject: [PATCH 048/117] cluster: unstuck cluster manager when update the initializing cluster (#13875) Signed-off-by: Yuchen Dai --- .../common/upstream/cluster_manager_impl.cc | 41 ++++-- source/common/upstream/cluster_manager_impl.h | 10 +- .../upstream/cluster_manager_impl_test.cc | 129 ++++++++++++++++++ test/integration/ads_integration.cc | 5 +- test/integration/ads_integration.h | 3 +- test/integration/ads_integration_test.cc | 114 ++++++++++++++++ test/integration/base_integration_test.cc | 44 +++--- 7 files changed, 310 insertions(+), 36 deletions(-) diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 5a2fb1328c36..6eaef38a901b 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -63,10 +63,20 @@ void ClusterManagerInitHelper::addCluster(Cluster& cluster) { const auto initialize_cb = [&cluster, this] { onClusterInit(cluster); }; if (cluster.initializePhase() == Cluster::InitializePhase::Primary) { + // Remove the previous cluster before the cluster object is destroyed. + primary_init_clusters_.remove_if( + [name_to_remove = cluster.info()->name()](Cluster* cluster_iter) { + return cluster_iter->info()->name() == name_to_remove; + }); primary_init_clusters_.push_back(&cluster); cluster.initialize(initialize_cb); } else { ASSERT(cluster.initializePhase() == Cluster::InitializePhase::Secondary); + // Remove the previous cluster before the cluster object is destroyed. + secondary_init_clusters_.remove_if( + [name_to_remove = cluster.info()->name()](Cluster* cluster_iter) { + return cluster_iter->info()->name() == name_to_remove; + }); secondary_init_clusters_.push_back(&cluster); if (started_secondary_initialize_) { // This can happen if we get a second CDS update that adds new clusters after we have @@ -613,7 +623,9 @@ bool ClusterManagerImpl::addOrUpdateCluster(const envoy::config::cluster::v3::Cl // and easy to understand. const bool all_clusters_initialized = init_helper_.state() == ClusterManagerInitHelper::State::AllClustersInitialized; - loadCluster(cluster, version_info, true, warming_clusters_); + // Preserve the previous cluster data to avoid early destroy. The same cluster should be added + // before destroy to avoid early initialization complete. + const auto previous_cluster = loadCluster(cluster, version_info, true, warming_clusters_); auto& cluster_entry = warming_clusters_.at(cluster_name); if (!all_clusters_initialized) { ENVOY_LOG(debug, "add/update cluster {} during init", cluster_name); @@ -702,9 +714,10 @@ bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { return removed; } -void ClusterManagerImpl::loadCluster(const envoy::config::cluster::v3::Cluster& cluster, - const std::string& version_info, bool added_via_api, - ClusterMap& cluster_map) { +ClusterManagerImpl::ClusterDataPtr +ClusterManagerImpl::loadCluster(const envoy::config::cluster::v3::Cluster& cluster, + const std::string& version_info, bool added_via_api, + ClusterMap& cluster_map) { std::pair new_cluster_pair = factory_.clusterFromProto(cluster, *this, outlier_event_logger_, added_via_api); auto& new_cluster = new_cluster_pair.first; @@ -749,11 +762,20 @@ void ClusterManagerImpl::loadCluster(const envoy::config::cluster::v3::Cluster& } }); } - - cluster_map[cluster_reference.info()->name()] = std::make_unique( - cluster, version_info, added_via_api, std::move(new_cluster), time_source_); - const auto cluster_entry_it = cluster_map.find(cluster_reference.info()->name()); - + ClusterDataPtr result; + auto cluster_entry_it = cluster_map.find(cluster_reference.info()->name()); + if (cluster_entry_it != cluster_map.end()) { + result = std::exchange(cluster_entry_it->second, + std::make_unique(cluster, version_info, added_via_api, + std::move(new_cluster), time_source_)); + } else { + bool inserted = false; + std::tie(cluster_entry_it, inserted) = + cluster_map.emplace(cluster_reference.info()->name(), + std::make_unique(cluster, version_info, added_via_api, + std::move(new_cluster), time_source_)); + ASSERT(inserted); + } // If an LB is thread aware, create it here. The LB is not initialized until cluster pre-init // finishes. For RingHash/Maglev don't create the LB here if subset balancing is enabled, // because the thread_aware_lb_ field takes precedence over the subset lb). @@ -776,6 +798,7 @@ void ClusterManagerImpl::loadCluster(const envoy::config::cluster::v3::Cluster& } updateClusterCounts(); + return result; } void ClusterManagerImpl::updateClusterCounts() { diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 147bbdd4c35c..ba3c55ba2af1 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -477,8 +477,14 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable cds_cluster( + new NiceMock()); + cds_cluster->info_->name_ = "cds_cluster"; + + // This part tests static init. + InSequence s; + EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) + .WillOnce(Return(std::make_pair(cds_cluster, nullptr))); + ON_CALL(*cds_cluster, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(factory_, createCds_()).WillOnce(Return(cds)); + EXPECT_CALL(*cds, setInitializedCb(_)); + EXPECT_CALL(*cds_cluster, initialize(_)); + + create(parseBootstrapFromV3Json(json)); + + ReadyWatcher cm_initialized; + cluster_manager_->setInitializedCb([&]() -> void { cm_initialized.ready(); }); + + const std::string ready_cluster_yaml = R"EOF( + name: fake_cluster + connect_timeout: 0.250s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: fake_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 11001 + )EOF"; + + const std::string warming_cluster_yaml = R"EOF( + name: fake_cluster + connect_timeout: 0.250s + type: STRICT_DNS + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: fake_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.com + port_value: 11001 + )EOF"; + + { + SCOPED_TRACE("Add a primary cluster staying in warming."); + EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)); + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(parseClusterFromV3Yaml(warming_cluster_yaml), + "warming")); + + // Mark all the rest of the clusters ready. Now the only warming cluster is the above one. + EXPECT_CALL(cm_initialized, ready()).Times(0); + cds_cluster->initialize_callback_(); + } + + { + SCOPED_TRACE("Modify the only warming primary cluster to immediate ready."); + EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)); + EXPECT_CALL(*cds, initialize()); + EXPECT_TRUE( + cluster_manager_->addOrUpdateCluster(parseClusterFromV3Yaml(ready_cluster_yaml), "ready")); + } + { + SCOPED_TRACE("All clusters are ready."); + EXPECT_CALL(cm_initialized, ready()); + cds->initialized_callback_(); + } + EXPECT_TRUE(Mock::VerifyAndClearExpectations(cds_cluster.get())); +} + TEST_F(ClusterManagerImplTest, ModifyWarmingCluster) { time_system_.setSystemTime(std::chrono::milliseconds(1234567891234)); create(defaultConfig()); @@ -2984,6 +3083,33 @@ TEST_F(ClusterManagerInitHelperTest, StaticSdsInitialize) { cluster1.initialize_callback_(); } +// Verify that primary cluster can be updated in warming state. +TEST_F(ClusterManagerInitHelperTest, TestUpdateWarming) { + InSequence s; + + auto sds = std::make_unique>(); + ON_CALL(*sds, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(*sds, initialize(_)); + init_helper_.addCluster(*sds); + init_helper_.onStaticLoadComplete(); + + NiceMock updated_sds; + ON_CALL(updated_sds, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(updated_sds, initialize(_)); + init_helper_.addCluster(updated_sds); + + // The override cluster is added. Manually drop the previous cluster. In production flow this is + // achieved by ClusterManagerImpl. + sds.reset(); + + ReadyWatcher primary_initialized; + init_helper_.setPrimaryClustersInitializedCb([&]() -> void { primary_initialized.ready(); }); + + EXPECT_CALL(*this, onClusterInit(Ref(updated_sds))); + EXPECT_CALL(primary_initialized, ready()); + updated_sds.initialize_callback_(); +} + TEST_F(ClusterManagerInitHelperTest, UpdateAlreadyInitialized) { InSequence s; @@ -3087,6 +3213,7 @@ TEST_F(ClusterManagerInitHelperTest, AddSecondaryAfterSecondaryInit) { init_helper_.addCluster(cluster1); NiceMock cluster2; + cluster2.info_->name_ = "cluster2"; ON_CALL(cluster2, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster2); @@ -3099,6 +3226,8 @@ TEST_F(ClusterManagerInitHelperTest, AddSecondaryAfterSecondaryInit) { init_helper_.startInitializingSecondaryClusters(); NiceMock cluster3; + cluster3.info_->name_ = "cluster3"; + ON_CALL(cluster3, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); EXPECT_CALL(cluster3, initialize(_)); init_helper_.addCluster(cluster3); diff --git a/test/integration/ads_integration.cc b/test/integration/ads_integration.cc index 7d81b1de0a1b..6a0db9004244 100644 --- a/test/integration/ads_integration.cc +++ b/test/integration/ads_integration.cc @@ -34,8 +34,9 @@ AdsIntegrationTest::AdsIntegrationTest(const envoy::config::core::v3::ApiVersion void AdsIntegrationTest::TearDown() { cleanUpXdsConnection(); } -envoy::config::cluster::v3::Cluster AdsIntegrationTest::buildCluster(const std::string& name) { - return ConfigHelper::buildCluster(name, "ROUND_ROBIN", api_version_); +envoy::config::cluster::v3::Cluster AdsIntegrationTest::buildCluster(const std::string& name, + const std::string& lb_policy) { + return ConfigHelper::buildCluster(name, lb_policy, api_version_); } envoy::config::cluster::v3::Cluster AdsIntegrationTest::buildTlsCluster(const std::string& name) { diff --git a/test/integration/ads_integration.h b/test/integration/ads_integration.h index 0da99aea566a..8c9a8e0ab3e6 100644 --- a/test/integration/ads_integration.h +++ b/test/integration/ads_integration.h @@ -22,7 +22,8 @@ class AdsIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, public Ht void TearDown() override; - envoy::config::cluster::v3::Cluster buildCluster(const std::string& name); + envoy::config::cluster::v3::Cluster buildCluster(const std::string& name, + const std::string& lb_policy = "ROUND_ROBIN"); envoy::config::cluster::v3::Cluster buildTlsCluster(const std::string& name); diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index 01aae9dc9f73..053dfcbc9868 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -1152,6 +1152,120 @@ TEST_P(AdsClusterV3Test, BasicClusterInitialWarming) { test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); } +// Update the only warming cluster. Verify that the new cluster is still warming and the cluster +// manager as a whole is not initialized. +TEST_P(AdsClusterV3Test, ClusterInitializationUpdateTheOnlyWarmingCluster) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + const auto eds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse( + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + // Update lb policy to MAGLEV so that cluster update is not skipped due to the same hash. + sendDiscoveryResponse( + cds_type_url, {buildCluster("cluster_0", "MAGLEV")}, {buildCluster("cluster_0", "MAGLEV")}, + {}, "2", false); + EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); + sendDiscoveryResponse( + eds_type_url, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1", false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); +} + +// Primary cluster is warming during cluster initialization. Update the cluster with immediate ready +// config and verify that all the clusters are initialized. +TEST_P(AdsClusterV3Test, TestPrimaryClusterWarmClusterInitialization) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + auto loopback = Network::Test::getLoopbackAddressString(ipVersion()); + addFakeUpstream(FakeHttpConnection::Type::HTTP2); + auto port = fake_upstreams_.back()->localAddress()->ip()->port(); + + // This cluster will be blocked since endpoint name cannot be resolved. + auto warming_cluster = ConfigHelper::buildStaticCluster("fake_cluster", port, loopback); + // Below endpoint accepts request but never return. The health check hangs 1 hour which covers the + // test running. + auto blocking_health_check = TestUtility::parseYaml(R"EOF( + timeout: 3600s + interval: 3600s + unhealthy_threshold: 2 + healthy_threshold: 2 + tcp_health_check: + send: + text: '01' + receive: + - text: '02' + )EOF"); + *warming_cluster.add_health_checks() = blocking_health_check; + + // Active cluster has the same name with warming cluster but has no blocking health check. + auto active_cluster = ConfigHelper::buildStaticCluster("fake_cluster", port, loopback); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse(cds_type_url, {warming_cluster}, + {warming_cluster}, {}, "1", false); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_.back()->waitForRawConnection(fake_upstream_connection)); + + // fake_cluster is in warming. + test_server_->waitForGaugeGe("cluster_manager.warming_clusters", 1); + + // Now replace the warming cluster by the config which will turn ready immediately. + sendDiscoveryResponse(cds_type_url, {active_cluster}, + {active_cluster}, {}, "2", false); + + // All clusters are ready. + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); +} + +// Two cluster warming, update one of them. Verify that the clusters are eventually initialized. +TEST_P(AdsClusterV3Test, ClusterInitializationUpdateOneOfThe2Warming) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + const auto eds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse( + cds_type_url, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0"), buildCluster("cluster_1")}, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0"), buildCluster("cluster_1")}, + {}, "1", false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); + + // Update lb policy to MAGLEV so that cluster update is not skipped due to the same hash. + sendDiscoveryResponse( + cds_type_url, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0", "MAGLEV"), buildCluster("cluster_1")}, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0", "MAGLEV"), buildCluster("cluster_1")}, + {}, "2", false); + EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0", "cluster_1"}, + {"cluster_0", "cluster_1"}, {})); + sendDiscoveryResponse( + eds_type_url, + {buildClusterLoadAssignment("cluster_0"), buildClusterLoadAssignment("cluster_1")}, + {buildClusterLoadAssignment("cluster_0"), buildClusterLoadAssignment("cluster_1")}, {}, "1", + false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); +} + // Verify CDS is paused during cluster warming. TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { initialize(); diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index ade05762720c..354d309ef86f 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -416,6 +416,22 @@ AssertionResult BaseIntegrationTest::compareDiscoveryRequest( } } +AssertionResult compareSets(const std::set& set1, const std::set& set2, + absl::string_view name) { + if (set1 == set2) { + return AssertionSuccess(); + } + auto failure = AssertionFailure() << name << " field not as expected.\nExpected: {"; + for (const auto& x : set1) { + failure << x << ", "; + } + failure << "}\nActual: {"; + for (const auto& x : set2) { + failure << x << ", "; + } + return failure << "}"; +} + AssertionResult BaseIntegrationTest::compareSotwDiscoveryRequest( const std::string& expected_type_url, const std::string& expected_version, const std::vector& expected_resource_names, bool expect_node, @@ -442,12 +458,12 @@ AssertionResult BaseIntegrationTest::compareSotwDiscoveryRequest( } EXPECT_TRUE( IsSubstring("", "", expected_error_substring, discovery_request.error_detail().message())); - const std::vector resource_names(discovery_request.resource_names().cbegin(), - discovery_request.resource_names().cend()); - if (expected_resource_names != resource_names) { - return AssertionFailure() << fmt::format( - "resources {} do not match expected {} in {}", absl::StrJoin(resource_names, ","), - absl::StrJoin(expected_resource_names, ","), discovery_request.DebugString()); + const std::set resource_names_in_request(discovery_request.resource_names().cbegin(), + discovery_request.resource_names().cend()); + if (auto resource_name_result = compareSets( + std::set(expected_resource_names.cbegin(), expected_resource_names.cend()), + resource_names_in_request, "Sotw resource names")) { + return resource_name_result; } if (expected_version != discovery_request.version_info()) { return AssertionFailure() << fmt::format("version {} does not match expected {} in {}", @@ -457,22 +473,6 @@ AssertionResult BaseIntegrationTest::compareSotwDiscoveryRequest( return AssertionSuccess(); } -AssertionResult compareSets(const std::set& set1, const std::set& set2, - absl::string_view name) { - if (set1 == set2) { - return AssertionSuccess(); - } - auto failure = AssertionFailure() << name << " field not as expected.\nExpected: {"; - for (const auto& x : set1) { - failure << x << ", "; - } - failure << "}\nActual: {"; - for (const auto& x : set2) { - failure << x << ", "; - } - return failure << "}"; -} - AssertionResult BaseIntegrationTest::waitForPortAvailable(uint32_t port, std::chrono::milliseconds timeout) { Event::TestTimeSystem::RealTimeBound bound(timeout); From 93b393b8d183ea4287f875ff40b7513dcf5adb7a Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 9 Nov 2020 18:27:05 +0000 Subject: [PATCH 049/117] docs: Add page about securing envoy to quick-start (#13880) Signed-off-by: Ryan Northey --- .../_include/envoy-demo-tls-client-auth.yaml | 69 ++++++ .../_include/envoy-demo-tls-sni.yaml | 61 ++++++ .../_include/envoy-demo-tls-validation.yaml | 53 +++++ .../quick-start/_include/envoy-demo-tls.yaml | 57 +++++ docs/root/start/quick-start/index.rst | 1 + docs/root/start/quick-start/securing.rst | 199 ++++++++++++++++++ 6 files changed, 440 insertions(+) create mode 100644 docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml create mode 100644 docs/root/start/quick-start/_include/envoy-demo-tls-sni.yaml create mode 100644 docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml create mode 100644 docs/root/start/quick-start/_include/envoy-demo-tls.yaml create mode 100644 docs/root/start/quick-start/securing.rst diff --git a/docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml b/docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml new file mode 100644 index 000000000000..ab581b1ec0cf --- /dev/null +++ b/docs/root/start/quick-start/_include/envoy-demo-tls-client-auth.yaml @@ -0,0 +1,69 @@ +static_resources: + + listeners: + - name: listener_0 + address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + http_filters: + - name: envoy.filters.http.router + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + require_client_certificate: true + common_tls_context: + validation_context: + trusted_ca: + filename: certs/cacert.pem + match_subject_alt_names: + - exact: proxy-postgres-frontend.example.com + tls_certificates: + certificate_chain: + filename: certs/servercert.pem + private_key: + filename: certs/serverkey.pem + + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + tls_certificates: + certificate_chain: + filename: certs/clientcert.pem + private_key: + filename: certs/clientkey.pem diff --git a/docs/root/start/quick-start/_include/envoy-demo-tls-sni.yaml b/docs/root/start/quick-start/_include/envoy-demo-tls-sni.yaml new file mode 100644 index 000000000000..68e7ab7569ed --- /dev/null +++ b/docs/root/start/quick-start/_include/envoy-demo-tls-sni.yaml @@ -0,0 +1,61 @@ +static_resources: + + listeners: + - name: listener_0 + address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + http_filters: + - name: envoy.filters.http.router + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + filter_chain_match: + server_names: + - my-service-name.example.com + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + certificate_chain: + filename: certs/servercert.pem + private_key: + filename: certs/serverkey.pem + + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + sni: www.envoyproxy.io diff --git a/docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml b/docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml new file mode 100644 index 000000000000..4260b1fca986 --- /dev/null +++ b/docs/root/start/quick-start/_include/envoy-demo-tls-validation.yaml @@ -0,0 +1,53 @@ +static_resources: + + listeners: + - name: listener_0 + address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + http_filters: + - name: envoy.filters.http.router + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + validation_context: + trusted_ca: + filename: certs/cacert.pem + match_subject_alt_names: + - exact: proxy-postgres-backend.example.com diff --git a/docs/root/start/quick-start/_include/envoy-demo-tls.yaml b/docs/root/start/quick-start/_include/envoy-demo-tls.yaml new file mode 100644 index 000000000000..2bcf2acf2cb1 --- /dev/null +++ b/docs/root/start/quick-start/_include/envoy-demo-tls.yaml @@ -0,0 +1,57 @@ +static_resources: + + listeners: + - name: listener_0 + address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + http_filters: + - name: envoy.filters.http.router + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + certificate_chain: + filename: certs/servercert.pem + private_key: + filename: certs/serverkey.pem + + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext diff --git a/docs/root/start/quick-start/index.rst b/docs/root/start/quick-start/index.rst index 566b82f0a084..cc2078926cc7 100644 --- a/docs/root/start/quick-start/index.rst +++ b/docs/root/start/quick-start/index.rst @@ -13,4 +13,5 @@ provides an introduction to the types of configuration Envoy can be used with. configuration-static configuration-dynamic-filesystem configuration-dynamic-control-plane + securing next-steps diff --git a/docs/root/start/quick-start/securing.rst b/docs/root/start/quick-start/securing.rst new file mode 100644 index 000000000000..efacfa1191df --- /dev/null +++ b/docs/root/start/quick-start/securing.rst @@ -0,0 +1,199 @@ +.. _start_quick_start_securing: + +Securing Envoy +============== + +Envoy provides a number of features to secure traffic in and out of your network, and +between proxies and services within your network. + +Transport Layer Security (``TLS``) can be used to secure all types of ``HTTP`` traffic, including ``WebSockets``. + +Envoy also has support for transmitting and receiving generic ``TCP`` traffic with ``TLS``. + +.. warning:: + + The following guide takes you through individual aspects of securing traffic. + + To secure traffic over a network that is untrusted, you are strongly advised to make use of encryption + and mutual authentication wherever you control both sides of the connection or where relevant protocols are available. + + Here we provide a guide to using :ref:`mTLS ` which provides both encryption + and mutual authentication. + + You are also strongly encouraged to :ref:`validate ` all certificates + wherever possible. + + It is your responsibility to ensure the integrity of your certificate chain, and outside the scope of this guide. + +.. _start_quick_start_securing_contexts: + +Upstream and downstream TLS contexts +------------------------------------ + +Machines connecting to Envoy to proxy traffic are "downstream" in relation to Envoy. + +Specifying a ``TLS`` context that clients can connect to is achieved by setting the +:ref:`DownstreamTLSContext ` + +You will also need to provide valid certificates. + +.. literalinclude:: _include/envoy-demo-tls.yaml + :language: yaml + :linenos: + :lines: 1-37 + :emphasize-lines: 28-37 + :caption: :download:`envoy-demo-tls.yaml <_include/envoy-demo-tls.yaml>` + +Connecting to an "upstream" ``TLS`` service is conversely done by adding an +:ref:`UpstreamTLSContext ` +to the :ref:`cluster `. + +.. literalinclude:: _include/envoy-demo-tls.yaml + :language: yaml + :linenos: + :lineno-start: 39 + :lines: 39-57 + :emphasize-lines: 16-19 + :caption: :download:`envoy-demo-tls.yaml <_include/envoy-demo-tls.yaml>` + +.. _start_quick_start_securing_validation: + +Validate an endpoint's certificates when connecting +--------------------------------------------------- + +When Envoy connects to an upstream ``TLS`` service, it does not, by default, validate the certificates +that it is presented with. + +You can use the :ref:`validation_context ` +to specify how Envoy should validate these certificates. + +Firstly, you can ensure that the certificates are from a mutually trusted certificate authority: + +.. literalinclude:: _include/envoy-demo-tls-validation.yaml + :language: yaml + :linenos: + :lineno-start: 43 + :lines: 43-53 + :emphasize-lines: 6-9 + :caption: :download:`envoy-demo-tls-validation.yaml <_include/envoy-demo-tls-validation.yaml>` + +You can also ensure that the "Subject Alternative Names" for the cerficate match. + +This is commonly used by web certificates (X.509) to identify the domain or domains that a +certificate is valid for. + +.. literalinclude:: _include/envoy-demo-tls-validation.yaml + :language: yaml + :linenos: + :lineno-start: 43 + :lines: 43-53 + :emphasize-lines: 6-7, 10-11 + :caption: :download:`envoy-demo-tls-validation.yaml <_include/envoy-demo-tls-validation.yaml>` + +.. note:: + + If the "Subject Alternative Names" for a certificate are for a wildcard domain, eg ``*.example.com``, + this is what you should use when matching with ``match_subject_alt_names``. + +.. note:: + + See :ref:`here ` to view all + of the possible configurations for certificate validation. + +.. _start_quick_start_securing_mtls: + +Use mututal TLS (mTLS) to enforce client certificate authentication +------------------------------------------------------------------- + +With mutual TLS (mTLS), Envoy also provides a way to authenticate connecting clients. + +At a minimum you will need to set +:ref:`require_client_certificate ` +and specify a mutually trusted certificate authority: + +.. literalinclude:: _include/envoy-demo-tls-client-auth.yaml + :language: yaml + :linenos: + :lineno-start: 27 + :lines: 27-39 + :emphasize-lines: 6, 8-10 + :caption: :download:`envoy-demo-tls-client-auth.yaml <_include/envoy-demo-tls-client-auth.yaml>` + +You can further restrict the authentication of connecting clients by specifying the allowed +"Subject Alternative Names" in +:ref:`match_subject_alt_names `, +similar to validating upstream certificates :ref:`described above `. + +.. literalinclude:: _include/envoy-demo-tls-client-auth.yaml + :language: yaml + :linenos: + :lineno-start: 27 + :lines: 27-39 + :emphasize-lines: 7, 11-12 + :caption: :download:`envoy-demo-tls-client-auth.yaml <_include/envoy-demo-tls-client-auth.yaml>` + +.. note:: + + See :ref:`here ` to view all + of the possible configurations for certificate validation. + +.. _start_quick_start_securing_mtls_client: + +Use mututal TLS (mTLS) to connect with client certificates +---------------------------------------------------------- + +When connecting to an upstream with client certificates you can set them as follows: + +.. literalinclude:: _include/envoy-demo-tls-client-auth.yaml + :language: yaml + :linenos: + :lineno-start: 45 + :lines: 45-69 + :emphasize-lines: 21-25 + :caption: :download:`envoy-demo-tls-client-auth.yaml <_include/envoy-demo-tls-client-auth.yaml>` + +.. _start_quick_start_securing_sni: + +Provide multiple TLS domains at the same IP address +--------------------------------------------------- + +``SNI`` is an extension to the ``TLS`` protocol which allows multiple domains served +from the same ``IP`` address to be secured with ``TLS``. + +To secure specific domains on a listening connection with ``SNI``, you should set the +:ref:`filter_chain_match ` of the +:ref:`listener `: + +.. literalinclude:: _include/envoy-demo-tls-sni.yaml + :language: yaml + :linenos: + :lineno-start: 27 + :lines: 27-35 + :emphasize-lines: 2-4 + :caption: :download:`envoy-demo-tls-sni.yaml <_include/envoy-demo-tls-sni.yaml>` + +See here for :ref:`more info about creating multiple endpoints with SNI ` + +.. _start_quick_start_securing_sni_client: + +Connect to an endpoint with SNI +------------------------------- + +When connecting to a ``TLS`` endpoint that uses ``SNI`` you should set +:ref:`sni ` in the configuration +of the :ref:`UpstreamTLSContext `. + +This will usually be the DNS name of the service you are connecting to. + +.. literalinclude:: _include/envoy-demo-tls-sni.yaml + :language: yaml + :linenos: + :lineno-start: 56 + :lines: 56-61 + :emphasize-lines: 6 + :caption: :download:`envoy-demo-tls-sni.yaml <_include/envoy-demo-tls-sni.yaml>` + +When connecting to an Envoy endpoint that is protected by ``SNI``, this must match one of the +:ref:`server_names ` set in the endpoint's +:ref:`filter_chain_match `, as +:ref:`described above `. From e1c138b427ff9bcdf5ebcb2639cc2d22f98ac1a8 Mon Sep 17 00:00:00 2001 From: Alex Konradi Date: Mon, 9 Nov 2020 13:51:52 -0500 Subject: [PATCH 050/117] http: add request header timer (#13341) Add a new config field for an additional timeout that will cancel streams that take too long to send headers, and implement it in the HTTP Connection Manager. Signed-off-by: Alex Konradi --- .../v3/http_connection_manager.proto | 10 ++- .../v4alpha/http_connection_manager.proto | 10 ++- docs/protodoc_manifest.yaml | 3 + docs/root/faq/configuration/timeouts.rst | 8 ++- docs/root/version_history/current.rst | 1 + .../v3/http_connection_manager.proto | 10 ++- .../v4alpha/http_connection_manager.proto | 10 ++- include/envoy/stream_info/stream_info.h | 4 ++ source/common/http/conn_manager_config.h | 7 +++ source/common/http/conn_manager_impl.cc | 29 ++++++++- source/common/http/conn_manager_impl.h | 15 ++++- .../network/http_connection_manager/config.cc | 2 + .../network/http_connection_manager/config.h | 6 ++ source/server/admin/admin.h | 1 + .../http/conn_manager_impl_fuzz_test.cc | 4 ++ test/common/http/conn_manager_impl_test.cc | 63 +++++++++++++++++++ .../common/http/conn_manager_impl_test_base.h | 4 ++ test/common/http/conn_manager_utility_test.cc | 1 + .../tls/integration/ssl_integration_test.cc | 6 +- .../http_timeout_integration_test.cc | 44 +++++++++++++ test/integration/utility.cc | 18 ++++-- test/integration/utility.h | 7 ++- 22 files changed, 244 insertions(+), 19 deletions(-) diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index a4c115c68da0..c2254c4c117a 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -37,7 +37,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 41] +// [#next-free-field: 42] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -360,6 +360,14 @@ message HttpConnectionManager { google.protobuf.Duration request_timeout = 28 [(udpa.annotations.security).configure_for_untrusted_downstream = true]; + // The amount of time that Envoy will wait for the request headers to be received. The timer is + // activated when the first byte of the headers is received, and is disarmed when the last byte of + // the headers has been received. If not specified or set to 0, this timeout is disabled. + google.protobuf.Duration request_headers_timeout = 41 [ + (validate.rules).duration = {gte {}}, + (udpa.annotations.security).configure_for_untrusted_downstream = true + ]; + // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. // This is used so that Envoy provides a grace period for new streams that diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index ceb7f4a65a1f..a44d35f86ae2 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -36,7 +36,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 41] +// [#next-free-field: 42] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; @@ -359,6 +359,14 @@ message HttpConnectionManager { google.protobuf.Duration request_timeout = 28 [(udpa.annotations.security).configure_for_untrusted_downstream = true]; + // The amount of time that Envoy will wait for the request headers to be received. The timer is + // activated when the first byte of the headers is received, and is disarmed when the last byte of + // the headers has been received. If not specified or set to 0, this timeout is disabled. + google.protobuf.Duration request_headers_timeout = 41 [ + (validate.rules).duration = {gte {}}, + (udpa.annotations.security).configure_for_untrusted_downstream = true + ]; + // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. // This is used so that Envoy provides a grace period for new streams that diff --git a/docs/protodoc_manifest.yaml b/docs/protodoc_manifest.yaml index 2e2afff3264d..ecdf19115a34 100644 --- a/docs/protodoc_manifest.yaml +++ b/docs/protodoc_manifest.yaml @@ -42,6 +42,9 @@ fields: envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.stream_idle_timeout: edge_config: example: 300s # 5 mins + envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.request_headers_timeout: + edge_config: + example: 10s # 10 seconds envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.request_timeout: edge_config: note: > diff --git a/docs/root/faq/configuration/timeouts.rst b/docs/root/faq/configuration/timeouts.rst index bdf42877c33b..2b44ce135346 100644 --- a/docs/root/faq/configuration/timeouts.rst +++ b/docs/root/faq/configuration/timeouts.rst @@ -50,6 +50,12 @@ context request/stream is interchangeable. This timeout is not enforced by default as it is not compatible with streaming requests (requests that never end). See the stream idle timeout that follows. However, if using the :ref:`buffer filter `, it is recommended to configure this timeout. +* The HTTP connection manager :ref:`request_headers_timeout + ` + determines the amount of time the client has to send *only the headers* on the request stream + before the stream is cancelled. This can be used to prevent clients from consuming too much + memory by creating large numbers of mostly-idle streams waiting for headers. The request header + timeout is disabled by default. * The HTTP connection manager :ref:`stream_idle_timeout ` is the amount of time that the connection manager will allow a stream to exist with no upstream @@ -120,4 +126,4 @@ Transport Socket * The :ref:`transport_socket_connect_timeout ` specifies the amount of time Envoy will wait for a downstream client to complete transport-level negotiations. When configured on a filter chain with a TLS or ALTS transport socket, this limits - the amount of time allowed to finish the encrypted handshake after establishing a TCP connection. \ No newline at end of file + the amount of time allowed to finish the encrypted handshake after establishing a TCP connection. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 8d45e31362d5..e8b052a3b41e 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -48,6 +48,7 @@ New Features * grpc: implemented header value syntax support when defining :ref:`initial metadata ` for gRPC-based `ext_authz` :ref:`HTTP ` and :ref:`network ` filters, and :ref:`ratelimit ` filters. * hds: added support for delta updates in the :ref:`HealthCheckSpecifier `, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS. * health_check: added option to use :ref:`no_traffic_healthy_interval ` which allows a different no traffic interval when the host is healthy. +* http: added HCM :ref:`timeout config field ` to control how long a downstream has to finish sending headers before the stream is cancelled. * http: added frame flood and abuse checks to the upstream HTTP/2 codec. This check is off by default and can be enabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to true. * jwt_authn: added support for :ref:`per-route config `. * listener: added an optional :ref:`default filter chain `. If this field is supplied, and none of the :ref:`filter_chains ` matches, this default filter chain is used to serve the connection. diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index d26ce2ffee96..250c91077fa1 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -37,7 +37,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 41] +// [#next-free-field: 42] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -362,6 +362,14 @@ message HttpConnectionManager { google.protobuf.Duration request_timeout = 28 [(udpa.annotations.security).configure_for_untrusted_downstream = true]; + // The amount of time that Envoy will wait for the request headers to be received. The timer is + // activated when the first byte of the headers is received, and is disarmed when the last byte of + // the headers has been received. If not specified or set to 0, this timeout is disabled. + google.protobuf.Duration request_headers_timeout = 41 [ + (validate.rules).duration = {gte {}}, + (udpa.annotations.security).configure_for_untrusted_downstream = true + ]; + // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. // This is used so that Envoy provides a grace period for new streams that diff --git a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto index ceb7f4a65a1f..a44d35f86ae2 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/http_connection_manager/v4alpha/http_connection_manager.proto @@ -36,7 +36,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 41] +// [#next-free-field: 42] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager"; @@ -359,6 +359,14 @@ message HttpConnectionManager { google.protobuf.Duration request_timeout = 28 [(udpa.annotations.security).configure_for_untrusted_downstream = true]; + // The amount of time that Envoy will wait for the request headers to be received. The timer is + // activated when the first byte of the headers is received, and is disarmed when the last byte of + // the headers has been received. If not specified or set to 0, this timeout is disabled. + google.protobuf.Duration request_headers_timeout = 41 [ + (validate.rules).duration = {gte {}}, + (udpa.annotations.security).configure_for_untrusted_downstream = true + ]; + // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. // This is used so that Envoy provides a grace period for new streams that diff --git a/include/envoy/stream_info/stream_info.h b/include/envoy/stream_info/stream_info.h index 821053465367..dc4a67973976 100644 --- a/include/envoy/stream_info/stream_info.h +++ b/include/envoy/stream_info/stream_info.h @@ -115,6 +115,8 @@ struct ResponseCodeDetailValues { const std::string MaxDurationTimeout = "max_duration_timeout"; // The per-stream total request timeout was exceeded. const std::string RequestOverallTimeout = "request_overall_timeout"; + // The per-stream request header timeout was exceeded. + const std::string RequestHeaderTimeout = "request_header_timeout"; // The request was rejected due to the Overload Manager reaching configured resource limits. const std::string Overload = "overload"; // The HTTP/1.0 or HTTP/0.9 request was rejected due to HTTP/1.0 support not being configured. @@ -168,6 +170,8 @@ struct ResponseCodeDetailValues { const std::string DownstreamLocalDisconnect = "downstream_local_disconnect"; // The max connection duration was exceeded. const std::string DurationTimeout = "duration_timeout"; + // The max request downstream header duration was exceeded. + const std::string DownstreamHeaderTimeout = "downstream_header_timeout"; // The response was generated by the admin filter. const std::string AdminFilterResponse = "admin_filter_response"; // The original stream was replaced with an internal redirect. diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index b67afc95a64c..12a7885273e1 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -58,6 +58,7 @@ namespace Http { COUNTER(downstream_rq_response_before_rq_complete) \ COUNTER(downstream_rq_rx_reset) \ COUNTER(downstream_rq_timeout) \ + COUNTER(downstream_rq_header_timeout) \ COUNTER(downstream_rq_too_large) \ COUNTER(downstream_rq_total) \ COUNTER(downstream_rq_tx_reset) \ @@ -289,6 +290,12 @@ class ConnectionManagerConfig { */ virtual std::chrono::milliseconds requestTimeout() const PURE; + /** + * @return request header timeout for incoming connection manager connections. Zero indicates a + * disabled request header timeout. + */ + virtual std::chrono::milliseconds requestHeadersTimeout() const PURE; + /** * @return delayed close timeout for downstream HTTP connections. Zero indicates a disabled * timeout. See http_connection_manager.proto for a detailed description of this timeout. diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index ae40a06b0daa..4589054b1c11 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -227,6 +227,10 @@ void ConnectionManagerImpl::doDeferredStreamDestroy(ActiveStream& stream) { stream.stream_idle_timer_ = nullptr; } stream.filter_manager_.disarmRequestTimeout(); + if (stream.request_header_timer_ != nullptr) { + stream.request_header_timer_->disableTimer(); + stream.request_header_timer_ = nullptr; + } stream.completeRequest(); stream.filter_manager_.onStreamComplete(); @@ -639,10 +643,19 @@ ConnectionManagerImpl::ActiveStream::ActiveStream(ConnectionManagerImpl& connect } if (connection_manager_.config_.requestTimeout().count()) { - std::chrono::milliseconds request_timeout_ms_ = connection_manager_.config_.requestTimeout(); + std::chrono::milliseconds request_timeout = connection_manager_.config_.requestTimeout(); request_timer_ = connection_manager.read_callbacks_->connection().dispatcher().createTimer( [this]() -> void { onRequestTimeout(); }); - request_timer_->enableTimer(request_timeout_ms_, this); + request_timer_->enableTimer(request_timeout, this); + } + + if (connection_manager_.config_.requestHeadersTimeout().count()) { + std::chrono::milliseconds request_headers_timeout = + connection_manager_.config_.requestHeadersTimeout(); + request_header_timer_ = + connection_manager.read_callbacks_->connection().dispatcher().createTimer( + [this]() -> void { onRequestHeaderTimeout(); }); + request_header_timer_->enableTimer(request_headers_timeout, this); } const auto max_stream_duration = connection_manager_.config_.maxStreamDuration(); @@ -736,6 +749,14 @@ void ConnectionManagerImpl::ActiveStream::onRequestTimeout() { StreamInfo::ResponseCodeDetails::get().RequestOverallTimeout); } +void ConnectionManagerImpl::ActiveStream::onRequestHeaderTimeout() { + connection_manager_.stats_.named_.downstream_rq_header_timeout_.inc(); + sendLocalReply(request_headers_ != nullptr && + Grpc::Common::isGrpcRequestHeaders(*request_headers_), + Http::Code::RequestTimeout, "request header timeout", nullptr, absl::nullopt, + StreamInfo::ResponseCodeDetails::get().RequestHeaderTimeout); +} + void ConnectionManagerImpl::ActiveStream::onStreamMaxDurationReached() { ENVOY_STREAM_LOG(debug, "Stream max duration time reached", *this); connection_manager_.stats_.named_.downstream_rq_max_duration_reached_.inc(); @@ -819,6 +840,10 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapPtr&& he connection_manager_.read_callbacks_->connection().dispatcher()); request_headers_ = std::move(headers); filter_manager_.requestHeadersInitialized(); + if (request_header_timer_ != nullptr) { + request_header_timer_->disableTimer(); + request_header_timer_.reset(); + } Upstream::HostDescriptionConstSharedPtr upstream_host = connection_manager_.read_callbacks_->upstreamHost(); diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 09c66089b7fe..ebc4e59a3563 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -314,6 +314,8 @@ class ConnectionManagerImpl : Logger::Loggable, void onIdleTimeout(); // Per-stream request timeout callback. void onRequestTimeout(); + // Per-stream request header timeout callback. + void onRequestHeaderTimeout(); // Per-stream alive duration reached. void onStreamMaxDurationReached(); bool hasCachedRoute() { return cached_route_.has_value() && cached_route_.value(); } @@ -354,11 +356,18 @@ class ConnectionManagerImpl : Logger::Loggable, Tracing::SpanPtr active_span_; ResponseEncoder* response_encoder_{}; Stats::TimespanPtr request_response_timespan_; - // Per-stream idle timeout. + // Per-stream idle timeout. This timer gets reset whenever activity occurs on the stream, and, + // when triggered, will close the stream. Event::TimerPtr stream_idle_timer_; - // Per-stream request timeout. + // Per-stream request timeout. This timer is enabled when the stream is created and disabled + // when the stream ends. If triggered, it will close the stream. Event::TimerPtr request_timer_; - // Per-stream alive duration. + // Per-stream request header timeout. This timer is enabled when the stream is created and + // disabled when the downstream finishes sending headers. If triggered, it will close the + // stream. + Event::TimerPtr request_header_timer_; + // Per-stream alive duration. This timer is enabled once when the stream is created and, if + // triggered, will close the stream. Event::TimerPtr max_stream_duration_timer_; std::chrono::milliseconds idle_timeout_ms_{}; State state_; diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 93fff20c3ca9..2222f052e590 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -223,6 +223,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( stream_idle_timeout_( PROTOBUF_GET_MS_OR_DEFAULT(config, stream_idle_timeout, StreamIdleTimeoutMs)), request_timeout_(PROTOBUF_GET_MS_OR_DEFAULT(config, request_timeout, RequestTimeoutMs)), + request_headers_timeout_( + PROTOBUF_GET_MS_OR_DEFAULT(config, request_headers_timeout, RequestHeaderTimeoutMs)), drain_timeout_(PROTOBUF_GET_MS_OR_DEFAULT(config, drain_timeout, 5000)), generate_request_id_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, generate_request_id, true)), preserve_external_request_id_(config.preserve_external_request_id()), diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 47cc707bdb89..180e67cec294 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -125,6 +125,9 @@ class HttpConnectionManagerConfig : Logger::Loggable, } std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } + std::chrono::milliseconds requestHeadersTimeout() const override { + return request_headers_timeout_; + } absl::optional maxStreamDuration() const override { return max_stream_duration_; } @@ -233,6 +236,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, absl::optional max_stream_duration_; std::chrono::milliseconds stream_idle_timeout_; std::chrono::milliseconds request_timeout_; + std::chrono::milliseconds request_headers_timeout_; Router::RouteConfigProviderSharedPtr route_config_provider_; Config::ConfigProviderPtr scoped_routes_config_provider_; std::chrono::milliseconds drain_timeout_; @@ -255,6 +259,8 @@ class HttpConnectionManagerConfig : Logger::Loggable, static const uint64_t StreamIdleTimeoutMs = 5 * 60 * 1000; // request timeout is disabled by default static const uint64_t RequestTimeoutMs = 0; + // request header timeout is disabled by default + static const uint64_t RequestHeaderTimeoutMs = 0; }; /** diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index e16f827da776..feddd8855c4b 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -134,6 +134,7 @@ class AdminImpl : public Admin, uint32_t maxRequestHeadersCount() const override { return max_request_headers_count_; } std::chrono::milliseconds streamIdleTimeout() const override { return {}; } std::chrono::milliseconds requestTimeout() const override { return {}; } + std::chrono::milliseconds requestHeadersTimeout() const override { return {}; } std::chrono::milliseconds delayedCloseTimeout() const override { return {}; } absl::optional maxStreamDuration() const override { return max_stream_duration_; diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index 2e97cfbd42f1..fdf1bbe7ef2a 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -154,6 +154,9 @@ class FuzzConfig : public ConnectionManagerConfig { } std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } + std::chrono::milliseconds requestHeadersTimeout() const override { + return request_headers_timeout_; + } std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } Router::RouteConfigProvider* routeConfigProvider() override { if (use_srds_) { @@ -232,6 +235,7 @@ class FuzzConfig : public ConnectionManagerConfig { absl::optional max_stream_duration_; std::chrono::milliseconds stream_idle_timeout_{}; std::chrono::milliseconds request_timeout_{}; + std::chrono::milliseconds request_headers_timeout_{}; std::chrono::milliseconds delayed_close_timeout_{}; bool use_remote_address_{true}; Http::ForwardClientCertType forward_client_cert_; diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index a022f18c2cd8..95cb95f2acf7 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -11,6 +11,7 @@ using testing::HasSubstr; using testing::InSequence; using testing::Invoke; using testing::InvokeWithoutArgs; +using testing::Mock; using testing::Return; using testing::ReturnRef; @@ -2835,6 +2836,68 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnConnectionTermin filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } +TEST_F(HttpConnectionManagerImplTest, RequestHeaderTimeoutDisarmedAfterHeaders) { + request_headers_timeout_ = std::chrono::milliseconds(10); + setup(false, ""); + + Event::MockTimer* request_header_timer; + EXPECT_CALL(*codec_, dispatch(_)) + .WillOnce(Invoke([&](Buffer::Instance&) -> Http::Status { + request_header_timer = setUpTimer(); + EXPECT_CALL(*request_header_timer, enableTimer(request_headers_timeout_, _)); + + decoder_ = &conn_manager_->newStream(response_encoder_); + return Http::okStatus(); + })) + .WillOnce(Return(Http::okStatus())) + .WillOnce([&](Buffer::Instance&) { + RequestHeaderMapPtr headers{new TestRequestHeaderMapImpl{ + {":authority", "localhost:8080"}, {":path", "/"}, {":method", "GET"}}}; + + EXPECT_CALL(*request_header_timer, disableTimer).Times(1); + decoder_->decodeHeaders(std::move(headers), false); + return Http::okStatus(); + }); + + Buffer::OwnedImpl first_line("GET /HTTP/1.1\r\n"); + Buffer::OwnedImpl second_line("Host: localhost:8080\r\n"); + Buffer::OwnedImpl empty_line("\r\n"); + conn_manager_->onData(first_line, false); + EXPECT_TRUE(request_header_timer->enabled_); + conn_manager_->onData(second_line, false); + EXPECT_TRUE(request_header_timer->enabled_); + conn_manager_->onData(empty_line, false); + Mock::VerifyAndClearExpectations(codec_); + Mock::VerifyAndClearExpectations(request_header_timer); + + expectOnDestroy(); + filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); +} + +TEST_F(HttpConnectionManagerImplTest, RequestHeaderTimeoutCallbackDisarmsAndReturns408) { + request_headers_timeout_ = std::chrono::milliseconds(10); + setup(false, ""); + + Event::MockTimer* request_header_timer; + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> Http::Status { + request_header_timer = setUpTimer(); + EXPECT_CALL(*request_header_timer, enableTimer(request_headers_timeout_, _)).Times(1); + + conn_manager_->newStream(response_encoder_); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, setTrackedObject(_)).Times(2); + return Http::okStatus(); + })); + + Buffer::OwnedImpl fake_input("GET /resource HTTP/1.1\r\n\r\n"); + conn_manager_->onData(fake_input, false); // kick off request + + // The client took too long to send headers. + EXPECT_CALL(*request_header_timer, disableTimer).Times(1); + request_header_timer->invokeCallback(); + + EXPECT_EQ(1U, stats_.named_.downstream_rq_header_timeout_.value()); +} + TEST_F(HttpConnectionManagerImplTest, MaxStreamDurationDisabledIfSetToZero) { max_stream_duration_ = std::chrono::milliseconds(0); setup(false, ""); diff --git a/test/common/http/conn_manager_impl_test_base.h b/test/common/http/conn_manager_impl_test_base.h index 067ca0a1f369..35cd115e38ee 100644 --- a/test/common/http/conn_manager_impl_test_base.h +++ b/test/common/http/conn_manager_impl_test_base.h @@ -83,6 +83,9 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan } std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } + std::chrono::milliseconds requestHeadersTimeout() const override { + return request_headers_timeout_; + } std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } absl::optional maxStreamDuration() const override { return max_stream_duration_; @@ -170,6 +173,7 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan absl::optional max_connection_duration_; std::chrono::milliseconds stream_idle_timeout_{}; std::chrono::milliseconds request_timeout_{}; + std::chrono::milliseconds request_headers_timeout_{}; std::chrono::milliseconds delayed_close_timeout_{}; absl::optional max_stream_duration_{}; NiceMock random_; diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index 4b4348710844..10f755873a2b 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -109,6 +109,7 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD(absl::optional, maxStreamDuration, (), (const)); MOCK_METHOD(std::chrono::milliseconds, streamIdleTimeout, (), (const)); MOCK_METHOD(std::chrono::milliseconds, requestTimeout, (), (const)); + MOCK_METHOD(std::chrono::milliseconds, requestHeadersTimeout, (), (const)); MOCK_METHOD(std::chrono::milliseconds, delayedCloseTimeout, (), (const)); MOCK_METHOD(Router::RouteConfigProvider*, routeConfigProvider, ()); MOCK_METHOD(Config::ConfigProvider*, scopedRouteConfigProvider, ()); diff --git a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc index bdfb6b5504ea..14a9c49370f6 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc @@ -192,12 +192,12 @@ class RawWriteSslIntegrationTest : public SslIntegrationTest { initialize(); // write_request_cb will write each of the items in request_chunks as a separate SSL_write. - auto write_request_cb = [&request_chunks](Network::ClientConnection& client) { + auto write_request_cb = [&request_chunks](Buffer::Instance& buffer) { if (!request_chunks.empty()) { - Buffer::OwnedImpl buffer(request_chunks.front()); - client.write(buffer, false); + buffer.add(request_chunks.front()); request_chunks.pop_front(); } + return false; }; auto client_transport_socket_factory_ptr = diff --git a/test/integration/http_timeout_integration_test.cc b/test/integration/http_timeout_integration_test.cc index 77ea916b467a..c2df233dd2ca 100644 --- a/test/integration/http_timeout_integration_test.cc +++ b/test/integration/http_timeout_integration_test.cc @@ -4,6 +4,8 @@ namespace Envoy { +using testing::HasSubstr; + INSTANTIATE_TEST_SUITE_P(IpVersions, HttpTimeoutIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); @@ -499,4 +501,46 @@ void HttpTimeoutIntegrationTest::testRouterRequestAndResponseWithHedgedPerTryTim EXPECT_EQ("200", response->headers().getStatusValue()); } +// Starts a request with a header timeout specified, sleeps for longer than the +// timeout, and ensures that a timeout is received. +TEST_P(HttpTimeoutIntegrationTest, RequestHeaderTimeout) { + if (downstreamProtocol() != Http::CodecClient::Type::HTTP1) { + // This test requires that the downstream be using HTTP1. + return; + } + + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + auto* request_headers_timeout = hcm.mutable_request_headers_timeout(); + request_headers_timeout->set_seconds(1); + request_headers_timeout->set_nanos(0); + }); + initialize(); + + const std::string input_request = ("GET / HTTP/1.1\r\n" + // Omit trailing \r\n that would indicate the end of headers. + "Host: localhost\r\n"); + std::string response; + + auto connection_driver = createConnectionDriver( + lookupPort("http"), input_request, + [&response](Network::ClientConnection&, const Buffer::Instance& data) -> void { + response.append(data.toString()); + }); + + while (!connection_driver->allBytesSent()) { + connection_driver->run(Event::Dispatcher::RunType::NonBlock); + } + test_server_->waitForGaugeGe("http.config_test.downstream_rq_active", 1); + ASSERT_FALSE(connection_driver->closed()); + + timeSystem().advanceTimeWait(std::chrono::milliseconds(1001)); + connection_driver->run(); + + // The upstream should send a 40x response and send a local reply. + EXPECT_TRUE(connection_driver->closed()); + EXPECT_THAT(response, AllOf(HasSubstr("408"), HasSubstr("header"))); +} + } // namespace Envoy diff --git a/test/integration/utility.cc b/test/integration/utility.cc index bbcc7a3b33fd..f6d369e6ce6a 100644 --- a/test/integration/utility.cc +++ b/test/integration/utility.cc @@ -33,11 +33,12 @@ namespace { RawConnectionDriver::DoWriteCallback writeBufferCallback(Buffer::Instance& data) { auto shared_data = std::make_shared(); shared_data->move(data); - return [shared_data](Network::ClientConnection& client) { + return [shared_data](Buffer::Instance& dest) { if (shared_data->length() > 0) { - client.write(*shared_data, false); + dest.add(*shared_data); shared_data->drain(shared_data->length()); } + return false; }; } @@ -144,11 +145,15 @@ RawConnectionDriver::RawConnectionDriver(uint32_t port, DoWriteCallback write_re Network::Address::IpVersion version, Event::Dispatcher& dispatcher, Network::TransportSocketPtr transport_socket) - : dispatcher_(dispatcher) { + : dispatcher_(dispatcher), remaining_bytes_to_send_(0) { api_ = Api::createApiForTest(stats_store_); Event::GlobalTimeSystem time_system; - callbacks_ = std::make_unique( - [this, write_request_callback]() { write_request_callback(*client_); }); + callbacks_ = std::make_unique([this, write_request_callback]() { + Buffer::OwnedImpl buffer; + const bool close_after = write_request_callback(buffer); + remaining_bytes_to_send_ += buffer.length(); + client_->write(buffer, close_after); + }); if (transport_socket == nullptr) { transport_socket = Network::Test::createRawBufferSocket(); @@ -165,6 +170,7 @@ RawConnectionDriver::RawConnectionDriver(uint32_t port, DoWriteCallback write_re client_->addConnectionCallbacks(*callbacks_); client_->addReadFilter( Network::ReadFilterSharedPtr{new ForwardingFilter(*this, response_data_callback)}); + client_->addBytesSentCallback([&](uint64_t bytes) { remaining_bytes_to_send_ -= bytes; }); client_->connect(); } @@ -183,6 +189,8 @@ void RawConnectionDriver::run(Event::Dispatcher::RunType run_type) { dispatcher_ void RawConnectionDriver::close() { client_->close(Network::ConnectionCloseType::FlushWrite); } +bool RawConnectionDriver::allBytesSent() const { return remaining_bytes_to_send_ == 0; } + WaitForPayloadReader::WaitForPayloadReader(Event::Dispatcher& dispatcher) : dispatcher_(dispatcher) {} diff --git a/test/integration/utility.h b/test/integration/utility.h index 497fe872472b..cad8b153363d 100644 --- a/test/integration/utility.h +++ b/test/integration/utility.h @@ -63,7 +63,10 @@ using BufferingStreamDecoderPtr = std::unique_ptr; */ class RawConnectionDriver { public: - using DoWriteCallback = std::function; + // Callback that is executed to write data to connection. The provided buffer + // should be populated with the data to write. If the callback returns true, + // the connection will be closed after writing. + using DoWriteCallback = std::function; using ReadCallback = std::function; RawConnectionDriver(uint32_t port, DoWriteCallback write_request_callback, @@ -86,6 +89,7 @@ class RawConnectionDriver { void waitForConnection(); bool closed() { return callbacks_->closed(); } + bool allBytesSent() const; private: struct ForwardingFilter : public Network::ReadFilterBaseImpl { @@ -137,6 +141,7 @@ class RawConnectionDriver { Event::Dispatcher& dispatcher_; std::unique_ptr callbacks_; Network::ClientConnectionPtr client_; + uint64_t remaining_bytes_to_send_; }; /** From 46c227a19f156ef616f94715dd8c547fab3a7229 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 9 Nov 2020 23:14:24 +0000 Subject: [PATCH 051/117] ci/docs: Use commit sha for all pull request artifact uploads (#13917) Follow up from #13826 Risk Level: Low Testing: Docs Changes: Release Notes: Signed-off-by: Ryan Northey --- ci/upload_gcs_artifact.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ci/upload_gcs_artifact.sh b/ci/upload_gcs_artifact.sh index 44e6d685ee15..aea9dbc3f24f 100755 --- a/ci/upload_gcs_artifact.sh +++ b/ci/upload_gcs_artifact.sh @@ -18,8 +18,8 @@ if [ ! -d "${SOURCE_DIRECTORY}" ]; then exit 1 fi -if [[ "$TARGET_SUFFIX" == "docs" ]]; then - # docs upload to the last commit sha (first 7 chars) in the developers branch +if [[ "$BUILD_REASON" == "PullRequest" ]]; then + # non-master upload to the last commit sha (first 7 chars) in the developers branch UPLOAD_PATH="$(git log --pretty=%P -n 1 | cut -d' ' -f2 | head -c7)" else UPLOAD_PATH="${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER:-${BUILD_SOURCEBRANCHNAME}}" @@ -30,16 +30,16 @@ GCS_LOCATION="${GCS_ARTIFACT_BUCKET}/${UPLOAD_PATH}/${TARGET_SUFFIX}" echo "Uploading to gs://${GCS_LOCATION} ..." gsutil -mq rsync -dr "${SOURCE_DIRECTORY}" "gs://${GCS_LOCATION}" -# For docs uploads, add a redirect `PR_NUMBER` -> `COMMIT_SHA` -if [[ "$TARGET_SUFFIX" == "docs" ]]; then +# For PR uploads, add a redirect `PR_NUMBER` -> `COMMIT_SHA` +if [[ "$BUILD_REASON" == "PullRequest" ]]; then REDIRECT_PATH="${SYSTEM_PULLREQUEST_PULLREQUESTNUMBER:-${BUILD_SOURCEBRANCHNAME}}" - TMP_REDIRECT="/tmp/docredirect/${REDIRECT_PATH}/docs" + TMP_REDIRECT="/tmp/redirect/${REDIRECT_PATH}/${TARGET_SUFFIX}" mkdir -p "$TMP_REDIRECT" echo "" \ > "${TMP_REDIRECT}/index.html" - GCS_REDIRECT="${GCS_ARTIFACT_BUCKET}/${REDIRECT_PATH}" + GCS_REDIRECT="${GCS_ARTIFACT_BUCKET}/${REDIRECT_PATH}/${TARGET_SUFFIX}" echo "Uploading redirect to gs://${GCS_REDIRECT} ..." - gsutil -h "Cache-Control:no-cache,max-age=0" -mq rsync -dr "/tmp/docredirect/${REDIRECT_PATH}" "gs://${GCS_REDIRECT}" + gsutil -h "Cache-Control:no-cache,max-age=0" -mq rsync -dr "${TMP_REDIRECT}" "gs://${GCS_REDIRECT}" fi echo "Artifacts uploaded to: https://storage.googleapis.com/${GCS_LOCATION}/index.html" From d9af0e985a2fef2c921641b12f4786a658ad0923 Mon Sep 17 00:00:00 2001 From: fpliu233 <62083774+fpliu233@users.noreply.github.com> Date: Mon, 9 Nov 2020 15:22:46 -0800 Subject: [PATCH 052/117] Adding AsyncClientSingleton to store all the cache and use GetState to initialize gRPC (#13842) Signed-off-by: Fangpeng Liu --- source/common/grpc/BUILD | 15 +++ .../common/grpc/google_async_client_cache.cc | 47 ++++++++ .../common/grpc/google_async_client_cache.h | 66 +++++++++++ .../common/grpc/google_async_client_impl.cc | 6 + .../common/ext_authz/ext_authz_grpc_impl.cc | 17 --- .../common/ext_authz/ext_authz_grpc_impl.h | 33 ------ .../extensions/filters/http/ext_authz/BUILD | 1 + .../filters/http/ext_authz/config.cc | 24 ++-- .../filters/http/ext_authz/config.h | 3 - test/common/grpc/BUILD | 10 ++ .../grpc/google_async_client_cache_test.cc | 104 ++++++++++++++++++ .../extensions/filters/common/ext_authz/BUILD | 1 - .../ext_authz/ext_authz_grpc_impl_test.cc | 60 ---------- .../filters/http/ext_authz/config_test.cc | 6 +- .../ext_authz/ext_authz_integration_test.cc | 24 +++- 15 files changed, 281 insertions(+), 136 deletions(-) create mode 100644 source/common/grpc/google_async_client_cache.cc create mode 100644 source/common/grpc/google_async_client_cache.h create mode 100644 test/common/grpc/google_async_client_cache_test.cc diff --git a/source/common/grpc/BUILD b/source/common/grpc/BUILD index 3daea1ce4395..3837cadc79d0 100644 --- a/source/common/grpc/BUILD +++ b/source/common/grpc/BUILD @@ -206,3 +206,18 @@ envoy_cc_library( "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) + +envoy_cc_library( + name = "google_async_client_cache", + srcs = ["google_async_client_cache.cc"], + hdrs = ["google_async_client_cache.h"], + external_deps = [ + "grpc", + ], + deps = [ + "//include/envoy/grpc:async_client_manager_interface", + "//include/envoy/server:factory_context_interface", + "//include/envoy/singleton:instance_interface", + "//include/envoy/thread_local:thread_local_interface", + ], +) diff --git a/source/common/grpc/google_async_client_cache.cc b/source/common/grpc/google_async_client_cache.cc new file mode 100644 index 000000000000..5150f8182b20 --- /dev/null +++ b/source/common/grpc/google_async_client_cache.cc @@ -0,0 +1,47 @@ +#include "common/grpc/google_async_client_cache.h" + +namespace Envoy { +namespace Grpc { + +void AsyncClientCache::init(const ::envoy::config::core::v3::GrpcService& grpc_proto_config) { + // The cache stores Google gRPC client, so channel is not created for each + // request. + ASSERT(grpc_proto_config.has_google_grpc()); + auto shared_this = shared_from_this(); + tls_slot_.set([shared_this, grpc_proto_config](Event::Dispatcher&) { + return std::make_shared(shared_this->async_client_manager_, + shared_this->scope_, grpc_proto_config); + }); +} + +const Grpc::RawAsyncClientSharedPtr AsyncClientCache::getAsyncClient() { + return tls_slot_->async_client_; +} + +AsyncClientCacheSharedPtr AsyncClientCacheSingleton::getOrCreateAsyncClientCache( + Grpc::AsyncClientManager& async_client_manager, Stats::Scope& scope, + ThreadLocal::SlotAllocator& tls, + const ::envoy::config::core::v3::GrpcService& grpc_proto_config) { + const std::size_t cache_key = MessageUtil::hash(grpc_proto_config.google_grpc()); + const auto it = async_clients_.find(cache_key); + if (it != async_clients_.end()) { + return it->second; + } + AsyncClientCacheSharedPtr async_client = + std::make_shared(async_client_manager, scope, tls); + async_client->init(grpc_proto_config); + async_clients_.emplace(cache_key, async_client); + return async_client; +} + +SINGLETON_MANAGER_REGISTRATION(google_grpc_async_client_cache); + +AsyncClientCacheSingletonSharedPtr +getAsyncClientCacheSingleton(Server::Configuration::ServerFactoryContext& context) { + return context.singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(google_grpc_async_client_cache), + [] { return std::make_shared(); }); +} + +} // namespace Grpc +} // namespace Envoy diff --git a/source/common/grpc/google_async_client_cache.h b/source/common/grpc/google_async_client_cache.h new file mode 100644 index 000000000000..3d13d8ff8424 --- /dev/null +++ b/source/common/grpc/google_async_client_cache.h @@ -0,0 +1,66 @@ +#include + +#include "envoy/grpc/async_client_manager.h" +#include "envoy/server/factory_context.h" +#include "envoy/singleton/instance.h" +#include "envoy/thread_local/thread_local.h" + +namespace Envoy { +namespace Grpc { + +// The RawAsyncClient client cache for Google grpc so channel is not created +// for each request. +// TODO(fpliu233): Remove when the cleaner and generic solution for gRPC is +// live. Tracking in #2598 and #13417. +class AsyncClientCache : public std::enable_shared_from_this { +public: + AsyncClientCache(AsyncClientManager& async_client_manager, Stats::Scope& scope, + ThreadLocal::SlotAllocator& tls) + : async_client_manager_(async_client_manager), scope_(scope), tls_slot_(tls) {} + void init(const ::envoy::config::core::v3::GrpcService& grpc_proto_config); + const RawAsyncClientSharedPtr getAsyncClient(); + +private: + /** + * Per-thread cache. + */ + struct ThreadLocalCache : public ThreadLocal::ThreadLocalObject { + ThreadLocalCache(AsyncClientManager& async_client_manager, Stats::Scope& scope, + const ::envoy::config::core::v3::GrpcService& grpc_proto_config) { + const AsyncClientFactoryPtr factory = + async_client_manager.factoryForGrpcService(grpc_proto_config, scope, true); + async_client_ = factory->create(); + } + RawAsyncClientSharedPtr async_client_; + }; + + AsyncClientManager& async_client_manager_; + Stats::Scope& scope_; + ThreadLocal::TypedSlot tls_slot_; +}; + +using AsyncClientCacheSharedPtr = std::shared_ptr; + +class AsyncClientCacheSingleton : public Singleton::Instance { +public: + AsyncClientCacheSingleton() = default; + AsyncClientCacheSharedPtr + getOrCreateAsyncClientCache(Grpc::AsyncClientManager& async_client_manager, Stats::Scope& scope, + ThreadLocal::SlotAllocator& tls, + const ::envoy::config::core::v3::GrpcService& grpc_proto_config); + +private: + // The tls slots with client cache stored with key as hash of + // envoy::config::core::v3::GrpcService::GoogleGrpc config. + // TODO(fpliu233): When new configuration is pushed, the old client cache will not get cleaned up. + // Remove when the cleaner and generic solution for gRPC is live. Tracking in #2598 and #13417. + absl::flat_hash_map async_clients_; +}; + +using AsyncClientCacheSingletonSharedPtr = std::shared_ptr; + +AsyncClientCacheSingletonSharedPtr +getAsyncClientCacheSingleton(Server::Configuration::ServerFactoryContext& context); + +} // namespace Grpc +} // namespace Envoy diff --git a/source/common/grpc/google_async_client_impl.cc b/source/common/grpc/google_async_client_impl.cc index 9239fe4d74c5..8f94df661e21 100644 --- a/source/common/grpc/google_async_client_impl.cc +++ b/source/common/grpc/google_async_client_impl.cc @@ -90,6 +90,12 @@ GoogleAsyncClientImpl::GoogleAsyncClientImpl(Event::Dispatcher& dispatcher, // have comparable overhead to what we are doing in Grpc::AsyncClientImpl, i.e. no expensive // new connection implied. std::shared_ptr channel = GoogleGrpcUtils::createChannel(config, api); + // Get state with try_to_connect = true to try connection at channel creation. + // This is for initializing gRPC channel at channel creation. This GetState(true) is used to poke + // the gRPC lb at channel creation, it doesn't have any effect no matter it succeeds or fails. But + // it helps on initialization. Otherwise, the channel establishment still happens at the first + // request, no matter when we create the channel. + channel->GetState(true); stub_ = stub_factory.createStub(channel); scope_->counterFromStatName(stat_names.google_grpc_client_creation_).inc(); // Initialize client stats. diff --git a/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.cc b/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.cc index cfb417d6c927..5340af7d3eb3 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.cc +++ b/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.cc @@ -142,23 +142,6 @@ void GrpcClientImpl::toAuthzResponseHeader( } } -const Grpc::RawAsyncClientSharedPtr AsyncClientCache::getOrCreateAsyncClient( - const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& proto_config) { - // The cache stores Google gRPC client, so channel is not created for each request. - ASSERT(proto_config.has_grpc_service() && proto_config.grpc_service().has_google_grpc()); - auto& cache = tls_slot_->getTyped(); - const std::size_t cache_key = MessageUtil::hash(proto_config.grpc_service().google_grpc()); - const auto it = cache.async_clients_.find(cache_key); - if (it != cache.async_clients_.end()) { - return it->second; - } - const Grpc::AsyncClientFactoryPtr factory = - async_client_manager_.factoryForGrpcService(proto_config.grpc_service(), scope_, true); - const Grpc::RawAsyncClientSharedPtr async_client = factory->create(); - cache.async_clients_.emplace(cache_key, async_client); - return async_client; -} - } // namespace ExtAuthz } // namespace Common } // namespace Filters diff --git a/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.h b/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.h index 792f7bb748cc..9db0831a1274 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.h +++ b/source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.h @@ -81,39 +81,6 @@ class GrpcClientImpl : public Client, using GrpcClientImplPtr = std::unique_ptr; -// The client cache for RawAsyncClient for Google grpc so channel is not created for each request. -// TODO(fpliu233): The cache will cause resource leak that a new channel is created every time a new -// config is pushed. Improve gRPC channel cache with better solution. -class AsyncClientCache : public Singleton::Instance { -public: - AsyncClientCache(Grpc::AsyncClientManager& async_client_manager, Stats::Scope& scope, - ThreadLocal::SlotAllocator& tls) - : async_client_manager_(async_client_manager), scope_(scope), tls_slot_(tls.allocateSlot()) { - tls_slot_->set([](Event::Dispatcher&) { return std::make_shared(); }); - } - - const Grpc::RawAsyncClientSharedPtr getOrCreateAsyncClient( - const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& proto_config); - -private: - /** - * Per-thread cache. - */ - struct ThreadLocalCache : public ThreadLocal::ThreadLocalObject { - ThreadLocalCache() = default; - // The client cache stored with key as hash of - // envoy::config::core::v3::GrpcService::GoogleGrpc config. - // TODO(fpliu233): Remove when the cleaner and generic solution for gRPC is live. - absl::flat_hash_map async_clients_; - }; - - Grpc::AsyncClientManager& async_client_manager_; - Stats::Scope& scope_; - ThreadLocal::SlotPtr tls_slot_; -}; - -using AsyncClientCacheSharedPtr = std::shared_ptr; - } // namespace ExtAuthz } // namespace Common } // namespace Filters diff --git a/source/extensions/filters/http/ext_authz/BUILD b/source/extensions/filters/http/ext_authz/BUILD index 0d789c30c048..c7583679783b 100644 --- a/source/extensions/filters/http/ext_authz/BUILD +++ b/source/extensions/filters/http/ext_authz/BUILD @@ -45,6 +45,7 @@ envoy_cc_extension( ":ext_authz", "//include/envoy/registry", "//include/envoy/stats:stats_macros", + "//source/common/grpc:google_async_client_cache", "//source/common/protobuf:utility_lib", "//source/extensions/filters/common/ext_authz:ext_authz_http_lib", "//source/extensions/filters/http:well_known_names", diff --git a/source/extensions/filters/http/ext_authz/config.cc b/source/extensions/filters/http/ext_authz/config.cc index 788740278af1..01c77e4705d7 100644 --- a/source/extensions/filters/http/ext_authz/config.cc +++ b/source/extensions/filters/http/ext_authz/config.cc @@ -8,6 +8,7 @@ #include "envoy/extensions/filters/http/ext_authz/v3/ext_authz.pb.validate.h" #include "envoy/registry/registry.h" +#include "common/grpc/google_async_client_cache.h" #include "common/protobuf/utility.h" #include "common/runtime/runtime_features.h" @@ -52,13 +53,18 @@ Http::FilterFactoryCb ExtAuthzFilterConfig::createFilterFactoryFromProtoTyped( const uint32_t timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config.grpc_service(), timeout, DefaultTimeout); - auto async_client_cache = getAsyncClientCacheSingleton(context); + Grpc::AsyncClientCacheSingletonSharedPtr async_client_cache_singleton = + Grpc::getAsyncClientCacheSingleton(context.getServerFactoryContext()); + Grpc::AsyncClientCacheSharedPtr async_client_cache = + async_client_cache_singleton->getOrCreateAsyncClientCache( + context.clusterManager().grpcAsyncClientManager(), context.scope(), + context.threadLocal(), proto_config.grpc_service()); callback = [async_client_cache, filter_config, timeout_ms, proto_config, transport_api_version = proto_config.transport_api_version()]( Http::FilterChainFactoryCallbacks& callbacks) { auto client = std::make_unique( - async_client_cache->getOrCreateAsyncClient(proto_config), - std::chrono::milliseconds(timeout_ms), transport_api_version); + async_client_cache->getAsyncClient(), std::chrono::milliseconds(timeout_ms), + transport_api_version); callbacks.addStreamDecoderFilter(Http::StreamDecoderFilterSharedPtr{ std::make_shared(filter_config, std::move(client))}); }; @@ -103,18 +109,6 @@ ExtAuthzFilterConfig::createRouteSpecificFilterConfigTyped( REGISTER_FACTORY(ExtAuthzFilterConfig, Server::Configuration::NamedHttpFilterConfigFactory){"envoy.ext_authz"}; -SINGLETON_MANAGER_REGISTRATION(google_grpc_async_client_cache); - -Filters::Common::ExtAuthz::AsyncClientCacheSharedPtr -getAsyncClientCacheSingleton(Server::Configuration::FactoryContext& context) { - return context.singletonManager().getTyped( - SINGLETON_MANAGER_REGISTERED_NAME(google_grpc_async_client_cache), [&context] { - return std::make_shared( - context.clusterManager().grpcAsyncClientManager(), context.scope(), - context.threadLocal()); - }); -} - } // namespace ExtAuthz } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/ext_authz/config.h b/source/extensions/filters/http/ext_authz/config.h index 5ca66d423805..600c572f0507 100644 --- a/source/extensions/filters/http/ext_authz/config.h +++ b/source/extensions/filters/http/ext_authz/config.h @@ -34,9 +34,6 @@ class ExtAuthzFilterConfig ProtobufMessage::ValidationVisitor& validator) override; }; -Filters::Common::ExtAuthz::AsyncClientCacheSharedPtr -getAsyncClientCacheSingleton(Server::Configuration::FactoryContext& context); - } // namespace ExtAuthz } // namespace HttpFilters } // namespace Extensions diff --git a/test/common/grpc/BUILD b/test/common/grpc/BUILD index eac8fbefa8a5..af6bf2f58106 100644 --- a/test/common/grpc/BUILD +++ b/test/common/grpc/BUILD @@ -184,3 +184,13 @@ envoy_cc_test_library( "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) + +envoy_cc_test( + name = "google_async_client_cache_test", + srcs = ["google_async_client_cache_test.cc"], + deps = [ + "//source/common/grpc:google_async_client_cache", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:global_lib", + ], +) diff --git a/test/common/grpc/google_async_client_cache_test.cc b/test/common/grpc/google_async_client_cache_test.cc new file mode 100644 index 000000000000..6d36ff2efb74 --- /dev/null +++ b/test/common/grpc/google_async_client_cache_test.cc @@ -0,0 +1,104 @@ +#include "common/grpc/google_async_client_cache.h" + +#include "test/mocks/server/factory_context.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Grpc { +namespace { + +class AsyncClientCacheTest : public testing::Test { +public: + AsyncClientCacheTest() { + client_cache_singleton_ = std::make_unique(); + } + + void expectClientCreation() { + factory_ = new Grpc::MockAsyncClientFactory; + async_client_ = new Grpc::MockAsyncClient; + EXPECT_CALL(async_client_manager_, factoryForGrpcService(_, _, true)) + .WillOnce(Invoke([this](const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) { + EXPECT_CALL(*factory_, create()).WillOnce(Invoke([this] { + return Grpc::RawAsyncClientPtr{async_client_}; + })); + return Grpc::AsyncClientFactoryPtr{factory_}; + })); + } + + void expectCacheAndClientEqual(const AsyncClientCacheSharedPtr& expected_client_cache, + const RawAsyncClientSharedPtr& expected_client, + const ::envoy::config::core::v3::GrpcService config, + const std::string& error_message) { + AsyncClientCacheSharedPtr actual_client_cache = + client_cache_singleton_->getOrCreateAsyncClientCache(async_client_manager_, scope_, tls_, + config); + EXPECT_EQ(expected_client_cache, actual_client_cache) << error_message; + EXPECT_EQ(expected_client, actual_client_cache->getAsyncClient()) << error_message; + } + + void expectCacheAndClientNotEqual(const AsyncClientCacheSharedPtr& expected_client_cache, + const RawAsyncClientSharedPtr& expected_client, + const ::envoy::config::core::v3::GrpcService config, + const std::string& error_message) { + AsyncClientCacheSharedPtr actual_client_cache = + client_cache_singleton_->getOrCreateAsyncClientCache(async_client_manager_, scope_, tls_, + config); + EXPECT_NE(expected_client_cache, actual_client_cache) << error_message; + EXPECT_NE(expected_client, actual_client_cache->getAsyncClient()) << error_message; + } + + NiceMock tls_; + Grpc::MockAsyncClientManager async_client_manager_; + Grpc::MockAsyncClient* async_client_ = nullptr; + Grpc::MockAsyncClientFactory* factory_ = nullptr; + NiceMock scope_; + std::unique_ptr client_cache_singleton_; + std::unique_ptr client_cache_; +}; + +TEST_F(AsyncClientCacheTest, Deduplication) { + Stats::IsolatedStoreImpl scope; + testing::InSequence s; + + ::envoy::config::core::v3::GrpcService config; + config.mutable_google_grpc()->set_target_uri("dns://test01"); + config.mutable_google_grpc()->set_credentials_factory_name("test_credential01"); + + expectClientCreation(); + AsyncClientCacheSharedPtr test_client_cache_01 = + client_cache_singleton_->getOrCreateAsyncClientCache(async_client_manager_, scope_, tls_, + config); + RawAsyncClientSharedPtr test_client_01 = test_client_cache_01->getAsyncClient(); + // Fetches the existing client and they should be equal. + std::string error_message = "Fetched client should be same as the existing one."; + expectCacheAndClientEqual(test_client_cache_01, test_client_01, config, error_message); + + config.mutable_google_grpc()->set_credentials_factory_name("test_credential02"); + expectClientCreation(); + // Different credentials use different clients. + error_message = "different config should use different clients."; + expectCacheAndClientNotEqual(test_client_cache_01, test_client_01, config, error_message); + + AsyncClientCacheSharedPtr test_client_cache_02 = + client_cache_singleton_->getOrCreateAsyncClientCache(async_client_manager_, scope_, tls_, + config); + RawAsyncClientSharedPtr test_client_02 = test_client_cache_02->getAsyncClient(); + + config.mutable_google_grpc()->set_credentials_factory_name("test_credential02"); + // No creation, fetching the existing one. + error_message = "Fetched existing client should be the same."; + expectCacheAndClientEqual(test_client_cache_02, test_client_02, config, error_message); + + // Different targets use different clients. + error_message = "different config with different targets should use different clients."; + config.mutable_google_grpc()->set_target_uri("dns://test02"); + expectClientCreation(); + expectCacheAndClientNotEqual(test_client_cache_01, test_client_01, config, error_message); + expectCacheAndClientNotEqual(test_client_cache_02, test_client_02, config, error_message); +} + +} // namespace +} // namespace Grpc +} // namespace Envoy diff --git a/test/extensions/filters/common/ext_authz/BUILD b/test/extensions/filters/common/ext_authz/BUILD index ebf3ad3eac1f..7c840b57ca09 100644 --- a/test/extensions/filters/common/ext_authz/BUILD +++ b/test/extensions/filters/common/ext_authz/BUILD @@ -32,7 +32,6 @@ envoy_cc_test( deps = [ "//source/extensions/filters/common/ext_authz:ext_authz_grpc_lib", "//test/extensions/filters/common/ext_authz:ext_authz_test_common", - "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/tracing:tracing_mocks", "//test/test_common:test_runtime_lib", "@envoy_api//envoy/service/auth/v3:pkg_cc_proto", diff --git a/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc b/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc index d51a6bd5c104..6f5a4e042e3f 100644 --- a/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc +++ b/test/extensions/filters/common/ext_authz/ext_authz_grpc_impl_test.cc @@ -11,7 +11,6 @@ #include "test/extensions/filters/common/ext_authz/test_common.h" #include "test/mocks/grpc/mocks.h" #include "test/mocks/stream_info/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/test_common/test_runtime.h" @@ -355,65 +354,6 @@ TEST_P(ExtAuthzGrpcClientTest, AuthorizationOkWithDynamicMetadata) { client_->onSuccess(std::move(check_response), span_); } -class AsyncClientCacheTest : public testing::Test { -public: - AsyncClientCacheTest() { - client_cache_ = std::make_unique(async_client_manager_, scope_, tls_); - } - - void expectClientCreation() { - factory_ = new Grpc::MockAsyncClientFactory; - async_client_ = new Grpc::MockAsyncClient; - EXPECT_CALL(async_client_manager_, factoryForGrpcService(_, _, true)) - .WillOnce(Invoke([this](const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) { - EXPECT_CALL(*factory_, create()).WillOnce(Invoke([this] { - return Grpc::RawAsyncClientPtr{async_client_}; - })); - return Grpc::AsyncClientFactoryPtr{factory_}; - })); - } - - NiceMock tls_; - Grpc::MockAsyncClientManager async_client_manager_; - Grpc::MockAsyncClient* async_client_ = nullptr; - Grpc::MockAsyncClientFactory* factory_ = nullptr; - std::unique_ptr client_cache_; - NiceMock scope_; -}; - -TEST_F(AsyncClientCacheTest, Deduplication) { - Stats::IsolatedStoreImpl scope; - testing::InSequence s; - - envoy::extensions::filters::http::ext_authz::v3::ExtAuthz config; - config.mutable_grpc_service()->mutable_google_grpc()->set_target_uri("dns://test01"); - config.mutable_grpc_service()->mutable_google_grpc()->set_credentials_factory_name( - "test_credential01"); - - expectClientCreation(); - Grpc::RawAsyncClientSharedPtr test_client_01 = client_cache_->getOrCreateAsyncClient(config); - // Fetches the existing client. - EXPECT_EQ(test_client_01, client_cache_->getOrCreateAsyncClient(config)); - - config.mutable_grpc_service()->mutable_google_grpc()->set_credentials_factory_name( - "test_credential02"); - expectClientCreation(); - // Different credentials use different clients. - EXPECT_NE(test_client_01, client_cache_->getOrCreateAsyncClient(config)); - Grpc::RawAsyncClientSharedPtr test_client_02 = client_cache_->getOrCreateAsyncClient(config); - - config.mutable_grpc_service()->mutable_google_grpc()->set_credentials_factory_name( - "test_credential02"); - // No creation, fetching the existing one. - EXPECT_EQ(test_client_02, client_cache_->getOrCreateAsyncClient(config)); - - // Different targets use different clients. - config.mutable_grpc_service()->mutable_google_grpc()->set_target_uri("dns://test02"); - expectClientCreation(); - EXPECT_NE(test_client_01, client_cache_->getOrCreateAsyncClient(config)); - EXPECT_NE(test_client_02, client_cache_->getOrCreateAsyncClient(config)); -} - } // namespace ExtAuthz } // namespace Common } // namespace Filters diff --git a/test/extensions/filters/http/ext_authz/config_test.cc b/test/extensions/filters/http/ext_authz/config_test.cc index 167779f8aeed..bff444e127cb 100644 --- a/test/extensions/filters/http/ext_authz/config_test.cc +++ b/test/extensions/filters/http/ext_authz/config_test.cc @@ -37,7 +37,11 @@ void expectCorrectProtoGrpc(envoy::config::core::v3::ApiVersion api_version) { fmt::format(yaml, TestUtility::getVersionStringFromApiVersion(api_version)), *proto_config); testing::StrictMock context; - EXPECT_CALL(context, singletonManager()).Times(1); + testing::StrictMock server_context; + EXPECT_CALL(context, getServerFactoryContext()) + .Times(1) + .WillOnce(testing::ReturnRef(server_context)); + EXPECT_CALL(server_context, singletonManager()).Times(1); EXPECT_CALL(context, threadLocal()).Times(1); EXPECT_CALL(context, messageValidationVisitor()).Times(1); EXPECT_CALL(context, clusterManager()).Times(1); diff --git a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc index 0d48ac15cfb7..385aee15ff89 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc @@ -813,13 +813,18 @@ TEST_P(ExtAuthzGrpcIntegrationTest, GoogleAsyncClientCreation) { initializeConfig(); setDownstreamProtocol(Http::CodecClient::Type::HTTP2); HttpIntegrationTest::initialize(); - initiateClientConnection(4, Headers{}, Headers{}); - waitForExtAuthzRequest(expectedCheckRequest(Http::CodecClient::Type::HTTP2)); + int expected_grpc_client_creation_count = 0; if (clientType() == Grpc::ClientType::GoogleGrpc) { - // Make sure one Google grpc client is created. - EXPECT_EQ(1, test_server_->counter("grpc.ext_authz.google_grpc_client_creation")->value()); + // Make sure Google grpc client is created before the request coming in. + // Since this is not laziness creation, it should create one client per + // thread before the traffic comes. + expected_grpc_client_creation_count = + test_server_->counter("grpc.ext_authz.google_grpc_client_creation")->value(); } + + initiateClientConnection(4, Headers{}, Headers{}); + waitForExtAuthzRequest(expectedCheckRequest(Http::CodecClient::Type::HTTP2)); sendExtAuthzResponse(Headers{}, Headers{}, Headers{}, Http::TestRequestHeaderMapImpl{}, Http::TestRequestHeaderMapImpl{}); @@ -846,8 +851,9 @@ TEST_P(ExtAuthzGrpcIntegrationTest, GoogleAsyncClientCreation) { RELEASE_ASSERT(result, result.message()); if (clientType() == Grpc::ClientType::GoogleGrpc) { - // Make sure one Google grpc client is created. - EXPECT_EQ(1, test_server_->counter("grpc.ext_authz.google_grpc_client_creation")->value()); + // Make sure no more Google grpc client is created no matter how many requests coming in. + EXPECT_EQ(expected_grpc_client_creation_count, + test_server_->counter("grpc.ext_authz.google_grpc_client_creation")->value()); } sendExtAuthzResponse(Headers{}, Headers{}, Headers{}, Http::TestRequestHeaderMapImpl{}, Http::TestRequestHeaderMapImpl{}); @@ -869,6 +875,12 @@ TEST_P(ExtAuthzGrpcIntegrationTest, GoogleAsyncClientCreation) { EXPECT_EQ("200", response_->headers().getStatusValue()); EXPECT_EQ(response_size_, response_->body().size()); + if (clientType() == Grpc::ClientType::GoogleGrpc) { + // Make sure no more Google grpc client is created no matter how many requests coming in. + EXPECT_EQ(expected_grpc_client_creation_count, + test_server_->counter("grpc.ext_authz.google_grpc_client_creation")->value()); + } + cleanup(); } From 4ccf21e0e39b9820791dfc4b504e53913a7b5db8 Mon Sep 17 00:00:00 2001 From: Anatole Beuzon <8351433+anatolebeuzon@users.noreply.github.com> Date: Tue, 10 Nov 2020 01:31:48 +0100 Subject: [PATCH 053/117] fix: Envoy not being started as PID 1 (#13946) Previously, su-exec was starting Envoy as a child process of PID 1, instead of PID 1 directly. Signals like SIGTERM sent by the container runtime did not reach Envoy, therefore preventing it from shutting down gracefully. This commit ensures that Envoy always start as PID 1. Risk Level: low Testing: did not build an official Envoy image, but verified with a minimal Dockerfile on top of an Envoy image that the change works. docker exec ps show that Envoy is PID 1 and docker stop && docker logs shows that Envoy does receive SIGTERMs from the container runtime. Fixes #13944 Signed-off-by: Anatole Beuzon --- ci/docker-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/docker-entrypoint.sh b/ci/docker-entrypoint.sh index 4815acb1956a..6e584d37e3d7 100755 --- a/ci/docker-entrypoint.sh +++ b/ci/docker-entrypoint.sh @@ -24,7 +24,7 @@ if [ "$ENVOY_UID" != "0" ]; then fi # Ensure the envoy user is able to write to container logs chown envoy:envoy /dev/stdout /dev/stderr - su-exec envoy "${@}" + exec su-exec envoy "${@}" else exec "${@}" fi From 5c76f2135c9582535c7a47d5327823c8e3434668 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 10 Nov 2020 00:40:52 +0000 Subject: [PATCH 054/117] repokitteh: Fix for welcome message (#13937) Signed-off-by: Ryan Northey --- ci/repokitteh/modules/newcontributor.star | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/repokitteh/modules/newcontributor.star b/ci/repokitteh/modules/newcontributor.star index 4cf644bc200f..a192bd8613e7 100644 --- a/ci/repokitteh/modules/newcontributor.star +++ b/ci/repokitteh/modules/newcontributor.star @@ -14,7 +14,9 @@ def get_pr_author_association(issue_number): path="repos/envoyproxy/envoy/pulls/%s" % issue_number)["json"]["author_association"] def is_newcontributor(issue_number): - return get_pr_author_association(issue_number) == "FIRST_TIME_CONTRIBUTOR" + return ( + get_pr_author_association(issue_number) + in ["NONE", "FIRST_TIME_CONTRIBUTOR", "FIRST_TIMER"]) def should_message_newcontributor(action, issue_number): return ( From 0ffcb6b54c1e92dd48badd6b67695d5ae9473086 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 10 Nov 2020 01:01:05 +0000 Subject: [PATCH 055/117] docs: Add quick-start admin page (#13847) Signed-off-by: Ryan Northey --- .../quick-start/_include/envoy-demo.yaml | 7 - docs/root/start/quick-start/admin.rst | 256 ++++++++++++++++++ .../configuration-dynamic-control-plane.rst | 25 +- .../configuration-dynamic-filesystem.rst | 25 +- .../quick-start/configuration-static.rst | 25 +- docs/root/start/quick-start/index.rst | 1 + 6 files changed, 261 insertions(+), 78 deletions(-) create mode 100644 docs/root/start/quick-start/admin.rst diff --git a/docs/root/start/quick-start/_include/envoy-demo.yaml b/docs/root/start/quick-start/_include/envoy-demo.yaml index b3697360e120..99bb743a97c5 100644 --- a/docs/root/start/quick-start/_include/envoy-demo.yaml +++ b/docs/root/start/quick-start/_include/envoy-demo.yaml @@ -46,10 +46,3 @@ static_resources: typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext sni: www.envoyproxy.io - -admin: - access_log_path: /dev/null - address: - socket_address: - address: 0.0.0.0 - port_value: 9901 diff --git a/docs/root/start/quick-start/admin.rst b/docs/root/start/quick-start/admin.rst new file mode 100644 index 000000000000..1071c5e4c6b3 --- /dev/null +++ b/docs/root/start/quick-start/admin.rst @@ -0,0 +1,256 @@ +.. _start_quick_start_admin: + +Envoy admin interface +===================== + +The optional admin interface provided by Envoy allows you to view configuration and statistics, change the +behaviour of the server, and tap traffic according to specific filter rules. + +.. note:: + + This guide provides configuration information, and some basic examples of using a couple of the admin + endpoints. + + See the :ref:`admin docs ` for information on all of the available endpoints. + +.. admonition:: Requirements + + Some of the examples below make use of the `jq `_ tool to parse the output + from the admin server. + +.. _start_quick_start_admin_config: + +``admin`` +--------- + +The :ref:`admin message ` is required to enable and configure +the administration server. + +The ``address`` key specifies the listening :ref:`address ` +which in the demo configuration is ``0.0.0.0:9901``. + +You must set the :ref:`access_log_path ` to +specify where to send admin access logs. + +In this example, the logs are simply discarded. + +.. code-block:: yaml + :emphasize-lines: 2, 5-6 + + admin: + access_log_path: /dev/null + address: + socket_address: + address: 0.0.0.0 + port_value: 9901 + +.. warning:: + + The Envoy admin endpoint can expose private information about the running service, and + can also be used to shut it down. + + As the endpoint is not authenticated it is essential that you limit access to it. + + You may wish to restrict the network address the admin server listens to in your own deployment as part + of your strategy to limit access to this endpoint. + + +``stat_prefix`` +--------------- + +The Envoy +:ref:`HttpConnectionManager ` +must be configured with +:ref:`stat_prefix `. + +This provides a key that can be filtered when querying the stats interface +:ref:`as shown below ` + +In the :download:`envoy-demo.yaml <_include/envoy-demo.yaml>` the listener is configured with the +:ref:`stat_prefix ` +of ``ingress_http``. + +.. literalinclude:: _include/envoy-demo.yaml + :language: yaml + :linenos: + :lines: 1-29 + :emphasize-lines: 13-14 + +.. _start_quick_start_admin_config_dump: + +Admin endpoints: ``config_dump`` +-------------------------------- + +The :ref:`config_dump ` endpoint returns Envoy's runtime +configuration in ``json`` format. + +The following command allows you to see the types of configuration available: + +.. code-block:: console + + $ curl -s http://localhost:9901/config_dump | jq -r '.configs[] | .["@type"]' + type.googleapis.com/envoy.admin.v3.BootstrapConfigDump + type.googleapis.com/envoy.admin.v3.ClustersConfigDump + type.googleapis.com/envoy.admin.v3.ListenersConfigDump + type.googleapis.com/envoy.admin.v3.ScopedRoutesConfigDump + type.googleapis.com/envoy.admin.v3.RoutesConfigDump + type.googleapis.com/envoy.admin.v3.SecretsConfigDump + +To view the :ref:`socket_address ` of the first +:ref:`dynamic_listener ` currently configured, +you could: + +.. code-block:: console + + $ curl -s http://localhost:19000/config_dump?resource=dynamic_listeners | jq '.configs[0].active_state.listener.address' + { + "socket_address": { + "address": "0.0.0.0", + "port_value": 10000 + } + } + +.. note:: + + See the reference section for :ref:`config_dump ` for further information + on available parameters and responses. + +.. tip:: + + Enabling the :ref:`admin ` interface with + dynamic configuration can be particularly useful as it allows you to use the + :ref:`config_dump ` endpoint to see how Envoy is configured at + a particular point in time. + +.. _start_quick_start_admin_stats: + +Admin endpoints: ``stats`` +-------------------------- + +The :ref:`admin stats ` endpoint allows you to retrieve runtime information about Envoy. + +The stats are provided as ``key: value`` pairs, where the keys use a hierarchical dotted notation, +and the values are one of ``counter``, ``histogram`` or ``gauge`` types. + +To see the top-level categories of stats available, you can: + +.. code-block:: console + + $ curl -s http://localhost:9901/stats | cut -d. -f1 | sort | uniq + cluster + cluster_manager + filesystem + http + http1 + listener + listener_manager + main_thread + runtime + server + vhost + workers + +The stats endpoint accepts a :ref:`filter ` argument, which +is evaluated as a regular expression: + +.. code-block:: console + + $ curl -s http://localhost:19000/stats?filter='^http\.ingress_http' + http.ingress_http.downstream_cx_active: 0 + http.ingress_http.downstream_cx_delayed_close_timeout: 0 + http.ingress_http.downstream_cx_destroy: 3 + http.ingress_http.downstream_cx_destroy_active_rq: 0 + http.ingress_http.downstream_cx_destroy_local: 0 + http.ingress_http.downstream_cx_destroy_local_active_rq: 0 + http.ingress_http.downstream_cx_destroy_remote: 3 + http.ingress_http.downstream_cx_destroy_remote_active_rq: 0 + http.ingress_http.downstream_cx_drain_close: 0 + http.ingress_http.downstream_cx_http1_active: 0 + http.ingress_http.downstream_cx_http1_total: 3 + http.ingress_http.downstream_cx_http2_active: 0 + http.ingress_http.downstream_cx_http2_total: 0 + http.ingress_http.downstream_cx_http3_active: 0 + http.ingress_http.downstream_cx_http3_total: 0 + http.ingress_http.downstream_cx_idle_timeout: 0 + http.ingress_http.downstream_cx_max_duration_reached: 0 + http.ingress_http.downstream_cx_overload_disable_keepalive: 0 + http.ingress_http.downstream_cx_protocol_error: 0 + http.ingress_http.downstream_cx_rx_bytes_buffered: 0 + http.ingress_http.downstream_cx_rx_bytes_total: 250 + http.ingress_http.downstream_cx_ssl_active: 0 + http.ingress_http.downstream_cx_ssl_total: 0 + http.ingress_http.downstream_cx_total: 3 + http.ingress_http.downstream_cx_tx_bytes_buffered: 0 + http.ingress_http.downstream_cx_tx_bytes_total: 1117 + http.ingress_http.downstream_cx_upgrades_active: 0 + http.ingress_http.downstream_cx_upgrades_total: 0 + http.ingress_http.downstream_flow_control_paused_reading_total: 0 + http.ingress_http.downstream_flow_control_resumed_reading_total: 0 + http.ingress_http.downstream_rq_1xx: 0 + http.ingress_http.downstream_rq_2xx: 3 + http.ingress_http.downstream_rq_3xx: 0 + http.ingress_http.downstream_rq_4xx: 0 + http.ingress_http.downstream_rq_5xx: 0 + http.ingress_http.downstream_rq_active: 0 + http.ingress_http.downstream_rq_completed: 3 + http.ingress_http.downstream_rq_http1_total: 3 + http.ingress_http.downstream_rq_http2_total: 0 + http.ingress_http.downstream_rq_http3_total: 0 + http.ingress_http.downstream_rq_idle_timeout: 0 + http.ingress_http.downstream_rq_max_duration_reached: 0 + http.ingress_http.downstream_rq_non_relative_path: 0 + http.ingress_http.downstream_rq_overload_close: 0 + http.ingress_http.downstream_rq_response_before_rq_complete: 0 + http.ingress_http.downstream_rq_rx_reset: 0 + http.ingress_http.downstream_rq_timeout: 0 + http.ingress_http.downstream_rq_too_large: 0 + http.ingress_http.downstream_rq_total: 3 + http.ingress_http.downstream_rq_tx_reset: 0 + http.ingress_http.downstream_rq_ws_on_non_ws_route: 0 + http.ingress_http.no_cluster: 0 + http.ingress_http.no_route: 0 + http.ingress_http.passthrough_internal_redirect_bad_location: 0 + http.ingress_http.passthrough_internal_redirect_no_route: 0 + http.ingress_http.passthrough_internal_redirect_predicate: 0 + http.ingress_http.passthrough_internal_redirect_too_many_redirects: 0 + http.ingress_http.passthrough_internal_redirect_unsafe_scheme: 0 + http.ingress_http.rq_direct_response: 0 + http.ingress_http.rq_redirect: 0 + http.ingress_http.rq_reset_after_downstream_response_started: 0 + http.ingress_http.rq_total: 3 + http.ingress_http.rs_too_large: 0 + http.ingress_http.tracing.client_enabled: 0 + http.ingress_http.tracing.health_check: 0 + http.ingress_http.tracing.not_traceable: 0 + http.ingress_http.tracing.random_sampling: 0 + http.ingress_http.tracing.service_forced: 0 + http.ingress_http.downstream_cx_length_ms: P0(nan,2.0) P25(nan,2.075) P50(nan,3.05) P75(nan,17.25) P90(nan,17.7) P95(nan,17.85) P99(nan,17.97) P99.5(nan,17.985) P99.9(nan,17.997) P100(nan,18.0) + http.ingress_http.downstream_rq_time: P0(nan,1.0) P25(nan,1.075) P50(nan,2.05) P75(nan,16.25) P90(nan,16.7) P95(nan,16.85) P99(nan,16.97) P99.5(nan,16.985) P99.9(nan,16.997) P100(nan,17.0) + + +You can also pass a :ref:`format ` argument, for example to return ``json``: + +.. code-block:: console + + $ curl -s "http://localhost:19000/stats?filter=http.ingress_http.rq&format=json" | jq '.stats' + +.. code-block:: json + + [ + { + "value": 0, + "name": "http.ingress_http.rq_direct_response" + }, + { + "value": 0, + "name": "http.ingress_http.rq_redirect" + }, + { + "value": 0, + "name": "http.ingress_http.rq_reset_after_downstream_response_started" + }, + { + "value": 3, + "name": "http.ingress_http.rq_total" + } + ] diff --git a/docs/root/start/quick-start/configuration-dynamic-control-plane.rst b/docs/root/start/quick-start/configuration-dynamic-control-plane.rst index 3ceb430abe36..914a057eafc0 100644 --- a/docs/root/start/quick-start/configuration-dynamic-control-plane.rst +++ b/docs/root/start/quick-start/configuration-dynamic-control-plane.rst @@ -19,7 +19,7 @@ At a minimum, you will need to start Envoy configured with the following section - :ref:`dynamic_resources ` to tell Envoy which configurations should be updated dynamically - :ref:`static_resources ` to specify where Envoy should retrieve its configuration from. -You can also add an :ref:`admin ` section if you wish to monitor Envoy or +You can also add an :ref:`admin ` section if you wish to monitor Envoy or retrieve stats or configuration information. The following sections walk through the dynamic configuration provided in the @@ -72,26 +72,3 @@ The ``xds_cluster`` is configured to query a control plane at http://my-control- :lines: 17-35 :lineno-start: 17 :emphasize-lines: 3-17 - -.. _start_quick_start_dynamic_admin: - -``admin`` ---------- - -Configuring the :ref:`admin ` section is -the same as for :ref:`static configuration `. - -Enabling the :ref:`admin ` interface with -dynamic configuration, allows you to use the :ref:`config_dump ` -endpoint to see how Envoy is currently configured. - -.. literalinclude:: _include/envoy-dynamic-control-plane-demo.yaml - :language: yaml - :linenos: - :lines: 33-40 - :lineno-start: 33 - :emphasize-lines: 3-8 - -.. warning:: - - You may wish to restrict the network address the admin server listens to in your own deployment. diff --git a/docs/root/start/quick-start/configuration-dynamic-filesystem.rst b/docs/root/start/quick-start/configuration-dynamic-filesystem.rst index f6d55cf7a43b..dc6c02100e1b 100644 --- a/docs/root/start/quick-start/configuration-dynamic-filesystem.rst +++ b/docs/root/start/quick-start/configuration-dynamic-filesystem.rst @@ -19,7 +19,7 @@ For the given example you will also need two dynamic configuration files: - :ref:`lds.yaml ` for listeners. - :ref:`cds.yaml ` for clusters. -You can also add an :ref:`admin ` section if you wish to monitor Envoy or +You can also add an :ref:`admin ` section if you wish to monitor Envoy or retrieve stats or configuration information. The following sections walk through the dynamic configuration provided in the @@ -90,26 +90,3 @@ proxies over ``TLS`` to https://www.envoyproxy.io. :language: yaml :linenos: :emphasize-lines: 8, 14-15, 19-20 - -.. _start_quick_start_dynamic_fs_admin: - -``admin`` ---------- - -Configuring the :ref:`admin ` section is -the same as for :ref:`static configuration `. - -Enabling the :ref:`admin ` interface with -dynamic configuration, allows you to use the :ref:`config_dump ` -endpoint to see how Envoy is currently configured. - -.. literalinclude:: _include/envoy-dynamic-filesystem-demo.yaml - :language: yaml - :linenos: - :lines: 9-16 - :lineno-start: 9 - :emphasize-lines: 3-8 - -.. warning:: - - You may wish to restrict the network address the admin server listens to in your own deployment. diff --git a/docs/root/start/quick-start/configuration-static.rst b/docs/root/start/quick-start/configuration-static.rst index 74e539b529d0..c9600c99bfd3 100644 --- a/docs/root/start/quick-start/configuration-static.rst +++ b/docs/root/start/quick-start/configuration-static.rst @@ -7,7 +7,7 @@ To start Envoy with static configuration, you will need to specify :ref:`listene and :ref:`clusters ` as :ref:`static_resources `. -You can also add an :ref:`admin ` section if you wish to monitor Envoy +You can also add an :ref:`admin ` section if you wish to monitor Envoy or retrieve stats. The following sections walk through the static configuration provided in the @@ -55,26 +55,5 @@ proxies over ``TLS`` to https://www.envoyproxy.io. .. literalinclude:: _include/envoy-demo.yaml :language: yaml :lineno-start: 27 - :lines: 27-50 + :lines: 27-48 :emphasize-lines: 3-22 - -.. _start_quick_start_static_admin: - -``admin`` ---------- - -The :ref:`admin message ` is required to enable and configure -the administration server. - -The ``address`` key specifies the listening :ref:`address ` -which in the demo configuration is ``0.0.0.0:9901``. - -.. literalinclude:: _include/envoy-demo.yaml - :language: yaml - :lineno-start: 48 - :lines: 48-55 - :emphasize-lines: 3-8 - -.. warning:: - - You may wish to restrict the network address the admin server listens to in your own deployment. diff --git a/docs/root/start/quick-start/index.rst b/docs/root/start/quick-start/index.rst index cc2078926cc7..e9daf8cf96ef 100644 --- a/docs/root/start/quick-start/index.rst +++ b/docs/root/start/quick-start/index.rst @@ -13,5 +13,6 @@ provides an introduction to the types of configuration Envoy can be used with. configuration-static configuration-dynamic-filesystem configuration-dynamic-control-plane + admin securing next-steps From e7a05f7bbb568422c7febffddc20c6c3bc2a29ba Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 9 Nov 2020 22:46:24 -0500 Subject: [PATCH 056/117] grpc: upstream disconnect test (#13898) Signed-off-by: Alyssa Wilk --- .../grpc_web_filter_integration_test.cc | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/extensions/filters/http/grpc_web/grpc_web_filter_integration_test.cc b/test/extensions/filters/http/grpc_web/grpc_web_filter_integration_test.cc index 2eaf4ef7fdb1..5a8831476b53 100644 --- a/test/extensions/filters/http/grpc_web/grpc_web_filter_integration_test.cc +++ b/test/extensions/filters/http/grpc_web/grpc_web_filter_integration_test.cc @@ -110,5 +110,41 @@ TEST_P(GrpcWebFilterIntegrationTest, GrpcWebTrailersNotDuplicated) { } } +TEST_P(GrpcWebFilterIntegrationTest, UpstreamDisconnect) { + const auto downstream_protocol = std::get<1>(GetParam()); + const bool http2_skip_encoding_empty_trailers = std::get<2>(GetParam()); + + if (downstream_protocol == Http::CodecClient::Type::HTTP1) { + config_helper_.addConfigModifier(setEnableDownstreamTrailersHttp1()); + } else { + skipEncodingEmptyTrailers(http2_skip_encoding_empty_trailers); + } + + setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); + + Http::TestRequestTrailerMapImpl request_trailers{{"request1", "trailer1"}, + {"request2", "trailer2"}}; + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto encoder_decoder = codec_client_->startRequest( + Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {"content-type", "application/grpc-web"}, + {":authority", "host"}}); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + codec_client_->sendData(*request_encoder_, 1, false); + codec_client_->sendTrailers(*request_encoder_, request_trailers); + waitForNextUpstreamRequest(); + + ASSERT_TRUE(fake_upstream_connection_->close()); + response->waitForEndStream(); + EXPECT_EQ("503", response->headers().getStatusValue()); + + codec_client_->close(); +} + } // namespace } // namespace Envoy From f015cf526fe8ba5dbf22f03f97db243a1d1b5c43 Mon Sep 17 00:00:00 2001 From: serverglen Date: Tue, 10 Nov 2020 13:36:21 +0800 Subject: [PATCH 057/117] fix dubbo proxy parse hessian2 missing some case in switch (#13924) Signed-off-by: liushuai06 --- .../network/dubbo_proxy/hessian_utils.cc | 6 ++ .../network/dubbo_proxy/hessian_utils_test.cc | 72 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/source/extensions/filters/network/dubbo_proxy/hessian_utils.cc b/source/extensions/filters/network/dubbo_proxy/hessian_utils.cc index c06ca93dd2c4..777bf3854968 100644 --- a/source/extensions/filters/network/dubbo_proxy/hessian_utils.cc +++ b/source/extensions/filters/network/dubbo_proxy/hessian_utils.cc @@ -210,6 +210,9 @@ long HessianUtils::peekLong(Buffer::Instance& buffer, size_t* size, uint64_t off case 0xe8: case 0xe9: case 0xea: + case 0xeb: + case 0xec: + case 0xed: case 0xee: case 0xef: @@ -229,6 +232,8 @@ long HessianUtils::peekLong(Buffer::Instance& buffer, size_t* size, uint64_t off case 0xf9: case 0xfa: case 0xfb: + case 0xfc: + case 0xfd: case 0xfe: case 0xff: @@ -340,6 +345,7 @@ int HessianUtils::peekInt(Buffer::Instance& buffer, size_t* size, uint64_t offse case 0xc9: case 0xca: case 0xcb: + case 0xcc: case 0xcd: case 0xce: case 0xcf: diff --git a/test/extensions/filters/network/dubbo_proxy/hessian_utils_test.cc b/test/extensions/filters/network/dubbo_proxy/hessian_utils_test.cc index cea84965b576..6f5d6387af07 100644 --- a/test/extensions/filters/network/dubbo_proxy/hessian_utils_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/hessian_utils_test.cc @@ -174,6 +174,30 @@ TEST(HessianUtilsTest, peekLong) { } // Single octet longs + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xeb'})); + size_t size; + EXPECT_EQ(11, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(1, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xec'})); + size_t size; + EXPECT_EQ(12, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(1, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xed'})); + size_t size; + EXPECT_EQ(13, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(1, size); + } + { Buffer::OwnedImpl buffer; buffer.add(std::string({'\xef'})); @@ -223,6 +247,38 @@ TEST(HessianUtilsTest, peekLong) { EXPECT_EQ(2, size); } + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xfc', 0x00})); + size_t size; + EXPECT_EQ(1024, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(2, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xfc', '\xff'})); + size_t size; + EXPECT_EQ(1279, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(2, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xfd', 0x00})); + size_t size; + EXPECT_EQ(1280, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(2, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xfd', '\xff'})); + size_t size; + EXPECT_EQ(1535, HessianUtils::peekLong(buffer, &size)); + EXPECT_EQ(2, size); + } + { Buffer::OwnedImpl buffer; buffer.add(std::string({'\xff', '\xff'})); @@ -409,6 +465,22 @@ TEST(HessianUtilsTest, peekInt) { EXPECT_EQ(2, size); } + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xcc', 0x00})); + size_t size; + EXPECT_EQ(1024, HessianUtils::peekInt(buffer, &size)); + EXPECT_EQ(2, size); + } + + { + Buffer::OwnedImpl buffer; + buffer.add(std::string({'\xcc', '\xff'})); + size_t size; + EXPECT_EQ(1279, HessianUtils::peekInt(buffer, &size)); + EXPECT_EQ(2, size); + } + { Buffer::OwnedImpl buffer; buffer.add(std::string({'\xcf', '\xff'})); From e90ba1f94aa17fd9cd65abff4c79312108bd8dfc Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 10 Nov 2020 05:44:20 +0000 Subject: [PATCH 058/117] docs/ci: Publish master docs after docker (#13859) Signed-off-by: Ryan Northey --- .azure-pipelines/pipelines.yml | 62 ++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 8e26b1f61ff2..5afb77b97b20 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -41,6 +41,7 @@ stages: - job: docs dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel. + condition: ne(variables['PostSubmit'], true) pool: vmImage: "ubuntu-18.04" steps: @@ -59,34 +60,14 @@ stages: GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) displayName: "Generate docs" - - script: ci/run_envoy_docker.sh 'ci/upload_gcs_artifact.sh /source/generated/docs docs' + - script: | + ci/run_envoy_docker.sh 'ci/upload_gcs_artifact.sh /source/generated/docs docs' displayName: "Upload Docs to GCS" env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) GCS_ARTIFACT_BUCKET: $(GcsArtifactBucket) - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: "$(Build.SourcesDirectory)/generated/docs" - artifactName: docs - - - task: InstallSSHKey@0 - inputs: - hostName: "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" - sshPublicKey: "$(DocsPublicKey)" - sshPassphrase: "$(SshDeployKeyPassphrase)" - sshKeySecureFile: "$(DocsPrivateKey)" - condition: and(succeeded(), eq(variables['PostSubmit'], true), ne(variables['NoSync'], true)) - - - script: docs/publish.sh - displayName: "Publish to GitHub" - workingDirectory: $(Build.SourcesDirectory) - env: - AZP_BRANCH: $(Build.SourceBranch) - AZP_SHA1: $(Build.SourceVersion) - condition: and(succeeded(), eq(variables['PostSubmit'], true), ne(variables['NoSync'], true)) - - job: dependencies dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel. pool: @@ -305,6 +286,43 @@ stages: artifactName: docker condition: always() + - stage: docs + dependsOn: ["docker"] + condition: and(succeeded(), eq(variables['PostSubmit'], true), ne(variables['NoSync'], true)) + jobs: + - job: publish + pool: + vmImage: "ubuntu-18.04" + steps: + - task: Cache@2 + inputs: + key: "docs | ./WORKSPACE | **/*.bzl" + path: $(Build.StagingDirectory)/repository_cache + continueOnError: true + + - script: ci/run_envoy_docker.sh 'ci/do_ci.sh docs' + workingDirectory: $(Build.SourcesDirectory) + env: + ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) + BAZEL_REMOTE_CACHE: grpcs://remotebuildexecution.googleapis.com + BAZEL_REMOTE_INSTANCE: projects/envoy-ci/instances/default_instance + GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) + displayName: "Generate docs" + + - task: InstallSSHKey@0 + inputs: + hostName: "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" + sshPublicKey: "$(DocsPublicKey)" + sshPassphrase: "$(SshDeployKeyPassphrase)" + sshKeySecureFile: "$(DocsPrivateKey)" + + - script: docs/publish.sh + displayName: "Publish to GitHub" + workingDirectory: $(Build.SourcesDirectory) + env: + AZP_BRANCH: $(Build.SourceBranch) + AZP_SHA1: $(Build.SourceVersion) + - stage: verify dependsOn: ["docker"] jobs: From 1d6d68bd659a78ad53b46799efa2f810c3eb2dd7 Mon Sep 17 00:00:00 2001 From: Yitao Hu Date: Tue, 10 Nov 2020 01:09:27 -0500 Subject: [PATCH 059/117] Minor update of bazel doc (#13715) Signed-off-by: Yitao Hu --- bazel/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bazel/README.md b/bazel/README.md index 024e4b88b49d..6980607d8c88 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -83,10 +83,15 @@ for how to update or override dependencies. echo "build --config=clang" >> user.bazelrc ``` - Note: Either `libc++` or `libstdc++-7-dev` (or higher) must be installed. These are typically - available via a package manager, but may not be available in default repositories depending on - OS version. To build against `libc++` build with the `--config=libc++` instead of the - `--config=clang` flag. + Note: Either `libc++` or `libstdc++-7-dev` (or higher) must be installed. + + #### Config Flag Choices + Different [config](https://docs.bazel.build/versions/master/guide.html#--config) flags specify the compiler libraries: + + - `--config=libc++` means using `clang` + `libc++` + - `--config=clang` means using `clang` + `libstdc++` + - no config flag means using `gcc` + `libstdc++` + ### macOS On macOS, you'll need to install several dependencies. This can be accomplished via [Homebrew](https://brew.sh/): From e98518faa57f0584116ec6f44b4cf0c6d511b323 Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 10 Nov 2020 17:59:31 +0000 Subject: [PATCH 060/117] docs: Update quick-start (#13927) Signed-off-by: Ryan Northey --- docs/root/_static/envoy-admin.png | Bin 0 -> 85336 bytes .../api-v3/config/accesslog/accesslog.rst | 2 + docs/root/api-v3/config/config.rst | 2 + docs/root/start/quick-start/admin.rst | 21 +- docs/root/start/quick-start/run-envoy.rst | 252 +++++++++++++++--- docs/root/start/quick-start/securing.rst | 35 ++- docs/root/start/start.rst | 4 +- 7 files changed, 254 insertions(+), 62 deletions(-) create mode 100644 docs/root/_static/envoy-admin.png diff --git a/docs/root/_static/envoy-admin.png b/docs/root/_static/envoy-admin.png new file mode 100644 index 0000000000000000000000000000000000000000..d1063866a07cc3f5f028e9f83200576be1277f15 GIT binary patch literal 85336 zcmb@uWk8kN+BJ-cv`R{fQqtWa-OU0dMN+yu6r`oQyB6KuB@H6E=PW)0wMU%QyT$s zr5E7K^@W}v`12cEVO3j23jWT5pRH+ZK=$XEzRV!+ds+`Ir*qyTG zEoJH+Wq}`39bOpL+cclI)GTSFk<1dknNqdQeBrfZp)DkF;*VXEuWKY5Y}S*RdXj3- zo7OR23cMix@&E3ZA}5LW$uPthPjBF!EUZQ1BK-H^hUJejKN5$cC0hKF*+M4~mV1Nc z^l10K)N!P+UF2u%!s;%;&e~d$Vq}+|LQuKGy*r`in?I(jNLGa|i&r#1aao|X`G235 zjtYf-sZ=w~4i9gDh>J_^xqUv8ZF!X%*&Dj~?A0jN!T#?o$@7&{6KtFyt=6pU z*S&qcC2BOeP_=uNMG{{|wBxpDy!b*_;v%~c@0(vVg1*cSEPuD+55GLd%*@Q;!AZx) zs&rl+iN(SKG1J-JY<*zwxOg|gREP^t?@8_5)wYYN%+$$Or%!pJ?t^*HAv2^(Q7dl> z6Y8-9G|KM|qYwvh$dLYh?HXnXq;!(9B$kTe17$~FH=CnPZ}!j?83}Kh#eCCl@Lmot zzrr=j_QU|y2ihYFIy#e&QJSLW+TKQ5lt;W6|2Zi?DsgeES6Jm!@11U^om$NVh@PHt z9C0UkU0a-vwb7YZP8FD`srixoUC9gLt>T6TyE;wHU2Ac|#gp>pUJjuY_hTjU?q#cz4oYnsxXa}YwMDP)x zD61W_7^5mR);X3-%H)53#+u~sa=%O!C_A(){Hh9mE^e-z4+*;#A{H_u5qF#ZX_#rL^4u2r3f=>U~J9SzKBgqr`SVDi& zRwtF@N3L``-+P%v&an-*zmadr)9sa+zi=l5QNn;c66UW|dL~i``P1Gav%reknJP%5 zByTGhN0&g-05jl$4XMB`Zs)4yA9#gZ=?m`RnAu3RP1 zhIrvQ0By@bbcKHAF%)T*<9C}2aE?HgLR89KP@Z}xZj~ymFhn@(v>r5J6218gJ)vi) zxq!Gy)}qR6p5NS^*US??ezREqISI)yadu;0->b^&vwHnl2`TQ2mfK3+Cdx zzf!+TYcTyyD3!c8Z0m$B@#ZvDWwI8_5-EGL`Ah6cl77JZM`&JbQG} zY0Wtp$IZ8LJQyX=AoEX3YHGc^%j67~b1te)N1{hah#Mn?RCK)8$Y!2iaV+Z)ZXO=T z&CB(Qb?U1vqMh05kmi0jly7!;tVf;co%ln%{Pk7x+=mxR&gVNlhXtlx^XFXAZpSRI z78l`E3zS8IYa`IG`ZN0MlIWmAX+M!=Qh23ZQ|3r^raPxk@n{jl2a~zFx%Qie&MKI> zxw$uf*RHu|b06V6xtXOFiaZil3-*q(TQrwxT3?x#%6Ou8fb#p#j@isJkJzU2E zC)@8Be}`}z%j`a*??K*nnayoU)S3NdoYdm!a2tt>AG1-5v=lxp^yA2<T~$E<(o9ZEP@$E(JSvnnbox;Pyi98R2h;Jm%P6MK6WZ=F2& z`4_WPv~*jqiH^p%iy3dCi5o7@TuoW=IFnGno#L@s?f(r>jKs zv{|VO$lt#|nr3B6c1zyasG`2x*-zxt?ttw#xs141Wn~?$fQ_1sENoUF}>BdbU9HMSf^Fk#<2YUapGi-9a_ZlZ}1 zj)tu1Hf(r`fB+~Z1tBFOYh*Zd&)l36krG;uxJsD_kIXupM*-bB_+j>yYxE8GQWb9B zJq>g!b0-;L+3y7!IXCyu`uzs4q5X~{ds@HOshf%VB?Zf>U|;p;aXrZ`EuT(5C(THGSY;4J4e z5q|jkj#QapWLOZ0UlqI1Ub^IOJG|3a_wUYT1QAytIk%Dcvd4daG|;{fI&gpaE|9wH z*MEQcEr8`v;L7enC?EC;pC@@>MEBDVpC1RUo(}X%LB}(xo%|C@fh3q&j33+XxD+Z* zqf8>9m6V@_^R9YE-hKep?5^%}a{KAm`6hyFD3+9SV{)t4^U#K`=(9_8Wmbxax9G3w zot+HcHrh_L_Gv64(#UnK z`^=g%a+PL?Bh;TmjZGL*`T;Q#CRd_!Y%l77S^`a_puoaRh(W!vqrhY>$;*|{|FseT z+d(?#aNT&h)_kv$>}X24&1Sw*#MNaK5=TW@qDswDHGy}6a}otFqbKx~K$o+`sZW_^ zI4@rv1`Zpp#^iS+%Xoclt<~O*liKCAq7JJrgSxSc^K5ZW6@7a0BjLWJ<-IKhHz|^Z zbcTp6$-*mb-Ty5a2%uOE_1VmmZFl%X8NwDeN_G34&1j1UorA{?!4eP zv5AU#ux42U2)IT~OqSmN7T=x^Cs5T_n@s}jmHo{5oylG3`B3UeUO+3%!7SJA}Q=Gr6Wrm;4k{4X4Mt>tm8 zNh5Ui`3~kve{VJ|EE?gqL{i+?*s{8+t*A?~n4KD#o9oY6pp83x+Z~`IgixnV*`nEc zTPVOSy3U^(eB|Nt&DZlw=b|#pS!)%Y2L9^WTGuc?8`FN{x%kdRwFpzC|8m#i7~6cA zfg9uc5cW}f0MS&P8$9TIk}!jf0WY!HvM9Tro*70*%R>DfF_^f#e2*}HM0GWrzng{L z?v)6s1X=hpji&@-&#a(TKdU_pC{r_A8*qqs&)(73DF6e*{R1-fLRc zS3irqu5%UVPUFRTuN;?)+}-*3r1_B$!*>N8LA%j3_6wv+v?@(aDi4ovWom~SpLSmq z{hl&?Zrj$D@#-La8UMT=A-uOgJw2H;=4+W!TrUV8J!&2(!+J$1BczJ6j)YX(RAg>u zGCs`5@Jc%+@LGC~am8Q_tS&wMovWLhlgAwFM*psU=Kfmr1tRiF5ll3~>Z0Ufz1$)e z?=wVxe(rs8u6Z(v9wOb@-RHZInre_6_jfW8H5J$Lozjru*P>-LSw%MEq`X*)Xil5J zV~?}@leNmlNh1Aa?fbo%^;cTg_wdi*t z`i7m7yjMW^cb>`hjU8m0d77zVUJp`y)|x}38mcD-fmjyDr|lM?utJ$E~&+fb(qnOKHgmJ0zqz=ZxC?cSQ`RrU*`9*wDwB(Kj6L{|2wx_H_1v`bQ;} zZ&h1GsY?Bqfrq9eURs&vy!LeG*2Di>86|vcV-GlxGu`ib>R~$qCgeM@f zt5#E`nW*U&sFnMCzrDU650~)E{u9pk_Y(xzg?ZFSDp%?|qBW|sk9&8p6Y3F{;>q>} z{{r{v&G`Dq{~ZgKdM(XRs%U;#zHuT#{LjyHjP1wcz88P7_%0}^O8Vb-=)hd0d;`8Y z(m*2qw{e<${~7id;ejXOFIfg=vY1TNmk|b8QKUVTAgp)*RcT4>2&Ax~jj20lx41N4La`I6em2`*{$2jF2Wq zXRYSX)dnh!&Z%zbO98*^)uXpPrmQw={co&oY(7v?wVPJSDJi}9R$NTv+{l%joLupB z)CUB>iF0FqK1tEbO3jvD;X?3eD+XbCT29gNa|6x6`t%R%%c__HepDkpia9UPkWBWB z*}eNbGuzVNp&A;5M??sU8R6)prQrmLoIHPWcjpcVFp6Joy65lT3NlH&($3DD#Kgq5 ze*UdP6B7^!x(eL&vo7!Zhh1Iza(0|~>oenCzPEu=m*^m@_;sh0^2f{cOAa0}N*Wr{ z_wT)C8o2B1cO{0j8i%)0D$Td|_ls}w$Es)yY@X%FrWnAg%qkF2vvs?J!RH&Q$X8fG zq3KXcgYvkQ&W`{};Xnt6-7W4~?o5Es_nd80J)@&QrbC?5Z#Q$tnbZZ{J zf{YBR%h^tvpcL|KokKCxM`)my#al`!O9~T+PoN_38gHNaCG~8JMACSoH6rLt% z@zvH(Daj*W8JWNtiCi}M;csMM#YVL|*p=B!?>q$s6KYvO>~utZU}sJ|DpOeay-Wb% z+kfu;%SnZ~G4*A4Fa#VEjcA5fW?~{fBO{ZzSObDrn!Dx{z79Q(Uvnwlo4B|{4x6sc zYiybjD0I@4b!H~s{&{o70A73rH~6=05M;vSSSm{VI)>^LhuK;EHI0!IZf*j;e_j_k z2r*njQgW)?5X~RKpB5o(r=g)iOiD~|eK099I=Zd^f8=K6-9uVh(u%5)AdI~Jbx85! zrHYTpGx0ZX-hiO8GIpq{yQnr6_Kn41WMtIPiZ*f{qr?CA9|hM97K3A^3Br#6QN3z( z@EPVUEWEO|I#TF@W;nhc&S~qn7^AH$ud4JTrPv}O%BAuHE$w1dt;OSmqXO)M!$b2m zTu9w(THI*rPi;k-^}mLOnl~8e=;)kJHd-$Zmk*DQjD4m-R@O|ro{WgXao=4m2Tc!U zJ%^e5&DJ~EYz!0cy5eA92$`BvYu4MFDe-uRwpOY81Oy-sCi3*v*=@@~aF{LM0+^y<_o&P8*xNec@LA`|>d3Xmwb>oa*}Wv%Vy#YLSTz3g=~ z0$yI3{XdaGia@2uHvN{CUTZn8Lg41y*6B`Z6^kcED@{}wnHV>~7B{ANr2U~$m&fp) z<8eu!;4*t$&xwg0b1*u@S8fJF4JPD6Z9&m2TJD;2!oCRvE-5FoqvNve7@x;E5}jNIR0XmK|4wapabMJ3e}8}C zR6Dr2IW0$L=cVOkFPZho%LL=$DN4CPqKt-9JBnz=nVHkc0xVn3YUqL3qi0HEKIzOw zH%Njc86$px2FMP7UBHSb=R~x%yDIG@>=cxyjEnnGQ^M^0=k9bBDQ^>7H44#Q_%&;O z)>BC3V-~}cf}2liZKmxLY%QCsS`=Jd37AyMW$$Tr)4P=*S?bm7TJb!v)zg!5)$=V)4c2{BMKZ!tQ$I^cN>`W@w1D)n9IYMUeEZF2eee}7ZdVWb zRHTX$d!^XKDc@#<-=4POcJT9)bdNkkDZ{YisKl2nb&f+~KZHwmJd# z65}gwGEROxGS8P!Sx(M|hTjr&=tebMqnhV`4-d9oegq@kCCZ>72Dbfmc1dJ^xv^%equ&XL1vWDWHMLd$|(f zY{qA|rIZtu6dy0a&;K3k&&v8bn@fHsD77ZUzd?61so*?1JtgJlPTX(04hFTuet%9{ zLqntSEF~_kXS&ja!)^=23}%J#Sb$6tCxi2eQA~y7%XHeYCmCC8X4$_Lj*gC}8Xf4Y zH0#(48&d8fp0>bjw8uQ3)E}PnwEQl{);>vmFB)>RQ@|I|pre@KMUp=zpaqen5SEw2 zcox{{DU5&DuSY;FX=;MWkVg({qkO)Z}Cla0-(0@~@n4o!Z*k9~~ZLlXyfy#sY})Ue0OO<7{V2 z#NoIpv2HK0xZ_%1#n%djlWKi!?KKwG+Hh5+w^-2as;{?qSV96U*9~stbnj$y43Jqw zG*XNSNigoxnwlS-+P6=>brZS8t{dF><1JsqEi5k&fG~8_KMO?0wM2(Ip1*C$TZL^~ z>iDfJF9(H$uz|IR*(ybzt$|Rra=t8L)$hgV`uXNb3+TTGM@R2iS))NOq1W$?Br6Pz ziNR(v=pR-iqxn>pDc)z%0|2SGlvKlxeFCewUb_zpG9GUtyvMzZTBUKPVTyZio?=dg zSy_4crS;*LhbI0$gj+yE1HUhZ&Je7e<*ZWo+CG|2TjnN+?daN<@Erw(A06*~B4{9R za#~u0=Y7$orc;bkq$+czbU$jYFfcGI4;J{9i!=~gVi|NRY-W0+s2;l2vuDo+6so=8 zsVwM0Po_oBz~r} zfVxmp-i}S#O{bFA$xE@13F7u?s!5Y8TRNrn;+pXD^vRnT*I*t?>k6uwEQ$b6(2jsO zR6Ye-Ld$1*%RZXwm$kDO;FaVs~J6{@$4D-IIXSor8y@X9N>y87y3s>b9v0mrxUfZZ<9*_ z38Aa-y4J}_ZMz&y>mnfVVwtX=fjF#G+=qc?X0t9cgENl@fxKb5Qs(AH{@Hkqs#b08 zEp2yaQY2}bBlGn6^VbtJ!B)#%jOH_{bzOF(^z@;rP&yTj#kid831@AMiK{jG{lZ%6 zTjx^2zJ}_xx(ZqpV|PV0wSdy;ggn|JFcbbv?!t9b#A_!ctA*9qTdWNjrh{jysAH87wr2uOW0nWvvev!!i=0yDJ7<~gak@fF1pwkHZRWK(!IY&ikv zYcgJ-v+^tKq^Ry7(9iE}bLuygo*t>Xb;91hK6zWtY`#;B{f1ln=_U)N$XC&y=WSKrvqw{IH$Nk-O9g=!F;>e77y&XFr z8EL7T@3GHR&G7u3_ClH4gs?nD;i+dj1%TRD zIm$sw9wkSXdn`-%+?tbdoe{0m9nWQ%_1n4LoyOqz^rvdOTD;zO4gKgrS74>HA&|t@ z(yR_K+FjodNonTW+uMtbihA_&HEuwbF*iv>LU37`LZNp!(@kt-$HJp072hn)tdE3Y zljDN1%$3sL)W>mDSZ_j}*FWmuhwpd{EE!>}_L%QdJ6uIDj8k zI{eXvgoIwceEHEz904ovnS;XwvZzho^-}(#`_vTfiAsrTK@5S=h#sY#xZl&LdB;!G z$~iko8hB)8u&O8D5Oht5UKI1*;%{%aeXpcL4%^wW2!_w$rL_naiFIpju>iSPXeB78 zt*@`&pUji6-+Z53Za5Sk8rpKb-(1Xc_@j6HyDZNshRH-x=jdp}-R<@9<$6j)badBD zt<6-E8<()Ku z-q@3sCfJLMi`?KW`ePXcuUUpw)4hc}9A==hD&_kY<`0T0G7tZ$EqxPw2^d%a<}am}Fu;T277! zc-4p96Bf3*xv)Y+LfUk1MNux+YE->*TaA6}?(RP0w4QW&zx;wY{!gnH{3j5qa1rFP zWUQ=J{M&fxsj1&iPHcaVj>d7=WLMF-U>ijAbbvR)!z3xxs8vXyZZl$jjfn}OR4M42 zQE%jj%$%HvsHm)GG5|ZY=rOW<3RNOHB`}43DYabdkLxq9cG%YfpD(SYMaa)D(HHjW zuNQ#-Ck}(>AY|POB*+6aw6|wnU0wa*?ag5PhhnFH!DE$^llut3#FOX9!%f5VaZ=RkRX-bD&Ov?j3k=jf*%&#Ew-nJJn0^Y6 zPJr17itTuTtiAKZyKi4d4MSs6WG&_7BD^KAQHu&F5W%K^9U7jV@wD_wq`9vO$79x)1{!n@gi8 ziAQS7LD#i0#f8{K%vQ%^z0cpijat`6%OgWX%r5UqV-;`e0E>xt#?P*Kwd`llH0vxX zMjS7(Y-t7>&*%C;Qe3PhSPXC4Hk|yHJlUU55%fh}+}@USa^e638uA;)A1NMpZ$OmI zHM)#!pMtVw-gIe@>~ZG==Ff|pt+yQgffr*hI#}*nl5DTJSLE{& zQ&W>LFdze6Gwi)+neC+dtkNz0|Mr_aK5|4kRLyOd@i5b7*2cQ1&`JRh+8ulD;AAf z2NGRFs+(7%+qM1D(o!tDRXWz4*=$`DBwyKTJ;?!}n>GNdjK-i*%6s5Q{X#+v7h1jp zR>;TW?l|S*;)33Evc$Pj#LBM5Y4{_S`zZ^oYTr^k^bbBwxE#i=bN?J?o_4eQ#pR{d z{q4Dm-R@B)ST8=eCVaj(OBuq0T}#g#e|~q}A9wmCX*=OJCf{ULK1(;n?f?W{ zSLLSk%M4Vq#*$gykVbe?pIgZjYToqNsgTQeoTO zBK{ruK{399%K@-&d3kwY(0olu=q^93C@-Hj;HWaM{27d^JhHr(B%n9yf&8ect$jsA zBzu2%Td52M9X<=-yy+&F^SuEu%}v%j&;ce}NK|wpR*D!LMU0lS|ICa=A(xh_Dh|je z*`P9yyGuj!=39I7n#Jb}OE0Mz8N(CI>%?8CX=#Ih{zOIyFOv6qeqy+&qFv5oM&8*; zO8YhJGZ9fOp(PXGNxG-NvT*6ks;pCbl7Vk<2*l!Ta9(vL|kKk~Vu` ze0~h=J2^U90-a~g~@MEdxX8AX#TmPY?B{vL6qu*z7ZjErwKEA)%*c(-mnY zC35+rPtg8wM~ejEc7mt=>ggfoC@MjjyBG7!R<@W(U1n zz4=#l*I*9pz|@_t_{!22;Zx+G1OoWer%!-URdaDarANqp_3lw-W~R}QnIBR+AS4~- z4^ono=S+!Tf7H-OWSu+*#G2@rFYf>{`lz3u1f+{y*kzp>wWb7!)Mdbr{mGTznQKe{ zXsHAYYuU0X!OhKl92^{p>{gxxT=qsoAL!_ct%|ssCMg;#Mk0N$0r^wN*!{t*=JTA{MM|5DC%m>wGYubczhv3n2bHpZ{o2$Rj9romAssq|rxTJ|*SuPN<`^7%N2z z3Y(m=vX5`87y4AxxwO_CZ!R}YfAJA z-8n!3El}($+G}I*NpUj1BAsB%indLeOMTljJ6qT#UdyVbC8Cvb%viqO&=!^<>a-c9 zl7vMD0YG$bui>P;>SfO-FSG9M^EFjvEXf#}^{F?q9QrBg3L%6%svtQM*enb{7nh1- z6vq33LD@_**WgrMwHuXLP=H)MmS zpHy_W(@#99&Tc$Y zhi%R=V`Q6v1+dT$!vHAip!<(C{fuSQ4>+4*&}n;Wb`9nLn$N~kD&-FdNkM_#@!)e| ziu{=!$mRjo(hfkpsFr>KjLS0;Qqr;7SwO+O0WP7&ZVAuciK%J}ZJ@LKGoDpj5Zzv# z0%uToY;0~NYTYpCnPPz12I+uI$ce^G7gJ+tteCw11{I(hLBE0C1o=^jk3|!P!Kkwbc)UQdT zK&;+e%#$oFyKoiWeEatDi`$uE;yQmnig^WsaiAw;dysX76R)^f?&5ftQlwErK5t-? zMhkGV?CwiX;eVFuIf;Cs?pZxh>3eZ}8^&cpAY-tm^BWakcfYuvBQlIX$9TDmQ&JNh zqG|p(-$dSPO_2pRC^A~peVmzy$l@VffkaVB-}7d46*O=Tvwl1kA2B#!a%kx@(t`yv zj~6sMp19Q$-A8o5kmhXEYJ)G13)T}VxDCrgu)|VPhJbe&%(8_H3#Z|vk^qwxGZb}1 zrAx-pK48>#Uo?~L0%fWMROyJjvBF&(W`GB=42?my*-eVfB*h%Y+4PbiX|v*08ph_0m@c;Tk*N|abW_p(sMCvAF;DNa}ceC z<@uEvJG%E%#WskDh!x{iXLf_}wN=DmS^&Z{DKRm>OCcB=02BTkjj$hJ5*jHe(MnFQ!GE>3;wjrR{0eX?rVkd-X{a4ug9awb;Pz}G* zM1A43ef#d+%m$&r%H@U92;iR}RP+uCE$Q9MTkpff z@;D*PiI_8-Ilx1RKQPYdt1fpip`xT*TIoSwN#qThG6hqD<<=i@nPl$83WhLRX5)?* z<7DKD3JPt@A-o#v_RD)ki6jQFGiH$>OLMyW(iVfq-y%MD)vK%-`#KKaoevb+a(c#raUJl1HF0?C|5$lm5n=9XY@UBG!Z~YeSw5xc5u9_LGX@$ zbvxyz{`z8LKjjd}BD&YX%w6+tIpYJnzeX~sHLDWMAWW|wZ`<8W(@ zuWdgN`cwE)Kp!<*!@sue2haj1s0$bSh6Vx<0x2o!gO~;|GZVnjVA6U6Mo*91ZEXZZ z#HA#MIsTkcE8wDfeDLdi$iZIyKBL=}Ef`ri!00hqXGe8D@4*)q7Up`rTm8U$l$6ke zA+=mGdi@t$*AVYDH7KvyKy~^_#Fq?cjH7<0p@TuLeYmLECqNQG_dz3)0c;0|-1p?< zzOhjLa#*#6thhKrAQsKi8O&m$F4gU3MAUZYkN+zOc{^zCsiT`OkdQKfG-GEnkv_VK z!+ub0k?`Q<0FtAg5{7bqR7C~z+3swgSM$A@oCiRj&rxwZpeY_*pXp!DR?*bE-$sFa zJf2(*c@jlVmh3(lHx9dr9wrfL>YoJ#z5I5&%ajQ634nuCZPr2OyInT~USsWyvsHcO zFO)Qe6LL7B(6*rkXH-*dZ14a;ma#i+k$@~KP%V9&FrdtV=^~E6nt%fK2qZgb+gGRC zzTivIGjpNi`f`1KhTi2WG(}rTc}sznj69-h{iS?T{b;2MA96CU>3mImp?IIsXm8u( zI$y^cre9XW9Xgd}uo`BlQY=*kmdaSSr1i6Fp5?@edA-3M?1zGRxydKKGpjO1nb&tZ zVRhW{=qWt>Ved?JEQsqwLW2quysivEYYyetAHxb+n#_maOJte+2trfN%fweWJd|@X z@oXzIJ7Wk>T$U4gE-JfLLQ}AYJPI8QI_6KBdh_Q^rvd+1tqpVsS)vR0Jh0w@hYnDgzb#YYpQ2wD~@SXS%+CMXKhA$zlUCpTMFIa%@= z8arh#dj@$k+SBQp4L}c|VTKO&8La?Uywz7f+q{U;FshS;n%rCpz=d|r-<`3UIU8_g zjQj$|q(oNUaJOjo>xKN7FU`LkP7*c&fI1o$L<<8{?9Jt|(?KizgM{pHHJXCMW{xj+ zDShsPf>-hFJsFt~ppD|zQ#_J^aJIa>+&;iI-w&8ir`^hlUxR~d!CZT9+1X=zBFIgo zBep5`twn|Cl9d|_d>Mm6Ah$CEs`*KYLn(Z+)4>FQAp=lKNUA${#=4J&#WShw$hyi5 zCZeG5x~Zv2NJ!+UgXlgN<(Kh9vxf&@^QP+bk@(0-U%h$L^FD-E!gB1oprIkLdd^i5 zb~n#=)6(p5@g25bHe+iSaG>{l zuVQ0hFxQ<%uSw;oq&V7kq&ub|%bbfyR z>6mu{fAoi29u>@&_yZIjgc`oXWXr>~Jygl-zUY|4vOUr~*p08Bl1IbN?x*g9sy}sw<3$o49eeB?=2tN(50Q`wdZmYplNwLImf?` zY8cI-hoLH#?5Riz8@}PAOm1;UXV>2G$?1G=Lu;VxMq z3cu%qvFJ42CR+_|TUhJ5T9Ey0%okbq0v9z5wyOvpJm$1<3@_i3)uD7L)?#2KOxiMr zOZ(cA?BAP7Wge47<)y$9c}0G2jpcKg`n5re>nFmX`3)+?2gofrQ*o!;6I?7}kGGHm z)dc*Mw2KH$VIvOiU-I(w6i+d^9QH`K-?gPFi`#>dzd!jH5pI9L1-7^jiHWjHj3Ad8 z1`@+_p5{7W&v~SQ`wpU_q0t5eEpS$XlCLu|()~jA2XbG%*RcOA@9qxl1ysrf!v+HnW$*cdz0&j=N({9PD4zuywZ9i{zaN{^ z0v{Q3il^sevz>P?lK@+Yfr$zPgA9@_Dj~P3Zb36Z*}M2_yW&g=9u9r^e4ab@m*0yV zZ!v7jYDn-%=Elr{b@j(hC;Awj4;;Sr)qJaWVKs4u8@S9LC3j_#IU&o=`=vf6Xe}2#KP6FNz(PReb~L3gE7i)+&&_+P zvs7pMH9h_|`va{J-MTgZ>l=GwkHsBJY(!+_0;?x)AH03RJ$g%^VLh;X2au}ardAi&^{L*Tpn2~us_b`JAYs0H!y9l* z$GIJ4##*wSyxNHNH3?*@(S?n-smXTFRnu*I;w?PtP7bKkgS?kZy>?ko#s4u4rjvr- zunTyS5O-8A7$b(`j!~2Vyo*2Bzj9M+6Eqbh2!@^R#eONyy3g@^|8=xlIRV=rU#(|1i45<&pX%Cia3r z_lubH)Jiygj~-*G**4aLXY%FC1+XE}X;;Et=@b;41j_lf;@fYfuYf61gNCxV512=> zleD+5K8tVe;rC3U1q7$E zra3w~v;ON?Ep90Ic{4M*vX+?c*1ZiZlrg~SiHhAoaeLsO)H47dm>_AS z%C3`^2K;mBY&Zw!=T8=HE(Q8AF@c^OrY+a9sYw2HG#iD1fNN=eTRml|1CoAa+cAcH zt@~i-%uTvotG_zgj7gAquo!bNul!>%o*ly=;V3kLdEdCYZBCVw0~;@s;b3TlO*}9Y zQ)5v9{Rs}ZBo_Dfd`65&m@`pk>pN?qoJ)vSi=E)yt@)|Ide~F125N8T`(|VNfQ!}F z2R|Qtq8e(wXbIQGqXrMj_!(IjN}Td(Qv(zS$!*fZkf9hYBy=c!grA z&+{;6dL{hpMW>~&sik@gY$jP|bKjO0mrriD5}QQ-CzBxIZ^i%m$HB^lp2M&nR`bst zf|`ISQ`WOxzK_cGtQ3ggQj!L^#K5U}3&R%wzZ;0a6^jXTSb+0oWU9R9jSTbm6o}a_ z&OdL;G)eg9ArHTFK&YvxuG8(h%gb;p=l~=Ce}7Z-MSpb_wb16rKNaEM@3#@l;Jx2G zSZp7hj{46}roRC)Ra$L6sB-DvHd~)z)=RIJwroKfIC(tW6jtH5{Gtk8`~ClZ>JLl5 z|Nm28ll-syUF?6>K#c#6Km7k*>i^w89Ka$W;IRI|ce^S3K;44KTLS8246WuxJJH>P z9VtFvO(hW!XK56O0qF;0D%va#b{#B5K+T#y%pKi4I5{C9A^G8cdv(}`L}z((aky5L z-Yy_02vYvnuV2sib*IYo`{S5=fv+z#B}IF=G=%R?^;*;IY|1dGu;||vF(aI>QJlam znpa!li83GVBwm4mzSLr#mywC-!Ce5uhoB3v`$M5X%6zbmnw!(ASD7xhqw(nhZto`n zS5{N@2idx*<>`~En z69&ylBx8hhb;cfM_AoJh1GmZRGm-xdP|GT^{b^Q{acUYNT!7#oELcFD@96G+O$^)o z>jkhmpLICy#MVAojnN)=9DIQ3z;g=fP4{Q|;i3mmR6^GSk~altH`7jd3Jla>cRG!j ze>b`~xL>Yf1F1L`pmZR?K7IDgYToVK3S5ZTy#!3lNL~Dl$Nep}T7@4Vl$KUkJ3-KL z0Lpu6YH9{dmVibEUi<-|WR|EEPV+|7YW6O)dLf{pb@%llfk_wG3A+W+c!ccj?Srml z;+e&%EC%9PfJPezG`?5(_^$G^aDc4@@>C0OiU7jqQOsFOj()0d*Q;eb8?t~CkMFwfJx_vkB{}kYl9TRg%zq*2Jm}5AAv%s zfUF4?HRHG(B$dM3*kupsPwwM>CJm3o;TL3C4=BX|y z_?hB<<^N#5Gw8?kRSVa*QX3q#;Tevfgwsh1fL__3=an<_c64(x_WDUT}4+t zV z0;JQ*uH{#+UQOHZUTwTL;{qDc*yw0^m&-7C?!d2Ka*~pg+cz_h|A6Dby}mjbm91pl zI*v0e&S?ECcY2@F=n}GDdUwx$cd_F-hl9ryM#_{BW`j!gRbDqzKt^1k2VX*Xkk6Vj zOi1BFni841f3YCNis}jJI==PX)FABiT6f;I z$KXax$Hc@0zWUuAE|bAeCz(7@sUNC?`^6&sYjpJDS~TH1TI2Bo|BQ_Ha1ZJjxK*>W zV+wGKAkaLcC?QJ#)+KV<1!#NRVq#-sgOmW)t{0(f@95sYfAS6B+Xtr=uyk-(F2Gqd zU1ry&fK;(s>Og=4(q21}#|^)P1R~%44HF7Pr35BG`v4aJ*oe9k{sziYv7oG?Bg~B@uuq>71B|0r|xV+}<%RUeArEJVMH(xg1IQg2InO#2Eg@6cPWoMTq9zk{jiplWg zgEMApyihVrA_`dNOTo3_la22oA!u-PjVG_Gs;b}$K9zStPmyexCK`b6OV`At?+F_B zxBPsn7QU?yPVXMRj)vr*5oRhy8gJCq)qw|JOjdTi<@*z0amD&{4~QzP?KK-~>vY%4 zqlZHU`w$i%9}pNAczJ8R+AE(e^#(5R2#^X19`?Ynd!NYHyC|U&98FmibaRT*o(lG^ zqpJ(6R$7HGk5;?Vc&wjq-9x9}f{UEIoBJpiJQa8r;^v#L_v!<8Bv>gVG1D^wMeIGb0 zIB;EpMH{{-3uuUJwuGzXVQ=>n?HnA)iHW}>Vbf|js9&0&#H%#9IsxX2W~mm~q|zo> zK^=SbDl9=B`aT?dd*JojD;jd65^ z@3Kh2?GzO)r_P2O!2T|+tr>*#L5T@5(JSnu(%u|)+09)G*6`cz`RvKj@}=&*XaEOLeU z&(cbj^X8{n-ESO^W0Jup{RSf)c=m$>u`iltInFRS4LC(U&bbO+YWuAXCiSfiB-A<{ zz5^y2=q2552+x`7R||o#BT)gkKZXAvZ|@zB_22)EpAqeW>`+D;WF#V?qKFbD$`(mh zR#HSm*=3V7L_&y?JuClAE}(u6R!=`xiA0uoe!!s#iWvHGV=_*%%9nd;9jdyM^%eZF{KxxR%}?D zL^tl`Qqdzj_L%?gNOydyo}0E|!s;-b7^>IDGY2u$`SJD0w~!(-AqvK4Pq3z*A;g~$ zvV<)BNPi;NhN+A<4!tm1LgqTaPx33s677b+?ZO7A(M2~PVFc+b0IC2G5A(l&)L^kA zPiKw1psjh%;zT7)g^sT7`L_lueA)I>O&qI(W-hdh%k>F(VSF=~0t!1iI;5%iU7x;# zQ2*!XyBaE|m+b9rZB^-o%}q^V$?RudsdUFayVZko;0;PBtR3MCu05Xo-@nGHdRjb- zh+u~Xm&pX%ch0X;_JRIVU-o@=11glKH->jnFg?q_?yR$%e0Hsx;qNFvSYsJ(k&PW{ zv^_iYHuWm?cbACI5fh(#uR1jJZ4Hl;673hOW>Qqt37PXzCt8fknOdC*ZE zcdIpJjk~R*;~>f}egT2?*lNYCJ5(-T-U-)k=FkVce>Fbb?AvR)jT<+j>J|pUbj*iE zjmqDk*9Y1t#qWiCWE2yod)6~>N z%PeqgUHco5^>=|a_YMv|z@CU|_hNgN!^)K_!$$0fzjLe(NMd52i_(UCklK2p)Pmx?PgkOUO12ZKmXo)id7JAw{{7?c%E=e-&+aBKU%dMS zucwC=krT4qzir5pm3Z>+uf`S0&;RvDBj`W>m@Z+bKkwhMjsKie<5v3j8@-8=5BT@% zwG=#g|Mg*XDPHtS|9-s@k8f`P+W0QT9&VR(%)U*t|OVZ6; zj22O|x@hgqwZ7#rLSSlIG^h4odob}ZFd*Pgxv>$}sFH@<+Fpgfx67(3U)*2pnuV0< z=e~~d)AD^%G^e>*X8Y;0q%-%slUdVfFUkCIiTM>Lv0aNApQav82( z^NT&^E8pR~uRZFNy*-mJJAZc%{li#OX;qo%2iLoKj!}4(bKiL zhTS^)*UzGIEWZ)=%C|f$-%|79sME;jh|t4|iu6fIzpgv+osK<4zmEA*$mjdsiaAfBEo!cvXI`yON=iDLtQUz=lQG+M6Q}eilmUJ)N`3zPSJ4a$*~Bd_%g|0BJwz1J9dNL}rlz7Hcw0pI&E#O!cfH)S zdT`I*kq~miMZV6DLlX3_yqufh*Vyc?AJ#=5?@BJtwDt?Jaoq|#{7fd!er&kllmF-F zS8P74fycD8MDv#hJhK=2XU-*6^(@Zm$Sk*Q;NoJCl-zOTsO3<@7=22L)k>Ov-?cw~ zr*9V*_s(-U|24iC{kkr#A=L!t1Mm#>fDShW zNQ64~KHOL~Lv?&nFJBVrFZO8_Y+l$^Cc&ik3=OU6_N8Ps~nBk4r8Tq~MW)_n)lb?3O z9ix+Ty;dhABElR{5G$k*?_Up&k6Qt8V!m9|(9jSjjM&@SrlM>yls|TiDf1NmvVsM( zRxH_+<$j96$2(LD5h1|>K2q%3c$=9I+rgwSntv9^ z-`y4xl{NvoNSL?&QsBqHZ#&dHNiQODp3RfjcHK8Do2e$1k3-@8$Jevy$jOvD1Y{$j#oor| z1<)%d69`iQi!9zZ(n-6*h^nJEZ()2->St8AK%n@<#J)x4p!3nmxQaBF-w~Cgqodcm zZm)VXoQ=&iBO~Jr)KNsHj=D1Of;G@5vCCiFNE20wrvV-Jx`mmsUi4lA6A|tD*t?l3 zagzbYXpefsSBKX~+VtGZ935?bM^|6&-uu#z;|xY<`gP0Z zfa((OkaC!~MkKb2);(2~b?E={LZ>BN^@UF6O3s@C?sr#B+e`Qd*qYGqdIo_i+O$&P zz0dLiVVz4Ky4An*X8EGEwd#!<~->> zwaT!wk0dEzZ8yeP3KG-~0WhGRX={S^wi2z+)#_;|YF?jyapd;IVk-1CcFon}$qBMY zj~_3iG7%RSZ^C}nS_N4%H}@Ub2Rg3zZos-JlTR{j56#f0@aw5?k?UMQ?P$Rt>?jW6b>lrMX0 z_mG)UZ>a?pwdLBJ@vMZ|N0Wo2Csl>!8}@J7#K0ld^fe)rXW{o?K(L0f23_VxNUsI~ z2@Uk4k3kQ0EO5t3E3)m6^5e*U~s zTf5b~)EG}3OE(KPVq}~^wD|T@7En(DpBQxx3=9xU4_NoAHIG~Q`dyi)6Rb6Lvh3qZ zYX1MQuXcw)q$74?eu?fG%`-7`99Ba+u7;Ht(R)9 zeXQQlaibFbvAU9q*!7+@H!Fftx|8#5&u5E!|Gawh>8+OT$1W>rTa3)t*$rMYU@@?^ z=H@L9Y1CKc+q3SmMEc7kH0*vHl_sB`tiSN#eXpzPFq3}-T=~Z#)Fcip)FL_44ce$A z!nwA)CVY$6@J4|Hsmfv88(>yZSmfLr0FN5Mwt@UTIApG3zpHj%$E=m-ED0-QQBl!d zz)J{VIRf+v{#UdvVTh2J`O#T}E)6;dLGf45pKk+RU=A9=2dx|+DHAAi2-^4FvNZ&g z29?(*FeoQc;KZRj^0$Ob^H=S0E|i0t0Ccgye1}49BbE(9$w=gMo`CaeHJMG2{*h#t zKGZWn=PQIe*wv4xTeV+xbTq|;#LBdpkx^s!uaM$@MwKo9?rHft zm*343UsXAM*n&$RZI3fHW;q*Y1Sw4@f8%(2tN)2$i!f2X$c|hg|KMPW_$t^j^Ij z5113N?upeD^^T8!2DL(zRggmM1+Z6GSxFX8DaUaO4Hcq!g2@XH3=Yqd9H+_GXzhW5 z?kB|wmQ`q6@52%C7QL|fDH**NM`%za6=Ul=0S`04+HXm^5&e~+Ux7)CbQRTz*->8%CZ=~pIsWKia#(<<4yYW+idUvJPcdoEpxcHD>-?4fa;HV3VEf0HBb5_&&|U7YF)yNdhn6R~xtI4IOt&cR`%Wu2m~owSoS?jIBRXWFT6+jUG~T&785JS)&Dej+}y6(F^?71qmK1; zj$Kea{1+Xd^LLeLZ`twhSNzW!%m1$DYYH~Lh>mhs`G9<%wftcEKb_>||J}azzkB=t zNn;$GZ^9jS(^2B-&Cg>Y?{n?rctk`trbk=S?b0oK$YqK!2WZOvTzjA%kMW_PsQ5|H z>?U^Y;^N}AfDhKi00ao_Z>g>SxAhZP5@b+Hud9pMi0`_nOe`w+b%%{-w6@Z}I`hns zgM0nY)~%-&-DmB%gr%c4Tpk_VMv)<+$_r?gTw*4Nz>!?+m%Mq!XuEC5m^ZFm8Pr|J7pO4j!+PplT(61R2V+la%a-e#w~i`AN}&F>mqMc1Gg&h ze84%eP5AA4^M(UkTGKy8J_n+ggHoKPf|`nI78F)>B_`z2_GK5j zc#D{2NdtmqRRzl-9M#(-CBtC|@+RU+tbJdBYcUCE8Pwlu%XgK9wc@2-?hVw|gr4j` zN5DPe3a<$8p{r;tyeGgoqQB1otCQ?lns8|wQJ}$8N4=Sj?jycF94u*0t{YiJ zi@B4?$AAOayk$#qpsGwcRttd^SFGyf>k`@obol{PcJ7=E4aNHa%o5xmvj6H|xJO&u z`udD9)Q&TZM={lJrhpG#HYy8q}knk<~5w!S@t%AN!hlnBHY1V zj2rwo-We|6YK^eE*)Dan<9bo>pQW#69n#)!1Q=FrY6*DqB-HJ(tgL&Ui|(%}u`uE7 zj1*>OYB!e!3#Z{-j`5l`hK2W(GqTtO&i6q2JnlSaLce``Xg)Ya167?ToH{F?b( z^6|xNoa~m5QrgBC%6p)WlC5u?K9U$x*B=)Q_ z&IgFQ8v6&F_~Bf)W^bc5gFjvoyN_3!5LnX};5rD6PP$(&#yzAJ#Gb9sy?|(yj^}7} z#jHEXM1|;3(biU!f-8#!EsQpcAO{qnsTA03EaHQ5{;dVzBEbu2q)f3X1B6J0Hjbnt z04_HZF@@=X9#Z*B#U6C9^W3fCzHiCIT%#y0K5EEEoo1YW|7c*|@NUQW) z%V^6gC04D|@ZKzB&Q!lMC;H*LeX|SN9X+B>sX&Za1a!iT{1OzrEm}UL_2>S661Hz} zLO*c>^`)9TY@0R;oqmm@+VTRd_dlXA@eS4NcwMOW@>mLV7{tU*7RGbu;BFr8hk7|0 zCia2pq{U3M{1jlmT|a(U@2yegrJ<#rC}p<}tNdD57ba##Py7#K>7C-CjUX5vaNh6$ z_uysv)QQ$j#${fsxHVddVRUTF31$Qq>FdHfzV`E`U|)s!R9uJ)&mRKdAbf{f2-7IQ zlA7KcQbX*~sKR^lu^7tNO?mUfd+g|AmDcF;A~;0>U>A#&{p(Bt*LxcgDk%hr`oO`1 z+M}v&sEz=*v)c7+dJg3d-AYV-PAy|VT{sG+FnA?dl&Ks5Yv!y8rz z6{re2I-a6WpMWPL6|No>cURk8be7y*QOkiIsRhB~J%ORCo^Vd^gLS9X-fXbO;9q0$ zIIxNm%pdV}D$dT*U}>2~WZ^GFA-?jx8h{d^d=xeOEj|2uAR_NL!-Gl#p^Ltl8 zOX)fFGlKCnpML!qcZJxHkUu2YlbVw90`~#4y(lU#GO~Stpos*#Y9313T15Fh7PmZc z`t)W<2p}xdYU{Z6&Px0k0jK?4Bh)$kgPvF%J$0(%({t_a?g#I}#K-8pnI!9ypRw;? z5Hk-c`Q&eQHu3S9z8gwP;>IQ)@6h-;&xH3sIhL8Z!#3$ceQUM>{phg;rpz-=H*PTP zoPQG^!!dwX`eh6{Wd+5-BdWnMu|yXAF6WaLTo z(JNq)?amy0%Cl>iCoHmO5Ow+F$rG*oTZaIe2nah(1+NjItXlPeyAq))05U|uuR~yU zAAW9O$m{?`xFU zTv03Xqced)YS%8B<4gA6A0Z+zoR`07pn9Xq<39QPTS;$p!tH%C)>~M155;~3$XLqt z@fwed_hQ5WDX9S9J!-XMc}&L+b2|_5zivL3YTA;E7|xOLMU%8Je5L|?N#B7`G@s>ezd-9Xv7l^{rMxga^)6h-G@>0Ompdz zlasw5DVa<(W$X8JVkbdFN0^f&@{KTx)zB^-8R@+FBX>PC0IOE5nzXc<<8gh%aa$dw z0Z20NsS$I#^fdF(q)-%3R!8`Bq6IWsf-1+DVAa$cQ%fm0^5D8 zf|J?;=S09!AWjgy`oo|UIGw*v!|>&+tsESrV8God>Fmt^;^O!&iJ$Y+f=?1>{Rc<~ zCQU<=Ur#IBKKsiMwJDu&NgZp{Q+{b@>iG0}AsVxNueC%%8Tc)l(rZF&deztIq8qYU zT(S$7st@;MI3p6?RkVpi@>R1E@pIV?rOZ-F<}sGem3y9W>1XN*53X!x+QVFR{>EQb)0Xun~h z#4)OC)`PkR|Re zOyv-BkdcpMMw!K*N4@1a3IMgkh4TtCo76LC>QXP)a!<ROw9E^u_AE?Ag!wDtJBLdRQ$F;}7j+mcEtiBwMvxFbUf7iZ{6T8M9XeSH@^02+$(Fc;#@T z(A}5EEm@_Tg1?<*@b?eTS(@Ry(okjCoN9cj>Z(y4FTc9F#ZGPU&_|E9W?d7!_5SXD ztRbz3z^4;V9J{6+Fg}3J;i4vM(GbuKciA_V!+P#+k}&|Bff6wFQZ-vesF1#~4=JI5?K{Ihm4g-l zNSYeDbGH3g0FgZ4?c_8xl)}iMn{7en%q@fESV#L%s}Qv%A-59nqiPnp^`Zm|0tf2!=409-?z1G*$P&z`5ZRGS&b`1>MN!OT>n~{ zPR$2Gmb5DRx8~fRKYy<7+l#9qp>xp4rJP;JWgO32u-dwHtCb^*!iDT>f&krm(i-*l z_|L#Mhst{=s{7q27ZpebE z&Ny#!0LnyQ{zn>i>;C!k2Rr4jYY%=sOox)2l(JYnt4*N4lE(j><4S+QXpH7t9>}tC zT4z9zt5sEm#18;B!#9wL^IGkthp%r8=4AT8tr+B)MeQNlt+y5g0m!TWoHqI)|!lr;>oVN<)iHpcO!MB zzrH$Os`(^Fd0*W5eRTY>*YDh+pBqkiL*2g#Jqh^X}a~!3*yVuqDw9&n+xWf;eQ}DG{!c19A5Nbj7#A*D~&Y zi-(U+<33(YfJlOrQXui!zHJ)?x@6F*gphJ-$3h1;CAPYL5YMp~&giGUA^~?Jhm>*s z3_y~vZ_e+-!~gm7=X22egbJY`9bt7~xx9*sPl*)bb|g9rgOahglCB(!nJs#~p02I} z=;qBk@}QUf@>-1tG%+iN4+Tm-(uYGQ)cyUtB1mm6=byI~^yfybp`}Hi7V*0V?gYUP zXePL5pJNRl(Ej$OeN|&XzAkFr7z8^3ueb_rdWu!MlwBmV-t`qo)JlCPm@EP20vK?q z(YLQC{+%F5LsEJnC?<$~FSLCoK5jG>VB2Yoi1r8t-A?fVbC*|-9NS3M4v|!MSC<@! zL9!Fxx0(B`s-m(U@!ikOu%?o*m z9B4r397}3y9)iQS16Us=o3l*R#Bh=H!5bp*;qQu(cy8D;n&Y7D2GVSh7 zT8{VE<8+qt%JOGwFY7$KPV~QcFnT_v(6>?FabU7$-}OF!6aXKjQE3rDeSgJ7$R;5qSU2{d61 z`VnG>00tyI|NZ5A^Ty9T%Cpk|!02_e>|bEUIRj=R5E_{`u&xAyfFgts-h^a-;h7x2 zJ&p0I3zEvH ztwYePkF})rLNhrCMkxvqHkuzjyXoHdRxMQLSS`zl01U5lf-+Vs=Q=4p-dlHe7(H7;1O_o0ks6=i z`)~r#0(bh=+)vD?2-TPNx-NzK=o`PpGPrkS@}@k?0x#wzU~H7xA#E=EB?nOU+Gs#KnhJAeZ5UaJCb#JKvb&ro_ zYw8BPWpS}iQMKS_53ej^HS2*Yd;BKu%-1(9GWZ>nV1Uo#{R}=&Dz~#}e=ug>Y>GJ6 z{_EM-PKpfID+|26rKJ@>$Lf^X;l=3GIyFBM$25JV@sya#s{>FmSlBh=IZnSbR=S^w zd^7o?628wF*F_p3(=lf`e0a^VGQW@o)o9^{C!JX0?&BWSBGAcu0^eGPN+oSypfi3vPq&I(!2;+|G!#2g z4cZ)uo?gM^qjRn4Xt2B|JQ?^&gH2|RV8yESECxY)G(V%+{h+msLLlWeGaK6#G%4*l zIoLa?M0CAjby~4y9+9>YCD($5^g9$Yd+u;DGn-OMp` z|2O#%(=?ViYZw7o5z94@N;L7P5Z-$9UTJWC1^t7|?It`!>`&%Icm(u96r%*Sts$ip zo3y|%$Z%GOEA zYoEC4yG#eK2)kHrZH@Me=ELBN2b`i{yHcoJFuz8~=t#1}e(pWCfQEKMfmW)qXQqI( zblYq77_X9Ffq{V+!5_C08CzymORozM|NF7ArnfWJfs8f*9LbJ$KrB?(#kmiw+O8>Q z=3Pn+e_X-e{b9Xk(P4l=@s(JYM6Qfx9MPZq@dJOSjHmB7d=?TAQBi}U`xekW&}lXn zjx0_nZyXx>l9tflwp~?b$nEaY4oe-)wd>wJ8Q8ULn{}WcS55y7)SyRCtd;(C&m><{ zh`CJIV5Gs~sdL?mh&?k_FOPj_bNO98Htw;y(*Nw?!yjfBS~s)ZaO$Zpkkba7SiGik zELmUHI^dV{4u*RVTHiUnv$75hNI^6*I%Oo01S&YeaB3B=i&6qBhKb%@{XA&^jBs(# z5AL~CnXHk(Uf2jOvw9TNrJ?^=48pO7(~+mbJuv@26MK-1xVv|=e&0&hYt_(;& zrW~Ys)krwOkkk>xcrwbwzQA(dkTdw5Z9l?B&)lv(jy?w7iMM87cX&_isafjLS>jEH zx&%U2!4E>h!kYo?Pdae#+ZTv``U?rGT3cm-v7v|D;!*avcj*G;lO&o3*(qhs&8&z- zh3pCytE`u9I?Ot99fP~DUPI$_g?rcbkK6cH?bg_x)tZr!vr^H0ZHR+~#J-zpR`b3e{k2vf=2UqCrr3V3`Z}POu;l> zgAhx5MDFYG@7~iLsf@80hTnu`6(E;!7N0n?wrp_ zdG(_(iQ!)gxra3fx+HlVl6IGFFOf4SsD5eL#9M<&E2CDjZ*^Sed!QVS&qw13Y3 zvpb_kHL@bcNP)q9QSE$Rmc|<`x=#y6*J2X#ovEP}WDIkb+!fbVJjwUsZGOiy*cLG( z7&A|SGN2rAnuvC~)0Y4eW<^`T7)*SxfFqK=%015&$5gc2bHxmSCVPb_7XkRG|=+~M|sm#G}G9eb8{Y^ zAqoM^S`n}BH~iiav>2eoL#%G_PFrA^y7Y8${sh*F(2~{9NpwHxnNwX(H*fZ$4aVD~ zyxTLY|7fu$?md8RBXe_Q)Ym`_^?-oKwGZ*V4xBZgT#I(ci=UJz*+e zUi)(-oIC$VVZJFvXXDnlDg#h{RsQPDWGauI9NaMTLuhWz+jQ%i?k_JYmttc+rkaF{ znW@KT%Fi*cL+WpPdu~B=g{BZS0Vcs`Xa`v~z3mPuo<>D|p7$n{b0t`=!AL9lkAOEt z_}W7%Ri$iog#Ey2l+V{%3+Mp%^DdkMh%Thj!@AyBuc!7h?Zg?}-Sgn$02r8Jub|m+ zY}2}R#AwT;8dwPk=vPk(WE|(6osbSn1N8)X1O%08s#P>-H)UgDLIFv$!$A*+n1G#v z+TvSFb?#o+9(L^6Q`s|Q<2E;R&mCe!!ca}2QU)WZZ@1+vAO?&u8moo(GY1HligOCO zpu+3NZtx25g7nhV*XS)1-p=@Vrb-0Z!74$@UOOD#6ApIl*UFTVe`^6^zsM*bRO*3L z0glW9JpD{qAlLa0-H?=$V!DwO0{{;Uw09~A@+vqp<`p38wBTDz%s%P?Ck}ZFJH)T6 zj^M9-t;T)yfz+AI)C~}>{TxY_ta>_jCo~jRCL5#0S$iE_7>JrC%eW+=-aA%w|AYPa zt1fm-=PQef5b5Hyl$9NExw-DCZgy(;p@Y=y>~Ayuf?C5wK1)xX#_1u^`wZN820yb- z4Lf))Z!i$PIlOCCsgKgdVh^$V8y$M8!gZpe)Ki2OU)V~ZPnVAnFZbaX@kvPVOz$|s zzxT7j)aAh*O{SDP4<4+-eKY|jN_2D3`a=j)3Y2kORZJZ~aJ}o@G(`PB8R!ot=azC0 zA`ft#cRP{v*IDM9~PrWT%QU_IpDjJ6bDb#jwM^jIQ!6m_vl% z6Wt6^XP`4g&!;>3vMG^{k+B51oQVIr`03Ghgw6#%eq4d!*9^N0x@wH8KRLqK9!iAQ zLt=oIb=TCjSZ640#X7yN#Xy=wr8=BidPen7Nx^OEA$%^tPnv;c*a>=GK9uPplrM}% zn3AG!g<+bOp}JW6o`jt)0G?ScAz_P|zpg*2roq zqfoGDy@YfFCgTYFrWQ62DA>tC1?VCS@OcDi;7hkl{Mn#wP2d31ZU4&&L>!}#5w9}9 zCF7=oCqYt(0eK!~bt~ZyB;r_}hth@c`v80eFvDpCs*9W#g8Kh_{W!YYIM95+b{lhj zF$%c(;=pwMZa1w|m31Q6De#p5f5X!;UK@a~{5i7E>dC&24)GiuPC^Vf&8a)Gt~nS( z^0Jn7jlR(_U)vr0t7I01@0xwBGlSH>M#gT*b!PnFoAMEB>RFB%TAD44GzzCh$8b>CsyMNy+d3m@)cf&!)VQyaDP2Js2ms`%#>@&Sm zzA`{&R*ok7+7J{F%>Mp!murvU46@5F+aeRq<;(nrzqf01Y~Sv5{iobYc6N=?*4PIP z0QqIY6c0l*AZKGEf}`%pIVNBWxp7bz&I3@NLkH*%=oQdAJ7Ca9Tf;c|x#NC|~VaUxOPp2TYq|Aek=Xp{R)Z5wuT5sh7O!4@`KtWdk77X`;mrlzND;CO}h=OTU&4bp9Ye>w{b3q)6)9-B}(c@i_s9YUBcIPmW( z@)knhc4mlurt{Xq$xC)-6r#ihDv{7m%frJ12@o;M5%o=#p7NzjmkvNak8wrhblkGC zTu_{ow0eM1#ue;P4(>Sh1mE$PVhHQGn0UM0n^%)!9J?m59F?NfGcYu~4TZo(yhZ)E zoda19px^n3lm$NaL#}iEp$M$Khg?2N0gzP8c5-G7l7kH{U7|(xg-g=BfaB=-z}}Ns znmEc+?#AW+T$JxD7KftRKV?8HNy}oj!A>2;UAw+4lOU z)e#a!_4SPCBs{#l?!0=nLqJfl7U$@!{jC>&^3VsrX1%v`d6=B!Q$i+ z({Du_b665nz$F7Lc>yun_1T}>LFnAW8iXK&)u;kd=N$zU224-aB-|*41?>d`zLI{7 zH(}xSpQ!z=xC*C&EciwFKsyNDHH!A})TQQZ&DFB`7K?MSD|B=^K0o(PFyC?E!`YJ0 zk&Ks@`WBN@EbnQ55)G4;+xb8f%M1VDJYRhwBN6flI`_>x|a((lEczV1x za(ybf=JuuX(ZOODCELl_xkbmpR`6K%D}Ub9=(oN5MD+8MU4=EW%uCkKcC7fb_-?gw zNMBK<@>t>qZaTVLWvHP2fI=~29=HJ&I>_+8DaUCaWU_{)rVkJaNr(i*rf4A=!+e!L z#Q^dzM5?+M?lbsaor*KhP!NcCkQ-&aGKMx_<(;Ycef2LYLpNOQ^T4y4`&2>y((vdC zDk`zJ_GZ&FbT!d@4*b7@O#7PwK#5?>bnuMwLyGvQ;Z+ZD2OnWVFQj1w9-PxnLeoys9!;jnk?HkUG zU{Q>xb#QoYqD}z|MCjqN911q{j=6^+h6So`3_qO<^KH@ z|A$}Uzx#j*y_Eqyz5D%xqSZ#GpU5QoHE9WkPfZTi8<=Qpa$Qb5H)6T%2E%!6?Te^} z9ol_XGVtcKxzux*s;f2J(DYN}J zM}9}?!Grqh zI94S1Ot+rkrN@aLYiVg|uiDte)syQ`%fCgWZOSq1?CdY0$V2F1J{M$B}E*Tw_cs1wf@EF)f+Z6hX)|U>C3loZ|%XW_UTM9%4=ndv4Iz{&Fw5Gf8pcI=yh+%&w>tEG(dN(PwWwqgoEkNwut(gu>L^jXI4AX~ zYKtOgO*vgbMLZ`0*9!yqTCUpdyB8R^{g`jv@e>uU?GrQN1@7yv%+8rQXq~UW8?13i zlEPT7nOtMe# z1PfugLY&jC@s8Yt)2_&*@c~&`^zq{gY&0Zc41RtJ1r8MV;9y25(|0(44K+0mxf`^% zgq*pDcv*vs7a{q1cw+uHo48bRN{e$kJ2hrKa&TP+z)#et$q+*wk7C9@8dtdoUN+J?6fi{E?XNYU3P|)p>KyL~KOGL=j^aHM2kFHcgT?+Hr zCX~P=4Q$-b9;_*Wlt70=uznA^Wu@uP)>d|q+67?R1ma8q4^jGrEED2n&Rqhy^Bueq zj>p>oE*J9}mXwr=nT{-nbs+x~@M}YJb8lcaJc!{yf3S91f{%{|MbK>sFku+2O}Tg+ z<|$6>L%|;$Q6+xC)&&+grmk}DatorUu!NmHd-fg(;KGWE_zG4v39GgO6eN5#%|KGR z@!Zv&wm3zewzsJ8diW5a7C+wg7up{1tbxHF7*CHnFjB}C4JM-yg9h?WIe`A(rq9F! zMEUaNiyXMEUPWYxsR-?t!5k!GSyf0mJW5#{t6WqXSz7i=jNw6?1T2+ zk?p{o{m#F;WQb})y)60sJ6foiigE9tO7jHmKn1*{48uTYsOlNKWIVjQD`9sp1hY%S z!NH-EdqZP*gOvlkL{z}$uQ#7w|l*6Hc#U54}( zX762$(it~@?}b<}@ykR2CKk7(Br80aIM!@84k7hKywyuU+F$DG3ehQhq6OvQ=B9!$ z?>4taQXACwUXWsZ{Pbz{CN7y`V2xL&hcT_v5-NI8*?ORe1~7rc)3+c5 zz6^5C?OWFF$rC<3G2c5nD&WXu90&HJ06P{yf#4@!Cn`HCvJf+jC+GuO-mpA??@N6> z`ig_(v_ibC^04HE`jLB47tag>6vtzwz4ZCX8a!+arR}(#*x-U!9;iGsZp|}L(GZ_C z*z|Cjv!-f`K@qLs>;f~lz#HD5Ut^T%R8-`{q%@cjY}i}0X?fvp+{p-Tm1loCpUR(k z)d!c(p|68K9$zybdO*%?-I{U*qbNE*j;{sJ57P*X(jP@VcU$hm(}^ot59O`zz!^j?bK5q# zi5(zAsHtvKfSz3Mw?&%TW_tQNxJ}f=B=jZE<>n$7mIIJLV8HRt0mt_q zJUAZCT!?wmjje&S)L67v1XcA^)YOy#v?0)t@*PRMlXarc^JE; zC?{r-TV`hFk+DPe-#3Gz;0=5l7PfPmR_)};4cKG@B(fmAP*hPV#wZs?Vi$J!g1S0- zRC(&Rqj4GF`qb4MP;7!ZyN$8)5x=B-fgQwow)+7Q5@I&cpkYF-WPlwOSdb9!M-MDL zM>I8=!QN=hj9`4=1A+vOs%dJLnLcx&#fDt&(1K1AYat^V(E7E!8sZHQ((^-P?v2!! za)(Cjd0~$qlU6kY!xWcJ)+XFWoXoir`h(l|@6-O7?cD@5mF_^xE@gl78TcIzzyJa7 z`;Ik>kkYWu&CQWg3Ha4rNWV@@tfHkY?CjL9dj@clXvM?ey2eprU*JrlxDgGmK5Qp% z(QT5W3Sn9dh|b2%K;9~fol?jn`G%nU^smZT;kN6{D+A&Kb6v(JXnr5>4qSk`rKLqf zFMoe?nb#sZX<_hmIY|`0!PK)RV`I?&;7FalbD8#+{9${t>d5hg9OHRmultehh%35- z>XP1Ne#$>ObSYmN@Bju;&|105mk9~$blU@1Tl;I`qyhIF$3{Xh!t%*{Y+7jC4+B%c z!p-NDaD(jcCt_Z`;+B!ov)yp*`oF|KJFuZf$Oo6^>rVdIWPD7o3&r zpZ7VQS4dt4qlr9u?U;**Lzzla8=ilChpMYh(qVRfE#HdpiM_h&M-Shoqs;s3IwDKJ1q4*qgclUzueZ$|s9?ufoz1uA|b~E!{#m3&Q(*nUY zUrw**;Su3HME^W1sQx&&3C2AWBy*$&IT5pGVjxeHjg3v+x(+*zp@qeIsL_-*uc3f* zrlA0-Ncr|FX2olDI1-QGvFy(M`mMy98GNem_M`W6&b3xvY0pY)lXH9gm<=dUOx>v` z2S}w@9ucXIJxEbmxhQ}6&mN_(;DT5Bp8%|;G9EivUyHf>cl2aq3s9ef);0Rl1FXq~ zp!4EyfvnsrX)B+)xC5G1C<7AuHb_&Dc(W1fb-*@y%Io;}LKJ+^t{y{jCS@DMl$eP` zI=3QdWgUnQYFXL*wWe`F{aAg|LY0Ob&dhAEFuO5lKJ<6zr@Ae7X-*e7+6$bW43b#6 z4!q{(ty_H{)g?XKGnbk3!3*^}ZL6Q+L-FbX$)Ul)2P8)(rX<2d4@ zAfJdY47v=`3scy?keDclSKkFY+XYB>Z9VD+PZaZP?M1O~-)=Vj`~=xZ zF40dNaS8+ag&fH%O0EH{~@kiF=;Ga=Gbk&v`K+{g9QHR8GcFpNx-}Tbk!KHdb6^g&3#F z;Js`1Jo2mk^r`CX?bE9*nxi}uwPRv$c*|1-EwiUOAL4WHR6BioNiHCDUNKrFCjFq$ zf|=~;iCxPh!otFCUS9TJ&SH+P_9jXSo0jOG`_IMMMt6yoRF+HiFh#53%x* z3m4e1e!M!JzdOsSFD8GSB?PDIu0>l8{yw}+p4q z^gHsvX$)2!GsM*XW;f|8Gqx#sujnJfm^ zmO&BI1G2jmhd(_;>2-A5=I5v>(E;5)yY1{#g2kdmf*6SjJKXbx1QMEoRagGSAA}OG zK<*SlQz07$f5k)4jNrWqy9LuzO)Tq!!^79SIPHOP0~t!9`a{4o;c`#7lNst+*`1PO zas5D2Sc{EBR#G+RS8s7a*!U>xo5VyD&gAcz8d>sEfR6!X+D-zi$TbgQQ><%bK74Y+ z^@-@7aIyRM`3?sT*3j%+$j-}PQB*#2IQK?!UZa=2cJXNqn)@3n%lMydX;2LJQa{J# z85B3l{wK9!c|AG{-CL9WE%RcUdIjHMi3H^1;9eGV%XS$DiJ>ViuRb$B@WNR(zGdnh z-LYfG(6zt0s$|ftTG2_1fvbR`)*sPnh*>m()(DD&(BTIF&-HSyZ@05Bb4#_5`w0)w zM~D$^P`{&PfNB4I+*SRc$LcroD)SGYn{sYU(ABo!_x*}@>iqW~ zKQUaM}Sc}Dg+o0&IKRn^{xCVUIJOELs?s z45!H*-@bk0b+p{QpxZ9d!&u#*z5|On^p8wfxIUt!_JnOgao7gaODE6y{IHcRxBqGs zA>d}HV=cgs&{Uw9c%yE$te538fHT0=hx%(A6)v_T{Tp2w368X_F>C$TthQFz=(=U18g?(BIlop)$d05{--$AA+Mkl(}R1gV|28Am^)yN7YxUnK**_|n)| zmT5QGH#4n4^}_4(=7b@ca|-(UT%=zFSNRKA$_eq@kVd@}Egzht63&-|{vvdjrvIM{ z_>$lq(VV}w505}KL>dHWPIk5=?l!w}gSe(&tyiIyNYt+N-N2lmmv^1D_|?1nSsWW;;rlQgrB~_66S6gEX&M zbb016x<_7YMoa@pBp}7Kq}v29V*t%FBj?MPFYm(JinKBs5*GwsdJ_Ouo%~xIsN`5A zZF$e)Y1Jp$jcx}&;bWO2FHHd-wMt@BRiW%Ol%9li?`65Lj6XzGhF&`aL5Zli}VOB zcW|ghX^liSF2c08{h|}#>%(elbV%VfymG}01GfiwCqRz}ykV!+ZX&$v9&XwSf;PZ&g_H-O-wnSe z{4~|n4x`-%zWD_d`0LlNyAZ*91e@VE8QTd0JpvE7HI^5;-*H@r^b|<4I1J2^ERm=M z$_1hbfYY6NB6QVmQE|C)^%4NG+z76{aN$DJdou>q2YcGL>en{be z+(zeLKKcn&a~7{-`EEM8+K6KS=FyF)o3-?RpgtXh1~^fh8EJt6m?pk_%J0>*R*RW& zW5zEF3Pp$uasgmyO*c1a+UfWU@$JOm`cm39N;1UpRpI{Q0F-j(!? zsnxI$9fnL25a?z$whLyPX(%XTXfIy8NTRRE-2lF1Xkx;b_M;NxlMb>SaEm}S3UQz~ za=eRyX6YwXBVLXWx}@BMT4p1Lg?nC}3;@lpu`%7T3G@SW=(kZa@&HJ|LJgxqqP=V= z-r$o(*g6WNDJ2l|gPEqLrbg0aHx^BFMgGnEN`Qo*-7Z9QDB)$dY}ry!T1rcRiMEqg z0PehDvLNb@_z9hcl@1OL-(Z3b(-xEwmt|31Rc$je zV;T4U)k((emv&BG<6GJf3ouna`??^j(QbLqLopojr(NH_d*Fk1<PG$nuOox~js2)%38DhWqmoouV-J zi4#fOi@42?)_S+5h~7S91DdVf#1HXNIL-dM!t5e zHrujc!%{%$s>hD?>}FFTDA?`b;N|@%9S+%@7;QKrB6a=n5#!We9V3Uuh5MFux&lH5 z!H^M=h7(l+V969PY44;EzBvp!N%rDYTJ^d^@pm8`0W&UgByEA43sp1CEd$-bwykB$N zBMWI`?_PHkfz}@|u{P%BCJF3PNCGU3mLI24>m7>y<)U^`G#p~() zSy1z2>x!$riUQ2&qiP`g{_~Z{zESRaxbx!5ZQBj1J~piqf6}LHted18!)0P_bout0 zpXbkSS>u0q{B!&PhJUl~E&5v5zQ5JcQ$6Y~`s7!n;HoS8_V1^hR?OLUUqMO-zrj?G zpa0wL?_IUppT9^e$=Tj~?T7&;zrSj!69&S@zW=nnbJr{`Q6unsoDi_TBPMzo3Y*w5 z$d(?Kh+~HxeY*AdYF%#r>nd+;bWt1vJ+d^0qiKyE5}!({o%ByjVMCQjz+hX}PaeR-6(k6CW z!1si10HD{_-MwLQE@vQPFL{$jES;4aHrcUCURLk?0~3$M%er6n^l*>OJtr^R-y9m$ z@j(H&K+4y`nCwK(7msw4`W~%%cN@hBs*UB4wc7L6LCOl{RgFPPxHh&?S6B< z)aI{T89(fu3l583;io3YxOZMK*%s<#jL+~3)*4@b1}$p(t%G9=yD)?|5kx`@Mj?l(AL=$(=g8zo6s)2d^PNFOHo!BEp}Y)V#O0BO z1#d5*I@SD@0e1xr1qZ8wq2Y_KrDRS+T$VQPCWnsOZ&RDzn#+0(QxE(yk4<>GWbInz zv%PIT{2k}=6QfuPr?&VS)j2%vzp`=bRu-^J0U;qPS1>Ye1^kT7)N17XPlK1RAN{_i zFrwmHqzs1^PA~Zh#!wU29njTF&z@}tY6rWb$mD=G%6vF)R3ICPMOlw3vkgK{gkrGX3ib~&pbPe6&1w{CmoZRH2}K&hrWEm3fx48ZcizrKwlWjQ zH}cCsWt3M`v;%_!IiP7l*el$0QsY7-OkB)B%A9A3eG%ocGJX_eXzPucy8jG3eZpph9Wlk2j2`A`Kxm zNwNuL>o7xmIqc%cPMjdg7*gAQudOl1eeMFwGF~ZGF6I;oZFTK$xMscfLrnCdLU~jc zC6r%Y)zXn~W`u{MoHJ&Chj{Wh-;`WS<^o7ofAT zkrvo?l(#Ejvr_8JQJk4X+ZQi;}+((vrhZMe(N%QK_?>A1Vtn%$}QM!@tsSWjDB+`K!^3 z5`C5H3SMO0)jfKBq$mAH`qtI!JKEaU-jO-8(tR3)&L~H==BnCcd=6j?_yK6CAZQDL zh=Hh>V(~wWK1{;-+|%3p9mdu?AQwYZ!?pw^6Gh)JcL^Q^_@`F_R^Z%oaVKI5y-{wK z)&Qt;ZUPFPtG0r3o-c0d3$m zIoQg?$jDe?DFZnnaMwcIymW8@*r30Ioq%LcIif=nIXvcXTp3|TA`C7vO{!uP36D&@ zahY&ucQ+xUiS!VQoRF4SBOOpj85}_vxGkDua9d% zzIT@GXSW zQVM2vc7cLwXkEWLTrJ41ijK3sLL-Y26x1U9Z;s^b&8oL6zgoNUD@`zQa#BQ#O$5NyveKlGVjF)l=!ms^hoXG3r0X1dSq4=VWpK5}b8!479iVpeS!gc0dsb z{dGUd^9E_TuzUa*d_3q-4HAE^_l?z*a7NC zOF@pUtuU&;asZ^fLq|u4NZPR`s439DGXhv5X-_!eZoI|6f)z0Ia8MhKJMx1nUO(O@Qi%{s0kG!ew(rBLy7=bW1s(P5};QsoJeIz5Ry$ z-F+JiCoG2V>~dJX=UDwJMy@+?k4?`VyX{uM4EchjgMG*&-HNT}zUr7S%!&>eKY#G> z?S$j6w)zYPj8F?;njKRcjFKC&t=2PeQ_8tR!*ovieYGiM>~0u$ZXq~aNEhMvBkHWxo1YkIRt35}MG__1dDHs*QvNhR$R zb^}`lqc3DrAZ!8u9&=GlK;i)u{{A zG7Vkbhy%AIaqE-bU4e@O-wnl9SmxklZ7mqWFCPNZR>OCVf@WG_W20H}JKRQ4E-^LF zGx4g2C|w9Ehi5OpuFiCUG9UW-wG3g@`1nGA{Vbo%)P$S>bepxK<4Oky2dFy)#l;z= zom#?6H!#!3`|hKVwpi9}gcgungqf>LrLhYf^p>kssJ#*N1lfg#vt`Qm0jUH$7AS($ zQ2>xJq{NW2yn-j?8TK9R`IC5hCb>`)L@XD=X4^p&cgRpTYJ*7VC~1j3F|V=Cu@SvA zFcsltLJ=4a4=!`dsg7bsP0&kRAVwz|T!;#{>dnu{#*@3(b+U|%G8}4to^aCQ`?ke} z!NqG_J5*>^ua-T3hx*E+3Q@<$`xk~OZHJr!78_?D8ohqx?{5_4b#u_B>G8w(YI6(o zp}Nkpk=xHih6dj)WlH}QU=g^Ot5OmR-ViZ~5kIOvfBtb}yFqaEI#ZgZE5q-J(1z2V zFIyT?xkeH&F!gW!yb=M&q!`Bp=~Pt=S+MGGLO+o%c2#8%Wj;)agM_UZg;YJ&7s@aO4J zYv@GEupwq15lLLI)31ysp^Y}TJ;&-xm5e}U;0I{|9U$E$j-vwvJL#*{}by4^39rPP#XJgYG zF;Z=D-VNyL?uoRyjC)~WRACyl(7@j9s32Z*#h2OW<># z1Gcsu_D&o1NRu%bdd~5Q*dfUS%(9+<9Rh0xU7bzu4x-hyNOFUt8zRBy#&-k?(3G#ity*3T;K|Kwx9+vM6 ze;QC$udzmph915I_zfc5%+=K&AM9+-K23wu_5-$>&#*OUx*HhWXqp3l7IELilTJ+p zZ=FESXg8)Xl1$V=8zW&}V--qWiO0@|>1nr1?qgd!et<&s4<%Rcz{o^`4T}(IMdJQ2 zoPr+Q7cry&5RMNK%avcX?|GA$yu6jD!af;xRLWsyUb@jKy@{W)%P?#_2x|~)O1ItP z2DHa(U!Ay5n3+5zhmgfCrV^>P5+haX(1e zj2l3JS)B5>X3ryV>c=C{4=gMZlb;+io4=J4raRlM&(-g~V)vZW=!rm|ni@&6`?GiH zQf-ZSnh^A6a-hsnz@n=g8PZA7sjb<|%8s(Lbp>!9vv&;1-E4VF>y7&_nS+w4Ke9(` zO$LV9E5wHy2Oo>IPQAYM!OSso%+em&S!g#-kNnVt6Z_*u)5&Z0m|RHFSmHzxfUd%E z0jeF13Q_o0mxGZ)f5`M|I^*Tb&v2rXu`S0iZ=`7l&AROve%-hs;|b%&dc7VfcMZ4= zuc!z?Q4oJ{{v<@_fB~23>gtwk%R-Y~lVYkT+KL5bG}2%gvVRkaa7d_zZY~H2l5uwE zr@jYbD4%dUOUthsgvWJQhM{JA2o?t?Ohh@mXdjv!W5;h6me}A3B6asoRKnxO9f0h} zoX+xcaW?>B;iL#_7$ua>6z6KrTz&Gb?p0_f!41R|DYO$SpqotNcL!H@46gxwoe} z8(c^Y)n}rvAahTM_rRK-R(_VMt;ej7Q*mrie*nb#!R+W3T0I`ClGD|TTwI)O4ddJ! zH*Y2aLTVcSLo?l@0Mah)dFQUM-{`WJW3w7J$7AOsTl1AOP+u_N(N=-fleFMCMPg8# zpC9Xi`fIm8{;0X*vdQI3muLv`53Yl(0iptjy8Sek`>Sc-yzQuLAr%$?XiFLy1d$VjP#X-R(EMV~ z;}s11z=J9Pa2z5f$fQsDm8DxwnnU7a4HN|+g&gpiL&uLVTfO?G)~v-Xp$x z8Bp}nv6&0S578K6W?>NCZLTXX*;U6p6E7t=WDJhp-Ol{Vm3RH#`#}(|LXk#oVj_Mh zF_npumhR|R$>FcGiB85X+xGiZ`VH6dU(F1Q!maPi-2^{Z*C^C|iad1OT97Ry{YCnF zC%c8bE{E2KNw0RX`;`YI9C@LYuc0s)W%&`6@=V43uv_;+n4XwfS0uAZB%A6D&uULe;t{9>h>Dv$G{lOGiWSWauXiwkq)=p%QV-fLfpzOtoyF;BY2Ug||9*xR65XG92CcM7 zpUNuT7XI$YIn@Q(GeCJA*c{BRm4T_{&-Iz|{6Z(Ge@NNf?W;qevc0=ZS7p=zm#)cu zBTxH|eaSgvA$`gIj!@;`xd;p=hHDQVj<>HJm%ocet3cQyh%~L|riS5|6N)pP*t3|k zLCBqB{x_5GJcUcN8uG~yif5{*70@vt^B2P3iOZ0qjiqXKFLu`6Q<28Un{9?uvXtj? zGi+Synf?XGz8=Ea_w;;oYD37#ZVvK#(u3J}zE&XbZEzXPa7ISPf~$gJa7dTiuLH(4 zT=qV`PSjTEdjI-dBPC%`|KwzQT>tN0H!#pX$bd#uM#lYPS&4s0RL-`Pb?ZvXN_;Xy zzH__%z9RgNM%5(4OINw?2#Sv{0TM=D!yKfdq3uD{;eXVnnf33*C9l(B6#lt}Ge0kp zZ{gK~wB@76vDw-C@75ZMV{0xh8JDfx*Y!nlm&A#a z&&rek;@W{dQm1GBm%MZTG_DaQ1NOiZ&vy#BUmBYJGtN`xf3O#6)44zYr3Lu=8UHW( z=n_R>^+#O(Mf%Y*`ogThxP^)(ITeBBpjQ~ zT;%uaSata-f3@-0`(UOn9N-XbwMkV;DY-;_qUPLe*4wIZL2pIchy zIe(2!O4im?g-fQvpu9_{s;7%`aoimfV)5U~ReALi+C=>v{LEW{sw*s?xE(o>NxB z?nj__ZIF4*d?mQ|wFr5#@ioZXIS?i+x=i4-(5iPUeJIR9oLB#vA`NyVY<8f@>pVHu zaGvqc^TATfNQewYD9nF<{UtK)w@MA^I&qvhaRU5qY;oysuLW6<%R`X9)oHkaVgQP9 z{^`hIYi=kyJQR~})f)c%`myx}?gB}h^H%~c=;^6n&17g``Rb}+?&8X5pG#p;Q6{#( z4rZ+9vQkyi^0dsdUb|lB?xWAJdDU+e-JE=L_~Q1QpB|5%KD`FKMbXf3^qR;bnm+i7 zZT|9aEyrEbUh4jo(yE&d9>-7-P79f{mqRl5R*c7*n-{hev%i)Jj*z!+Qqke!zx)Za zRQA~h_M$lS$Ds=mqtZ0uXifvMJ9t9R;kK12@B(}BDHvMGCl?^IzrjS>D%C72;mG## z2Rqhq-Q$w_w*IH9%fJtxJZq|2nb(=ceY%Tr(l`IR-wj1eDA72BZUL?7i%l;C@Gphz z(-{B21Y`%4%psT|FlPO7&G5*rsEAvyAOZngfoaHmh(z9rb3J2LFm;ab;DlJXa)p6l z-6+R4nPsl{jY*Rbc1l}E2Mx|DVjRQSNAZTj6@X?O_$K5*_#u4)`Axxao@q}1zxlS} z8%!fd2%wMr@0G+sH#(Z~e9NCZ8R53UPMD32W3A&T``pF;ozBjqZS5@sxvz^9%B8ts zp7*bLc@{P1xfLUWp?R@X`?H44=3Hl1SjIn!^GRB!c2RbPhdXQ=<~hzw&UE>;<+Uu* z>@!e9)|+K~PF`o3Y0T41TJcE0`M&Tj}YdhK+ockK9&ZjQWU)idQQqk_9`v8SHu>T%#eHHz8UV2@*oY7SYH z#UZ?6lY1`^moy})SVV`=OW(aVhSrVfV~7?K+ERkVpgyria0@_M!-Vl@S+`ZVKIB38 zp;REX4AgsYV0B3B*x}jf2|c#R(OYkl+UaTL)Tr3Mo5mQ5g`k4jeI`pC8et zcmwF85J;KKEx-japhZD8C3zWqexw>Qvap(yzi^Jv)Ntkl)!doBmWkSfA_hT$m!GQ? z>@R*sr&)NYKf;4|-37W!5^EYOcNELS^%s7WxvIEg+r%bY%~{tslXD9a&8N+rY8=)_ zPre-x>)uuA7S%1={h)l_J*r#hzCR?fL@Z65ieNgRE1|(8deyMUJ3Dnzw}B2KS~AFh z!n&1yKVU|j8B1@}Y`ym*z@=}vanN+Dmx;s}uJ-$jUl_7`)M;BTZ5Lou!}QD(E9fBh*-|DsB4TVri$LsdmondnqGB9xl4(b zPwJr&?r`s`v-cEbs7XRMrU+KO|+2?)x zNdHXt9!!#1Dl^^-W^jcNZ6Cpb!)b^GF|JMMLaNT3@WpQ!oPv zHSWvx^iYNIg4i&?#p8#wjh+n0>SF*4b7$viA>5+jx#=?zp>iOVQ5)JEJP?8y2*RL) z_%_jUf@-hY6^I$>Q9d4i&LmnXUMFMEe`fQay|~lSzVU_K7J=I(_#7W6YSUK9yDUaN zN=S%!Ebg>=)v383B85$FgW?Mhztz=HII8w&%p z&KXQW#h2(g!^Y>&UkKbR6TDd_P|Yk-d-p<8*JR_3Tetk%scfqn-m%e5^aUgvi&~Fd zjgi%sT{JXl*`^ZbWv^c$|EsjVG)Yt0kVZS#!n-xqq9Atd2MOB=o}JRC^5^Om2YH{G z--gVtc=}qg-P4hr5^=k9%LH4M(s2&gx!Lizi`_?cEz&MDvh&C~vwqN9@D-Q;m6tWz zotKpJFxW$(Q}xq9GcY<@KVM}rtj3DP@C1^*h(^pb{xR;Q&RTh22nn!>(j*xLgD$m3 z+9RPB@t_@An3S=Z-U=x9)-85`WJ>{$UYz7m*9@PG{kx&YFC;lA{q#(}dskU_RXg^! z(pl%a$P=@B`z?ewthfGg{e4>cGb#@`kHE{5YTG*N7UnXqoC%rktcd7p%3JnjgKlAR zg7&GdIk&g&(+9)!)<=kRW+WY;v8#++Y@6BBbDm0Ko8;wlsZsm0L|LD0zIOUY&#*!6 zr?Ei zV*y36V#6PPHJ+c{!FNA%-so^Mo0yS;)o=h`YH`cP4*j73fJlX|uT-c?pRN=Ko} zY4JEE*(;2niIE^aKWbRbr|0ci9A-B>?dcfxjH`?+sE~IQlv~S7OrscwqMxAE`RB7h zqJ*Hb{~@>s2%>--MPhG2<*6jRD#^$ns4$WU3Ca-Q&0n92(~Chw5Je=`#{EJ zIT-~HgQ|IdTg8W4k4yjaK|aci$nn-+n`S@GrNg+wUqz^xgSX+~JwMf)_HZ8P88{cl z51st>mhHqs)-v4(k?qyl`Z$fT{H76>>cMO z8}Kdhdj<9NBCVsJ=3k_BFvxU2#_{&cUl(`F3OtFw*_ER`U*2p`mW$p98^XuKB+3S} z-&v6Pu88o*b+sV0E%Erxo|G6VIfcooV9C3oxpJtWfd?oE`J~HOL ziW`aeburBO$*U8qRd}1C2Xp4wKd$?|zX=J=WEn2jWa=ILbd#QoYkKL`J&}6^{(LKL zBz}dO@Ydiz^%ZbHtN5>3nL(100=LU*53Gw&c7C;ji|fcxzT)54>peKAE_~cx>w45Z zYB?HZ7ZdGH9d~0-IYmsyok(J|H7={!1)fD!iI*=pToodt;^f9%RZ7n z9&+GaFJtVCsJ28EDb5V>{cxtcnRtwg>)!Qa$CWD+sa5VZn6LfY`zEePghVgwEw^gi z)2EEewuUYye~|?h75<f28*q{Ug06>7M~Y%l;7{ zwCc1b`%zn4!y8a%<`x2KoxTW?CE^UkC0qK|GqY5AQPlqvEA+qjYtv=KR?|}X3J8b@ z@}*kJD%)!<|KWUB=DtE_puhk6F~$}8_2txR>mQ+X#D7PcH~qb{&f33x;va|d3y8Ec zmWw~NwzZyA~cTZ9IxJUBPpjXUVV{cj{gE9~IIok32m0zZFof2%t| z|4INm^ZLu>D_qFG1GVQtqg?Z>j&jb?@{&*NG957!(Y~XGkc4*KgX>qt&bkR z)_+puaD6>9tzu-Y;jXpx)Q6v+#4xLZrss5)@6MZi)&2V*i`Lc12#H8ZEvdEdQDpwn z`{Y*XE=A^;l*gfCFTus&K=6!qM7crApk5^{c41t>JJasyTd#>e@#_ zn)#(Z(P-$;ba(unN3YiruH2#DBp80i{Dveil~wX{;ne|&@krx@MZl@@<{s!Tuwg_f zUMUUrotdx&<-UyQ2eI05M3;Vu1jGv6JgLs&p+Z;*D~I@4Hoy)@z!CH{d#Q0w5JUn4 z4!)vHEDCrgki~p`gA-6}qQSR!5cxH-DE^0%-fhn$oU}Gx;_#1SDm=1AqDP`-Jh(iT zt~5Ex;LB4YQljOCgQ9K1?Lr~y!&Lj+s9K-y4^q{>oznSwb;Qf+<*xgFj{O3}I}BNA2l5FOfHaf2 z=_BcI2qv1Q@_}X7eyQ6lbh~UT{NNY=KI7|>Ry@i~N(!kC7N;+jx5{;AWE%9``F7Bb zvumy|?YiE=_pka~zf99DOjC>U>Pwa}d($ipZ7a=4`S#qAQBs9VV%xS&W8dt%*iSjN z)5%%L6}ts4By8KhJ@)BS3&-A)(~|=hcm!+#eL!rgpC00ZeHbb+t)wHr3*1iiH98Jr z^NU(ND{)}s4cFGr&ZLZhH71}dHhpDIB9Z@FH%Hj6k^kuSa6(9n>O02LKT z59QGpwZ%zIP+Cn*eJ!SyD?e?wI1bMe8I6tbKT^yCIVV09w}u)*g8%&a{@XWBa8Jcx zp%~;s@!jvB7KkrgMTiCek%K9`8#cG8xzS2s+)T(r!tviF(v9Z52X=PIeP9kH9Xe*i za2%AT5K~A?OA8T8W@hey_68|RKIk!3qagJn#s(;3yzw0}oDD$z#)zBS$9oA!i7CQp zIEYt>_@~fRs=B&n8Z4kJAPE2vHx?ytuzTGKqY)vdq46NBHh&G#Bt!8pPObvfF)?F! zfn155CmFS5jwQt#w+1Do$S1{!3!8J39Eu3rfx$(~A)mr!7C_w-cI(!oaP{>!Nummt zhd;UUO`9YTF9G-N#t}^7d=GywGQbE~11J!Q%}}D59cx|{L`xwl9;(sL%(C{1w4!T; zf|nk>ERH^UM5-A5Y(k{YN&sG%yvTz7DbeE14k!VyZ-^G{Oqr2o9ls!qQ9b_F1^(~z z@+f#SOJW+f|9*;!O!ue@Q)N4ki%#(G420t;_MjBQ)lu#xk7vf&X4kId%MX#eFx)$S zq9(P^`!tnj;!($yD74dDxKB;YEqUuXN=NFzJkLEn-*POXMm6?oD(XeU;JrEO9jJn# zvan0RXPUVAd{72`*S#qFfTz#8bSgd3{MGZgK!)3!ZRd-1&+|;Oy6%u_@B6}0VSewT z(JYk$GwsRNcS~88GOZQdN)_V5EAgq4X??L;aHcZFX7OzRB$U<@hlL5_ilmDekJ*W$VJ@T1$IoKVr+q9_9Lj2P9{ORDLMd5C%z7Wh9(`Xb_W5Wu;!yTT*Q!1M{!tDh#hj zDaa$|&WifYclwBo0wX7810s0_)e)fi%Qc zn2)`N&5hXFLE0ftsRaJ#54au)*b~We2j@&TwE22CqCwyagN_dx2|na{s(~Uu1t9{$ zqu1#*qg z5y@PDfr~)K6OJ0c?mKRs*0hm`Rc*IoMC;nYvX37Xpu|6ogg!_ZR>A#53HJ0SHB{o+ z4Cdfd@NCCDr_YU)7Z+Y%czfn?mY!Gapf^=ML|2ysmLnn~K zb)aG0+1>#`^LI0Em^mg@?qnVE89Ku6B-s9oTX3tO<+tqPH-}~iO)twkHnW;|F24Kx zIpZ$#>NC3JA5W*77aAw>-zx|FzM_Co9(Kn<^DSW$spc74J->dfopYZyZFlVF zwVsTU%fG@=$@*)iBa}No+N*fE!)_OLP|)F5_eqHMzste##rSoJVq2(fmenc(Hc zD9oU~B1S3@tPUjCID2KrBQ%$?|4)PJhz*0Y}3P@vM(MNs6pA+Be$O3mskYfBdO^p-@@k+ zV>dpddAVxX2Vx=H%11TlhaF-WL?9T9{=_C%w33;nGoh4ZiH@}nH%@;c{j$*l7;!si$2(%5aOlxaM!a>~$#^2xs+ zl?;-kC~WzeoHo$SF}|wOv(7wzb`@S$V;b2y4Nx?!Oy>Z*)Ab!-@tM}X zwQOV;Z;M9;biB-kfNAyM{EMnM70BiD|)s)0Eab_856&;2dSXY$+0Ol2LeMRB#F;r<**pN{smApy; zSSZO7E;5RqnmUhYT8*UKV8c*Y%TPZt$fm}jG-$(?M5wX-bCurp&@>moZ=VYV*wP5I z8Y);_=0mxA&mJc|tAqe;L@u7moLO%H& zJR{0LTiF`nehCN)(u_5fg$$7-F(P$&V{-w&K~8ezrQ}?7N*gvXY{u$P^?^qTz-kbg zNE%r|T8*jzku1Z;7PF|O?8X%YqPRLHsqlXQqpm1BYHH5Swyqa$n<_C3eC4)UGpOYJ z{p;T4S9Lm7ACAr*&^TDZC3QV2EcRQ@Y`5Ih#Q5fH`8{d#2kzcQB>3fbkrpk_7h5b> zy{3NNfAT8d4kM9J0s3tAGc@yW-z}GAp9)-vb^e7sWr{bZg0bD{#s)*ORuL#dFe8wC zib7!mJ9kc9`ugZ6%0EP2U&UK4K-3m`6{1Rqx0IZ`#Fd=)Mr5;4n8wfjC~=UA{^XlW z7je7z`#W#4!~>MxQh}u-7@@_&#TAU=QKa)`$>Cs%vLd_>EC|#=47)Ax*tC}~3!yL~ z(b<&2mVNBp+zgSEzJ+;jx;dx1-G6*^(hA2Ic@zj_#>j8+W0q5Zk*F#LTeG?a8|uN6 zGdiYu8ctY+&9@86%Qsc-!L4a}>-OT?ky~SOrq@xHyMNdHqdA(Fi3& z!_`}?axM$dYrVh4gDtTt6gHkqka59|Pc9LR1>~AERWnL)9c%TX<&oCqV@y zC3MWGb-XZfbPLhc)h-(HPCxdmWjW34xN@y%goK;gzGq7!TdlHZ=EJ?%Z+(iS3meG^ zuQB>4xaFn%p7?ob#};bJ$0fT|_APvWVAx_OZYFf{m%DlP*=5)UL?`6Wk8vI_5V5a) zZYYyGS-DnR{ORX~l9FjptIk~g{a)Tk8TkVq1cRxd)OtOo*okMST*c!qyHPBbJAGN%e?QDATby z7}j=ctmD7YzpQRraeT&|wB2yah1s>DA{k*cVO6cY@a!d-VFH>D5fjg=kEQVJs;opo ziO%buUHim?m_(VockaN6xpwpBfrn)V<8J#pCpAKlE&U!L#jDrrc!Lc^ZS(kLZlc|9 zNN3O0na7!(p6f0izc`Z8{Z8`!J*iU@P16@c->tsDQ4*xdx$CgR$tNcitn~PW6ywd0 z4vbKJeE#A}E!~+H$>6f~24%J8m#^4%cf~oI#Z77bw<_+(V$<)syeWUwZVT(|v!cQn zA6vOVDM^MWZ$2)RctnowzFqxx!00Fv%8=XsP}XVG-3md1aO#9V%EX38hH)8`-hL4? zf;KR`n&CntQde1I<>Y?*@Ib#AB(Y*cx`enRSZMU|YT4*GUjfx?-!@)_kPSfMHHfuh zp^l&anktGBQJW4tr-jEXSVRT|D29Tr5MHI3b0mdJcoZUcD6~$&5DGWwo=VBQ8_658 z=4MQ=@sqYT|NS9Et zx3`b&yi$auWqZ$>#Dpfup4!1ILYnV!woUhMX~+$eJP{8NJ{R$ZOS;Mi|E?B8x)Z`l zV#)i4KGeNo3Q>zxIjXR(~35EI8y!(m= zkzA||193AO&UxCdUHf*bujs;jzJ`HY0;Fc}kzvJCqaOc&jsG+v?veXXCIKv^z^}lE zs4f!64B~c<% zCQkwWq=1Sr{d9*Ki8&=d9B~hrg7||v^a7-}2#rX?)GN~6!1YL`gkb+>JHdNx&2~C0 z)k1^>`jhU4bol4b?*t|A8CEgsvQHI|!J_j9ZW#apEQYEkz08g8EqNM~6Nz_C^0uMs zB|%Lj$^i8ogPhzurP(I}zn>^T0T^Cd)`6lSJ+mPA>%Z)M`xM7Tep{9^YCYTIB zQY3K9M7bln4wMU{FXqM9LD)|U40W7T;#y0ua%|IWNZ~~L)9Y*dQZD_%WRc+6) z@JP|hOQN~z&fLhAfwf^P#@7??gtt9vI(&KjmJCQqB&11p`|rIYzngw(D4(A?KmQ8O zZ7ViBd?-jknrq=u>QdW+uBScH=Q-mlqXM)$)Pilfcgo}^<*c1q!Fg4FpipU3*r5lD z8&5W;Gqlhw?N}&K7tM%$tj;vnU&SlfDz2Pm*|Pt&(2B~%nLZS5lsht~sgunP*oYRC{H@eB;*XMyV zZ*_GpSGL7|yrH#5O|)C>DyI)&co4mg*plCq6%b!hfN%&3nes~_bHMQ{V^A&`X^Aw( z1IKmw+M1PK;oW0d`#yZQ79jytz->UJiFKZ6dAp0mITBtPeLZQ{QE8(^Ua1;|#<&eI z(fR3-btDr5$(F<~gdm;Zx@#PVfhNHf;RjPIiVL&`AfZkkVjtk>C{ysDc#Qz~jhNR3n*5l>15$44`6$_(2iD z*I^%66Cl1JDVGSu+=^0$^vmd7=3`F8_KxnW84R1lCrD<#-ofl*gpnpz|3Q@a)r`)v zQxq5!nLn}LYDY|PT6((3OTyP!+YZLqH)>pq+H=7_RegzoP36i!DXFs8yZC>2_KQo} z7jL0YP>krtT!X-c#;Hj;7X4}GP9;f|2G!7$l_UaAn$jIyYD#C=*weak?e^8mOPQuB z>B4>=d1%7Nk`|vjJh(KLdf3)t^^UJnZXW;ojsmdc(^A}|e!*AV;`)re4qrakTsGfq zY8NpUbiM4z=N~U+|9;mX;VX-#yE~nRo~}=nk9F1EpkP5cDYn<{$yJt5L>8A zww`u|X?2ifjL0noZBu?5aR+b?fhuv&{}qK%wL$rpkMPIm0%cmt8NFX6b|g`B(+}QP zShPl0A&isv#RjI>BteP|@4w$}jmA@bp5sSlejNCjmRiT`tQhj-i4OhUHN7XP=L68= z_i%H@xxS;zJ~PTCnL%-|c=yj=_Vk4C5xYkP<<(sE^#&-eTCeRBG6)l(zh3ejrh3LR zWn3S%^u+%yRrW9-yv}a3s&#tKOt1{CfMnw6#-R=KSh9bQdYc&@(dl&j>jHRmx zDQvb=_&cFPBeF&&gOT$1g?><*$KU%L?@1^f8p^-!`=Vwqe^?=t{JJqgQ3nzZjLtG@ z47TbRcw>)zjvILxlOsL|PYDT#$C;b-&JnW^5ff~ZZecQc4jwz~1E;Z}7^JL~-t&U| z*mdgoNL?;-`d{U_7I-N@E1};I{>Z_+_rVT|*Vpb^cR)nX-JPpxk5g#&W4%S=U#1xr zW?l^;%M2f<7yi9^rOTn#TXL32>ckVv+{+{!q53vf@;isu z;`De*UC+09ZQ%`%fADu~?8{_ly&xi7S$ksZDOcy2n1ziu&j`lZl%P>Eg&zoCVKd?H zYci`BQ(yL#Sa^Dld+2a$y_i>x5WiW*d}fr6YsZsR8@GhkR=-Gw`B_pKAkmA^7jSWs z^lf66nOMY#05YKf;*tlEkyUTbIX&3paWDzF;I3rY0qR4opD2aY(RxHwXF9cpfH8WH zfPWm$#B73W^r*+WIJQ)fuZQ4%S}^j6P<`@w6h0|2&)Hwn!~%mD1%f$3!Wc0^q^N+8 zp_3{^fe-@8RHsNcR4bf2PI3~*BkY=SFpNS!X%Oo|L%jPe%*@^Zqw|r71T>6fwiy^0 zu)3kYXW`@w1fN2(l3;WH0KZqf(cVvzaVVm#5jBNViws-y_4OrF;b99)m_so~czy^{ zf{pb))x_c?Ye$y03aLeqTF2De2ni`HTnxHo=$;kev5>%wDF`gZI%h$%k&um@yLXE{JFTG+Xkg`c`7+51 z2lqja9|E!heI{5_1zmzFXW<6V#bFzjqG?z~(2G=5-k4z4mX&WYGE zzjL866VyAj70?eDMJ3#sDv&rN868xK-T~F-3uwVlVo(OjfWdt1D`=q*vGU}zD<)%b z11Bm*cT9D$X`E=kXTrnZ+cfqjb(Pkp5LN*U!Dai}O%k7+SReG(nf8b7JWs!SqQ5Am zE>%DG8BOt$sn~n3cEuZr7G&N_!@~8p@E2?rpAo;JlM2QDG&v02APs zrF`?afL9SQGr`$lCIVTVj<|2+#t9;F8GwRVTBUP?1O~KRoc)zWNE*~+5@)(}2wjT8 zF7J&%WJClTred#vd5I*%BMS%~@5Xo^trF2((#>=|$;c25tj8a@q!4;rhe@?LaTZf)_BOg2fn>T*9* z08MKD!s+e8tCofE?Dl!uc+Td1|J_8Rq*Xhh4R-Rivn}W~X4WoDdnxf@?ttRwuT^R_ zB~}t%RjYQ^q^rA#i6!XE(b3I(o9|eEvA&KYOn%Te)+eX>*j)$+H(5$76$+#Krwp0k ziD#5^ifV#7^s_?b96z-9`5iDWs^V|(L{zDGqi6n>Dm#|33f4PZYdQ{=GjiVNRAgQV znt6M1ez_s_C5ekYhoB05a%>d4NL7rd9MA#PfQNr&wZ`Fd)LKi0jB)THk-JTgH)4Av z!a1b#1oLXlVD69g(WCxwif*h%hs)}CAc+ryRD%UfPxqE!%m`!vm>M~Hu~IuC2OdNbG$T+4>`>U`IcaKyBj9&{ zD2)7hcp-!MZmeRA)xtSNMz`V!;_Xp6TsWI51S@baqG|NOb;4%bcw zhbFUF^}5Yk4ms}ggX3Woz)&{bQSa_Z?rF2|+Q0u$ad?o1j#mDwE4iY)lT#(DX+K2; zB;8ye@;2ZaGQEawI4~SM8#plL=xt+(pJYvM>*pAG2q?jU%EBBiVr2YKl zDMwZ)GaFlk-H$x9uE^P?1zVqR7WQaj1IOdK*)?((0T+D_1H*>|DfqZLVdF7smyk4) z)db>3{PHf;Sy|L&w={%Ff(*LsGz=_6oz+!x20X{kojW(?9O^#Sm?@5uY4j~i-#o=^ zC17EoPv%=KHpA!)n&pat7u#dL^!;=CPQ|6=<%P(DAxW}Cx(@XQ+_=70Uh^5(AGRXg zLIDS5cr^$TKOn&*lqv6xLNi!oa^}IsL{dFb5z-W1tu z7w8n34>{SUe0-exIP|rPeaG;_4GLGz9XYD~V(aw%yT|GtN$ojvrbFplbRtrSsn1JS z#|n_-R)8=hu?iu7By32bc`+paAD zAvmBv#3){pIQ)^BGWf_+4vA8gxBH3CUaJx5t>rgAfL+1`eL%HRm14RDanJ-^!5o`l z*`g$XJHmR=tm^nxKb&7xv$pX7eK9#6DGXg)f&lA~;}{c##AFi~6{3jYFM9IUpeL~H z`9ny*YSk(d!cjDy@_+LH#M7r?bl5UTA+WcQ%?4v&NVL}>h$@!sy<6V=hzDE)W#K@K zdF_R)_~;}a)#lbEn&5!0UAv~`g80E7B36JXFbxXn0SIPc6E*rx4^8e3!RZOm@_kX! zy2^tm{_z|=Xt?EMAyn0xzJ>}+3um`9S9C_{?3S^l<4K%+66{Z#&8!dQdL|xBShc>A zzBD6mbe6$1H+rN}CAH}|v&f*frmi2)h|+9_a)Vb#*PHR(KYV$F0>dXd!zT}v-J&;p z!^g4NLTU8T;>9q}p`NYJ;|*$(k1VC4-u7Z!t4Di8S@@%mz>$v~*JSP9a4WFtx$1VF zJ?=bvJlr4JN4&M7PMS;E7fbz{$b?VAF~<$&fa|D&5P6QsxS8NFWC|o)+|`%(V`b*s z+|Or;k_!GpGCB$lB66-03=;7oVmVK?>`xPD9S?*g@Cu=djvb>1RRqeEBo-I#X+h3D z7W2{}$Epsn!PX0N(|A}+pZxlQ#Z4-`X+DGu=^y3=e?+rS*YXukMk zc&MEGwmbb?*n>Nl!<6X?ieJ->jgJYgQYk&swVB_0)9yF>9VDbvg3Yo9!q3l&mzR^? zVZ%fvb(v%R+ch(87p(Vw6q5CtP)Z*>sI!7==hsX7l~*%UQtWFX#6As`m}*r2$u)r6 z{jOzX7`*XkT66W@S1D#myUppKt(Pu_} zlcYlfD@Np8PDnVCGoDfk&ncSF0MO|+_wHk89toUF-~Yg=KAElriajz}4P#TNOrM#* z$AGavoPl_5PA&BLXq)j{rqiF5ucDC z3*FSlG}#~(U-)|Rz?nKHC|QWGdg@a7?@1oNF=$ly-Qf2Q0}&k%Hy@bek4kFf!({!(!W-bL4Vr8JO(uX(gJ8}V=p4pvA}N@ zqwYv|AGa?WlQt$1i8v(*TMR;#kWc?&6n>$-8^(YVB1HI0f>oe~wL1G-bAOLdfH>w4C29IHa^Ipi)3Wab8m3PZ%@#WIKL_+<-|xr^=S95Cb5l=i09 z=wTix;~mA*q4^Dr_lG*3HSd>nTA6&{PWN%; zfoM57cL8zn-@F*~L`3Cl|BZS7L6h0U37Wi`pw($}vl1ju6xlgw{n3{*US(ut>kO2X zWR~<;?jN!|?|DSZR=0dk^EanyLN$zv zs0jNYNzp{0n^EFEDwNmi(7`O1{RAC4E}2YKgSd4iVkxk60D|ZYcS)d7BJskQlZVkA z_u+o>BA1F+16xfgb&%Y)O$n=#&}2|*i&q|$_{*IUo*4TdAQK|>zK*uQ#ia;KkZo=4 z@`r!tQKGyfc3ucO0`W#f-UXe&4o_wbU<@vfA`-z%42Z#Xc{JV_M@y#Zf&l!E(vnn? zK#L*#Nan2g0bmuf0&A>9!a)McLAM%!qjcudY^O(Z)0^ZyhL{;QyIUR!3s`}_vj_0z z$?O3f1xhfzv-K$?Y1Lk+1gK0{ZOD`eTm%tsEIx@IR0uvtkg!FNw?eWFAiNH5?AFY0 z^ip(-imDyee~ebUb$%PID!r<7M9uV~%O1Q$MZ2%UYGd!?exeH zih%3fqPxD3qC!&Etf~T`_tJhc)|{y~v)wem$qJp|z^5gP_x>kwJUtfVlk+xF9z2_d zBcYzG8&>!`gP@t^-thiwYMCqMd%Nx4iK(4I_9nz9Rv481OL3+BM{#xk-rpqhBsIuY zQquVjQf(O~7#Z6Q1Va@D)6S?&ly(r9Y<527kN>{0(Yg=_u)VG8#jqcPP-B2dT$?F#T);Q7&A*+gckY4=M(nlK=K_zTM< zxBb(|#_zw=aQ)9T?0*~n{m0w>b0Vj_;^*bb9yTp)EzU(*I45kd%3oaadk>>X)x%qy z3`-LduJ7u(cEjI7D6LL6L@d9#RqT>+t9x;RNw|*UikCd)VPRh{C>?y=kep|%>R24P zQYfh?cZ6|I{}kQN0g+u^E7RP%$}g8k?&;q*yhp)xf4{WhF)ImAesGC4tEdWe?r*!6 zpTTY^%4R8Qpu6|48qY6AE}1UqTz5&pL>srHRL8;c>jif<>D#VL3$kHgxDyt6t@7OS zr@ZCt_iZ~){xh&(fJZl7^L}0kcTy^MeEjKk);{ZJW?YzEibLNn&j6p85pP%a_3cIv zlj&t)di*+%GfmoGk+qB;2|F~>PjkzZh11jZOYPjLp`fin;ZlU>zOEeolG`a1M4vl2 zU3}(MdMTsi?;2N}@aA_`(t>jAzOf*ucY$bT@`kI-OTVsH`C@rm?|&wM11ofGIh8Z{ zq>o3fyt{DEQdBPS^XH+z50`MJ%16k$-oIUVA_*GrpC7`7^&Xb&UMv3oQc*a$-t=xw ztjEOamd9ZTPy;s;?P^F=A?8B#3=Gts5{Mbh5t?D@K9k%gy@&%8T=?|hQ)kcKLJ=){ zzC3wmATbbFpT@Zg$PMIpgFl1lqkbY)FO!gGK!{11ioxLy%wwv}IpkUMfkkaMXk_n6;iX zIJ@sC>JFpsA}QNo^=u&M3Uct4QreJ-12&lMdr5M~s8%{p-V~^0^s?SE`u47TL*?Rm z+TluLn(g=2z}x)crx!Ud8tasz^s=^2PO*O5-*S_v7C^PWdROzA#VE=S7d62I{S(MF zW?}F-=mNWX%n+8AEiTMizj<&kqyo5Of4g*AyX}jCzazk6Dxr)m67z zbR7O=dO6E{88YG$BC{ASoZP1;WP07)i*Eko$cD<1iJ_bR{+^(2ppYO*?8I>%7;WpE zl2Hp`odE@o12qmj3*!DpNG8#bE`ICu1nL2dOD7Ypb2#bWidHM;d!BI1H}{F(xpqyPMx9!zZn!9 zh0OM$6GpCQ-sA9V$uX5BZx*jn_4X|rBpo0%-5Fw+p^D+aSe~rqPfl-S8WHd=cQyQV z6rfzeOI;@e6Rd>~?#ud`bkAeN0>bSQ3Q@)2F~dJ(DXLWf#U+CYIN9NnvaQ-Gou(fa z-)j;Hrgt1jD;oTvWPXdbJ|-;y{WtOBCR81GyZ{}CgW^Usgo9r|(<*fMERQ8i$KbOR zB@&@f)!UtExmbn@HW8`I$&K2Y6(}+jF}EZr4?GZ;u+aL$V_I-B8Kf?F&xB^Xjg(Ij zZz2k*`?1~NWT&ed6^y>_eldKP!8J_3j!fBq=08r>)@DbuDXflT2NU*H>2&Qoo9bE` z7oBfyoL$nu!m#U>@%8A^i_0kP(?2OgZ_Ym}2{kqQ@MTc}&q4E!Bz=E@psmubelMEB zdM7wen_JoV7g8q`yYF;Kx!Lr+-)wa9Vq93__5(MMPyF8DYhr-g$U|nvm%3c&&`%wP z4+XFfGQoz~E!h4l_4_d&CO(G56^KfeB5)gwU53xzD}g=>V=Kfu(PGIGRvccS7SrHS zv?NBUKTz`%^5|1S7DN>Z2OSzvqM%t>Fc`WKKun>a(C`EW8j=SZ_&D1 z0sx_Ga52Ayo!xB=SrTBXNIZ&2);UG${V(Exgw=c!=pO+CVV_QlXY{>KySgf>drg9n z4?$W(OuC7eH%aS-<9$46#{<|Oe1?cTjm)`85L#p7Dq@3=fNjF@CxNr6T}4HbcrHjc ziw7uT|0N;pTchydd@`5YEzASh=?Qt&b04LM&Je)aGpHCRze0Y@ieYJW5RYmzs$pCc zVyA^B%U3I15gn=yKPw@2PQXcVael;uu+DLKB*L3xpu18Q9Rm$8cEGXWZ&PY@zqk_) zX_v>M+h*y@%!XM7@_#5CEwZ)437d&HNciUT_kpHug5op9ixX_4*#oi;ap;~Siq6kR zMy%f|^p5T#rZOMa*`(K5jE4y(7zLVPKx>1hV1>nx-!lnQ@8=b5Rf3MF9$PgqoXmRO8l1=Qe$rfrXFn{9G-i6fW>;*yGKT*y_tJ(1TDQP=`xnYTs>OMpS6Xe zh%sf?6&yDkfWP%%)oH!MFK8&y3wo9xZVC*I!_PKJEK5%SyS^dp@qlWq9zQ=jiAt zGs2*QT5;y=r1)Xy88$ZkUE$F>7eDa$4aPH~NDdPgR&eiLx2EM~Z(*YBP1sO<{KP@Y z*|e6EYPDK?pesw@gXtMR%wIo8ySqVcdCj}>2Zd7czJ!TOX3?j_+1T7Fy=F9C!C_o) z>DrO5Wt54b8R51}BRaK!0QxP>t5=Di>G23(2&pf4{8(vV|2%2eQx4qTefGtb&NYQb zsS74gWIlcGTLQk;t*r+bB_#O2$xa7PxR*-g^md-{1R3h+Jf` zee=*a_3(CAY4gp$e!K@e{MHV3uf*B_$SauUyMHW8)><*u!JcrsIZATur6yls{d3yK zDe9hu;dJ+H+o=guzpK@uJ8@PM6k4qWO)rT5DTO6~X5{+wA%36?oJ}qUvPw=yVCk@e ztpXeX)0aaSdLY2kDSP#b6)pQxpTTtsUM$4+MFrf{Ky2!Sx{6h!vKLLfhQ=MWE92wi zgbf{SX9slZ8N!5DVT8+fEM12f_wlT5?gnii&VI!3cRLJ^0oV)gHiaUf>vAuX}54A-m*q#-hi)+0?v`-4vdYjt0V_~&4+_A zyN|pl7L`hll_(p?>9J|6@(mHA7#0WbF}SAqqQXF2HAT+;0jMXS&Sh%bW89KH)!7S~ zX)~Uj=sehB4ZI5eN0M_+@Ce9VhcG83l0?2^LgA!TAyJqhlxv)uw@M$F*T~}!BsJo~ zJ(IUtc7gUI8M?2Rd_8sQ#j85nu-bPOa$Hk$)kb5u-Y;9x?7*VfA;jQTnK7KMebbh7g*^z zd+wZwxJyV^=j(M)TOK(wy)tI&ZKpZCLmoF5=D)sLZrT6EtSXL6tKh-?v(uMdR9$Wz zyJgP3r|0vBi8i#EtK%HrhaKg8?V_HgH%t?7!`_!q>dFp;1XjhB$Ho!c5d>S;@&sV7dxwbi`i!tuTE4gb@hQUO=o*VQ9Ke=JxQ@^MdpZ?ZHzZ zNo2<}skA$LcB0@1E6s&--4!HDaJb#Hr8t8gb_3;NKJr}orR0W(sw7`38V`O034!GU z|9T7UJ|=Fm9x*EKe(<=B&O z69k0aaCC6E(d!QLDB*M1|)$IQdr#>?X47 z>Km(lPu9k}l@@HAXxyJ>kSN8g9uKQ_r-Vbexuw`w>|t|yvD%hee=_E zGcGPW@#D>@s+yXOyeYh`&a=LH!>JqQOIx*8^c&_sJU6+4C*YW}8y8he$%6&?5VUa3 zq>dON3ni@#ly1_)kswEBy;u;9LL(MoFu;>CGd&#=(TyNWIEZaObgK$y|I4$gc(Bj| zpiM{;gA51Ks)0W7FQ3fO5DY3C;@PW3Jx_94Fz$}SoR-v37{I1qy6V)z@jDPRRt4l% zAhRtgFCiB+ME=}`(=z(3ns?9V+>(+w+j$i{{A~dFRFJi*vU9Pg9!i|T9~pQj$~Kx-ad6vLoK7{A%Jhfd zl+(-kanYfYH$wTh^G_H%%+#~u8B(=D{Vwlb!QPl}8`@PUYSMvCx&x%F@9!@$a-`Nr zY@+fYd-Vw}8k`iamzI`xslypVK*t0JFlAu5J%!ku@_g+su#0L`2}Wtxivcg>Vb1*6gvFK7Z;9U~^1DIUe7SZZ^^l?7pbfacEGg=K4$=<%v zV0ZCiL6=MccC)oFWt@jX8iOg07cP(?n$}2e5Bk8Q@mW-af{rmamM>ra31F#|^O<}@ z!iB*pD&-q3>wkLwE%8}O&^PL{x62H%mRG)e^RP|)eAjdD#VCfrLq7)CJ68;m5II-Y zSCObh->kp)_N|})Z3Vga5|Wd1Y~ZH^HeOaJIUpmmY<$CEvlnq1Ksisssehn5b?W5g ziU&rEO!sZ~RX@JF-@@V&2Gev)7VI<(Et7RsO*`LZh}gGS++y3!NHb%LuI0voaw<3h z4~{SNoIE}qbFUgr`O-^2coPi8H;Mh4xT=0qxWQzUFXh-Qy_#CS!{j^h&NQ7g8++43 zE{d$Dq56gLo47#05t4qzii0TWNemU49D#eZQN--(lRlR#SDpfuB~imz?(yxmFzJ?t zEUCEWHxPYdGD7CXq!)>jU?LTupq=moR80ID zzz%6EhARjo?1DFnSu>-djnmjk?8a+qYDDLp(b_X^6rrF&CA?#T)S&!P$kJ0i-9!dF zIAEXSup-9}8PT9fhYeS4!{ON>fITt0)UAV z!*Y-ycNNOu-AFthm0E&#!$>G>pSlwEXp%7maSj`HZ6Fm-z{Vl7Qm{44d%Uz89xr_R z6aSEz#ZXY7Ya+wrYXokytBCzCMsoM{9vG~2+_Pu8vWxv#)NIwWXKWnqA{)&uB70qVD~?a{Ja?0(!JT%1TV?N|Kt-l2SFYb3xVH&GjG&M|1jIm?pXlOY4@|-v zivyj68>_2{I870wM-nfM)8x}9Gh8<_r20^2VB&HM13nA5ECV=^13#b4+{(wjw)Ug6 z!F?qewxoW7UoNclkrE+b=YIpJ5WqIj)19;PK_ds!g%pkp%NnM5q(iAIh(60FI&r(I$r2jBkq$R%nlz{{278CyPjfkd(y;V&1ab zhn8VHL1srFeo?IoKsp57?+Nl>?%cUULDnBgSz(v;^vqG>mPydRY(klmXLe38D~_Sj7sK*?ruFsA92|kb+d>UqjMT15 zG*OsereMljxmDJmN3*4K_LY9}S2l@Lo2gD7>kgh+CXj0->^uE(LrX)Bo$g*Q{ug(e zrZVesp}}WIE4N2%G)VT|H{JT(Dv1u(+jHI`hR{8 zzgt_D;AM}UHRb#_Rt>tZ#nkjUCZw_EXUI48%TR+g{jDz-tn&@QEz~`g z-=s6FC-^YGy+m~U!S4qXUKbbZ@(h{2TyVkH`SUmY{9-ilchLIW4GvwgQZ=*wL6`~i zJ_7@hLXqEb!hheLnyWL^%2~?3>WCVZK1pPO5?g5q1O`5#j=ma^Zbwc)EP`d#_>AuvQ6EA94wdX^>Z&?!F6%?3~~T>=3y z4pTW%)5E8v#Uii4?_F24_`^oMHxg+b&CB@T4+&4s9RG_}FFJ*?9}$K69?NyJ?Y^h+ zVUAY+vaeJ3f2r{I3;Mm_{5Si$ga0;}@cK9VI@*7;uVef-`#QFNv#%5N`}ug+t^)@a z4Avi36lQj>_&Y2S_~&@%|HHSU|CdvqCG)?gEdTfKqC@4hS#VvMXKOXyP=7N(!mF@w z=w9eyMcqvEr`NGbJNxA9r%u#Ga!db{3$PP7)X&X^Iu(F!%-=q;x;*;H^WY31cwl=Yw=QraFRv6#&F`KAA~>gc-V>E12_vYE;AIOq{fayGC_nbT`VO7WJrZ2 zyIP-nP0Pw;yfp+YS)8lDQ4|=EZ1t%=Y!6FrC zYq+2huie}%FlrL3NFf`aaQXr;`-D2Nupgv8SnUQd4h{!1t@{rn6= zqo*KcC(ZI6jTqILj|QK4_Sg57aSrYT4#&ho>m@))4q7I5Z-AmtP)VW&eTo;0Yq543 zOHN8MLnnoBdr#0T@e)_Tk1HR$@x#Bf^Ef%e@%1hbXB9fkj9C#xwDJBv66FZz9WvF$ ze^KrT%PY@FD+548B&9_CirIn#oU%o0(_dg*E`m!aX5Y1$TqoGI6FN$?w5i+VAH4&P z0}=_3i=;qm2zMB*;8T-ySfckv>><*jpQFfvkBcWxR5SD#gi41yls{PX!H`#30yNJWKVzV_#R{IpGv^%gFPY>ROWROF<< z!ux;j>+=B^OepWPxA$Y+uPL9-uvF4g3t-%S*br(hX-2jnUzsaMMXnU=Gd{5r1^f~U z=N|3r?(+}s+~s;2vAfv8xxMGb8n}YQdm9Qqt@3oSdIMXqua`&4rN4cQx<28w-F3{L zhIUn6b%mSFryq0){r%udya3S{M0hiEb5BGkpTt@s6akYf-^Ka55&C)yiWP85tAXjJ zq$Jb%zkdCih+>YyGakze9tRr`<;0|~uTNz9pze9|Kj7lCe}cVYj4Cgr^aWcei56ts@*cADsBMT&0q00|@12y-H&0(ZL2 zR-Dl3LH(yx0R|eya;-)KgvybI0-G*rT@TrrFYC}eaH6jp@&%19-KqIj zzaN~-0M?0M_$68k`+=uO9VCS&h;^i@cg47eu(Gf_sgz?OI1)VCwj=vC3?r&h&!f4k z!s1Ml#>yh}DLjie(ROWv(U@x3t|u6W$`Y~p^XGx?U87D}(p5KzXt{bB;bftvZ<~ssOtns_yU?-6z9a3 zhd21i+qcCZ#umn77m0}+VYp#1L!p3MMrc4L6pT_t(iX<ouY=Z8D9cb3^3fFy*ysyTr8{S+!c#e)2 zoMpqHBgviU)?i#pwSp-;7ccK2sqHVJyT@GH8@x_*W%LxXIl-dzDo)1>%aqYPaJdAT8V}?@$2v3k5{mZw;nl|v~A1& zjJx-DJ=EzRAl z)n>;unbVH|1DPxs+c*UD06TRqi_Q`TeG%{=2ov&%iV8L1LM$s19k_XUEimdrAjj3b zwS&qsfeQy*r<3Wg&z6q^T((Jyhb68Rfx7_d+nBma2sQj2wzb)LO5tV%n+`9&Mc)ma`V(V z%$IDw&7SVGCt-9kE+vJ1NpqN?Z^pD0MTcMT$H(4%%8E_FAm*Dysw7wAT;lS+qjxYQ zKR-<}St~sp%!~()YHCVJDJigLJtq7S^kliu^$|DnMhu2w(MasjvB#_jcRl>tc}y=l zR?xBy8BFOXwty3HfDBrR9|St4CjjWdji`nkE6K%W^wSc^y+P3)Wn964GdyX1)8e52c`zt!;){Nk*q|Qi6MEh)J0p zuu&2IUn^g?wdJ+t@|Scq@8j2UeQ=}TtFDvKnYJ`faNdaLZWTPr?gR%f!)TE36|rKL z{W))cn{@W=;}4bVeNJ<{NTjr8>P$PojNfv~)Q>uqBYUoeho^XdP@H%G`H-pbci@(3 znu*sN^&&Hpw9-f6KT|#dLyaRN%kFI5Vfvdgh^?~!gFNAVgFm-HR8$l-i6$|%!S(S- zNDXFtXQ}|&J`-G8p5lqcc$zbrL()NRM{Ieyo4@bw4Xg9YjzIW|yqa53KZix8?(`$B zp_q>zQR%Q2!<69=kdc@x`(K_M*vyu(&G9W1$YkJ*_L%S|ut)yH0oj9*D~8Cf3A3cb z7mX-7IJjX=i#E!h z`;F^fA{_DO)77#>!QYf`qM)BnAyzoVpfdWRZN4GETrvx8{lyP>mQn?%$T?dU(=?Nh zl$gstItKV6a53>K?4qv31LkFz6*lDhW?o$9;zjeTmD6u(AdfKVcSap_H#Rn>uzeWq zIBQ0ZC?5+0whsbkv|TYYUBkh_v6F=X4g64&2Z)g`nwkz1u1R9Lm8eqy<0QAEjfKTh zIOL((rv*65N)6)rb7zZ8I$4!R%ENt&B()hgn)y8Oa%_1Rzkc67IoGnyJJi`vzKVRL zzdWtIsyRN4p6Qi>Z3ZmozB*iJO|UoJns;nMy8VH{I&y}>s*G?}Ab|kqHtU1e-B>+r zqIWP(!cH8IYXgWB4UO>bkt-wRzNq&|niPw4*|`Hw;-OYjZ#iKv7pFM1yw5XLj{-*Wt6(l zsxuxYjM3W_*rdX7n`-b}w8SgW!Gw@&`}SoR#QQ)A3-?1 ztctS#+B=-{O2jFRCq*s4DqevXb2R7Tj@4TXH@ePFpImt~>J-`hkaH5>NnY z9e~c6WQF2MK{KKN>8Ke%GEl&WLclxr1?QazkuO6-14hAb9bd-15M~gW1&N)vZstTm zO#BUydlocC;=VxdAznPN*;~WLHq$mQ9s-|SbUP$MgjnffB*Z&5HYS3&$?`HFW2Bk` z)43V&6Q~Fp)Yi8*>(LmIq6E9qZrp9+&x4B>s+YE;3YHCjnDD55uy@qLIDm-!9>AYj z$~28%*D`?7>V+|pGs@HBgY`f-R*M~uu(zOZ-RN3$;LkJA$td&ii!)b^u$2{(C_QLj zxlj!ZM8(cMsJ_4N?M9KSnSp-%+K>dm3NB;aC`fY&;fK*EW|V-^H*xE?`!^9whIzW? zkQCGyc9h?SCKVelDiIv@&yVus{Lb^`QSZk+f?mCS92mo?t>@N}oh&_7@YkYdO}Prc z_#GXCCs7I1qWs%w7oY`5%!rcv2LjWyrm*0jFLn3-6|C}KVI8+(t;|v9T?s>6z)IBI zF|qdezWT4GbUxf@PnRtQgtL8HkoBrrdyR} z+9}uiz@S+2;?ePjmjQtt7H=LHe<;s%`)Z{hR^r^};OsVNFH2p8j!FYdjKNgDh5Y%) zF{%#}AKBy^HbjaK`!I0Jw?U~EPyqPsJ+llBLWh@|_ zMM(s_X_Qr&x?>x-P+XP~^|RUtA7|Z(4jiO#^jk+oq@_uVosFQ?evL6vm{();9t!1HREnyV zX}RIT5Vv}hn78+U{f_?bGdKTCSShsR-*(C8r6&Or@R@n^gR=eBMu0<4g_d$`p!t)F9_4;{MXQ1@Q=U)YS z8y+t>-N=SS2&3V|-sBB&IP$<($a$Q3Pe^KXbab0Rn+5CwKICD@8P>3}^7OG`vhX(( z8iXGF1ExiICy^r(d`RvyN3GHf49_xx?IBMl=C$y%M{jZ+=XKceu>XU~N*fr?3J^7tbmCIMIoCHGJER8$& z4`MVp9%ksrv}7>*7P%a7QyQ%Z8K}WD7*~xLz`+Bytn6_22An~}Xbt3M@@c|gU@9R| zP_uhKV$0AEC0}ts(@wbxqBVj4p!+0_bMo@nOgu=b0RWZw5|C?)8f@}M@!0d!vKb+} z{d!G2t7YyFu#I*dAxi0CEC`E75b+)cC)xZWT4a8s=Xw+_)w;cc!O& zN|ff#u8Sj$;bT0!wo13z`4Y~3)X8;si}AbtYQx78EBbr849<3&L;~4u0clFc#udOljJ|c}tY^Q?te<1MT;up5&G(`U z6|OA_n{%x-1N?5jgeiU6vudAx)%E-aN24RNUO)f(>b3XnV>du{K5<`Clk?n%X1_BX zd5D>oUc!CzMCn-aqxPY(IcX2~01@$H>+9+aqbFJtAA4un-FWd*z}4;RQBMv2V5OH~ zHa(>yV>LfM&TNyEREUV=y{VnbP=D6cB0;a_S>H;0{iFAS1l?zq1%G|lzL~7*sO037 z?POOQFbH}0;a-~;t}8V!FYaWiOHd9LJ3ks{$jxgDPx@l&)M^={>+TgOM}$IDl8Mj> zU{fS9*2IwQ7V6S9xUcPXr(r}cJJ0U2-nFNw4WA9Vdsox;qf+n8yE8fd z=+OMZYCJzVq>FT1A@GWPFw2@VWml}{0 zp8+0vhNA7|miK_|{K>!gfad|`b(NG{_+yy*@`xnBNPz87+^q!Q? z$*v%#tf$n#q$xSC$BMPd5#$;&O@`6Xu6&ZNmJk;w2e``*c0@xBwJ6TXhbEI<=fc6-CY7p)Zw%qybFVxnKQYCU%H9uNvzos->~(2GKD z!b`q``|RKj;(1Q&fS~C-VY?`BAtz+YO=)7=o(Z@ zrOK~UhiUEI=XC|IT1LnDGRvR+Xc)2BaIacUB}!sY>bsy?*7~JK*5AEbocz_r*wQll z;bYY`?`kciUqz*^5XiK=8%m89Hgs4Yotp@%*&l75x@1f8wTck*e9ijr^zZNYLgJ3joksp~dxggWWg_SWuy@5PtJf{Llx}b1`^n@`tLVs$-$vxx)$AqXv`c zmk+w{NC7zW27ygtAC+!q&yaGl~E0yA%z| ziPFd;pomtzHwrK++-lb}JJY{3&zkG77j0nV&hTaPb0E7UtcO=|)Q&LcA+3N+rbQTD5yiV!+^y2O{hUgPcBQaG)=iiP_9QC?= zjG>`HOT^`ZL{QLjkZ9-nhR-mDs{FEy7I^;b8GM$XAFo_!S^rq(O55dW4YN;G)eB$y zm(i_W&&gfGqSBXm=JqXy+PJhutNa|Ll_$Bmt#?~%tncY*JEAJ!k8xik<)>`|0yjUt(qR9~|F|>eF98lwMkmyF{Gr&z?O?W!foXJ`Y+1O;Y7I$ z2pNz9E#)upM5aAHV?RxIJ;QFw39mKSIET05p!32GtP-1#wMP^WTwGjIRwzihcJ4ef z@>suh>L|FuAjMn-a7P@3z(VW$mA-9C#md8!G~>Dj{42QAbwoF^W*Wrb;o3W;`H*v; z!Ad&1`LFvVUo5yUzFi(<+t!@byWqAY>qkQ;g~E72S@EC_*VRaIZSzxM+vc%9%4Pbn z4BV6LUK+e3>g4bn*F3u@&!Kj0kqed*qN4TL>WNFPT@z*Akor8&=BuRB3-;5EotS}E z%PMZ=@Ctf2l}Ecu=To>T1N_6yo2}+&rQ34sm&;aGJ9cPmEpM~M7M$~)@3rR_v{4(=Bpz8{g2vzOS6^Us>p8v zw1-e@e_99NZkRjVL(NRJ!6@g&E4KscXY(FEeyys#lSRu7={m>*>~|< z#6gdVaQ(wj;85VTbZ|$Yv5;?hdHKXbFti9>cQt>aP=>|dEfbF$jsAQ?+GwP4?%l!) zA@+g@^f~^>Qn}dKR*an_XX5Gu`y{@64)F3Dch(j})5x)Erti}5LbAM{~v`6I+)$JbVi}U~d`3ruX3tYCRr_SmP zvS7>hO6PMJ3K73`dvX|CyU42s#)y5d8TXuy{QS$#GkX8{?(-HBmZD}eW391AE)E9K zrmESD&QFC&Wj^ZZiE%SkS-aNahGya;m}MNOu4eJOt=k){zIDFaY11aQFBi;e=xGiG zh+XNS$}1dO!)|+H!pRd37-aXl=C#M+^R;9CGnuv(gKtY6`vXkgLEuL+&F+0`kEl)H zY(}s6X?Qqus>0{lYuV6;J4YRmh8L|X50(+Je&5YJDTd0cIYDV9Ah&Ex1v;{sDBu$Z z#aKH-x&qtU^@t|*zIpRzSABRbxMnM0`%yjB?SZhEQkB8;O@l9U&~_2jw!Yyc9B+Z< z4<&C%mU+2b=({64=^QCdms|nQq>f8rQ^BmNkM>>6b0FYEvIgG@!fqq9TxbGbmJ0!) zMQ>P*kv%iGY8Y9&j@9jGox@QL|{|on1zcmcgbDJ#`M|VvohxiJ4f-d6kD*ge(mkM_@nFWfBYO)VFXGbwd*Zb^ZPuZs4Ep0lyN3O` zmVD%>sGH5r8iQ_#U?Fr0%*f<{Ppg*c3lMJ-U{lp|Bb9+TBMzRZ7f!D)(C?LB0j#&?~uu6pn_d!F4H#*-&MJ!cZMIN9!Zvk}VzE-D+*xlPQv~_i0wPVQd-?z+d_!Oe04vqc_d)lITz1Yz)_hQysLqQRHlT|{d z62DzJE?wG(N3^eAnqIRg>*aeRA)1K#Jq~ev66YFf8}98J#fSJAO<}yGk1iO#PkmV0$AZ9z@T*c|| zuVG>F#vpwilxbuzMx3_za(sR1z-*z#a)rfD0&GrL7oHNzHK_9PSW}=oQNXA|@+Ki~ zg6k!Mme+@HbE~c{A%qcP6$O7LGRVNnlafu#%boo7>pG@01Q5e?T|Tqj0?|1|2y=O2 z_u$25WH|!mVIrR~gio7DCJB~An5^iK&7u56_#WA?K$SiO5k1No5?lm2#~PdrrU(+6CMRW)Jh5atP(*JhBukX{e$rAZyPF^n~E znB;;o&<46P1b47N%br*OS|(!KwiBuWCXoHbi#}k|LK=dIeU7|ltG-*Rb{@)B>sQyW@NCmd9$VDg&Od- z>({IgyX`(N3NFFJlfFFEU#=4zRGBN0W}PB5UmNN?V@;*HTp!<8xWV}FT=d$N>yMj_ zn266#)G1cF--DOU>H`NZv^_A8wEX_=o>^^xDEblmaTH!4pZu?7E(@inI zUR!!hHl1&8S59W&LPgW~&Vi2ZeofwBUFABCjO>c$$w|BM=ewe^P`n&x956}PjmXR#9zN-M zAh!DRL*5-b`kbWtE|zj^KJ)mk*#7;QC~d{}?(N=E5p`2zXOiN!`0;T^NS<8EO_UNf z4R(c<7PV^QXHcP7U%I3Z^QXv|7(ILYoPxqatK8q?vU1W}J6^rIZD?fF=OCF^Q4uqq z|3EJ@s4i)#=T%T|zWlt*vdTB~2zT3?H@t%d#k!iB@t38uA%>cnci*ygtF4{gF+oAW zpsgnF6B4%Bojb=LT$h%Vq}blxUQ$t^w`={L6DNhP09AF}8+&{gZ(wa=ON%D!`t@Dn zjNdPo;%#itoDr9lO#AWU9E5n#alPmt?<>3BEh;VjAR~jH#_J#xBV*d5M_SNwUCRIR z=s@w0sj;y~jg5_M?d{2cv?nJgZO)(nS?9>UX_K~sf)|xa{o){Lx$${cY0-| zsgKXs#8#nj)r+Mk2AyWRJ{~b`Z)+R)^hwUj%8H$h?e6>UZo?J#qPP8 z?3^5Z7nfqxv?g)beya%BR~dEr+xYJ{jqrXMg)G zFy(oMg-fDU_=n mzpV3^{`~a+;r~?ancZ~O_` argument, f .. code-block:: console - $ curl -s "http://localhost:19000/stats?filter=http.ingress_http.rq&format=json" | jq '.stats' + $ curl -s "http://localhost:9901/stats?filter=http.ingress_http.rq&format=json" | jq '.stats' .. code-block:: json @@ -254,3 +254,14 @@ You can also pass a :ref:`format ` argument, f "name": "http.ingress_http.rq_total" } ] + + +Envoy admin web UI +------------------ + +Envoy also has a web user interface that allows you to view and modify settings and +statistics. + +Point your browser to http://localhost:9901. + +.. image:: /_static/envoy-admin.png diff --git a/docs/root/start/quick-start/run-envoy.rst b/docs/root/start/quick-start/run-envoy.rst index b3188c395522..fa1030004cbf 100644 --- a/docs/root/start/quick-start/run-envoy.rst +++ b/docs/root/start/quick-start/run-envoy.rst @@ -20,15 +20,17 @@ Once you have :ref:`installed Envoy `, you can check the version inform .. code-block:: console - $ envoy --version + $ envoy --version + ... .. tab:: Docker .. substitution-code-block:: console - $ docker run --rm \ - envoyproxy/|envoy_docker_image| \ - --version + $ docker run --rm \ + envoyproxy/|envoy_docker_image| \ + --version + ... .. _start_quick_start_help: @@ -44,15 +46,17 @@ flag: .. code-block:: console - $ envoy --help + $ envoy --help + ... .. tab:: Docker .. substitution-code-block:: console - $ docker run --rm \ - envoyproxy/|envoy_docker_image| \ - --help + $ docker run --rm \ + envoyproxy/|envoy_docker_image| \ + --help + ... .. _start_quick_start_config: @@ -70,7 +74,8 @@ The ``-c`` or ``--config-path`` flag tells Envoy the path to its initial configu .. code-block:: console - $ envoy -c envoy-demo.yaml + $ envoy -c envoy-demo.yaml + ... .. tab:: Docker @@ -79,10 +84,11 @@ The ``-c`` or ``--config-path`` flag tells Envoy the path to its initial configu .. substitution-code-block:: console - $ docker run --rm -d \ + $ docker run --rm -it \ -p 9901:9901 \ - -p 10000:10000 \ - envoyproxy/|envoy_docker_image| + -p 10000:10000 \ + envoyproxy/|envoy_docker_image| + ... To specify a custom configuration you can mount the config into the container, and specify the path with ``-c``. @@ -90,44 +96,52 @@ The ``-c`` or ``--config-path`` flag tells Envoy the path to its initial configu .. substitution-code-block:: console - $ docker run --rm -d \ - -v $(pwd)/envoy-custom.yaml:/envoy-custom.yaml \ - -p 9901:9901 \ - -p 10000:10000 \ - envoyproxy/|envoy_docker_image| \ - -c /envoy-custom.yaml + $ docker run --rm -it \ + -v $(pwd)/envoy-custom.yaml:/envoy-custom.yaml \ + -p 9901:9901 \ + -p 10000:10000 \ + envoyproxy/|envoy_docker_image| \ + -c /envoy-custom.yaml + ... -Check Envoy is proxying on http://localhost:10000 +Check Envoy is proxying on http://localhost:10000. .. code-block:: console $ curl -v localhost:10000 + ... -The Envoy admin endpoint should also be available at http://localhost:9901 +The Envoy admin endpoint should also be available at http://localhost:9901. .. code-block:: console $ curl -v localhost:9901 + ... + +You can exit the server with `Ctrl-c`. + +See the :ref:`admin quick start guide ` for more information about the Envoy admin interface. .. _start_quick_start_override: -Override the default configuration by merging a config file ------------------------------------------------------------ +Override the default configuration +---------------------------------- -You can provide a configuration override file using ``--config-yaml`` which will merge with the main +You can provide an override configuration using :option:`--config-yaml` which will merge with the main configuration. +This option can only be specified once. + Save the following snippet to ``envoy-override.yaml``: .. code-block:: yaml - listeners: - - name: listener_0 - address: - socket_address: - port_value: 20000 + admin: + address: + socket_address: + port_value: 9902 -Next, start the Envoy server using the override configuration. +Next, start the Envoy server using the override configuration: .. tabs:: @@ -135,26 +149,184 @@ Next, start the Envoy server using the override configuration. .. code-block:: console - $ envoy -c envoy-demo.yaml --config-yaml envoy-override.yaml + $ envoy -c envoy-demo.yaml --config-yaml "$(cat envoy-override.yaml)" + ... .. tab:: Docker .. substitution-code-block:: console - $ docker run --rm -d \ - -v $(pwd)/envoy-override.yaml:/envoy-override.yaml \ - -p 20000:20000 \ - envoyproxy/|envoy_docker_image| \ - --config-yaml /envoy-override.yaml + $ docker run --rm -it \ + -p 9902:9902 \ + -p 10000:10000 \ + envoyproxy/|envoy_docker_image| \ + -c /etc/envoy/envoy.yaml \ + --config-yaml "$(cat envoy-override.yaml)" + ... -Envoy should now be proxying on http://localhost:20000 +The Envoy admin interface should now be available on http://localhost:9902. .. code-block:: console - $ curl -v localhost:20000 + $ curl -v localhost:9902 + ... -The Envoy admin endpoint should also be available at http://localhost:9901 +.. note:: -.. code-block:: console + When merging ``yaml`` lists (e.g. :ref:`listeners ` + or :ref:`clusters `) the merged configurations + are appended. - $ curl -v localhost:9901 + You cannot therefore use an override file to change the configurations of previously specified + :ref:`listeners ` or + :ref:`clusters ` + +Validating your Envoy configuration +----------------------------------- + +You can start Envoy in :option:`validate mode <--mode>`. + +This allows you to check that Envoy is able to start with your configuration, without actually starting +or restarting the service, or making any network connections. + +If the configuration is valid the process will print ``OK`` and exit with a return code of ``0``. + +For invalid configuration the process will print the errors and exit with ``1``. + +.. tabs:: + + .. tab:: System + + .. code-block:: console + + $ envoy --mode validate -c my-envoy-config.yaml + [2020-11-08 12:36:06.543][11][info][main] [source/server/server.cc:583] runtime: layers: + - name: base + static_layer: + {} + - name: admin + admin_layer: + {} + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:95] loading tracing configuration + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:70] loading 0 static secret(s) + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:76] loading 1 cluster(s) + [2020-11-08 12:36:06.546][11][info][config] [source/server/configuration_impl.cc:80] loading 1 listener(s) + [2020-11-08 12:36:06.549][11][info][config] [source/server/configuration_impl.cc:121] loading stats sink configuration + configuration 'my-envoy-config.yaml' OK + + .. tab:: Docker + + .. substitution-code-block:: console + + $ docker run --rm \ + -v $(pwd)/my-envoy-config.yaml:/my-envoy-config.yaml \ + envoyproxy/|envoy_docker_image| \ + --mode validate \ + -c my-envoy-config.yaml + [2020-11-08 12:36:06.543][11][info][main] [source/server/server.cc:583] runtime: layers: + - name: base + static_layer: + {} + - name: admin + admin_layer: + {} + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:95] loading tracing configuration + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:70] loading 0 static secret(s) + [2020-11-08 12:36:06.543][11][info][config] [source/server/configuration_impl.cc:76] loading 1 cluster(s) + [2020-11-08 12:36:06.546][11][info][config] [source/server/configuration_impl.cc:80] loading 1 listener(s) + [2020-11-08 12:36:06.549][11][info][config] [source/server/configuration_impl.cc:121] loading stats sink configuration + configuration 'my-envoy-config.yaml' OK + +Envoy logging +------------- + +By default Envoy system logs are sent to ``/dev/stderr``. + +This can be overridden using :option:`--log-path`. + +.. tabs:: + + .. tab:: System + + .. code-block:: console + + $ mkdir logs + $ envoy -c envoy-demo.yaml --log-path logs/custom.log + ... + + .. tab:: Docker + + .. substitution-code-block:: console + + $ mkdir logs + $ chmod go+rwx logs/ + $ docker run --rm -it \ + -p 10000:10000 \ + -v $(pwd)/logs:/logs \ + envoyproxy/|envoy_docker_image| \ + -c /etc/envoy/envoy.yaml \ + --log-path logs/custom.log + ... + +:ref:`Access log ` paths can be set for the +:ref:`admin interface `, and for configured +:ref:`listeners `. + +Some Envoy :ref:`filters and extensions ` may also have additional logging capabilities. + +Envoy can be configured to log to :ref:`different formats `, and to +:ref:`different outputs ` in addition to files and ``stdout/err``. + +.. note:: + + If you are running Envoy on a Windows system Envoy will output to ``CON`` by default. + + This can also be used as a logging path when configuring logging. + +Debugging Envoy +--------------- + +The log level for Envoy system logs can be set using the :option:`-l or --log-level <--log-level>` option. + +The default is ``info``. + +The available log levels are: + +- ``trace`` +- ``debug`` +- ``info`` +- ``warning/warn`` +- ``error`` +- ``critical`` +- ``off`` + +You can also set the log level for specific components using the :option:`--component-log-level` option. + +The following example inhibits all logging except for the ``upstream`` and ``connection`` components, +which are set to ``debug`` and ``trace`` respectively. + +.. tabs:: + + .. tab:: System + + .. code-block:: console + + $ envoy -c envoy-demo.yaml -l off --component-log-level upstream:debug,connection:trace + ... + + .. tab:: Docker + + .. substitution-code-block:: console + + $ docker run --rm -d \ + -p 9901:9901 \ + -p 10000:10000 \ + envoyproxy/|envoy_docker_image| \ + -c /etc/envoy/envoy.yaml \ + -l off \ + --component-log-level upstream:debug,connection:trace + ... + +.. tip:: + + See ``ALL_LOGGER_IDS`` in :repo:`logger.h ` for a list of components. diff --git a/docs/root/start/quick-start/securing.rst b/docs/root/start/quick-start/securing.rst index efacfa1191df..cb9d50b52ae4 100644 --- a/docs/root/start/quick-start/securing.rst +++ b/docs/root/start/quick-start/securing.rst @@ -10,6 +10,10 @@ Transport Layer Security (``TLS``) can be used to secure all types of ``HTTP`` t Envoy also has support for transmitting and receiving generic ``TCP`` traffic with ``TLS``. +Envoy also offers a number of other ``HTTP``-based protocols for authentication and authorization +such as :ref:`JWT `, :ref:`RBAC ` +and :ref:`OAuth `. + .. warning:: The following guide takes you through individual aspects of securing traffic. @@ -20,20 +24,22 @@ Envoy also has support for transmitting and receiving generic ``TCP`` traffic wi Here we provide a guide to using :ref:`mTLS ` which provides both encryption and mutual authentication. - You are also strongly encouraged to :ref:`validate ` all certificates - wherever possible. + When using ``TLS``, you are strongly encouraged to :ref:`validate ` + all certificates wherever possible. It is your responsibility to ensure the integrity of your certificate chain, and outside the scope of this guide. .. _start_quick_start_securing_contexts: -Upstream and downstream TLS contexts ------------------------------------- +Upstream and downstream ``TLS`` contexts +---------------------------------------- Machines connecting to Envoy to proxy traffic are "downstream" in relation to Envoy. Specifying a ``TLS`` context that clients can connect to is achieved by setting the :ref:`DownstreamTLSContext ` +in the :ref:`transport_socket ` of a +:ref:`listener `. You will also need to provide valid certificates. @@ -46,7 +52,8 @@ You will also need to provide valid certificates. Connecting to an "upstream" ``TLS`` service is conversely done by adding an :ref:`UpstreamTLSContext ` -to the :ref:`cluster `. +to the :ref:`transport_socket ` of a +:ref:`cluster `. .. literalinclude:: _include/envoy-demo-tls.yaml :language: yaml @@ -102,10 +109,10 @@ certificate is valid for. .. _start_quick_start_securing_mtls: -Use mututal TLS (mTLS) to enforce client certificate authentication -------------------------------------------------------------------- +Use mututal ``TLS`` (``mTLS``) to enforce client certificate authentication +--------------------------------------------------------------------------- -With mutual TLS (mTLS), Envoy also provides a way to authenticate connecting clients. +With mutual ``TLS`` (``mTLS``), Envoy also provides a way to authenticate connecting clients. At a minimum you will need to set :ref:`require_client_certificate ` @@ -139,8 +146,8 @@ similar to validating upstream certificates :ref:`described above ` in the configuration diff --git a/docs/root/start/start.rst b/docs/root/start/start.rst index 2747baa3e334..2f7fdb876499 100644 --- a/docs/root/start/start.rst +++ b/docs/root/start/start.rst @@ -8,9 +8,7 @@ This section gets you started with a very simple configuration and provides some The fastest way to get started using Envoy is :ref:`installing pre-built binaries `. You can also :ref:`build it ` from source. -These examples use the :ref:`v3 Envoy API `, but use only the static configuration -feature of the API, which is most useful for simple requirements. For more complex requirements -:ref:`Dynamic Configuration ` is supported. +These examples use the :ref:`v3 Envoy API `. .. toctree:: :maxdepth: 3 From 8e6b176b89240d1b8ce3f3e4a8e276e4a40fcd1e Mon Sep 17 00:00:00 2001 From: phlax Date: Tue, 10 Nov 2020 18:00:05 +0000 Subject: [PATCH 061/117] examples: Add double proxy sandbox (#13748) Signed-off-by: Ryan Northey --- docs/root/start/sandboxes/double-proxy.rst | 174 ++++++++++++++++++ docs/root/start/sandboxes/index.rst | 1 + examples/double-proxy/Dockerfile-app | 7 + examples/double-proxy/Dockerfile-proxy | 5 + .../double-proxy/Dockerfile-proxy-backend | 9 + .../double-proxy/Dockerfile-proxy-frontend | 9 + examples/double-proxy/README.md | 2 + examples/double-proxy/docker-compose.yaml | 64 +++++++ examples/double-proxy/envoy-backend.yaml | 51 +++++ examples/double-proxy/envoy-frontend.yaml | 47 +++++ examples/double-proxy/envoy.yaml | 41 +++++ examples/double-proxy/service.py | 21 +++ examples/double-proxy/verify.sh | 54 ++++++ 13 files changed, 485 insertions(+) create mode 100644 docs/root/start/sandboxes/double-proxy.rst create mode 100644 examples/double-proxy/Dockerfile-app create mode 100644 examples/double-proxy/Dockerfile-proxy create mode 100644 examples/double-proxy/Dockerfile-proxy-backend create mode 100644 examples/double-proxy/Dockerfile-proxy-frontend create mode 100644 examples/double-proxy/README.md create mode 100644 examples/double-proxy/docker-compose.yaml create mode 100644 examples/double-proxy/envoy-backend.yaml create mode 100644 examples/double-proxy/envoy-frontend.yaml create mode 100644 examples/double-proxy/envoy.yaml create mode 100644 examples/double-proxy/service.py create mode 100755 examples/double-proxy/verify.sh diff --git a/docs/root/start/sandboxes/double-proxy.rst b/docs/root/start/sandboxes/double-proxy.rst new file mode 100644 index 000000000000..0aa68e46fc2e --- /dev/null +++ b/docs/root/start/sandboxes/double-proxy.rst @@ -0,0 +1,174 @@ +.. _install_sandboxes_double_proxy: + +Double proxy (with ``mTLS`` encryption) +======================================= + +This sandbox demonstrates a basic "double proxy" configuration, in which a simple Flask app +connects to a PostgreSQL database, with two Envoy proxies in between. + +``Envoy (front)`` -> ``Flask`` -> ``Envoy (postgres-front)`` -> ``Envoy (postgres-back)`` -> ``PostgreSQL`` + +This type of setup is common in a service mesh where Envoy acts as a "sidecar" between individual services. + +It can also be useful as a way of providing access for application servers to upstream services or +databases that may be in a different location or subnet, outside of a service mesh or sidecar-based setup. + +Another common use case is with Envoy configured to provide "Points of presence" at the edges of the cloud, +and to relay requests to upstream servers and services. + +This example encrypts the transmission of data between the two middle proxies and provides mutual authentication +using ``mTLS``. + +This can be useful if the proxies are physically separated or transmit data over untrusted networks. + +In order to use the sandbox you will first need to generate the necessary ``SSL`` keys and certificates. + +This example walks through creating a certificate authority, and using it to create a domain key and sign +certificates for the proxies. + +.. include:: _include/docker-env-setup.rst + +Change to the ``examples/double-proxy`` directory. + +Step 3: Create a certificate authority +************************************** + +First create a key for the certificate authority: + +.. code-block:: console + + $ pwd + envoy/examples/double-proxy + $ mkdir -p certs + $ openssl genrsa -out certs/ca.key 4096 + Generating RSA private key, 4096 bit long modulus (2 primes) + ..........++++ + ..........................................................................................................++++ + e is 65537 (0x010001) + +Now use the key to generate a certificate authority certificate. + +If you wish, you can interactively alter the fields in the certificate. + +For the purpose of this example, the defaults should be sufficient. + +.. code-block:: console + + $ openssl req -x509 -new -nodes -key certs/ca.key -sha256 -days 1024 -out certs/ca.crt + + You are about to be asked to enter information that will be incorporated + into your certificate request. + What you are about to enter is what is called a Distinguished Name or a DN. + There are quite a few fields but you can leave some blank + For some fields there will be a default value, + If you enter '.', the field will be left blank. + ----- + Country Name (2 letter code) [AU]: + State or Province Name (full name) [Some-State]: + Locality Name (eg, city) []: + Organization Name (eg, company) [Internet Widgits Pty Ltd]: + Organizational Unit Name (eg, section) []: + Common Name (e.g. server FQDN or YOUR name) []: + Email Address []: + +Step 4: Create a domain key +*************************** + +Create a key for the example domain: + +.. code-block:: console + + $ openssl genrsa -out certs/example.com.key 2048 + Generating RSA private key, 2048 bit long modulus (2 primes) + ..+++++ + .................................................+++++ + e is 65537 (0x010001) + +Step 5: Generate certificate signing requests for the proxies +************************************************************* + +Use the domain key to generate certificate signing requests for each of the proxies: + +.. code-block:: console + + $ openssl req -new -sha256 \ + -key certs/example.com.key \ + -subj "/C=US/ST=CA/O=MyExample, Inc./CN=proxy-postgres-frontend.example.com" \ + -out certs/proxy-postgres-frontend.example.com.csr + $ openssl req -new -sha256 \ + -key certs/example.com.key \ + -subj "/C=US/ST=CA/O=MyExample, Inc./CN=proxy-postgres-backend.example.com" \ + -out certs/proxy-postgres-backend.example.com.csr + +Step 6: Sign the proxy certificates +*********************************** + +You can now use the certificate authority that you created to sign the certificate requests. + +Note the ``subjectAltName``. This is used for reciprocally matching and validating the certificates. + +.. code-block:: console + + $ openssl x509 -req \ + -in certs/proxy-postgres-frontend.example.com.csr \ + -CA certs/ca.crt \ + -CAkey certs/ca.key \ + -CAcreateserial \ + -extfile <(printf "subjectAltName=DNS:proxy-postgres-frontend.example.com") \ + -out certs/postgres-frontend.example.com.crt \ + -days 500 \ + -sha256 + Signature ok + subject=C = US, ST = CA, O = "MyExample, Inc.", CN = proxy-postgres-frontend.example.com + Getting CA Private Key + + $ openssl x509 -req \ + -in certs/proxy-postgres-backend.example.com.csr \ + -CA certs/ca.crt \ + -CAkey certs/ca.key \ + -CAcreateserial \ + -extfile <(printf "subjectAltName=DNS:proxy-postgres-backend.example.com") \ + -out certs/postgres-backend.example.com.crt \ + -days 500 \ + -sha256 + Signature ok + subject=C = US, ST = CA, O = "MyExample, Inc.", CN = proxy-postgres-backend.example.com + Getting CA Private Key + +At this point you should have the necessary keys and certificates to secure the connection between +the proxies. + +They keys and certificates are stored in the ``certs/`` directory. + +Step 7: Start all of our containers +*********************************** + +Build and start the containers. + +This will load the required keys and certificates into the frontend and backend proxies. + +.. code-block:: console + + $ pwd + envoy/examples/double-proxy + $ docker-compose build --pull + $ docker-compose up -d + $ docker-compose ps + + Name Command State Ports + -------------------------------------------------------------------------------------------------------- + double-proxy_app_1 python3 /code/service.py Up + double-proxy_postgres_1 docker-entrypoint.sh postgres Up 5432/tcp + double-proxy_proxy-frontend_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10000->10000/tcp + double-proxy_proxy-postgres-backend_1 /docker-entrypoint.sh /usr ... Up 10000/tcp + double-proxy_proxy-postgres-frontend_1 /docker-entrypoint.sh /usr ... Up 10000/tcp + +Step 8: Check the flask app can connect to the database +******************************************************* + +Checking the response at http://localhost:10000, you should see the output from the Flask app: + +.. code-block:: console + + $ curl -s http://localhost:10000 + Connected to Postgres, version: PostgreSQL 13.0 (Debian 13.0-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit diff --git a/docs/root/start/sandboxes/index.rst b/docs/root/start/sandboxes/index.rst index 6c181e5fb3c7..1acabd3696e6 100644 --- a/docs/root/start/sandboxes/index.rst +++ b/docs/root/start/sandboxes/index.rst @@ -14,6 +14,7 @@ features. The following sandboxes are available: cache cors csrf + double-proxy dynamic-configuration-filesystem dynamic-configuration-control-plane ext_authz diff --git a/examples/double-proxy/Dockerfile-app b/examples/double-proxy/Dockerfile-app new file mode 100644 index 000000000000..f843db1f8a53 --- /dev/null +++ b/examples/double-proxy/Dockerfile-app @@ -0,0 +1,7 @@ +FROM python:3.8-alpine + +RUN apk update && apk add postgresql-dev gcc python3-dev musl-dev +RUN pip3 install -q Flask==0.11.1 requests==2.18.4 psycopg2-binary +RUN mkdir /code +ADD ./service.py /code +ENTRYPOINT ["python3", "/code/service.py"] diff --git a/examples/double-proxy/Dockerfile-proxy b/examples/double-proxy/Dockerfile-proxy new file mode 100644 index 000000000000..f70f44311461 --- /dev/null +++ b/examples/double-proxy/Dockerfile-proxy @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/double-proxy/Dockerfile-proxy-backend b/examples/double-proxy/Dockerfile-proxy-backend new file mode 100644 index 000000000000..875e76ad80ff --- /dev/null +++ b/examples/double-proxy/Dockerfile-proxy-backend @@ -0,0 +1,9 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-backend.yaml /etc/envoy.yaml +COPY ./certs/ca.crt /certs/cacert.pem +COPY ./certs/postgres-backend.example.com.crt /certs/servercert.pem +COPY ./certs/example.com.key /certs/serverkey.pem + +RUN chmod go+r /etc/envoy.yaml /certs/cacert.pem /certs/serverkey.pem /certs/servercert.pem +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/double-proxy/Dockerfile-proxy-frontend b/examples/double-proxy/Dockerfile-proxy-frontend new file mode 100644 index 000000000000..4b39ef030f9f --- /dev/null +++ b/examples/double-proxy/Dockerfile-proxy-frontend @@ -0,0 +1,9 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-frontend.yaml /etc/envoy.yaml +COPY ./certs/ca.crt /certs/cacert.pem +COPY ./certs/postgres-frontend.example.com.crt /certs/clientcert.pem +COPY ./certs/example.com.key /certs/clientkey.pem + +RUN chmod go+r /etc/envoy.yaml /certs/cacert.pem /certs/clientkey.pem /certs/clientcert.pem +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/double-proxy/README.md b/examples/double-proxy/README.md new file mode 100644 index 000000000000..42ac1ea14aab --- /dev/null +++ b/examples/double-proxy/README.md @@ -0,0 +1,2 @@ +To learn about this sandbox and for instructions on how to run it please head over +to the [Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/double-proxy.html). diff --git a/examples/double-proxy/docker-compose.yaml b/examples/double-proxy/docker-compose.yaml new file mode 100644 index 000000000000..5b4d7b6452ee --- /dev/null +++ b/examples/double-proxy/docker-compose.yaml @@ -0,0 +1,64 @@ +version: "3.7" +services: + + proxy-frontend: + build: + context: . + dockerfile: Dockerfile-proxy + networks: + edge: + ports: + - "10000:10000" + + app: + build: + context: . + dockerfile: Dockerfile-app + networks: + edge: + postgres-frontend: + + proxy-postgres-frontend: + build: + context: . + dockerfile: Dockerfile-proxy-frontend + networks: + postgres-frontend: + aliases: + - postgres + postgres-in-between: + + proxy-postgres-backend: + build: + context: . + dockerfile: Dockerfile-proxy-backend + networks: + postgres-backend: + postgres-in-between: + aliases: + - proxy-postgres-backend.example.com + + postgres: + image: postgres:latest + networks: + postgres-backend: + environment: + # WARNING! Do not use it on production environments because this will + # allow anyone with access to the Postgres port to access your + # database without a password, even if POSTGRES_PASSWORD is set. + # See PostgreSQL documentation about "trust": + # https://www.postgresql.org/docs/current/auth-trust.html + POSTGRES_HOST_AUTH_METHOD: trust + +networks: + edge: + name: edge + + postgres-backend: + name: postgres-backend + + postgres-frontend: + name: postgres-frontend + + postgres-in-between: + name: postgres-in-between diff --git a/examples/double-proxy/envoy-backend.yaml b/examples/double-proxy/envoy-backend.yaml new file mode 100644 index 000000000000..9095875fa01d --- /dev/null +++ b/examples/double-proxy/envoy-backend.yaml @@ -0,0 +1,51 @@ +static_resources: + listeners: + - name: postgres_listener + address: + socket_address: + address: 0.0.0.0 + port_value: 5432 + listener_filters: + - name: "envoy.filters.listener.tls_inspector" + typed_config: {} + filter_chains: + - filters: + - name: envoy.filters.network.postgres_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.postgres_proxy.v3alpha.PostgresProxy + stat_prefix: egress_postgres + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: postgres_tcp + cluster: postgres_cluster + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + require_client_certificate: true + common_tls_context: + tls_certificates: + certificate_chain: + filename: certs/servercert.pem + private_key: + filename: certs/serverkey.pem + validation_context: + match_subject_alt_names: + - exact: proxy-postgres-frontend.example.com + trusted_ca: + filename: certs/cacert.pem + + clusters: + - name: postgres_cluster + connect_timeout: 1s + type: strict_dns + load_assignment: + cluster_name: postgres_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: postgres + port_value: 5432 diff --git a/examples/double-proxy/envoy-frontend.yaml b/examples/double-proxy/envoy-frontend.yaml new file mode 100644 index 000000000000..b8900f0d6f3d --- /dev/null +++ b/examples/double-proxy/envoy-frontend.yaml @@ -0,0 +1,47 @@ +static_resources: + listeners: + - name: postgres_listener + address: + socket_address: + address: 0.0.0.0 + port_value: 5432 + filter_chains: + - filters: + - name: envoy.filters.network.postgres_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.postgres_proxy.v3alpha.PostgresProxy + stat_prefix: egress_postgres + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + stat_prefix: postgres_tcp + cluster: postgres_cluster + + clusters: + - name: postgres_cluster + connect_timeout: 1s + type: strict_dns + load_assignment: + cluster_name: postgres_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: proxy-postgres-backend.example.com + port_value: 5432 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + tls_certificates: + certificate_chain: + filename: certs/clientcert.pem + private_key: + filename: certs/clientkey.pem + validation_context: + match_subject_alt_names: + - exact: proxy-postgres-backend.example.com + trusted_ca: + filename: certs/cacert.pem diff --git a/examples/double-proxy/envoy.yaml b/examples/double-proxy/envoy.yaml new file mode 100644 index 000000000000..f63fc12697ee --- /dev/null +++ b/examples/double-proxy/envoy.yaml @@ -0,0 +1,41 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service1 + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: service1 + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service1 + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: app + port_value: 8000 diff --git a/examples/double-proxy/service.py b/examples/double-proxy/service.py new file mode 100644 index 000000000000..40c693c11efc --- /dev/null +++ b/examples/double-proxy/service.py @@ -0,0 +1,21 @@ +import sys + +from flask import Flask + +import psycopg2 + +app = Flask(__name__) + + +@app.route('/') +def hello(): + conn = psycopg2.connect("host=postgres user=postgres") + cur = conn.cursor() + cur.execute('SELECT version()') + msg = 'Connected to Postgres, version: %s' % cur.fetchone() + cur.close() + return msg + + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=8000, debug=True) diff --git a/examples/double-proxy/verify.sh b/examples/double-proxy/verify.sh new file mode 100755 index 000000000000..e933d8ece68c --- /dev/null +++ b/examples/double-proxy/verify.sh @@ -0,0 +1,54 @@ +#!/bin/bash -e + +export NAME=double-proxy +export MANUAL=true +export DELAY=5 + +# shellcheck source=examples/verify-common.sh +. "$(dirname "${BASH_SOURCE[0]}")/../verify-common.sh" + +mkdir -p certs + +run_log "Create a cert authority" +openssl genrsa -out certs/ca.key 4096 +openssl req -batch -x509 -new -nodes -key certs/ca.key -sha256 -days 1024 -out certs/ca.crt + +run_log "Create a domain key" +openssl genrsa -out certs/example.com.key 2048 + +run_log "Generate signing requests for each proxy" +openssl req -new -sha256 \ + -key certs/example.com.key \ + -subj "/C=US/ST=CA/O=MyExample, Inc./CN=proxy-postgres-frontend.example.com" \ + -out certs/proxy-postgres-frontend.example.com.csr +openssl req -new -sha256 \ + -key certs/example.com.key \ + -subj "/C=US/ST=CA/O=MyExample, Inc./CN=proxy-postgres-backend.example.com" \ + -out certs/proxy-postgres-backend.example.com.csr + +run_log "Generate certificates for each proxy" +openssl x509 -req \ + -in certs/proxy-postgres-frontend.example.com.csr \ + -CA certs/ca.crt \ + -CAkey certs/ca.key \ + -CAcreateserial \ + -extfile <(printf "subjectAltName=DNS:proxy-postgres-frontend.example.com") \ + -out certs/postgres-frontend.example.com.crt \ + -days 500 \ + -sha256 +openssl x509 -req \ + -in certs/proxy-postgres-backend.example.com.csr \ + -CA certs/ca.crt \ + -CAkey certs/ca.key \ + -CAcreateserial \ + -extfile <(printf "subjectAltName=DNS:proxy-postgres-backend.example.com") \ + -out certs/postgres-backend.example.com.crt \ + -days 500 \ + -sha256 + +bring_up_example + +run_log "Test app/db connection" +responds_with \ + "Connected to Postgres, version: PostgreSQL" \ + http://localhost:10000 From d76e656309128c7733d97994e761c56a457be0f4 Mon Sep 17 00:00:00 2001 From: Zach Reyes <39203661+zasweq@users.noreply.github.com> Date: Tue, 10 Nov 2020 16:47:53 -0500 Subject: [PATCH 062/117] [fuzz] Got rid of all uninteresting call logs in health check fuzzing (#13891) * Got rid of all uninteresting call logs in health check fuzzing Signed-off-by: Zach --- test/common/upstream/BUILD | 10 +- test/common/upstream/health_check_fuzz.cc | 2 +- test/common/upstream/health_check_fuzz.h | 4 +- ...ils.cc => health_check_fuzz_test_utils.cc} | 6 +- ...utils.h => health_check_fuzz_test_utils.h} | 10 +- .../upstream/health_checker_impl_test.cc | 201 +++++++++++++++++- 6 files changed, 212 insertions(+), 21 deletions(-) rename test/common/upstream/{health_checker_impl_test_utils.cc => health_check_fuzz_test_utils.cc} (96%) rename test/common/upstream/{health_checker_impl_test_utils.h => health_check_fuzz_test_utils.h} (93%) diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index a793dbe3ab4a..6837a9f5a0da 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -145,12 +145,12 @@ envoy_benchmark_test( ) envoy_cc_test_library( - name = "health_checker_impl_test_lib", + name = "health_check_fuzz_utils_lib", srcs = [ - "health_checker_impl_test_utils.cc", + "health_check_fuzz_test_utils.cc", ], hdrs = [ - "health_checker_impl_test_utils.h", + "health_check_fuzz_test_utils.h", ], deps = [ ":utility_lib", @@ -169,7 +169,7 @@ envoy_cc_test( "health_checker_impl_test.cc", ], deps = [ - ":health_checker_impl_test_lib", + ":utility_lib", "//source/common/buffer:buffer_lib", "//source/common/event:dispatcher_lib", "//source/common/http:headers_lib", @@ -710,7 +710,7 @@ envoy_cc_test_library( hdrs = ["health_check_fuzz.h"], deps = [ ":health_check_fuzz_proto_cc_proto", - ":health_checker_impl_test_lib", + ":health_check_fuzz_utils_lib", ":utility_lib", "//test/fuzz:utility_lib", ], diff --git a/test/common/upstream/health_check_fuzz.cc b/test/common/upstream/health_check_fuzz.cc index f981aba20cba..9fe5bd15092f 100644 --- a/test/common/upstream/health_check_fuzz.cc +++ b/test/common/upstream/health_check_fuzz.cc @@ -306,7 +306,7 @@ void TcpHealthCheckFuzz::raiseEvent(const Network::ConnectionEvent& event_type, void GrpcHealthCheckFuzz::allocGrpcHealthCheckerFromProto( const envoy::config::core::v3::HealthCheck& config) { - health_checker_ = std::make_shared( + health_checker_ = std::make_shared>( *cluster_, config, dispatcher_, runtime_, random_, HealthCheckEventLoggerPtr(event_logger_storage_.release())); ENVOY_LOG_MISC(trace, "Created Test Grpc Health Checker"); diff --git a/test/common/upstream/health_check_fuzz.h b/test/common/upstream/health_check_fuzz.h index 9749ef492ff5..82052ff0c7d9 100644 --- a/test/common/upstream/health_check_fuzz.h +++ b/test/common/upstream/health_check_fuzz.h @@ -3,7 +3,7 @@ #include #include "test/common/upstream/health_check_fuzz.pb.validate.h" -#include "test/common/upstream/health_checker_impl_test_utils.h" +#include "test/common/upstream/health_check_fuzz_test_utils.h" #include "test/fuzz/common.pb.h" namespace Envoy { @@ -114,7 +114,7 @@ class GrpcHealthCheckFuzz : public HealthCheckFuzz, public HealthCheckerTestBase void expectClientCreate(); void expectStreamCreate(); - std::shared_ptr health_checker_; + std::shared_ptr> health_checker_; }; } // namespace Upstream diff --git a/test/common/upstream/health_checker_impl_test_utils.cc b/test/common/upstream/health_check_fuzz_test_utils.cc similarity index 96% rename from test/common/upstream/health_checker_impl_test_utils.cc rename to test/common/upstream/health_check_fuzz_test_utils.cc index e724ccbb7482..b1e8b78387d0 100644 --- a/test/common/upstream/health_checker_impl_test_utils.cc +++ b/test/common/upstream/health_check_fuzz_test_utils.cc @@ -1,4 +1,4 @@ -#include "test/common/upstream/health_checker_impl_test_utils.h" +#include "test/common/upstream/health_check_fuzz_test_utils.h" #include "test/common/upstream/utility.h" @@ -14,8 +14,8 @@ void HttpHealthCheckerImplTestBase::expectSessionCreate( TestSessionPtr new_test_session(new TestSession()); test_sessions_.emplace_back(std::move(new_test_session)); TestSession& test_session = *test_sessions_.back(); - test_session.timeout_timer_ = new Event::MockTimer(&dispatcher_); - test_session.interval_timer_ = new Event::MockTimer(&dispatcher_); + test_session.timeout_timer_ = new NiceMock(&dispatcher_); + test_session.interval_timer_ = new NiceMock(&dispatcher_); expectClientCreate(test_sessions_.size() - 1, health_check_map); } diff --git a/test/common/upstream/health_checker_impl_test_utils.h b/test/common/upstream/health_check_fuzz_test_utils.h similarity index 93% rename from test/common/upstream/health_checker_impl_test_utils.h rename to test/common/upstream/health_check_fuzz_test_utils.h index 7b95c2a285ea..1074823314e7 100644 --- a/test/common/upstream/health_checker_impl_test_utils.h +++ b/test/common/upstream/health_check_fuzz_test_utils.h @@ -17,9 +17,9 @@ class HealthCheckerTestBase { std::shared_ptr cluster_{ std::make_shared>()}; NiceMock dispatcher_; - std::unique_ptr event_logger_storage_{ - std::make_unique()}; - MockHealthCheckEventLogger& event_logger_{*event_logger_storage_}; + std::unique_ptr> event_logger_storage_{ + std::make_unique>()}; + NiceMock& event_logger_{*event_logger_storage_}; NiceMock random_; NiceMock runtime_; }; @@ -41,8 +41,8 @@ class TestHttpHealthCheckerImpl : public HttpHealthCheckerImpl { class HttpHealthCheckerImplTestBase : public HealthCheckerTestBase { public: struct TestSession { - Event::MockTimer* interval_timer_{}; - Event::MockTimer* timeout_timer_{}; + NiceMock* interval_timer_{}; + NiceMock* timeout_timer_{}; Http::MockClientConnection* codec_{}; Stats::IsolatedStoreImpl stats_store_; Network::MockClientConnection* client_connection_{}; diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index e12d543bfb16..811b7dc86ff0 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -15,10 +15,10 @@ #include "common/json/json_loader.h" #include "common/network/utility.h" #include "common/protobuf/utility.h" +#include "common/upstream/health_checker_impl.h" #include "common/upstream/upstream_impl.h" #include "test/common/http/common.h" -#include "test/common/upstream/health_checker_impl_test_utils.h" #include "test/common/upstream/utility.h" #include "test/mocks/access_log/mocks.h" #include "test/mocks/api/mocks.h" @@ -29,6 +29,7 @@ #include "test/mocks/runtime/mocks.h" #include "test/mocks/upstream/cluster_info.h" #include "test/mocks/upstream/cluster_priority_set.h" +#include "test/mocks/upstream/health_check_event_logger.h" #include "test/mocks/upstream/host_set.h" #include "test/mocks/upstream/transport_socket_match.h" #include "test/test_common/printers.h" @@ -98,8 +99,49 @@ TEST(HealthCheckerFactoryTest, CreateGrpc) { .get())); } -class HttpHealthCheckerImplTest : public testing::Test, public HttpHealthCheckerImplTestBase { +class HealthCheckerTestBase { public: + std::shared_ptr cluster_{ + std::make_shared>()}; + NiceMock dispatcher_; + std::unique_ptr event_logger_storage_{ + std::make_unique()}; + MockHealthCheckEventLogger& event_logger_{*event_logger_storage_}; + NiceMock random_; + NiceMock runtime_; +}; + +class TestHttpHealthCheckerImpl : public HttpHealthCheckerImpl { +public: + using HttpHealthCheckerImpl::HttpHealthCheckerImpl; + + Http::CodecClient* createCodecClient(Upstream::Host::CreateConnectionData& conn_data) override { + return createCodecClient_(conn_data); + }; + + // HttpHealthCheckerImpl + MOCK_METHOD(Http::CodecClient*, createCodecClient_, (Upstream::Host::CreateConnectionData&)); + + Http::CodecClient::Type codecClientType() { return codec_client_type_; } +}; + +class HttpHealthCheckerImplTest : public testing::Test, public HealthCheckerTestBase { +public: + struct TestSession { + Event::MockTimer* interval_timer_{}; + Event::MockTimer* timeout_timer_{}; + Http::MockClientConnection* codec_{}; + Stats::IsolatedStoreImpl stats_store_; + Network::MockClientConnection* client_connection_{}; + NiceMock request_encoder_; + Http::ResponseDecoder* stream_response_callbacks_{}; + }; + + using TestSessionPtr = std::unique_ptr; + using HostWithHealthCheckMap = + absl::node_hash_map; + void allocHealthChecker(const std::string& yaml, bool avoid_boosting = true) { health_checker_ = std::make_shared( *cluster_, parseHealthCheckFromV3Yaml(yaml, avoid_boosting), dispatcher_, runtime_, random_, @@ -477,6 +519,60 @@ class HttpHealthCheckerImplTest : public testing::Test, public HttpHealthChecker addCompletionCallback(); } + void expectSessionCreate(const HostWithHealthCheckMap& health_check_map) { + // Expectations are in LIFO order. + TestSessionPtr new_test_session(new TestSession()); + new_test_session->timeout_timer_ = new Event::MockTimer(&dispatcher_); + new_test_session->interval_timer_ = new Event::MockTimer(&dispatcher_); + test_sessions_.emplace_back(std::move(new_test_session)); + expectClientCreate(test_sessions_.size() - 1, health_check_map); + } + + void expectClientCreate(size_t index, const HostWithHealthCheckMap& health_check_map) { + TestSession& test_session = *test_sessions_[index]; + test_session.codec_ = new NiceMock(); + ON_CALL(*test_session.codec_, protocol()).WillByDefault(Return(Http::Protocol::Http11)); + test_session.client_connection_ = new NiceMock(); + connection_index_.push_back(index); + codec_index_.push_back(index); + + EXPECT_CALL(dispatcher_, createClientConnection_(_, _, _, _)) + .Times(testing::AnyNumber()) + .WillRepeatedly(InvokeWithoutArgs([&]() -> Network::ClientConnection* { + const uint32_t index = connection_index_.front(); + connection_index_.pop_front(); + return test_sessions_[index]->client_connection_; + })); + EXPECT_CALL(*health_checker_, createCodecClient_(_)) + .WillRepeatedly( + Invoke([&](Upstream::Host::CreateConnectionData& conn_data) -> Http::CodecClient* { + if (!health_check_map.empty()) { + const auto& health_check_config = + health_check_map.at(conn_data.host_description_->address()->asString()); + // To make sure health checker checks the correct port. + EXPECT_EQ(health_check_config.port_value(), + conn_data.host_description_->healthCheckAddress()->ip()->port()); + } + const uint32_t index = codec_index_.front(); + codec_index_.pop_front(); + TestSession& test_session = *test_sessions_[index]; + std::shared_ptr cluster{ + new NiceMock()}; + Event::MockDispatcher dispatcher_; + return new CodecClientForTest( + Http::CodecClient::Type::HTTP1, std::move(conn_data.connection_), + test_session.codec_, nullptr, + Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), dispatcher_); + })); + } + + void expectStreamCreate(size_t index) { + test_sessions_[index]->request_encoder_.stream_.callbacks_.clear(); + EXPECT_CALL(*test_sessions_[index]->codec_, newStream(_)) + .WillOnce(DoAll(SaveArgAddress(&test_sessions_[index]->stream_response_callbacks_), + ReturnRef(test_sessions_[index]->request_encoder_))); + } + void respond(size_t index, const std::string& code, bool conn_close, bool proxy_close = false, bool body = false, bool trailers = false, const absl::optional& service_cluster = absl::optional(), @@ -512,6 +608,9 @@ class HttpHealthCheckerImplTest : public testing::Test, public HttpHealthChecker } } + void expectSessionCreate() { expectSessionCreate(health_checker_map_); } + void expectClientCreate(size_t index) { expectClientCreate(index, health_checker_map_); } + void expectSuccessStartFailedFailFirst( const absl::optional& health_checked_cluster = absl::optional()) { cluster_->prioritySet().getMockHostSet(0)->hosts_ = { @@ -561,6 +660,12 @@ class HttpHealthCheckerImplTest : public testing::Test, public HttpHealthChecker } MOCK_METHOD(void, onHostStatus, (HostSharedPtr host, HealthTransition changed_state)); + + std::vector test_sessions_; + std::shared_ptr health_checker_; + std::list connection_index_{}; + std::list codec_index_{}; + const HostWithHealthCheckMap health_checker_map_{}; }; TEST_F(HttpHealthCheckerImplTest, Success) { @@ -3462,8 +3567,36 @@ TEST_F(TcpHealthCheckerImplTest, ConnectionLocalFailure) { EXPECT_EQ(0UL, cluster_->info_->stats_store_.counter("health_check.passive_failure").value()); } -class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils { +class TestGrpcHealthCheckerImpl : public GrpcHealthCheckerImpl { public: + using GrpcHealthCheckerImpl::GrpcHealthCheckerImpl; + + Http::CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& conn_data) override { + auto codec_client = createCodecClient_(conn_data); + return Http::CodecClientPtr(codec_client); + }; + + // GrpcHealthCheckerImpl + MOCK_METHOD(Http::CodecClient*, createCodecClient_, (Upstream::Host::CreateConnectionData&)); +}; + +class GrpcHealthCheckerImplTestBase : public HealthCheckerTestBase { +public: + struct TestSession { + TestSession() = default; + + Event::MockTimer* interval_timer_{}; + Event::MockTimer* timeout_timer_{}; + Http::MockClientConnection* codec_{}; + Stats::IsolatedStoreImpl stats_store_; + Network::MockClientConnection* client_connection_{}; + NiceMock request_encoder_; + Http::ResponseDecoder* stream_response_callbacks_{}; + CodecClientForTest* codec_client_{}; + }; + + using TestSessionPtr = std::unique_ptr; + struct ResponseSpec { struct ChunkSpec { bool valid; @@ -3537,12 +3670,16 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils return ret; } - std::vector> - response_headers; // Encapsulates all three types of responses + std::vector> response_headers; std::vector body_chunks; std::vector> trailers; }; + GrpcHealthCheckerImplTestBase() { + EXPECT_CALL(*cluster_->info_, features()) + .WillRepeatedly(Return(Upstream::ClusterInfo::Features::HTTP2)); + } + void allocHealthChecker(const envoy::config::core::v3::HealthCheck& config) { health_checker_ = std::make_shared( *cluster_, config, dispatcher_, runtime_, random_, @@ -3600,6 +3737,55 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils addCompletionCallback(); } + void expectSessionCreate() { + // Expectations are in LIFO order. + TestSessionPtr new_test_session(new TestSession()); + new_test_session->timeout_timer_ = new Event::MockTimer(&dispatcher_); + new_test_session->interval_timer_ = new Event::MockTimer(&dispatcher_); + test_sessions_.emplace_back(std::move(new_test_session)); + expectClientCreate(test_sessions_.size() - 1); + } + + void expectClientCreate(size_t index) { + TestSession& test_session = *test_sessions_[index]; + test_session.codec_ = new NiceMock(); + test_session.client_connection_ = new NiceMock(); + connection_index_.push_back(index); + codec_index_.push_back(index); + + EXPECT_CALL(dispatcher_, createClientConnection_(_, _, _, _)) + .Times(testing::AnyNumber()) + .WillRepeatedly(InvokeWithoutArgs([&]() -> Network::ClientConnection* { + const uint32_t index = connection_index_.front(); + connection_index_.pop_front(); + return test_sessions_[index]->client_connection_; + })); + + EXPECT_CALL(*health_checker_, createCodecClient_(_)) + .WillRepeatedly( + Invoke([&](Upstream::Host::CreateConnectionData& conn_data) -> Http::CodecClient* { + const uint32_t index = codec_index_.front(); + codec_index_.pop_front(); + TestSession& test_session = *test_sessions_[index]; + std::shared_ptr cluster{ + new NiceMock()}; + Event::MockDispatcher dispatcher_; + + test_session.codec_client_ = new CodecClientForTest( + Http::CodecClient::Type::HTTP1, std::move(conn_data.connection_), + test_session.codec_, nullptr, + Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), dispatcher_); + return test_session.codec_client_; + })); + } + + void expectStreamCreate(size_t index) { + test_sessions_[index]->request_encoder_.stream_.callbacks_.clear(); + EXPECT_CALL(*test_sessions_[index]->codec_, newStream(_)) + .WillOnce(DoAll(SaveArgAddress(&test_sessions_[index]->stream_response_callbacks_), + ReturnRef(test_sessions_[index]->request_encoder_))); + } + // Starts healthchecker and sets up timer expectations, leaving up future specification of // healthcheck response for the caller. Useful when there is only one healthcheck attempt // performed during test case (but possibly on many hosts). @@ -3751,6 +3937,11 @@ class GrpcHealthCheckerImplTestBase : public GrpcHealthCheckerImplTestBaseUtils } MOCK_METHOD(void, onHostStatus, (HostSharedPtr host, HealthTransition changed_state)); + + std::vector test_sessions_; + std::shared_ptr health_checker_; + std::list connection_index_{}; + std::list codec_index_{}; }; class GrpcHealthCheckerImplTest : public testing::Test, public GrpcHealthCheckerImplTestBase {}; From 8def3cbf05fea33640cad08f9957f66d4bc772a3 Mon Sep 17 00:00:00 2001 From: Zach Reyes <39203661+zasweq@users.noreply.github.com> Date: Tue, 10 Nov 2020 16:48:56 -0500 Subject: [PATCH 063/117] [fuzz] Increased fuzz coverage for health check fuzz (#13905) * Increased fuzz coverage for health check fuzz Signed-off-by: Zach --- .../upstream/health_check_corpus/grpc_Success | 1 + .../upstream/health_check_corpus/http_Success | 1 + .../health_check_corpus/http_status-over-600 | 51 +++++++++++++++++++ .../health_check_corpus/http_status-under-100 | 51 +++++++++++++++++++ .../upstream/health_check_corpus/tcp_Success | 1 + test/common/upstream/health_check_fuzz.cc | 9 ++++ test/common/upstream/health_check_fuzz.proto | 1 + 7 files changed, 115 insertions(+) create mode 100644 test/common/upstream/health_check_corpus/http_status-over-600 create mode 100644 test/common/upstream/health_check_corpus/http_status-under-100 diff --git a/test/common/upstream/health_check_corpus/grpc_Success b/test/common/upstream/health_check_corpus/grpc_Success index c33d7837d660..93e78a6acf35 100644 --- a/test/common/upstream/health_check_corpus/grpc_Success +++ b/test/common/upstream/health_check_corpus/grpc_Success @@ -58,3 +58,4 @@ actions { } } } +upstream_cx_success: true diff --git a/test/common/upstream/health_check_corpus/http_Success b/test/common/upstream/health_check_corpus/http_Success index 56ac0b7128d2..7692c8e08549 100644 --- a/test/common/upstream/health_check_corpus/http_Success +++ b/test/common/upstream/health_check_corpus/http_Success @@ -45,3 +45,4 @@ actions { } } } +upstream_cx_success: true diff --git a/test/common/upstream/health_check_corpus/http_status-over-600 b/test/common/upstream/health_check_corpus/http_status-over-600 new file mode 100644 index 000000000000..f8c98fb6a7f8 --- /dev/null +++ b/test/common/upstream/health_check_corpus/http_status-over-600 @@ -0,0 +1,51 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + no_traffic_interval { + seconds: 1 + } + interval_jitter { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + http_health_check { + path: "/healthcheck" + service_name_matcher { + prefix: "locations" + } + expected_statuses { + start: 200 + end: 700 + } + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + + } + } + } +} diff --git a/test/common/upstream/health_check_corpus/http_status-under-100 b/test/common/upstream/health_check_corpus/http_status-under-100 new file mode 100644 index 000000000000..441d9433ff53 --- /dev/null +++ b/test/common/upstream/health_check_corpus/http_status-under-100 @@ -0,0 +1,51 @@ +health_check_config { + timeout { + seconds: 1 + } + interval { + seconds: 1 + } + no_traffic_interval { + seconds: 1 + } + interval_jitter { + seconds: 1 + } + unhealthy_threshold { + value: 2 + } + healthy_threshold: { + value: 2 + } + http_health_check { + path: "/healthcheck" + service_name_matcher { + prefix: "locations" + } + expected_statuses { + start: 50 + end: 500 + } + } +} +actions { + respond { + http_respond { + headers { + headers { + key: ":status" + value: "200" + } + } + status: 200 + } + tcp_respond { + + } + grpc_respond { + grpc_respond_headers { + + } + } + } +} diff --git a/test/common/upstream/health_check_corpus/tcp_Success b/test/common/upstream/health_check_corpus/tcp_Success index 3ea16b92943c..b63b25cfa02e 100644 --- a/test/common/upstream/health_check_corpus/tcp_Success +++ b/test/common/upstream/health_check_corpus/tcp_Success @@ -38,3 +38,4 @@ actions { } } } +upstream_cx_success: true diff --git a/test/common/upstream/health_check_fuzz.cc b/test/common/upstream/health_check_fuzz.cc index 9fe5bd15092f..e564e2fb5a48 100644 --- a/test/common/upstream/health_check_fuzz.cc +++ b/test/common/upstream/health_check_fuzz.cc @@ -104,6 +104,9 @@ void HttpHealthCheckFuzz::initialize(test::common::upstream::HealthCheckTestCase .WillByDefault(testing::Return(input.http_verify_cluster())); cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; + if (input.upstream_cx_success()) { + cluster_->info_->stats().upstream_cx_total_.inc(); + } expectSessionCreate(); expectStreamCreate(0); // This sets up the possibility of testing hosts that never become healthy @@ -212,6 +215,9 @@ void TcpHealthCheckFuzz::initialize(test::common::upstream::HealthCheckTestCase allocTcpHealthCheckerFromProto(input.health_check_config()); cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; + if (input.upstream_cx_success()) { + cluster_->info_->stats().upstream_cx_total_.inc(); + } expectSessionCreate(); expectClientCreate(); health_checker_->start(); @@ -317,6 +323,9 @@ void GrpcHealthCheckFuzz::initialize(test::common::upstream::HealthCheckTestCase allocGrpcHealthCheckerFromProto(input.health_check_config()); cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; + if (input.upstream_cx_success()) { + cluster_->info_->stats().upstream_cx_total_.inc(); + } expectSessionCreate(); ON_CALL(dispatcher_, createClientConnection_(_, _, _, _)) .WillByDefault(testing::InvokeWithoutArgs( diff --git a/test/common/upstream/health_check_fuzz.proto b/test/common/upstream/health_check_fuzz.proto index d4a4dcd2c7bd..2f1d3294a0cd 100644 --- a/test/common/upstream/health_check_fuzz.proto +++ b/test/common/upstream/health_check_fuzz.proto @@ -95,4 +95,5 @@ message HealthCheckTestCase { repeated Action actions = 2; bool http_verify_cluster = 3; //Determines if verify cluster setting is on bool start_failed = 4; + bool upstream_cx_success = 5; } From 28315946b12658b8460462c6d31c751bdf7936f9 Mon Sep 17 00:00:00 2001 From: Wayne Zhang Date: Tue, 10 Nov 2020 16:56:04 -0800 Subject: [PATCH 064/117] update grpc-httpjson-transcoding to a fix to support verb (#13957) Signed-off-by: Wayne Zhang --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 974c9deea62b..e2965701377c 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -507,13 +507,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "grpc-httpjson-transcoding", project_desc = "Library that supports transcoding so that HTTP/JSON can be converted to gRPC", project_url = "https://github.com/grpc-ecosystem/grpc-httpjson-transcoding", - version = "faf8af1e9788cd4385b94c8f85edab5ea5d4b2d6", - sha256 = "62c8cb5ea2cca1142cde9d4a0778c52c6022345c3268c60ef81666946b958ad5", + version = "b48d8aa15b3825e146168146755475ab918e95b7", + sha256 = "4147e992ec239fb78c435fdd9f68e8d93d89106f67278bf2995f3672dddba52b", strip_prefix = "grpc-httpjson-transcoding-{version}", urls = ["https://github.com/grpc-ecosystem/grpc-httpjson-transcoding/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.filters.http.grpc_json_transcoder"], - release_date = "2020-03-02", + release_date = "2020-11-05", cpe = "N/A", ), io_bazel_rules_go = dict( From d342e96e7ba2319329838e799021838354e88118 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 10 Nov 2020 19:56:43 -0500 Subject: [PATCH 065/117] config: requiring upgrade_type have letters (#13968) Signed-off-by: Alyssa Wilk --- .../config/route/v3/route_components.proto | 2 +- .../route/v4alpha/route_components.proto | 2 +- docs/root/version_history/current.rst | 1 + .../config/route/v3/route_components.proto | 2 +- .../route/v4alpha/route_components.proto | 2 +- test/common/router/config_impl_test.cc | 23 +++++++++++++++++++ 6 files changed, 28 insertions(+), 4 deletions(-) diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index f50973f8e13d..62633012cf47 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -751,7 +751,7 @@ message RouteAction { // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. string upgrade_type = 1 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; diff --git a/api/envoy/config/route/v4alpha/route_components.proto b/api/envoy/config/route/v4alpha/route_components.proto index 1808fa11fd78..9f3da5376ae0 100644 --- a/api/envoy/config/route/v4alpha/route_components.proto +++ b/api/envoy/config/route/v4alpha/route_components.proto @@ -747,7 +747,7 @@ message RouteAction { // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. string upgrade_type = 1 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index e8b052a3b41e..38d464d98e0d 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -24,6 +24,7 @@ Bug Fixes --------- *Changes expected to improve the state of the world and are unlikely to have negative effects* +* config: validate that upgrade configs have a non-empty :ref:`upgrade_type `, fixing a bug where an errant "-" could result in unexpected behavior. * dns: fix a bug where custom resolvers provided in configuration were not preserved after network issues. * dns_filter: correctly associate DNS response IDs when multiple queries are received. * http: fixed URL parsing for HTTP/1.1 fully qualified URLs and connect requests containing IPv6 addresses. diff --git a/generated_api_shadow/envoy/config/route/v3/route_components.proto b/generated_api_shadow/envoy/config/route/v3/route_components.proto index 279d7e72f984..6b97d3ff4a12 100644 --- a/generated_api_shadow/envoy/config/route/v3/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v3/route_components.proto @@ -762,7 +762,7 @@ message RouteAction { // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. string upgrade_type = 1 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; diff --git a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto index 07eeeefed423..5c9c2c46a202 100644 --- a/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto +++ b/generated_api_shadow/envoy/config/route/v4alpha/route_components.proto @@ -756,7 +756,7 @@ message RouteAction { // For each upgrade type present in upgrade_configs, requests with // Upgrade: [upgrade_type] will be proxied upstream. string upgrade_type = 1 - [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + [(validate.rules).string = {min_len: 1 well_known_regex: HTTP_HEADER_VALUE strict: false}]; // Determines if upgrades are available on this route. Defaults to true. google.protobuf.BoolValue enabled = 2; diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 3b147b46477c..9ee70ea170fc 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -7120,6 +7120,29 @@ TEST_F(RouteConfigurationV2, UpgradeConfigs) { EXPECT_FALSE(upgrade_map.find("disabled")->second); } +TEST_F(RouteConfigurationV2, EmptyFilterConfigRejected) { + const std::string yaml = R"EOF( +virtual_hosts: + - name: regex + domains: [idle.lyft.com] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/regex" + route: + cluster: some-cluster + upgrade_configs: + - upgrade_type: Websocket + - upgrade_type: disabled + - enabled: false + )EOF"; + + EXPECT_THROW_WITH_REGEX( + TestConfigImpl(parseRouteConfigurationFromYaml(yaml), factory_context_, true), EnvoyException, + "Proto constraint validation failed.*"); +} + TEST_F(RouteConfigurationV2, DuplicateUpgradeConfigs) { const std::string yaml = R"EOF( virtual_hosts: From a55ae7ee8b83b64be90ae788e624efcebe29ed5d Mon Sep 17 00:00:00 2001 From: Jose Ulises Nino Rivera Date: Tue, 10 Nov 2020 18:24:04 -0800 Subject: [PATCH 066/117] apple dns: retry connection establishment (#13942) Commit Message: apple dns - retry connection establishment Risk Level: med Testing: added new tests. Signed-off-by: Jose Nino --- .../arch_overview/listeners/dns_filter.rst | 2 + .../arch_overview/upstream/dns_resolution.rst | 24 +++ .../intro/arch_overview/upstream/upstream.rst | 1 + include/envoy/api/api.h | 4 +- source/common/api/api_impl.h | 2 +- source/common/event/dispatcher_impl.cc | 6 +- source/common/network/BUILD | 2 + source/common/network/apple_dns_impl.cc | 75 +++++++-- source/common/network/apple_dns_impl.h | 27 +++- test/common/network/BUILD | 2 + test/common/network/apple_dns_impl_test.cc | 145 ++++++++++++++++-- test/mocks/api/mocks.h | 2 +- 12 files changed, 260 insertions(+), 32 deletions(-) create mode 100644 docs/root/intro/arch_overview/upstream/dns_resolution.rst diff --git a/docs/root/intro/arch_overview/listeners/dns_filter.rst b/docs/root/intro/arch_overview/listeners/dns_filter.rst index 87295a8d0950..3e4c9d3cd35d 100644 --- a/docs/root/intro/arch_overview/listeners/dns_filter.rst +++ b/docs/root/intro/arch_overview/listeners/dns_filter.rst @@ -1,3 +1,5 @@ +.. _arch_overview_dns_filter: + DNS Filter ========== diff --git a/docs/root/intro/arch_overview/upstream/dns_resolution.rst b/docs/root/intro/arch_overview/upstream/dns_resolution.rst new file mode 100644 index 000000000000..def8b33841ea --- /dev/null +++ b/docs/root/intro/arch_overview/upstream/dns_resolution.rst @@ -0,0 +1,24 @@ +.. _arch_overview_dns_resolution: + +DNS Resolution +============== + +Many Envoy components resolve DNS: different cluster types ( +:ref:`strict dns `, +:ref:`logical dns `); +the :ref:`dynamic forward proxy ` system (which is +composed of a cluster and a filter); +the udp :ref:`dns filter `, etc. +Envoy uses `c-ares `_ as a third party DNS resolution library. +On Apple OSes Envoy additionally offers resolution using Apple specific APIs via the +``envoy.restart_features.use_apple_api_for_dns_lookups`` runtime feature. + +The Apple-based DNS Resolver emits the following stats rooted in the ``dns.apple`` stats tree: + + .. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + connection_failure, Counter, Number of failed attempts to connect to the DNS server + socket_failure, Counter, Number of failed attempts to obtain a file descriptor to the socket to the DNS server + processing_failure, Counter, Number of failures when processing data from the DNS server \ No newline at end of file diff --git a/docs/root/intro/arch_overview/upstream/upstream.rst b/docs/root/intro/arch_overview/upstream/upstream.rst index 3c976f0212c3..0694cb533c6a 100644 --- a/docs/root/intro/arch_overview/upstream/upstream.rst +++ b/docs/root/intro/arch_overview/upstream/upstream.rst @@ -6,6 +6,7 @@ Upstream clusters cluster_manager service_discovery + dns_resolution health_checking connection_pooling load_balancing/load_balancing diff --git a/include/envoy/api/api.h b/include/envoy/api/api.h index e9b3506c0312..aa310d2c40c2 100644 --- a/include/envoy/api/api.h +++ b/include/envoy/api/api.h @@ -57,9 +57,9 @@ class Api { virtual TimeSource& timeSource() PURE; /** - * @return a constant reference to the root Stats::Scope + * @return a reference to the root Stats::Scope */ - virtual const Stats::Scope& rootScope() PURE; + virtual Stats::Scope& rootScope() PURE; /** * @return a reference to the RandomGenerator. diff --git a/source/common/api/api_impl.h b/source/common/api/api_impl.h index 89dde910abd3..fa97e23870a9 100644 --- a/source/common/api/api_impl.h +++ b/source/common/api/api_impl.h @@ -28,7 +28,7 @@ class Impl : public Api { Thread::ThreadFactory& threadFactory() override { return thread_factory_; } Filesystem::Instance& fileSystem() override { return file_system_; } TimeSource& timeSource() override { return time_system_; } - const Stats::Scope& rootScope() override { return store_; } + Stats::Scope& rootScope() override { return store_; } Random::RandomGenerator& randomGenerator() override { return random_generator_; } ProcessContextOptRef processContext() override { return process_context_; } diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index 803ff4558ad5..d615e5986f5d 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -147,11 +147,11 @@ Network::DnsResolverSharedPtr DispatcherImpl::createDnsResolver( "using TCP for DNS lookups is not possible when using Apple APIs for DNS " "resolution. Apple' API only uses UDP for DNS resolution. Use UDP or disable " "the envoy.restart_features.use_apple_api_for_dns_lookups runtime feature."); - return Network::DnsResolverSharedPtr{new Network::AppleDnsResolverImpl(*this)}; + return std::make_shared(*this, api_.randomGenerator(), + api_.rootScope()); } #endif - return Network::DnsResolverSharedPtr{ - new Network::DnsResolverImpl(*this, resolvers, use_tcp_for_dns_lookups)}; + return std::make_shared(*this, resolvers, use_tcp_for_dns_lookups); } FileEventPtr DispatcherImpl::createFileEvent(os_fd_t fd, FileReadyCb cb, FileTriggerType trigger, diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 969e49f20673..ec11aa935263 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -106,8 +106,10 @@ envoy_cc_library( ":utility_lib", "//include/envoy/event:dispatcher_interface", "//include/envoy/event:file_event_interface", + "//include/envoy/event:timer_interface", "//include/envoy/network:dns_interface", "//source/common/common:assert_lib", + "//source/common/common:backoff_lib", "//source/common/common:linked_object", "//source/common/singleton:threadsafe_singleton", ], diff --git a/source/common/network/apple_dns_impl.cc b/source/common/network/apple_dns_impl.cc index 4ef8c495e2f2..f8994cb48e8c 100644 --- a/source/common/network/apple_dns_impl.cc +++ b/source/common/network/apple_dns_impl.cc @@ -44,15 +44,26 @@ DNSServiceErrorType DnsService::dnsServiceGetAddrInfo(DNSServiceRef* sdRef, DNSS return DNSServiceGetAddrInfo(sdRef, flags, interfaceIndex, protocol, hostname, callBack, context); } -AppleDnsResolverImpl::AppleDnsResolverImpl(Event::Dispatcher& dispatcher) - : dispatcher_(dispatcher) { +// Parameters of the jittered backoff strategy. +static constexpr std::chrono::milliseconds RetryInitialDelayMilliseconds(30); +static constexpr std::chrono::milliseconds RetryMaxDelayMilliseconds(30000); + +AppleDnsResolverImpl::AppleDnsResolverImpl(Event::Dispatcher& dispatcher, + Random::RandomGenerator& random, + Stats::Scope& root_scope) + : dispatcher_(dispatcher), initialize_failure_timer_(dispatcher.createTimer( + [this]() -> void { initializeMainSdRef(); })), + backoff_strategy_(std::make_unique( + RetryInitialDelayMilliseconds.count(), RetryMaxDelayMilliseconds.count(), random)), + scope_(root_scope.createScope("dns.apple.")), stats_(generateAppleDnsResolverStats(*scope_)) { ENVOY_LOG(debug, "Constructing DNS resolver"); initializeMainSdRef(); } -AppleDnsResolverImpl::~AppleDnsResolverImpl() { - ENVOY_LOG(debug, "Destructing DNS resolver"); - deallocateMainSdRef(); +AppleDnsResolverImpl::~AppleDnsResolverImpl() { deallocateMainSdRef(); } + +AppleDnsResolverStats AppleDnsResolverImpl::generateAppleDnsResolverStats(Stats::Scope& scope) { + return {ALL_APPLE_DNS_RESOLVER_STATS(POOL_COUNTER(scope))}; } void AppleDnsResolverImpl::deallocateMainSdRef() { @@ -78,13 +89,28 @@ void AppleDnsResolverImpl::initializeMainSdRef() { // However, using a shared connection brings some complexities detailed in the inline comments // for kDNSServiceFlagsShareConnection in dns_sd.h, and copied (and edited) in this implementation // where relevant. + // + // When error occurs while the main_sd_ref_ is initialized, the initialize_failure_timer_ will be + // enabled to retry initialization. Retries can also be triggered via query submission, @see + // AppleDnsResolverImpl::resolve(...) for details. auto error = DnsServiceSingleton::get().dnsServiceCreateConnection(&main_sd_ref_); - RELEASE_ASSERT(error == kDNSServiceErr_NoError, - fmt::format("error={} in DNSServiceCreateConnection", error)); + if (error != kDNSServiceErr_NoError) { + stats_.connection_failure_.inc(); + initialize_failure_timer_->enableTimer( + std::chrono::milliseconds(backoff_strategy_->nextBackOffMs())); + return; + } auto fd = DnsServiceSingleton::get().dnsServiceRefSockFD(main_sd_ref_); - RELEASE_ASSERT(fd != -1, "error in DNSServiceRefSockFD"); - ENVOY_LOG(debug, "DNS resolver has fd={}", fd); + // According to dns_sd.h: DnsServiceRefSockFD returns "The DNSServiceRef's underlying socket + // descriptor, or -1 on error.". Although it gives no detailed description on when/why this call + // would fail. + if (fd == -1) { + stats_.socket_failure_.inc(); + initialize_failure_timer_->enableTimer( + std::chrono::milliseconds(backoff_strategy_->nextBackOffMs())); + return; + } sd_ref_event_ = dispatcher_.createFileEvent( fd, @@ -93,6 +119,12 @@ void AppleDnsResolverImpl::initializeMainSdRef() { [this](uint32_t events) { onEventCallback(events); }, Event::FileTriggerType::Level, Event::FileReadyType::Read); sd_ref_event_->setEnabled(Event::FileReadyType::Read); + + // Disable the failure timer and reset the backoff strategy because the main_sd_ref_ was + // successfully initialized. Note that these actions will be no-ops if the timer was not armed to + // begin with. + initialize_failure_timer_->disableTimer(); + backoff_strategy_->reset(); } void AppleDnsResolverImpl::onEventCallback(uint32_t events) { @@ -102,6 +134,7 @@ void AppleDnsResolverImpl::onEventCallback(uint32_t events) { DNSServiceErrorType error = DnsServiceSingleton::get().dnsServiceProcessResult(main_sd_ref_); if (error != kDNSServiceErr_NoError) { ENVOY_LOG(warn, "DNS resolver error ({}) in DNSServiceProcessResult", error); + stats_.processing_failure_.inc(); // Similar to receiving an error in onDNSServiceGetAddrInfoReply, an error while processing fd // events indicates that the sd_ref state is broken. // Therefore, flush queries with_error == true. @@ -125,7 +158,28 @@ ActiveDnsQuery* AppleDnsResolverImpl::resolve(const std::string& dns_name, dns_name, address->asString()); } catch (const EnvoyException& e) { // Resolution via Apple APIs - ENVOY_LOG(debug, "DNS resolver local resolution failed with: {}", e.what()); + ENVOY_LOG(trace, "DNS resolver local resolution failed with: {}", e.what()); + + // First check that the main_sd_ref is alive by checking if the resolver is currently trying to + // initialize its main_sd_ref. + if (initialize_failure_timer_->enabled()) { + // No queries should be accumulating while the main_sd_ref_ is not alive. Either they were + // flushed when the error that deallocated occurred, or they have all failed in this branch of + // the code synchronously due to continuous inability to initialize the main_sd_ref_. + ASSERT(queries_with_pending_cb_.empty()); + + // Short-circuit the pending retry to initialize the main_sd_ref_ and try now. + initializeMainSdRef(); + + // If the timer is still enabled, that means the initialization failed. Synchronously fail the + // resolution, the callback target should retry. + if (initialize_failure_timer_->enabled()) { + callback(DnsResolver::ResolutionStatus::Failure, {}); + return nullptr; + } + } + + // Proceed with resolution after establishing that the resolver has a live main_sd_ref_. std::unique_ptr pending_resolution( new PendingResolution(*this, callback, dispatcher_, main_sd_ref_, dns_name)); @@ -204,6 +258,7 @@ AppleDnsResolverImpl::PendingResolution::~PendingResolution() { // thus the DNSServiceRef is null. // Therefore, only deallocate if the ref is not null. if (individual_sd_ref_) { + ENVOY_LOG(debug, "DNSServiceRefDeallocate individual sd ref"); DnsServiceSingleton::get().dnsServiceRefDeallocate(individual_sd_ref_); } } diff --git a/source/common/network/apple_dns_impl.h b/source/common/network/apple_dns_impl.h index d3fcda152c25..0ca2cd2517bc 100644 --- a/source/common/network/apple_dns_impl.h +++ b/source/common/network/apple_dns_impl.h @@ -8,8 +8,10 @@ #include "envoy/common/platform.h" #include "envoy/event/dispatcher.h" #include "envoy/event/file_event.h" +#include "envoy/event/timer.h" #include "envoy/network/dns.h" +#include "common/common/backoff_strategy.h" #include "common/common/linked_object.h" #include "common/common/logger.h" #include "common/common/utility.h" @@ -37,14 +39,31 @@ class DnsService { using DnsServiceSingleton = ThreadSafeSingleton; +/** + * All DNS resolver stats. @see stats_macros.h + */ +#define ALL_APPLE_DNS_RESOLVER_STATS(COUNTER) \ + COUNTER(connection_failure) \ + COUNTER(socket_failure) \ + COUNTER(processing_failure) + +/** + * Struct definition for all DNS resolver stats. @see stats_macros.h + */ +struct AppleDnsResolverStats { + ALL_APPLE_DNS_RESOLVER_STATS(GENERATE_COUNTER_STRUCT) +}; + /** * Implementation of DnsResolver that uses Apple dns_sd.h APIs. All calls and callbacks are assumed * to happen on the thread that owns the creating dispatcher. */ class AppleDnsResolverImpl : public DnsResolver, protected Logger::Loggable { public: - AppleDnsResolverImpl(Event::Dispatcher& dispatcher); + AppleDnsResolverImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random, + Stats::Scope& root_scope); ~AppleDnsResolverImpl() override; + static AppleDnsResolverStats generateAppleDnsResolverStats(Stats::Scope& scope); // Network::DnsResolver ActiveDnsQuery* resolve(const std::string& dns_name, DnsLookupFamily dns_lookup_family, @@ -111,8 +130,12 @@ class AppleDnsResolverImpl : public DnsResolver, protected Logger::Loggable; + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Return(initialize_failure_timer_)); EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)) - .WillOnce(Return(kDNSServiceErr_NoError)); + .WillOnce(DoAll( + WithArgs<0>(Invoke([](DNSServiceRef* ref) -> void { *ref = new _DNSServiceRef_t{}; })), + Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(DoAll(SaveArg<1>(&file_ready_cb_), Return(file_event_))); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); - resolver_ = std::make_unique(dispatcher_); + resolver_ = std::make_unique(dispatcher_, random_, stats_store_); } protected: MockDnsService dns_service_; TestThreadsafeSingletonInjector dns_service_injector_{&dns_service_}; + Stats::IsolatedStoreImpl stats_store_; std::unique_ptr resolver_{}; NiceMock dispatcher_; + Event::MockTimer* initialize_failure_timer_; NiceMock* file_event_; Event::FileReadyCb file_ready_cb_; + Random::RandomGeneratorImpl random_; }; TEST_F(AppleDnsImplFakeApiTest, ErrorInConnectionCreation) { - ON_CALL(dns_service_, dnsServiceCreateConnection(_)) - .WillByDefault(Return(kDNSServiceErr_Unknown)); - EXPECT_DEATH(std::make_unique(dispatcher_), - "error=-65537 in DNSServiceCreateConnection"); + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Return(initialize_failure_timer_)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_Unknown)); + EXPECT_CALL(*initialize_failure_timer_, enableTimer(_, _)); + + resolver_ = std::make_unique(dispatcher_, random_, stats_store_); + + EXPECT_EQ(1, TestUtility::findCounter(stats_store_, "dns.apple.connection_failure")->value()); } TEST_F(AppleDnsImplFakeApiTest, ErrorInSocketAccess) { - ON_CALL(dns_service_, dnsServiceCreateConnection(_)) - .WillByDefault(Return(kDNSServiceErr_NoError)); - ON_CALL(dns_service_, dnsServiceRefSockFD(_)).WillByDefault(Return(-1)); - EXPECT_DEATH(std::make_unique(dispatcher_), - "error in DNSServiceRefSockFD"); + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Return(initialize_failure_timer_)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_NoError)); + EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(-1)); + EXPECT_CALL(*initialize_failure_timer_, enableTimer(_, _)); + + resolver_ = std::make_unique(dispatcher_, random_, stats_store_); + + EXPECT_EQ(1, TestUtility::findCounter(stats_store_, "dns.apple.socket_failure")->value()); } TEST_F(AppleDnsImplFakeApiTest, InvalidFileEvent) { @@ -260,8 +278,11 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResult) { EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(Return(new NiceMock)); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); file_ready_cb_(Event::FileReadyType::Read); + + EXPECT_EQ(1, TestUtility::findCounter(stats_store_, "dns.apple.processing_failure")->value()); } TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResultWithPendingQueries) { @@ -282,6 +303,7 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResultWithPendingQueries) { kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); auto query = resolver_->resolve(hostname, Network::DnsLookupFamily::Auto, @@ -312,6 +334,7 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResultWithPendingQueries) { EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(Return(new NiceMock)); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); file_ready_cb_(Event::FileReadyType::Read); @@ -321,6 +344,7 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResultWithPendingQueries) { TEST_F(AppleDnsImplFakeApiTest, SynchronousErrorInGetAddrInfo) { createResolver(); + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, _, _, _, _, _, _)) .WillOnce(Return(kDNSServiceErr_Unknown)); // The Query's sd ref will be deallocated. @@ -348,6 +372,7 @@ TEST_F(AppleDnsImplFakeApiTest, QuerySynchronousCompletion) { // The query's ref is going to be deallocated when the query is destroyed. EXPECT_CALL(dns_service_, dnsServiceRefDeallocate(_)); + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -386,6 +411,7 @@ TEST_F(AppleDnsImplFakeApiTest, IncorrectInterfaceIndexReturned) { Network::Address::Ipv4Instance address(&addr4); + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -421,10 +447,15 @@ TEST_F(AppleDnsImplFakeApiTest, QueryCompletedWithError) { // to be deallocated due to the error. EXPECT_CALL(dns_service_, dnsServiceRefDeallocate(_)).Times(2); // A new main ref is created on error. - EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_NoError)); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)) + .WillOnce(DoAll( + WithArgs<0>(Invoke([](DNSServiceRef* ref) -> void { *ref = new _DNSServiceRef_t{}; })), + Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(Return(new NiceMock)); + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -468,6 +499,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleAddresses) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -510,6 +542,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleAddressesSecondOneFails) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -538,6 +571,8 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleAddressesSecondOneFails) { EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(Return(new NiceMock)); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); + reply_callback(nullptr, 0, 0, kDNSServiceErr_Unknown, hostname.c_str(), nullptr, 30, query); dns_callback_executed.WaitForNotification(); @@ -565,6 +600,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueries) { absl::Notification dns_callback_executed2; // Start first query. + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -584,6 +620,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueries) { ASSERT_NE(nullptr, query); // Start second query. + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4, StrEq(hostname2.c_str()), _, _)) @@ -632,6 +669,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { absl::Notification dns_callback_executed2; // Start first query. + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -653,6 +691,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { ASSERT_NE(nullptr, query); // Start second query. + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4, StrEq(hostname2.c_str()), _, _)) @@ -680,6 +719,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) .WillOnce(Return(new NiceMock)); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); // The second query fails. reply_callback2(nullptr, 0, 0, kDNSServiceErr_Unknown, hostname2.c_str(), nullptr, 30, query2); @@ -700,6 +740,7 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithOnlyNonAdditiveReplies) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -735,6 +776,7 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithNullAddress) { Network::Address::Ipv4Instance address(&addr4); DNSServiceGetAddrInfoReply reply_callback; + EXPECT_CALL(*initialize_failure_timer_, enabled()).WillOnce(Return(false)); EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, @@ -751,6 +793,83 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithNullAddress) { "invalid to add null address"); } +TEST_F(AppleDnsImplFakeApiTest, ErrorInConnectionCreationImmediateCallback) { + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Return(initialize_failure_timer_)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_Unknown)); + EXPECT_CALL(*initialize_failure_timer_, enableTimer(_, _)); + + resolver_ = std::make_unique(dispatcher_, random_, stats_store_); + + EXPECT_CALL(*initialize_failure_timer_, enabled()).Times(2).WillRepeatedly(Return(true)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_NoError)); + EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(-1)); + EXPECT_CALL(*initialize_failure_timer_, enableTimer(_, _)); + + absl::Notification dns_callback_executed; + EXPECT_EQ(nullptr, + resolver_->resolve("foo.com", Network::DnsLookupFamily::Auto, + [&dns_callback_executed](DnsResolver::ResolutionStatus status, + std::list&& response) -> void { + EXPECT_EQ(DnsResolver::ResolutionStatus::Failure, status); + EXPECT_TRUE(response.empty()); + dns_callback_executed.Notify(); + })); + dns_callback_executed.WaitForNotification(); +} + +TEST_F(AppleDnsImplFakeApiTest, ErrorInConnectionCreationQueryDispatched) { + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Return(initialize_failure_timer_)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)).WillOnce(Return(kDNSServiceErr_Unknown)); + EXPECT_CALL(*initialize_failure_timer_, enableTimer(_, _)); + + resolver_ = std::make_unique(dispatcher_, random_, stats_store_); + + file_event_ = new NiceMock; + EXPECT_CALL(*initialize_failure_timer_, enabled()) + .Times(2) + .WillOnce(Return(true)) + .WillOnce(Return(false)); + EXPECT_CALL(dns_service_, dnsServiceCreateConnection(_)) + .WillOnce(DoAll( + WithArgs<0>(Invoke([](DNSServiceRef* ref) -> void { *ref = new _DNSServiceRef_t{}; })), + Return(kDNSServiceErr_NoError))); + EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); + EXPECT_CALL(dispatcher_, createFileEvent_(0, _, _, _)) + .WillOnce(DoAll(SaveArg<1>(&file_ready_cb_), Return(file_event_))); + EXPECT_CALL(*initialize_failure_timer_, disableTimer()); + + absl::Notification dns_callback_executed; + const std::string hostname = "foo.com"; + DNSServiceGetAddrInfoReply reply_callback; + sockaddr_in addr4; + addr4.sin_family = AF_INET; + EXPECT_EQ(1, inet_pton(AF_INET, "1.2.3.4", &addr4.sin_addr)); + addr4.sin_port = htons(6502); + Network::Address::Ipv4Instance address(&addr4); + + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, kDNSServiceFlagsShareConnection | kDNSServiceFlagsTimeout, 0, + kDNSServiceProtocol_IPv4, StrEq(hostname.c_str()), _, _)) + .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); + + auto query = + resolver_->resolve(hostname, Network::DnsLookupFamily::V4Only, + [&dns_callback_executed](DnsResolver::ResolutionStatus status, + std::list&& response) -> void { + EXPECT_EQ(DnsResolver::ResolutionStatus::Success, status); + EXPECT_EQ(1, response.size()); + dns_callback_executed.Notify(); + }); + EXPECT_NE(nullptr, query); + + // The query's sd ref will be deallocated on completion. + EXPECT_CALL(dns_service_, dnsServiceRefDeallocate(_)); + reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), + address.sockAddr(), 30, query); + + dns_callback_executed.WaitForNotification(); +} + } // namespace } // namespace Network } // namespace Envoy diff --git a/test/mocks/api/mocks.h b/test/mocks/api/mocks.h index 5446fc61e1e6..c6a8222c4298 100644 --- a/test/mocks/api/mocks.h +++ b/test/mocks/api/mocks.h @@ -41,7 +41,7 @@ class MockApi : public Api { Event::TimeSystem&)); MOCK_METHOD(Filesystem::Instance&, fileSystem, ()); MOCK_METHOD(Thread::ThreadFactory&, threadFactory, ()); - MOCK_METHOD(const Stats::Scope&, rootScope, ()); + MOCK_METHOD(Stats::Scope&, rootScope, ()); MOCK_METHOD(Random::RandomGenerator&, randomGenerator, ()); MOCK_METHOD(ProcessContextOptRef, processContext, ()); From d525ae7262d5df33b274903f41bb3174b44dd032 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Wed, 11 Nov 2020 16:34:35 +0900 Subject: [PATCH 067/117] wasm: Force stop iteration after local response is sent (#13930) Resolves https://github.com/envoyproxy/envoy/issues/13857 ref: -https://github.com/proxy-wasm/proxy-wasm-rust-sdk/issues/44 -https://github.com/proxy-wasm/proxy-wasm-cpp-host/pull/88 -https://github.com/proxy-wasm/proxy-wasm-cpp-host/pull/93 Signed-off-by: mathetake --- bazel/repository_locations.bzl | 6 +++--- test/extensions/filters/http/wasm/wasm_filter_test.cc | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index e2965701377c..d55dc3c74462 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -830,8 +830,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host", - version = "40fd3d03842c07d65fed907a6b6ed0f89d68d531", - sha256 = "b5ae746e66b6209ea0cce86d6c21de99dacbec1da9cdadd53a9ec46bc296a3ba", + version = "b7d3d13d75bb6b50f192252258bb9583bf723fa4", + sha256 = "ae639f94a80adbe915849bccb32346720c59a579db09918e44aefafed6cbb100", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -842,7 +842,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", ], - release_date = "2020-10-27", + release_date = "2020-11-10", cpe = "N/A", ), emscripten_toolchain = dict( diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index 538481f36f6c..ea010b9ae71d 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -1436,7 +1436,8 @@ TEST_P(WasmHttpFilterTest, RootId2) { setupFilter("context2"); EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders2 2")))); Http::TestRequestHeaderMapImpl request_headers; - EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, true)); } } // namespace Wasm From 5fc8127146a363d1bb053d18a583cc58a201d790 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 11 Nov 2020 17:52:43 +0000 Subject: [PATCH 068/117] examples: Add TLS sandbox (#13844) Signed-off-by: Ryan Northey --- docs/root/start/sandboxes/index.rst | 1 + docs/root/start/sandboxes/tls.rst | 172 ++++++++++++++++++ examples/DEVELOPER.md | 12 ++ examples/tls/Dockerfile-proxy-http-https | 5 + examples/tls/Dockerfile-proxy-https-http | 5 + examples/tls/Dockerfile-proxy-https-https | 5 + .../tls/Dockerfile-proxy-https-passthrough | 5 + examples/tls/README.md | 2 + examples/tls/docker-compose.yaml | 42 +++++ examples/tls/envoy-http-https.yaml | 45 +++++ examples/tls/envoy-https-http.yaml | 104 +++++++++++ examples/tls/envoy-https-https.yaml | 108 +++++++++++ examples/tls/envoy-https-passthrough.yaml | 28 +++ examples/tls/verify.sh | 33 ++++ examples/verify-common.sh | 10 + 15 files changed, 577 insertions(+) create mode 100644 docs/root/start/sandboxes/tls.rst create mode 100644 examples/tls/Dockerfile-proxy-http-https create mode 100644 examples/tls/Dockerfile-proxy-https-http create mode 100644 examples/tls/Dockerfile-proxy-https-https create mode 100644 examples/tls/Dockerfile-proxy-https-passthrough create mode 100644 examples/tls/README.md create mode 100644 examples/tls/docker-compose.yaml create mode 100644 examples/tls/envoy-http-https.yaml create mode 100644 examples/tls/envoy-https-http.yaml create mode 100644 examples/tls/envoy-https-https.yaml create mode 100644 examples/tls/envoy-https-passthrough.yaml create mode 100755 examples/tls/verify.sh diff --git a/docs/root/start/sandboxes/index.rst b/docs/root/start/sandboxes/index.rst index 1acabd3696e6..aac98b800966 100644 --- a/docs/root/start/sandboxes/index.rst +++ b/docs/root/start/sandboxes/index.rst @@ -28,6 +28,7 @@ features. The following sandboxes are available: mysql postgres redis + tls wasm-cc zipkin_tracing skywalking_tracing diff --git a/docs/root/start/sandboxes/tls.rst b/docs/root/start/sandboxes/tls.rst new file mode 100644 index 000000000000..aed1ed2cccab --- /dev/null +++ b/docs/root/start/sandboxes/tls.rst @@ -0,0 +1,172 @@ +.. _install_sandboxes_tls: + +TLS +=== + +.. sidebar:: Requirements + + `jq `_ + Used to parse ``json`` output from the upstream echo servers. + +This example walks through some of the ways that Envoy can be configured to make +use of encrypted connections using ``HTTP`` over ``TLS``. + +It demonstrates a number of commonly used proxying and ``TLS`` termination patterns: + +- ``https`` -> ``http`` +- ``https`` -> ``https`` +- ``http`` -> ``https`` +- ``https`` passthrough + +To better understand the provided examples, and for a description of how ``TLS`` is +configured with Envoy, please see the :ref:`securing Envoy quick start guide `. + +.. warning:: + + For the sake of simplicity, the examples provided here do not authenticate any client certificates, + or validate any of the provided certificates. + + When using ``TLS``, you are strongly encouraged to :ref:`validate ` + all certificates wherever possible. + + You should also :ref:`authenticate clients ` + where you control both sides of the connection, or relevant protocols are available. + +.. include:: _include/docker-env-setup.rst + +Change directory to ``examples/tls`` in the Envoy repository. + +Step 3: Build the sandbox +************************* + +This starts four proxies listening on ``localhost`` ports ``10000-10003``. + +It also starts two upstream services, one ``HTTP`` and one ``HTTPS``, which echo back received headers +in ``json`` format. + +The upstream services listen on the internal Docker network on ports ``80`` and ``443`` respectively. + +.. code-block:: console + + $ pwd + envoy/examples/tls + $ docker-compose pull + $ docker-compose up --build -d + $ docker-compose ps + + Name Command State Ports + ----------------------------------------------------------------------------------------------- + tls_proxy-https-to-http_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10000->10000/tcp + tls_proxy-https-to-https_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10001->10000/tcp + tls_proxy-http-to-https_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10002->10000/tcp + tls_proxy-https-passthrough_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10003->10000/tcp + tls_service-http_1 node ./index.js Up + tls_service-https_1 node ./index.js Up + +Step 4: Test proxying ``https`` -> ``http`` +******************************************* + +The Envoy proxy listening on https://localhost:10000 terminates ``HTTPS`` and proxies to the upstream ``HTTP`` service. + +The :download:`https -> http configuration <_include/tls/envoy-https-http.yaml>` adds a ``TLS`` +:ref:`transport_socket ` to the +:ref:`listener `. + +Querying the service at port ``10000`` you should see an ``x-forwarded-proto`` header of ``https`` has +been added: + +.. code-block:: console + + $ curl -sk https://localhost:10000 | jq -r '.headers["x-forwarded-proto"]' + https + +The upstream ``service-http`` handles the request. + +.. code-block:: console + + $ curl -sk https://localhost:10000 | jq -r '.os.hostname' + service-http + +Step 5: Test proxying ``https`` -> ``https`` +******************************************** + +The Envoy proxy listening on https://localhost:10001 terminates ``HTTPS`` and proxies to the upstream ``HTTPS`` service. + +The :download:`https -> https configuration <_include/tls/envoy-https-https.yaml>` adds a ``TLS`` +:ref:`transport_socket ` to both the +:ref:`listener ` and the +:ref:`cluster `. + +Querying the service at port ``10001`` you should see an ``x-forwarded-proto`` header of ``https`` has +been added: + +.. code-block:: console + + $ curl -sk https://localhost:10001 | jq -r '.headers["x-forwarded-proto"]' + https + +The upstream ``service-https`` handles the request. + +.. code-block:: console + + $ curl -sk https://localhost:10001 | jq -r '.os.hostname' + service-https + +Step 6: Test proxying ``http`` -> ``https`` +******************************************* + +The Envoy proxy listening on http://localhost:10002 terminates ``HTTP`` and proxies to the upstream ``HTTPS`` service. + +The :download:`http -> https configuration <_include/tls/envoy-http-https.yaml>` adds a ``TLS`` +:ref:`transport_socket ` to the +:ref:`cluster `. + +Querying the service at port ``10001`` you should see an ``x-forwarded-proto`` header of ``http`` has +been added: + +.. code-block:: console + + $ curl -s http://localhost:10002 | jq -r '.headers["x-forwarded-proto"]' + http + +The upstream ``service-https`` handles the request. + +.. code-block:: console + + $ curl -s http://localhost:10002 | jq -r '.os.hostname' + service-https + + +Step 7: Test proxying ``https`` passthrough +******************************************* + +The Envoy proxy listening on https://localhost:10003 proxies directly to the upstream ``HTTPS`` service which +does the ``TLS`` termination. + +The :download:`https passthrough configuration <_include/tls/envoy-https-passthrough.yaml>` requires no ``TLS`` +or ``HTTP`` setup, and instead uses a simple +:ref:`tcp_proxy `. + +Querying the service at port ``10003`` you should see that no ``x-forwarded-proto`` header has been +added: + +.. code-block:: console + + $ curl -sk https://localhost:10003 | jq -r '.headers["x-forwarded-proto"]' + null + +The upstream ``service-https`` handles the request. + +.. code-block:: console + + $ curl -sk https://localhost:10003 | jq -r '.os.hostname' + service-https + +.. seealso:: + + :ref:`Securing Envoy quick start guide ` + Outline of key concepts for securing Envoy. + + :ref:`Double proxy sandbox ` + An example of securing traffic between proxies with validation and + mutual authentication using ``mTLS`` with non-``HTTP`` traffic. diff --git a/examples/DEVELOPER.md b/examples/DEVELOPER.md index dd2950829eb3..88e9a9b9f39c 100644 --- a/examples/DEVELOPER.md +++ b/examples/DEVELOPER.md @@ -85,6 +85,18 @@ responds_with \ -H 'Origin: https://example-service.com' ``` +#### Utility functions: `responds_without` + +You can also check that a request *does not* respond with given `HTTP` content: + +```bash +responds_without \ + "Anything unexpected" \ + "http://localhost:8000" +``` + +`responds_without` can accept additional curl arguments like `responds_with` + #### Utility functions: `responds_with_header` You can check that a request responds with an expected header as follows: diff --git a/examples/tls/Dockerfile-proxy-http-https b/examples/tls/Dockerfile-proxy-http-https new file mode 100644 index 000000000000..1d13a8c4821e --- /dev/null +++ b/examples/tls/Dockerfile-proxy-http-https @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-http-https.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml"] diff --git a/examples/tls/Dockerfile-proxy-https-http b/examples/tls/Dockerfile-proxy-https-http new file mode 100644 index 000000000000..3ab63a86915a --- /dev/null +++ b/examples/tls/Dockerfile-proxy-https-http @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-https-http.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml"] diff --git a/examples/tls/Dockerfile-proxy-https-https b/examples/tls/Dockerfile-proxy-https-https new file mode 100644 index 000000000000..a34183bd6e34 --- /dev/null +++ b/examples/tls/Dockerfile-proxy-https-https @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-https-https.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml"] diff --git a/examples/tls/Dockerfile-proxy-https-passthrough b/examples/tls/Dockerfile-proxy-https-passthrough new file mode 100644 index 000000000000..a460c25de9d0 --- /dev/null +++ b/examples/tls/Dockerfile-proxy-https-passthrough @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-https-passthrough.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml"] diff --git a/examples/tls/README.md b/examples/tls/README.md new file mode 100644 index 000000000000..61d68e1757a7 --- /dev/null +++ b/examples/tls/README.md @@ -0,0 +1,2 @@ +To learn about this sandbox and for instructions on how to run it please head over +to the [Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/tls.html). diff --git a/examples/tls/docker-compose.yaml b/examples/tls/docker-compose.yaml new file mode 100644 index 000000000000..1db7809a2220 --- /dev/null +++ b/examples/tls/docker-compose.yaml @@ -0,0 +1,42 @@ +version: "3.7" +services: + + proxy-https-to-http: + build: + context: . + dockerfile: Dockerfile-proxy-https-http + ports: + - "10000:10000" + + proxy-https-to-https: + build: + context: . + dockerfile: Dockerfile-proxy-https-https + ports: + - "10001:10000" + + proxy-http-to-https: + build: + context: . + dockerfile: Dockerfile-proxy-http-https + ports: + - "10002:10000" + + proxy-https-passthrough: + build: + context: . + dockerfile: Dockerfile-proxy-https-passthrough + ports: + - "10003:10000" + + service-http: + image: mendhak/http-https-echo + hostname: service-http + environment: + - HTTPS_PORT=0 + + service-https: + image: mendhak/http-https-echo + hostname: service-https + environment: + - HTTP_PORT=0 diff --git a/examples/tls/envoy-http-https.yaml b/examples/tls/envoy-http-https.yaml new file mode 100644 index 000000000000..2e896de2e78b --- /dev/null +++ b/examples/tls/envoy-http-https.yaml @@ -0,0 +1,45 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service-https + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: service-https + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service-https + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-https + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext diff --git a/examples/tls/envoy-https-http.yaml b/examples/tls/envoy-https-http.yaml new file mode 100644 index 000000000000..46dccfff0d6e --- /dev/null +++ b/examples/tls/envoy-https-http.yaml @@ -0,0 +1,104 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service-http + http_filters: + - name: envoy.filters.http.router + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + # The following self-signed certificate pair is generated using: + # $ openssl req -x509 -newkey rsa:2048 -keyout a/front-proxy-key.pem -out a/front-proxy-crt.pem -days 3650 -nodes -subj '/CN=front-envoy' + # + # Instead of feeding it as an inline_string, certificate pair can also be fed to Envoy + # via filename. Reference: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#config-core-v3-datasource. + # + # Or in a dynamic configuration scenario, certificate pair can be fetched remotely via + # Secret Discovery Service (SDS). Reference: https://www.envoyproxy.io/docs/envoy/latest/configuration/security/secret. + certificate_chain: + inline_string: | + -----BEGIN CERTIFICATE----- + MIICqDCCAZACCQCquzpHNpqBcDANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtm + cm9udC1lbnZveTAeFw0yMDA3MDgwMTMxNDZaFw0zMDA3MDYwMTMxNDZaMBYxFDAS + BgNVBAMMC2Zyb250LWVudm95MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAthnYkqVQBX+Wg7aQWyCCb87hBce1hAFhbRM8Y9dQTqxoMXZiA2n8G089hUou + oQpEdJgitXVS6YMFPFUUWfwcqxYAynLK4X5im26Yfa1eO8La8sZUS+4Bjao1gF5/ + VJxSEo2yZ7fFBo8M4E44ZehIIocipCRS+YZehFs6dmHoq/MGvh2eAHIa+O9xssPt + ofFcQMR8rwBHVbKy484O10tNCouX4yUkyQXqCRy6HRu7kSjOjNKSGtjfG+h5M8bh + 10W7ZrsJ1hWhzBulSaMZaUY3vh5ngpws1JATQVSK1Jm/dmMRciwlTK7KfzgxHlSX + 58ENpS7yPTISkEICcLbXkkKGEQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCmj6Hg + vwOxWz0xu+6fSfRL6PGJUGq6wghCfUvjfwZ7zppDUqU47fk+yqPIOzuGZMdAqi7N + v1DXkeO4A3hnMD22Rlqt25vfogAaZVToBeQxCPd/ALBLFrvLUFYuSlS3zXSBpQqQ + Ny2IKFYsMllz5RSROONHBjaJOn5OwqenJ91MPmTAG7ujXKN6INSBM0PjX9Jy4Xb9 + zT+I85jRDQHnTFce1WICBDCYidTIvJtdSSokGSuy4/xyxAAc/BpZAfOjBQ4G1QRe + 9XwOi790LyNUYFJVyeOvNJwveloWuPLHb9idmY5YABwikUY6QNcXwyHTbRCkPB2I + m+/R4XnmL4cKQ+5Z + -----END CERTIFICATE----- + private_key: + inline_string: | + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2GdiSpVAFf5aD + tpBbIIJvzuEFx7WEAWFtEzxj11BOrGgxdmIDafwbTz2FSi6hCkR0mCK1dVLpgwU8 + VRRZ/ByrFgDKcsrhfmKbbph9rV47wtryxlRL7gGNqjWAXn9UnFISjbJnt8UGjwzg + Tjhl6EgihyKkJFL5hl6EWzp2Yeir8wa+HZ4Achr473Gyw+2h8VxAxHyvAEdVsrLj + zg7XS00Ki5fjJSTJBeoJHLodG7uRKM6M0pIa2N8b6HkzxuHXRbtmuwnWFaHMG6VJ + oxlpRje+HmeCnCzUkBNBVIrUmb92YxFyLCVMrsp/ODEeVJfnwQ2lLvI9MhKQQgJw + tteSQoYRAgMBAAECggEAeDGdEkYNCGQLe8pvg8Z0ccoSGpeTxpqGrNEKhjfi6NrB + NwyVav10iq4FxEmPd3nobzDPkAftfvWc6hKaCT7vyTkPspCMOsQJ39/ixOk+jqFx + lNa1YxyoZ9IV2DIHR1iaj2Z5gB367PZUoGTgstrbafbaNY9IOSyojCIO935ubbcx + DWwL24XAf51ez6sXnI8V5tXmrFlNXhbhJdH8iIxNyM45HrnlUlOk0lCK4gmLJjy9 + 10IS2H2Wh3M5zsTpihH1JvM56oAH1ahrhMXs/rVFXXkg50yD1KV+HQiEbglYKUxO + eMYtfaY9i2CuLwhDnWp3oxP3HfgQQhD09OEN3e0IlQKBgQDZ/3poG9TiMZSjfKqL + xnCABMXGVQsfFWNC8THoW6RRx5Rqi8q08yJrmhCu32YKvccsOljDQJQQJdQO1g09 + e/adJmCnTrqxNtjPkX9txV23Lp6Ak7emjiQ5ICu7iWxrcO3zf7hmKtj7z+av8sjO + mDI7NkX5vnlE74nztBEjp3eC0wKBgQDV2GeJV028RW3b/QyP3Gwmax2+cKLR9PKR + nJnmO5bxAT0nQ3xuJEAqMIss/Rfb/macWc2N/6CWJCRT6a2vgy6xBW+bqG6RdQMB + xEZXFZl+sSKhXPkc5Wjb4lQ14YWyRPrTjMlwez3k4UolIJhJmwl+D7OkMRrOUERO + EtUvc7odCwKBgBi+nhdZKWXveM7B5N3uzXBKmmRz3MpPdC/yDtcwJ8u8msUpTv4R + JxQNrd0bsIqBli0YBmFLYEMg+BwjAee7vXeDFq+HCTv6XMva2RsNryCO4yD3I359 + XfE6DJzB8ZOUgv4Dvluie3TB2Y6ZQV/p+LGt7G13yG4hvofyJYvlg3RPAoGAcjDg + +OH5zLN2eqah8qBN0CYa9/rFt0AJ19+7/smLTJ7QvQq4g0gwS1couplcCEnNGWiK + 72y1n/ckvvplmPeAE19HveMvR9UoCeV5ej86fACy8V/oVpnaaLBvL2aCMjPLjPP9 + DWeCIZp8MV86cvOrGfngf6kJG2qZTueXl4NAuwkCgYEArKkhlZVXjwBoVvtHYmN2 + o+F6cGMlRJTLhNc391WApsgDZfTZSdeJsBsvvzS/Nc0burrufJg0wYioTlpReSy4 + ohhtprnQQAddfjHP7rh2LGt+irFzhdXXQ1ybGaGM9D764KUNCXLuwdly0vzXU4HU + q5sGxGrC1RECGB5Zwx2S2ZY= + -----END PRIVATE KEY----- + + clusters: + - name: service-http + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service-http + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-http + port_value: 80 diff --git a/examples/tls/envoy-https-https.yaml b/examples/tls/envoy-https-https.yaml new file mode 100644 index 000000000000..e838895d903a --- /dev/null +++ b/examples/tls/envoy-https-https.yaml @@ -0,0 +1,108 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service-https + http_filters: + - name: envoy.filters.http.router + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + # The following self-signed certificate pair is generated using: + # $ openssl req -x509 -newkey rsa:2048 -keyout a/front-proxy-key.pem -out a/front-proxy-crt.pem -days 3650 -nodes -subj '/CN=front-envoy' + # + # Instead of feeding it as an inline_string, certificate pair can also be fed to Envoy + # via filename. Reference: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#config-core-v3-datasource. + # + # Or in a dynamic configuration scenario, certificate pair can be fetched remotely via + # Secret Discovery Service (SDS). Reference: https://www.envoyproxy.io/docs/envoy/latest/configuration/security/secret. + certificate_chain: + inline_string: | + -----BEGIN CERTIFICATE----- + MIICqDCCAZACCQCquzpHNpqBcDANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtm + cm9udC1lbnZveTAeFw0yMDA3MDgwMTMxNDZaFw0zMDA3MDYwMTMxNDZaMBYxFDAS + BgNVBAMMC2Zyb250LWVudm95MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAthnYkqVQBX+Wg7aQWyCCb87hBce1hAFhbRM8Y9dQTqxoMXZiA2n8G089hUou + oQpEdJgitXVS6YMFPFUUWfwcqxYAynLK4X5im26Yfa1eO8La8sZUS+4Bjao1gF5/ + VJxSEo2yZ7fFBo8M4E44ZehIIocipCRS+YZehFs6dmHoq/MGvh2eAHIa+O9xssPt + ofFcQMR8rwBHVbKy484O10tNCouX4yUkyQXqCRy6HRu7kSjOjNKSGtjfG+h5M8bh + 10W7ZrsJ1hWhzBulSaMZaUY3vh5ngpws1JATQVSK1Jm/dmMRciwlTK7KfzgxHlSX + 58ENpS7yPTISkEICcLbXkkKGEQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCmj6Hg + vwOxWz0xu+6fSfRL6PGJUGq6wghCfUvjfwZ7zppDUqU47fk+yqPIOzuGZMdAqi7N + v1DXkeO4A3hnMD22Rlqt25vfogAaZVToBeQxCPd/ALBLFrvLUFYuSlS3zXSBpQqQ + Ny2IKFYsMllz5RSROONHBjaJOn5OwqenJ91MPmTAG7ujXKN6INSBM0PjX9Jy4Xb9 + zT+I85jRDQHnTFce1WICBDCYidTIvJtdSSokGSuy4/xyxAAc/BpZAfOjBQ4G1QRe + 9XwOi790LyNUYFJVyeOvNJwveloWuPLHb9idmY5YABwikUY6QNcXwyHTbRCkPB2I + m+/R4XnmL4cKQ+5Z + -----END CERTIFICATE----- + private_key: + inline_string: | + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2GdiSpVAFf5aD + tpBbIIJvzuEFx7WEAWFtEzxj11BOrGgxdmIDafwbTz2FSi6hCkR0mCK1dVLpgwU8 + VRRZ/ByrFgDKcsrhfmKbbph9rV47wtryxlRL7gGNqjWAXn9UnFISjbJnt8UGjwzg + Tjhl6EgihyKkJFL5hl6EWzp2Yeir8wa+HZ4Achr473Gyw+2h8VxAxHyvAEdVsrLj + zg7XS00Ki5fjJSTJBeoJHLodG7uRKM6M0pIa2N8b6HkzxuHXRbtmuwnWFaHMG6VJ + oxlpRje+HmeCnCzUkBNBVIrUmb92YxFyLCVMrsp/ODEeVJfnwQ2lLvI9MhKQQgJw + tteSQoYRAgMBAAECggEAeDGdEkYNCGQLe8pvg8Z0ccoSGpeTxpqGrNEKhjfi6NrB + NwyVav10iq4FxEmPd3nobzDPkAftfvWc6hKaCT7vyTkPspCMOsQJ39/ixOk+jqFx + lNa1YxyoZ9IV2DIHR1iaj2Z5gB367PZUoGTgstrbafbaNY9IOSyojCIO935ubbcx + DWwL24XAf51ez6sXnI8V5tXmrFlNXhbhJdH8iIxNyM45HrnlUlOk0lCK4gmLJjy9 + 10IS2H2Wh3M5zsTpihH1JvM56oAH1ahrhMXs/rVFXXkg50yD1KV+HQiEbglYKUxO + eMYtfaY9i2CuLwhDnWp3oxP3HfgQQhD09OEN3e0IlQKBgQDZ/3poG9TiMZSjfKqL + xnCABMXGVQsfFWNC8THoW6RRx5Rqi8q08yJrmhCu32YKvccsOljDQJQQJdQO1g09 + e/adJmCnTrqxNtjPkX9txV23Lp6Ak7emjiQ5ICu7iWxrcO3zf7hmKtj7z+av8sjO + mDI7NkX5vnlE74nztBEjp3eC0wKBgQDV2GeJV028RW3b/QyP3Gwmax2+cKLR9PKR + nJnmO5bxAT0nQ3xuJEAqMIss/Rfb/macWc2N/6CWJCRT6a2vgy6xBW+bqG6RdQMB + xEZXFZl+sSKhXPkc5Wjb4lQ14YWyRPrTjMlwez3k4UolIJhJmwl+D7OkMRrOUERO + EtUvc7odCwKBgBi+nhdZKWXveM7B5N3uzXBKmmRz3MpPdC/yDtcwJ8u8msUpTv4R + JxQNrd0bsIqBli0YBmFLYEMg+BwjAee7vXeDFq+HCTv6XMva2RsNryCO4yD3I359 + XfE6DJzB8ZOUgv4Dvluie3TB2Y6ZQV/p+LGt7G13yG4hvofyJYvlg3RPAoGAcjDg + +OH5zLN2eqah8qBN0CYa9/rFt0AJ19+7/smLTJ7QvQq4g0gwS1couplcCEnNGWiK + 72y1n/ckvvplmPeAE19HveMvR9UoCeV5ej86fACy8V/oVpnaaLBvL2aCMjPLjPP9 + DWeCIZp8MV86cvOrGfngf6kJG2qZTueXl4NAuwkCgYEArKkhlZVXjwBoVvtHYmN2 + o+F6cGMlRJTLhNc391WApsgDZfTZSdeJsBsvvzS/Nc0burrufJg0wYioTlpReSy4 + ohhtprnQQAddfjHP7rh2LGt+irFzhdXXQ1ybGaGM9D764KUNCXLuwdly0vzXU4HU + q5sGxGrC1RECGB5Zwx2S2ZY= + -----END PRIVATE KEY----- + + clusters: + - name: service-https + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service-https + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-https + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext diff --git a/examples/tls/envoy-https-passthrough.yaml b/examples/tls/envoy-https-passthrough.yaml new file mode 100644 index 000000000000..8ce160addf4a --- /dev/null +++ b/examples/tls/envoy-https-passthrough.yaml @@ -0,0 +1,28 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: service-https + stat_prefix: https_passthrough + + clusters: + - name: service-https + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service-https + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-https + port_value: 443 diff --git a/examples/tls/verify.sh b/examples/tls/verify.sh new file mode 100755 index 000000000000..96c92992ce26 --- /dev/null +++ b/examples/tls/verify.sh @@ -0,0 +1,33 @@ +#!/bin/bash -e + +export NAME=tls + +# shellcheck source=examples/verify-common.sh +. "$(dirname "${BASH_SOURCE[0]}")/../verify-common.sh" + +run_log "Test https -> http" +responds_with \ + '"x-forwarded-proto": "https",' \ + -k \ + https://localhost:10000 +curl -sk https://localhost:10000 | jq '.os.hostname' | grep '"service-http"' + +run_log "Test https -> https" +responds_with \ + '"x-forwarded-proto": "https",' \ + -k \ + https://localhost:10001 +curl -sk https://localhost:10001 | jq '.os.hostname' | grep '"service-https"' + +run_log "Test http -> https" +responds_with \ + '"x-forwarded-proto": "http",' \ + http://localhost:10002 +curl -s http://localhost:10002 | jq '.os.hostname' | grep '"service-https"' + +run_log "Test https passthrough" +responds_without \ + '"x-forwarded-proto"' \ + -k \ + https://localhost:10003 +curl -sk https://localhost:10003 | jq '.os.hostname' | grep '"service-https"' diff --git a/examples/verify-common.sh b/examples/verify-common.sh index 509faf3a846f..277336170f82 100644 --- a/examples/verify-common.sh +++ b/examples/verify-common.sh @@ -95,6 +95,16 @@ responds_with () { } } +responds_without () { + local expected + expected="$1" + shift + _curl "${@}" | grep "$expected" | [[ "$(wc -l)" -eq 0 ]] || { + echo "ERROR: curl without (${*}): $expected" >&2 + return 1 + } +} + responds_with_header () { local expected expected="$1" From bec30faa39a722b5d6e52bf5c29f1c8e9bc372a5 Mon Sep 17 00:00:00 2001 From: lopter-dbx <74276322+lopter-dbx@users.noreply.github.com> Date: Wed, 11 Nov 2020 10:31:14 -0800 Subject: [PATCH 069/117] event: reduce log level for min range timer complete message (#13970) Signed-off-by: Louis Opter --- source/common/event/scaled_range_timer_manager_impl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/common/event/scaled_range_timer_manager_impl.cc b/source/common/event/scaled_range_timer_manager_impl.cc index eca77f2186b0..3959976846d8 100644 --- a/source/common/event/scaled_range_timer_manager_impl.cc +++ b/source/common/event/scaled_range_timer_manager_impl.cc @@ -116,7 +116,7 @@ class ScaledRangeTimerManagerImpl::RangeTimerImpl final : public Timer { */ void onMinTimerComplete() { ASSERT(manager_.dispatcher_.isThreadSafe()); - ENVOY_LOG_MISC(info, "min timer complete for {}", static_cast(this)); + ENVOY_LOG_MISC(trace, "min timer complete for {}", static_cast(this)); ASSERT(absl::holds_alternative(state_)); const WaitingForMin& waiting = absl::get(state_); From da323928de2f000e9182f079f741ce6de1a60244 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 11 Nov 2020 14:19:56 -0500 Subject: [PATCH 070/117] http: removing the http1 and http2 connection pools (#13967) Signed-off-by: Alyssa Wilk --- source/common/http/conn_pool_base.cc | 2 + source/common/http/conn_pool_base.h | 47 ++++++++++++++- source/common/http/http1/conn_pool.cc | 58 ++++++++---------- source/common/http/http1/conn_pool.h | 54 ++++------------- source/common/http/http2/conn_pool.cc | 45 +++++--------- source/common/http/http2/conn_pool.h | 60 +++++-------------- .../grpc_client_integration_test_harness.h | 2 +- test/common/http/http1/conn_pool_test.cc | 21 ++++--- test/common/http/http2/conn_pool_test.cc | 13 +++- 9 files changed, 140 insertions(+), 162 deletions(-) diff --git a/source/common/http/conn_pool_base.cc b/source/common/http/conn_pool_base.cc index e785f87a7b0e..4fbd6cbd588b 100644 --- a/source/common/http/conn_pool_base.cc +++ b/source/common/http/conn_pool_base.cc @@ -63,6 +63,8 @@ HttpConnPoolImplBase::HttpConnPoolImplBase( } } +HttpConnPoolImplBase::~HttpConnPoolImplBase() { destructAllConnections(); } + ConnectionPool::Cancellable* HttpConnPoolImplBase::newStream(Http::ResponseDecoder& response_decoder, Http::ConnectionPool::Callbacks& callbacks) { diff --git a/source/common/http/conn_pool_base.h b/source/common/http/conn_pool_base.h index 16e4f39ac103..aad2d57ee3f1 100644 --- a/source/common/http/conn_pool_base.h +++ b/source/common/http/conn_pool_base.h @@ -34,8 +34,15 @@ class HttpPendingStream : public Envoy::ConnectionPool::PendingStream { HttpAttachContext context_; }; -// An implementation of Envoy::ConnectionPool::ConnPoolImplBase for shared code -// between HTTP/1.1 and HTTP/2 +class ActiveClient; + +/* An implementation of Envoy::ConnectionPool::ConnPoolImplBase for shared code + * between HTTP/1.1 and HTTP/2 + * + * NOTE: The connection pool does NOT do DNS resolution. It assumes it is being given a numeric IP + * address. Higher layer code should handle resolving DNS on error and creating a new pool + * bound to a different IP address. + */ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, public Http::ConnectionPool::Instance { public: @@ -45,6 +52,7 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, const Network::TransportSocketOptionsSharedPtr& transport_socket_options, Random::RandomGenerator& random_generator, std::vector protocol); + ~HttpConnPoolImplBase() override; // ConnectionPool::Instance void addDrainedCallback(DrainedCb cb) override { addDrainedCallbackImpl(cb); } @@ -71,8 +79,10 @@ class HttpConnPoolImplBase : public Envoy::ConnectionPool::ConnPoolImplBase, Envoy::ConnectionPool::AttachContext& context) override; virtual CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) PURE; + Random::RandomGenerator& randomGenerator() { return random_generator_; } protected: + friend class ActiveClient; Random::RandomGenerator& random_generator_; Http::Protocol protocol_; }; @@ -111,10 +121,43 @@ class ActiveClient : public Envoy::ConnectionPool::ActiveClient { } size_t numActiveStreams() const override { return codec_client_->numActiveRequests(); } uint64_t id() const override { return codec_client_->id(); } + HttpConnPoolImplBase& parent() { return *static_cast(&parent_); } Http::CodecClientPtr codec_client_; }; +/* An implementation of Envoy::ConnectionPool::ConnPoolImplBase for HTTP/1 and HTTP/2 + */ +class FixedHttpConnPoolImpl : public HttpConnPoolImplBase { +public: + using CreateClientFn = + std::function; + using CreateCodecFn = std::function; + + FixedHttpConnPoolImpl(Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, + Event::Dispatcher& dispatcher, + const Network::ConnectionSocket::OptionsSharedPtr& options, + const Network::TransportSocketOptionsSharedPtr& transport_socket_options, + Random::RandomGenerator& random_generator, CreateClientFn client_fn, + CreateCodecFn codec_fn, std::vector protocol) + : HttpConnPoolImplBase(host, priority, dispatcher, options, transport_socket_options, + random_generator, protocol), + codec_fn_(codec_fn), client_fn_(client_fn) {} + + CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) override { + return codec_fn_(data, this); + } + + Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override { + return client_fn_(this); + } + +protected: + const CreateCodecFn codec_fn_; + const CreateClientFn client_fn_; +}; + } // namespace Http } // namespace Envoy diff --git a/source/common/http/http1/conn_pool.cc b/source/common/http/http1/conn_pool.cc index 3aaf5aa2b572..c37e2f8e8635 100644 --- a/source/common/http/http1/conn_pool.cc +++ b/source/common/http/http1/conn_pool.cc @@ -23,35 +23,22 @@ namespace Envoy { namespace Http { namespace Http1 { -ConnPoolImpl::ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, - Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, - const Network::ConnectionSocket::OptionsSharedPtr& options, - const Network::TransportSocketOptionsSharedPtr& transport_socket_options) - : HttpConnPoolImplBase(std::move(host), std::move(priority), dispatcher, options, - transport_socket_options, random_generator, {Protocol::Http11}) {} - -ConnPoolImpl::~ConnPoolImpl() { destructAllConnections(); } - -Envoy::ConnectionPool::ActiveClientPtr ConnPoolImpl::instantiateActiveClient() { - return std::make_unique(*this); -} - -ConnPoolImpl::StreamWrapper::StreamWrapper(ResponseDecoder& response_decoder, ActiveClient& parent) +ActiveClient::StreamWrapper::StreamWrapper(ResponseDecoder& response_decoder, ActiveClient& parent) : RequestEncoderWrapper(parent.codec_client_->newStream(*this)), ResponseDecoderWrapper(response_decoder), parent_(parent) { RequestEncoderWrapper::inner_.getStream().addCallbacks(*this); } -ConnPoolImpl::StreamWrapper::~StreamWrapper() { +ActiveClient::StreamWrapper::~StreamWrapper() { // Upstream connection might be closed right after response is complete. Setting delay=true // here to attach pending requests in next dispatcher loop to handle that case. // https://github.com/envoyproxy/envoy/issues/2715 parent_.parent().onStreamClosed(parent_, true); } -void ConnPoolImpl::StreamWrapper::onEncodeComplete() { encode_complete_ = true; } +void ActiveClient::StreamWrapper::onEncodeComplete() { encode_complete_ = true; } -void ConnPoolImpl::StreamWrapper::decodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream) { +void ActiveClient::StreamWrapper::decodeHeaders(ResponseHeaderMapPtr&& headers, bool end_stream) { if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.fixed_connection_close")) { close_connection_ = HeaderUtility::shouldCloseConnection(parent_.codec_client_->protocol(), *headers); @@ -76,7 +63,7 @@ void ConnPoolImpl::StreamWrapper::decodeHeaders(ResponseHeaderMapPtr&& headers, ResponseDecoderWrapper::decodeHeaders(std::move(headers), end_stream); } -void ConnPoolImpl::StreamWrapper::onDecodeComplete() { +void ActiveClient::StreamWrapper::onDecodeComplete() { ASSERT(!decode_complete_); decode_complete_ = encode_complete_; @@ -90,44 +77,51 @@ void ConnPoolImpl::StreamWrapper::onDecodeComplete() { parent_.codec_client_->close(); } else { auto* pool = &parent_.parent(); - pool->dispatcher_.post([pool]() -> void { pool->onUpstreamReady(); }); + pool->dispatcher().post([pool]() -> void { pool->onUpstreamReady(); }); parent_.stream_wrapper_.reset(); pool->checkForDrained(); } } -ConnPoolImpl::ActiveClient::ActiveClient(ConnPoolImpl& parent) +void ActiveClient::StreamWrapper::onResetStream(StreamResetReason, absl::string_view) { + parent_.codec_client_->close(); +} + +ActiveClient::ActiveClient(HttpConnPoolImplBase& parent) : Envoy::Http::ActiveClient( - parent, parent.host_->cluster().maxRequestsPerConnection(), + parent, parent.host()->cluster().maxRequestsPerConnection(), 1 // HTTP1 always has a concurrent-request-limit of 1 per connection. ) { - parent.host_->cluster().stats().upstream_cx_http1_total_.inc(); + parent.host()->cluster().stats().upstream_cx_http1_total_.inc(); } -bool ConnPoolImpl::ActiveClient::closingWithIncompleteStream() const { +bool ActiveClient::closingWithIncompleteStream() const { return (stream_wrapper_ != nullptr) && (!stream_wrapper_->decode_complete_); } -RequestEncoder& ConnPoolImpl::ActiveClient::newStreamEncoder(ResponseDecoder& response_decoder) { +RequestEncoder& ActiveClient::newStreamEncoder(ResponseDecoder& response_decoder) { ASSERT(!stream_wrapper_); stream_wrapper_ = std::make_unique(response_decoder, *this); return *stream_wrapper_; } -CodecClientPtr ProdConnPoolImpl::createCodecClient(Upstream::Host::CreateConnectionData& data) { - CodecClientPtr codec{new CodecClientProd(CodecClient::Type::HTTP1, std::move(data.connection_), - data.host_description_, dispatcher_, random_generator_)}; - return codec; -} - ConnectionPool::InstancePtr allocateConnPool(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options) { - return std::make_unique( - dispatcher, random_generator, host, priority, options, transport_socket_options); + return std::make_unique( + std::move(host), std::move(priority), dispatcher, options, transport_socket_options, + random_generator, + [](HttpConnPoolImplBase* pool) { return std::make_unique(*pool); }, + [](Upstream::Host::CreateConnectionData& data, HttpConnPoolImplBase* pool) { + CodecClientPtr codec{new CodecClientProd( + CodecClient::Type::HTTP1, std::move(data.connection_), data.host_description_, + pool->dispatcher(), pool->randomGenerator())}; + return codec; + }, + std::vector{Protocol::Http11}); } } // namespace Http1 diff --git a/source/common/http/http1/conn_pool.h b/source/common/http/http1/conn_pool.h index a5034986fdd8..5b37d855e52f 100644 --- a/source/common/http/http1/conn_pool.h +++ b/source/common/http/http1/conn_pool.h @@ -12,29 +12,20 @@ namespace Http { namespace Http1 { /** - * A connection pool implementation for HTTP/1.1 connections. - * NOTE: The connection pool does NOT do DNS resolution. It assumes it is being given a numeric IP - * address. Higher layer code should handle resolving DNS on error and creating a new pool - * bound to a different IP address. + * An active client for HTTP/1.1 connections. */ -class ConnPoolImpl : public Http::HttpConnPoolImplBase { +class ActiveClient : public Envoy::Http::ActiveClient { public: - ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, - Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, - const Network::ConnectionSocket::OptionsSharedPtr& options, - const Network::TransportSocketOptionsSharedPtr& transport_socket_options); + ActiveClient(HttpConnPoolImplBase& parent); - ~ConnPoolImpl() override; - - // ConnPoolImplBase - Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override; - -protected: - class ActiveClient; + // ConnPoolImplBase::ActiveClient + bool closingWithIncompleteStream() const override; + RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override; struct StreamWrapper : public RequestEncoderWrapper, public ResponseDecoderWrapper, - public StreamCallbacks { + public StreamCallbacks, + protected Logger::Loggable { StreamWrapper(ResponseDecoder& response_decoder, ActiveClient& parent); ~StreamWrapper() override; @@ -47,9 +38,7 @@ class ConnPoolImpl : public Http::HttpConnPoolImplBase { void onDecodeComplete() override; // Http::StreamCallbacks - void onResetStream(StreamResetReason, absl::string_view) override { - parent_.codec_client_->close(); - } + void onResetStream(StreamResetReason, absl::string_view) override; void onAboveWriteBufferHighWatermark() override {} void onBelowWriteBufferLowWatermark() override {} @@ -58,32 +47,9 @@ class ConnPoolImpl : public Http::HttpConnPoolImplBase { bool close_connection_{}; bool decode_complete_{}; }; - using StreamWrapperPtr = std::unique_ptr; - class ActiveClient : public Envoy::Http::ActiveClient { - public: - ActiveClient(ConnPoolImpl& parent); - - ConnPoolImpl& parent() { return *static_cast(&parent_); } - - // ConnPoolImplBase::ActiveClient - bool closingWithIncompleteStream() const override; - RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override; - - StreamWrapperPtr stream_wrapper_; - }; -}; - -/** - * Production implementation of the ConnPoolImpl. - */ -class ProdConnPoolImpl : public ConnPoolImpl { -public: - using ConnPoolImpl::ConnPoolImpl; - - // ConnPoolImpl - CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) override; + StreamWrapperPtr stream_wrapper_; }; ConnectionPool::InstancePtr diff --git a/source/common/http/http2/conn_pool.cc b/source/common/http/http2/conn_pool.cc index b4e3be74a46f..c9c901f466d3 100644 --- a/source/common/http/http2/conn_pool.cc +++ b/source/common/http/http2/conn_pool.cc @@ -16,20 +16,7 @@ namespace Http2 { // side we do 2^29. static const uint64_t DEFAULT_MAX_STREAMS = (1 << 29); -ConnPoolImpl::ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, - Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, - const Network::ConnectionSocket::OptionsSharedPtr& options, - const Network::TransportSocketOptionsSharedPtr& transport_socket_options) - : HttpConnPoolImplBase(std::move(host), std::move(priority), dispatcher, options, - transport_socket_options, random_generator, {Protocol::Http2}) {} - -ConnPoolImpl::~ConnPoolImpl() { destructAllConnections(); } - -Envoy::ConnectionPool::ActiveClientPtr ConnPoolImpl::instantiateActiveClient() { - return std::make_unique(*this); -} - -void ConnPoolImpl::ActiveClient::onGoAway(Http::GoAwayErrorCode) { +void ActiveClient::onGoAway(Http::GoAwayErrorCode) { ENVOY_CONN_LOG(debug, "remote goaway", *codec_client_); parent_.host()->cluster().stats().upstream_cx_close_notify_.inc(); if (state_ != ActiveClient::State::DRAINING) { @@ -41,7 +28,7 @@ void ConnPoolImpl::ActiveClient::onGoAway(Http::GoAwayErrorCode) { } } -void ConnPoolImpl::ActiveClient::onStreamDestroy() { +void ActiveClient::onStreamDestroy() { parent().onStreamClosed(*this, false); // If we are destroying this stream because of a disconnect, do not check for drain here. We will @@ -52,7 +39,7 @@ void ConnPoolImpl::ActiveClient::onStreamDestroy() { } } -void ConnPoolImpl::ActiveClient::onStreamReset(Http::StreamResetReason reason) { +void ActiveClient::onStreamReset(Http::StreamResetReason reason) { if (reason == StreamResetReason::ConnectionTermination || reason == StreamResetReason::ConnectionFailure) { parent_.host()->cluster().stats().upstream_rq_pending_failure_eject_.inc(); @@ -68,7 +55,7 @@ uint64_t maxStreamsPerConnection(uint64_t max_streams_config) { return (max_streams_config != 0) ? max_streams_config : DEFAULT_MAX_STREAMS; } -ConnPoolImpl::ActiveClient::ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent) +ActiveClient::ActiveClient(HttpConnPoolImplBase& parent) : Envoy::Http::ActiveClient( parent, maxStreamsPerConnection(parent.host()->cluster().maxRequestsPerConnection()), parent.host()->cluster().http2Options().max_concurrent_streams().value()) { @@ -77,27 +64,27 @@ ConnPoolImpl::ActiveClient::ActiveClient(Envoy::Http::HttpConnPoolImplBase& pare parent.host()->cluster().stats().upstream_cx_http2_total_.inc(); } -bool ConnPoolImpl::ActiveClient::closingWithIncompleteStream() const { - return closed_with_active_rq_; -} +bool ActiveClient::closingWithIncompleteStream() const { return closed_with_active_rq_; } -RequestEncoder& ConnPoolImpl::ActiveClient::newStreamEncoder(ResponseDecoder& response_decoder) { +RequestEncoder& ActiveClient::newStreamEncoder(ResponseDecoder& response_decoder) { return codec_client_->newStream(response_decoder); } -CodecClientPtr ProdConnPoolImpl::createCodecClient(Upstream::Host::CreateConnectionData& data) { - CodecClientPtr codec{new CodecClientProd(CodecClient::Type::HTTP2, std::move(data.connection_), - data.host_description_, dispatcher_, random_generator_)}; - return codec; -} - ConnectionPool::InstancePtr allocateConnPool(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, const Network::ConnectionSocket::OptionsSharedPtr& options, const Network::TransportSocketOptionsSharedPtr& transport_socket_options) { - return std::make_unique( - dispatcher, random_generator, host, priority, options, transport_socket_options); + return std::make_unique( + host, priority, dispatcher, options, transport_socket_options, random_generator, + [](HttpConnPoolImplBase* pool) { return std::make_unique(*pool); }, + [](Upstream::Host::CreateConnectionData& data, HttpConnPoolImplBase* pool) { + CodecClientPtr codec{new CodecClientProd( + CodecClient::Type::HTTP2, std::move(data.connection_), data.host_description_, + pool->dispatcher(), pool->randomGenerator())}; + return codec; + }, + std::vector{Protocol::Http2}); } } // namespace Http2 diff --git a/source/common/http/http2/conn_pool.h b/source/common/http/http2/conn_pool.h index 833bb09c3702..cc5c335e38be 100644 --- a/source/common/http/http2/conn_pool.h +++ b/source/common/http/http2/conn_pool.h @@ -12,57 +12,27 @@ namespace Http { namespace Http2 { /** - * Implementation of a "connection pool" for HTTP/2. This mainly handles stats as well as - * shifting to a new connection if we reach max streams on the primary. This is a base class - * used for both the prod implementation as well as the testing one. + * Implementation of an active client for HTTP/2 */ -class ConnPoolImpl : public Envoy::Http::HttpConnPoolImplBase { +class ActiveClient : public CodecClientCallbacks, + public Http::ConnectionCallbacks, + public Envoy::Http::ActiveClient { public: - ConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, - Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, - const Network::ConnectionSocket::OptionsSharedPtr& options, - const Network::TransportSocketOptionsSharedPtr& transport_socket_options); + ActiveClient(HttpConnPoolImplBase& parent); + ~ActiveClient() override = default; - ~ConnPoolImpl() override; + // ConnPoolImpl::ActiveClient + bool closingWithIncompleteStream() const override; + RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override; - // ConnPoolImplBase - Envoy::ConnectionPool::ActiveClientPtr instantiateActiveClient() override; + // CodecClientCallbacks + void onStreamDestroy() override; + void onStreamReset(Http::StreamResetReason reason) override; - class ActiveClient : public CodecClientCallbacks, - public Http::ConnectionCallbacks, - public Envoy::Http::ActiveClient { - public: - ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent); - ActiveClient(Envoy::Http::HttpConnPoolImplBase& parent, - Upstream::Host::CreateConnectionData& data); - ~ActiveClient() override = default; + // Http::ConnectionCallbacks + void onGoAway(Http::GoAwayErrorCode error_code) override; - ConnPoolImpl& parent() { return static_cast(parent_); } - - // ConnPoolImpl::ActiveClient - bool closingWithIncompleteStream() const override; - RequestEncoder& newStreamEncoder(ResponseDecoder& response_decoder) override; - - // CodecClientCallbacks - void onStreamDestroy() override; - void onStreamReset(Http::StreamResetReason reason) override; - - // Http::ConnectionCallbacks - void onGoAway(Http::GoAwayErrorCode error_code) override; - - bool closed_with_active_rq_{}; - }; -}; - -/** - * Production implementation of the HTTP/2 connection pool. - */ -class ProdConnPoolImpl : public ConnPoolImpl { -public: - using ConnPoolImpl::ConnPoolImpl; - -private: - CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) override; + bool closed_with_active_rq_{}; }; ConnectionPool::InstancePtr diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index d610a221f8b7..be23d01b2791 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -298,7 +298,7 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { EXPECT_CALL(*mock_host_, createConnection_(_, _)).WillRepeatedly(Return(connection_data)); EXPECT_CALL(*mock_host_, cluster()).WillRepeatedly(ReturnRef(*cluster_info_ptr_)); EXPECT_CALL(*mock_host_description_, locality()).WillRepeatedly(ReturnRef(host_locality_)); - http_conn_pool_ = std::make_unique( + http_conn_pool_ = Http::Http2::allocateConnPool( *dispatcher_, random_, host_ptr_, Upstream::ResourcePriority::Default, nullptr, nullptr); EXPECT_CALL(cm_, httpConnPoolForCluster(_, _, _, _)) .WillRepeatedly(Return(http_conn_pool_.get())); diff --git a/test/common/http/http1/conn_pool_test.cc b/test/common/http/http1/conn_pool_test.cc index 0d02cc51e36b..3803add0f724 100644 --- a/test/common/http/http1/conn_pool_test.cc +++ b/test/common/http/http1/conn_pool_test.cc @@ -48,12 +48,19 @@ namespace { /** * A test version of ConnPoolImpl that allows for mocking beneath the codec clients. */ -class ConnPoolImplForTest : public ConnPoolImpl { +class ConnPoolImplForTest : public FixedHttpConnPoolImpl { public: ConnPoolImplForTest(Event::MockDispatcher& dispatcher, - Upstream::ClusterInfoConstSharedPtr cluster) - : ConnPoolImpl(dispatcher, random_, Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), - Upstream::ResourcePriority::Default, nullptr, nullptr), + Upstream::ClusterInfoConstSharedPtr cluster, + Random::RandomGenerator& random_generator) + : FixedHttpConnPoolImpl( + Upstream::makeTestHost(cluster, "tcp://127.0.0.1:9000"), + Upstream::ResourcePriority::Default, dispatcher, nullptr, nullptr, random_generator, + [](HttpConnPoolImplBase* pool) { return std::make_unique(*pool); }, + [](Upstream::Host::CreateConnectionData&, HttpConnPoolImplBase*) { + return nullptr; // Not used: createCodecClient overloaded. + }, + std::vector{Protocol::Http11}), api_(Api::createApiForTest()), mock_dispatcher_(dispatcher) {} ~ConnPoolImplForTest() override { @@ -116,7 +123,6 @@ class ConnPoolImplForTest : public ConnPoolImpl { Api::ApiPtr api_; Event::MockDispatcher& mock_dispatcher_; - NiceMock random_; Event::PostCb post_cb_; std::vector test_clients_; }; @@ -127,12 +133,13 @@ class ConnPoolImplForTest : public ConnPoolImpl { class Http1ConnPoolImplTest : public testing::Test { public: Http1ConnPoolImplTest() - : conn_pool_(std::make_unique(dispatcher_, cluster_)) {} + : conn_pool_(std::make_unique(dispatcher_, cluster_, random_)) {} ~Http1ConnPoolImplTest() override { EXPECT_EQ("", TestUtility::nonZeroedGauges(cluster_->stats_store_.gauges())); } + NiceMock random_; NiceMock dispatcher_; std::shared_ptr cluster_{new NiceMock()}; std::unique_ptr conn_pool_; @@ -283,7 +290,7 @@ TEST_F(Http1ConnPoolImplTest, VerifyAlpnFallback) { // Recreate the conn pool so that the host re-evaluates the transport socket match, arriving at // our test transport socket factory. - conn_pool_ = std::make_unique(dispatcher_, cluster_); + conn_pool_ = std::make_unique(dispatcher_, cluster_, random_); NiceMock outer_decoder; ConnPoolCallbacks callbacks; conn_pool_->expectClientCreate(Protocol::Http11); diff --git a/test/common/http/http2/conn_pool_test.cc b/test/common/http/http2/conn_pool_test.cc index 569991842e89..90b7a48350bf 100644 --- a/test/common/http/http2/conn_pool_test.cc +++ b/test/common/http/http2/conn_pool_test.cc @@ -36,9 +36,18 @@ namespace Envoy { namespace Http { namespace Http2 { -class TestConnPoolImpl : public ConnPoolImpl { +class TestConnPoolImpl : public FixedHttpConnPoolImpl { public: - using ConnPoolImpl::ConnPoolImpl; + TestConnPoolImpl(Event::Dispatcher& dispatcher, Random::RandomGenerator& random_generator, + Upstream::HostConstSharedPtr host, Upstream::ResourcePriority priority, + const Network::ConnectionSocket::OptionsSharedPtr& options, + const Network::TransportSocketOptionsSharedPtr& transport_socket_options) + : FixedHttpConnPoolImpl( + std::move(host), std::move(priority), dispatcher, options, transport_socket_options, + random_generator, + [](HttpConnPoolImplBase* pool) { return std::make_unique(*pool); }, + [](Upstream::Host::CreateConnectionData&, HttpConnPoolImplBase*) { return nullptr; }, + std::vector{Protocol::Http2}) {} CodecClientPtr createCodecClient(Upstream::Host::CreateConnectionData& data) override { // We expect to own the connection, but already have it, so just release it to prevent it from From 0e42452a48a8e8d10f5a230a94acb7e5fe84e38f Mon Sep 17 00:00:00 2001 From: htuch Date: Wed, 11 Nov 2020 15:56:37 -0500 Subject: [PATCH 071/117] config: v2 fatal-by-default. (#13950) This patch makes the v2 xDS API fatal-by-default. It's possible to still use v2 by: * Providing --bootstrap-version 2 on the CLI for v2 bootstrap files. * Setting envoy.reloadable_features.enable_deprecated_v2_api in the runtime. Many tests required fixups: * Unit tests were either upgraded to v3 (where there was no significant reason to remain v2) or a test runtime was introduced permitting v2 for deprecated feature tests. * Integration tests were generally migrated to v3. This now means that our integration tests coverage focuses on v3 (it was previously focused on v2). We have some limited v2 coverage still provided by the v2 ads_integration_tests. Risk level: High (this will break anyone who is still using v2 and has not enabled CLI or runtime override) Testing: Various tests updated as described above. New unit test added for bootstrap to server_test and to ads_integration_test for dynamic rejection behavior. Release Notes: Added. Signed-off-by: Harvey Tuch --- ci/filter_example_setup.sh | 2 +- .../_include/dns-cache-circuit-breaker.yaml | 2 +- docs/root/version_history/current.rst | 2 + source/common/protobuf/BUILD | 1 + source/common/protobuf/utility.cc | 17 +- source/common/protobuf/utility.h | 11 +- source/common/runtime/runtime_features.cc | 2 + source/server/server.cc | 2 +- .../common/access_log/access_log_impl_test.cc | 110 ++++--- .../config/delta_subscription_test_harness.h | 2 +- .../filesystem_subscription_test_harness.h | 2 +- test/common/config/grpc_mux_impl_test.cc | 14 +- .../config/grpc_subscription_test_harness.h | 2 +- .../config/http_subscription_test_harness.h | 6 +- test/common/config/new_grpc_mux_impl_test.cc | 6 +- test/common/http/header_utility_test.cc | 13 +- test/common/protobuf/BUILD | 1 + test/common/protobuf/utility_test.cc | 48 +-- test/common/router/config_impl_test.cc | 42 ++- test/common/router/rds_impl_test.cc | 10 +- .../common/router/router_upstream_log_test.cc | 10 +- test/common/router/scoped_rds_test.cc | 2 +- test/common/tcp_proxy/BUILD | 1 + test/common/tcp_proxy/tcp_proxy_test.cc | 8 + test/common/upstream/BUILD | 4 + .../upstream/cluster_manager_impl_test.cc | 7 +- test/common/upstream/eds_speed_test.cc | 2 + .../upstream/health_checker_impl_test.cc | 2 + .../upstream/original_dst_cluster_test.cc | 4 +- .../upstream/transport_socket_matcher_test.cc | 18 +- test/common/upstream/upstream_impl_test.cc | 5 +- test/config/utility.cc | 28 +- test/config/utility.h | 3 + test/config_test/BUILD | 1 + test/config_test/deprecated_configs_test.cc | 54 +-- .../http_grpc_access_log_integration_test.cc | 8 +- .../tcp_grpc_access_log_integration_test.cc | 15 +- .../aggregate/cluster_integration_test.cc | 5 +- .../clusters/aggregate/cluster_test.cc | 2 +- .../clusters/aggregate/cluster_update_test.cc | 4 +- .../clusters/dynamic_forward_proxy/BUILD | 1 + .../dynamic_forward_proxy/cluster_test.cc | 7 +- .../redis/redis_cluster_integration_test.cc | 4 +- .../aws_metadata_fetcher_integration_test.cc | 4 +- ...tive_concurrency_filter_integration_test.h | 2 +- .../http/csrf/csrf_filter_integration_test.cc | 8 +- .../fault/fault_filter_integration_test.cc | 6 +- .../filters/http/fault/fault_filter_test.cc | 7 - .../grpc_json_transcoder_integration_test.cc | 10 +- .../http/gzip/gzip_filter_integration_test.cc | 3 +- .../filters/http/gzip/gzip_filter_test.cc | 26 +- test/extensions/filters/http/jwt_authn/BUILD | 1 + .../filters/http/jwt_authn/matcher_test.cc | 8 +- .../filters/http/lua/lua_integration_test.cc | 22 +- .../http/rbac/rbac_filter_integration_test.cc | 10 +- .../http/tap/tap_filter_integration_test.cc | 10 +- .../network/http_connection_manager/BUILD | 1 + .../http_connection_manager/config_test.cc | 38 ++- .../local_ratelimit_integration_test.cc | 2 +- .../filters/network/rbac/integration_test.cc | 8 +- .../network/redis_proxy/config_test.cc | 4 +- .../redis_proxy_integration_test.cc | 10 +- .../filters/network/tcp_proxy/BUILD | 1 + .../filters/network/tcp_proxy/config_test.cc | 2 + .../network/thrift_proxy/integration_test.cc | 2 +- .../translation_integration_test.cc | 2 +- .../aws_iam/aws_iam_grpc_credentials_test.cc | 4 +- .../file_based_metadata/integration_test.cc | 4 +- test/extensions/health_checkers/redis/BUILD | 2 + .../health_checkers/redis/config_test.cc | 3 + .../health_checkers/redis/redis_test.cc | 2 + .../extensions/tracers/datadog/config_test.cc | 2 +- .../tracers/dynamic_ot/config_test.cc | 3 +- .../tracers/lightstep/config_test.cc | 2 +- .../tracers/opencensus/config_test.cc | 20 +- test/extensions/tracers/xray/config_test.cc | 8 +- test/extensions/tracers/zipkin/config_test.cc | 4 +- .../tracers/zipkin/zipkin_tracer_impl_test.cc | 52 ++- .../transport_sockets/tls/ssl_socket_test.cc | 15 +- test/integration/BUILD | 2 +- test/integration/ads_integration.cc | 5 +- test/integration/ads_integration.h | 5 +- test/integration/ads_integration_test.cc | 311 ++++++++++-------- .../alpn_selection_integration_test.cc | 2 +- test/integration/base_integration_test.cc | 2 +- test/integration/base_integration_test.h | 26 +- test/integration/hds_integration_test.cc | 2 +- test/integration/header_integration_test.cc | 3 +- .../http2_upstream_integration_test.cc | 8 +- test/integration/integration_admin_test.cc | 2 + .../listener_lds_integration_test.cc | 11 +- test/integration/protocol_integration_test.cc | 4 +- test/integration/rtds_integration_test.cc | 1 + .../sds_dynamic_integration_test.cc | 3 +- .../sds_generic_secret_integration_test.cc | 3 +- test/integration/server.cc | 29 +- test/integration/server.h | 11 +- .../tcp_conn_pool_integration_test.cc | 2 +- .../integration/tcp_proxy_integration_test.cc | 25 +- .../tcp_tunneling_integration_test.cc | 9 +- ...transport_socket_match_integration_test.cc | 2 +- test/integration/version_integration_test.cc | 4 +- test/integration/vhds_integration_test.cc | 9 +- test/integration/xds_integration_test.cc | 14 +- test/server/BUILD | 2 + test/server/api_listener_test.cc | 4 +- test/server/config_validation/BUILD | 2 +- test/server/config_validation/xds_fuzz.cc | 6 +- test/server/config_validation/xds_fuzz.h | 2 +- .../server/config_validation/xds_fuzz_test.cc | 2 +- test/server/config_validation/xds_verifier.cc | 2 +- test/server/config_validation/xds_verifier.h | 2 +- test/server/configuration_impl_test.cc | 2 + test/server/filter_chain_manager_impl_test.cc | 2 +- test/server/lds_api_test.cc | 24 +- test/server/listener_manager_impl_test.cc | 100 +++--- test/server/server_test.cc | 44 ++- .../zipkin_tracing_deprecated_config.yaml | 5 + test/test_common/resources.h | 20 +- test/test_common/test_runtime.h | 14 +- test/test_common/utility.cc | 49 --- test/test_common/utility.h | 7 - test/tools/router_check/BUILD | 1 + test/tools/router_check/router_check.cc | 3 + 124 files changed, 902 insertions(+), 684 deletions(-) diff --git a/ci/filter_example_setup.sh b/ci/filter_example_setup.sh index 774464f15a7c..ae0113c2d085 100644 --- a/ci/filter_example_setup.sh +++ b/ci/filter_example_setup.sh @@ -5,7 +5,7 @@ set -e # This is the hash on https://github.com/envoyproxy/envoy-filter-example.git we pin to. -ENVOY_FILTER_EXAMPLE_GITSHA="bebd0b2422ea7739905f1793565681d7266491e6" +ENVOY_FILTER_EXAMPLE_GITSHA="30e5df3b73aec14ca3b70e4537e0b42f2e9d7fd0" ENVOY_FILTER_EXAMPLE_SRCDIR="${BUILD_DIR}/envoy-filter-example" # shellcheck disable=SC2034 diff --git a/docs/root/configuration/http/http_filters/_include/dns-cache-circuit-breaker.yaml b/docs/root/configuration/http/http_filters/_include/dns-cache-circuit-breaker.yaml index d9f51b804d48..a7a01517b3b1 100644 --- a/docs/root/configuration/http/http_filters/_include/dns-cache-circuit-breaker.yaml +++ b/docs/root/configuration/http/http_filters/_include/dns-cache-circuit-breaker.yaml @@ -40,7 +40,7 @@ static_resources: http_filters: - name: envoy.filters.http.dynamic_forward_proxy typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.dynamic_forward_proxy.v2alpha.FilterConfig + "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig dns_cache_config: name: dynamic_forward_proxy_cache_config dns_lookup_family: V4_ONLY diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 38d464d98e0d..3a4dd5dffb4c 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -5,6 +5,8 @@ Incompatible Behavior Changes ----------------------------- *Changes that are expected to cause an incompatibility if applicable; deployment changes are likely required* +* config: v2 is now fatal-by-default. This may be overridden by setting :option:`--bootstrap-version` 2 on the CLI for a v2 bootstrap file and also enabling the runtime `envoy.reloadable_features.enable_deprecated_v2_api` feature. + Minor Behavior Changes ---------------------- *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/source/common/protobuf/BUILD b/source/common/protobuf/BUILD index 015177dcc186..12d2e4e424f9 100644 --- a/source/common/protobuf/BUILD +++ b/source/common/protobuf/BUILD @@ -73,6 +73,7 @@ envoy_cc_library( "//source/common/config:api_type_oracle_lib", "//source/common/config:version_converter_lib", "//source/common/protobuf:visitor_lib", + "//source/common/runtime:runtime_features_lib", "@com_github_cncf_udpa//udpa/annotations:pkg_cc_proto", "@envoy_api//envoy/annotations:pkg_cc_proto", "@envoy_api//envoy/type/v3:pkg_cc_proto", diff --git a/source/common/protobuf/utility.cc b/source/common/protobuf/utility.cc index f945a458d5de..e7842b35706e 100644 --- a/source/common/protobuf/utility.cc +++ b/source/common/protobuf/utility.cc @@ -16,6 +16,7 @@ #include "common/protobuf/protobuf.h" #include "common/protobuf/visitor.h" #include "common/protobuf/well_known.h" +#include "common/runtime/runtime_features.h" #include "absl/strings/match.h" #include "udpa/annotations/sensitive.pb.h" @@ -164,7 +165,7 @@ void tryWithApiBoosting(MessageXformFn f, Protobuf::Message& message) { try { f(message, MessageVersion::LatestVersionValidate); } catch (EnvoyException& e) { - MessageUtil::onVersionUpgradeWarn(e.what()); + MessageUtil::onVersionUpgradeDeprecation(e.what()); } // Now we do the real work of upgrading. Config::VersionConverter::upgrade(*earlier_message, message); @@ -275,8 +276,7 @@ void ProtoExceptionUtil::throwProtoValidationException(const std::string& valida throw ProtoValidationException(validation_error, message); } -// TODO(htuch): this is where we will also reject v2 configs by default. -void MessageUtil::onVersionUpgradeWarn(absl::string_view desc) { +void MessageUtil::onVersionUpgradeDeprecation(absl::string_view desc, bool reject) { const std::string& warning_str = fmt::format("Configuration does not parse cleanly as v3. v2 configuration is " "deprecated and will be removed from Envoy at the start of Q1 2021: {}", @@ -300,6 +300,15 @@ void MessageUtil::onVersionUpgradeWarn(absl::string_view desc) { if (loader != nullptr) { loader->countDeprecatedFeatureUse(); } + if (reject && + !Runtime::runtimeFeatureEnabled("envoy.reloadable_features.enable_deprecated_v2_api")) { + throw DeprecatedMajorVersionException(fmt::format( + "The v2 xDS major version is deprecated and disabled by default. Support for v2 will be " + "removed from Envoy at the start of Q1 2021. You may make use of v2 in Q3 2020 by setting " + "'--bootstrap-version 2' on the CLI for a v2 bootstrap file and also enabling the runtime " + "envoy.reloadable_features.enable_deprecated_v2_api flag. ({})", + desc)); + } } size_t MessageUtil::hash(const Protobuf::Message& message) { @@ -647,7 +656,7 @@ void MessageUtil::unpackTo(const ProtobufWkt::Any& any_message, Protobuf::Messag any_message_with_fixup.DebugString())); } Config::VersionConverter::annotateWithOriginalType(*earlier_version_desc, message); - MessageUtil::onVersionUpgradeWarn(any_full_name); + MessageUtil::onVersionUpgradeDeprecation(any_full_name); return; } } diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 24c3e639e45e..a23fe7d216f3 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -131,6 +131,14 @@ uint64_t fractionalPercentDenominatorToInt( namespace Envoy { +/** + * Exception class for rejecting a deprecated major version. + */ +class DeprecatedMajorVersionException : public EnvoyException { +public: + DeprecatedMajorVersionException(const std::string& message) : EnvoyException(message) {} +}; + class MissingFieldException : public EnvoyException { public: MissingFieldException(const std::string& field_name, const Protobuf::Message& message); @@ -367,8 +375,9 @@ class MessageUtil { * Invoke when a version upgrade (e.g. v2 -> v3) is detected. This may warn or throw * depending on where we are in the major version deprecation cycle. * @param desc description of upgrade to include in warning or exception. + * @param reject should a DeprecatedMajorVersionException be thrown on failure? */ - static void onVersionUpgradeWarn(absl::string_view desc); + static void onVersionUpgradeDeprecation(absl::string_view desc, bool reject = true); /** * Obtain a string field from a protobuf message dynamically. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 64ce93c8be1c..a961c25eaebb 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -101,6 +101,8 @@ constexpr const char* runtime_features[] = { // When features are added here, there should be a tracking bug assigned to the // code owner to flip the default after sufficient testing. constexpr const char* disabled_runtime_features[] = { + // v2 is fatal-by-default. + "envoy.reloadable_features.enable_deprecated_v2_api", // Allow Envoy to upgrade or downgrade version of type url, should be removed when support for // v2 url is removed from codebase. "envoy.reloadable_features.enable_type_url_downgrade_and_upgrade", diff --git a/source/server/server.cc b/source/server/server.cc index 3af9be2bc127..ee7792c719c7 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -258,7 +258,7 @@ void loadBootstrap(absl::optional bootstrap_version, envoy::config::bootstrap::v2::Bootstrap bootstrap_v2; load_function(bootstrap_v2, false); Config::VersionConverter::upgrade(bootstrap_v2, bootstrap); - MessageUtil::onVersionUpgradeWarn("v2 bootstrap"); + MessageUtil::onVersionUpgradeDeprecation("v2 bootstrap", false); } else { throw EnvoyException(fmt::format("Unknown bootstrap version {}.", *bootstrap_version)); } diff --git a/test/common/access_log/access_log_impl_test.cc b/test/common/access_log/access_log_impl_test.cc index 0b6ce4734f9d..0f9d94467d5c 100644 --- a/test/common/access_log/access_log_impl_test.cc +++ b/test/common/access_log/access_log_impl_test.cc @@ -69,7 +69,7 @@ TEST_F(AccessLogImplTest, LogMoreData) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -92,7 +92,7 @@ TEST_F(AccessLogImplTest, DownstreamDisconnect) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -114,9 +114,10 @@ TEST_F(AccessLogImplTest, RouteName) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null - format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH):256% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %ROUTE_NAME% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\"\n" + log_format: + text_format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH):256% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %ROUTE_NAME% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\"\n" )EOF"; InstanceSharedPtr log = AccessLogFactory::fromProto(parseAccessLogFromV3Yaml(yaml), context_); @@ -141,7 +142,7 @@ TEST_F(AccessLogImplTest, EnvoyUpstreamServiceTime) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -160,7 +161,7 @@ TEST_F(AccessLogImplTest, NoFilter) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -180,7 +181,7 @@ TEST_F(AccessLogImplTest, UpstreamHost) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -212,7 +213,7 @@ name: accesslog default_value: 1000000 runtime_key: key_b typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -250,7 +251,7 @@ name: accesslog default_value: 1000000 runtime_key: key_c typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -275,7 +276,7 @@ name: accesslog runtime_filter: runtime_key: access_log.test_key typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -318,7 +319,7 @@ name: accesslog numerator: 5 denominator: TEN_THOUSAND typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -362,7 +363,7 @@ name: accesslog denominator: MILLION use_independent_randomness: true typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -389,7 +390,7 @@ TEST_F(AccessLogImplTest, PathRewrite) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -408,7 +409,7 @@ name: accesslog filter: not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -427,7 +428,7 @@ name: accesslog filter: not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: "/dev/null" )EOF"; @@ -447,7 +448,7 @@ name: accesslog filter: traceable_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -484,7 +485,7 @@ name: accesslog filter: or_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -498,7 +499,7 @@ name: accesslog filter: and_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -521,7 +522,7 @@ name: accesslog runtime_key: key - not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -557,7 +558,7 @@ name: accesslog runtime_key: key - not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -600,7 +601,7 @@ name: accesslog runtime_key: key_b - not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -700,7 +701,7 @@ name: accesslog default_value: 499 runtime_key: hello typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -725,7 +726,7 @@ name: accesslog header: name: test-header typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -749,7 +750,7 @@ name: accesslog exact_match: exact-match-value typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -779,7 +780,7 @@ name: accesslog google_re2: {} regex: "\\d{3}" typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -814,7 +815,7 @@ name: accesslog start: -10 end: 0 typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -854,7 +855,7 @@ name: accesslog filter: response_flag_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -876,7 +877,7 @@ name: accesslog flags: - UO typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -903,7 +904,7 @@ name: accesslog - UO - RL typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -951,7 +952,7 @@ name: accesslog - NFCF - DT typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1001,7 +1002,7 @@ name: accesslog flags: - UnsupportedFlag typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1017,7 +1018,8 @@ name: accesslog "\"accesslog\"\nfilter {\n " " " "response_flag_filter {\n flags: \"UnsupportedFlag\"\n }\n}\ntyped_config {\n " - "[type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog] {\n path: \"/dev/null\"\n " + "[type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog] {\n path: " + "\"/dev/null\"\n " "}\n}\n"); } @@ -1029,7 +1031,7 @@ name: accesslog flags: - UnsupportedFlag typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1045,7 +1047,8 @@ name: accesslog "\"accesslog\"\nfilter {\n " " " "response_flag_filter {\n flags: \"UnsupportedFlag\"\n }\n}\ntyped_config {\n " - "[type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog] {\n path: \"/dev/null\"\n " + "[type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog] {\n path: " + "\"/dev/null\"\n " "}\n}\n"); } @@ -1053,9 +1056,10 @@ TEST_F(AccessLogImplTest, ValidGrpcStatusMessage) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null - format: "%GRPC_STATUS%\n" + log_format: + text_format: "%GRPC_STATUS%\n" )EOF"; InstanceSharedPtr log = AccessLogFactory::fromProto(parseAccessLogFromV3Yaml(yaml), context_); @@ -1092,7 +1096,7 @@ name: accesslog statuses: - {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1123,7 +1127,7 @@ name: accesslog statuses: - NOT_A_VALID_CODE typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1139,7 +1143,7 @@ name: accesslog statuses: - OK typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1160,7 +1164,7 @@ name: accesslog statuses: - {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1192,7 +1196,7 @@ name: accesslog statuses: - UNKNOWN typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1212,7 +1216,7 @@ name: accesslog statuses: - OK typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1237,7 +1241,7 @@ name: accesslog statuses: - OK typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1258,7 +1262,7 @@ name: accesslog statuses: - OK typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1286,7 +1290,7 @@ name: accesslog bool_match: true typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1322,7 +1326,7 @@ name: accesslog metadata_filter: match_if_key_not_found: false typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1351,7 +1355,7 @@ name: accesslog bool_match: true typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1369,7 +1373,7 @@ name: accesslog value: false typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1426,11 +1430,11 @@ name: accesslog extension_filter: name: test_header_filter typed_config: - "@type": type.googleapis.com/envoy.config.filter.accesslog.v2.HeaderFilter + "@type": type.googleapis.com/envoy.config.accesslog.v3.HeaderFilter header: name: test-header typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1507,7 +1511,7 @@ name: accesslog value: rate: 5 typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1535,7 +1539,7 @@ name: accesslog value: foo: bar typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; @@ -1550,7 +1554,7 @@ name: accesslog extension_filter: name: bar typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: /dev/null )EOF"; diff --git a/test/common/config/delta_subscription_test_harness.h b/test/common/config/delta_subscription_test_harness.h index ac2c18b74f43..b05d43ae87db 100644 --- a/test/common/config/delta_subscription_test_harness.h +++ b/test/common/config/delta_subscription_test_harness.h @@ -144,7 +144,7 @@ class DeltaSubscriptionTestHarness : public SubscriptionTestHarness { auto* resource = response->add_resources(); resource->set_name(cluster); resource->set_version(version); - resource->mutable_resource()->PackFrom(API_DOWNGRADE(*load_assignment)); + resource->mutable_resource()->PackFrom(*load_assignment); } } Protobuf::RepeatedPtrField removed_resources; diff --git a/test/common/config/filesystem_subscription_test_harness.h b/test/common/config/filesystem_subscription_test_harness.h index 2f52f2629497..d19da3bbed6f 100644 --- a/test/common/config/filesystem_subscription_test_harness.h +++ b/test/common/config/filesystem_subscription_test_harness.h @@ -82,7 +82,7 @@ class FilesystemSubscriptionTestHarness : public SubscriptionTestHarness { std::string file_json = "{\"versionInfo\":\"" + version + "\",\"resources\":["; for (const auto& cluster : cluster_names) { file_json += "{\"@type\":\"type.googleapis.com/" - "envoy.api.v2.ClusterLoadAssignment\",\"clusterName\":\"" + + "envoy.config.endpoint.v3.ClusterLoadAssignment\",\"clusterName\":\"" + cluster + "\"},"; } file_json.pop_back(); diff --git a/test/common/config/grpc_mux_impl_test.cc b/test/common/config/grpc_mux_impl_test.cc index fbf7230cea51..6544290bc7aa 100644 --- a/test/common/config/grpc_mux_impl_test.cc +++ b/test/common/config/grpc_mux_impl_test.cc @@ -382,7 +382,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { response->set_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { @@ -446,7 +446,7 @@ TEST_F(GrpcMuxImplTest, WildcardWatch) { response->set_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([&load_assignment](const std::vector& resources, const std::string&) { @@ -483,7 +483,7 @@ TEST_F(GrpcMuxImplTest, WatchDemux) { response->set_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(bar_callbacks, onConfigUpdate(_, "1")).Times(0); EXPECT_CALL(foo_callbacks, onConfigUpdate(_, "1")) .WillOnce(Invoke([&load_assignment](const std::vector& resources, @@ -504,13 +504,13 @@ TEST_F(GrpcMuxImplTest, WatchDemux) { response->set_version_info("2"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment_x; load_assignment_x.set_cluster_name("x"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment_x)); + response->add_resources()->PackFrom(load_assignment_x); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment_y; load_assignment_y.set_cluster_name("y"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment_y)); + response->add_resources()->PackFrom(load_assignment_y); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment_z; load_assignment_z.set_cluster_name("z"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment_z)); + response->add_resources()->PackFrom(load_assignment_z); EXPECT_CALL(bar_callbacks, onConfigUpdate(_, "2")) .WillOnce(Invoke([&load_assignment_y, &load_assignment_z]( const std::vector& resources, const std::string&) { @@ -907,7 +907,7 @@ TEST_F(GrpcMuxImplTest, WatchV3ResourceV2) { response->set_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([&load_assignment](const std::vector& resources, const std::string&) { diff --git a/test/common/config/grpc_subscription_test_harness.h b/test/common/config/grpc_subscription_test_harness.h index 4cb6e737daf3..f0950a8dca31 100644 --- a/test/common/config/grpc_subscription_test_harness.h +++ b/test/common/config/grpc_subscription_test_harness.h @@ -116,7 +116,7 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { last_cluster_names_.end()) { envoy::config::endpoint::v3::ClusterLoadAssignment* load_assignment = typed_resources.Add(); load_assignment->set_cluster_name(cluster); - response->add_resources()->PackFrom(API_DOWNGRADE(*load_assignment)); + response->add_resources()->PackFrom(*load_assignment); } } const auto decoded_resources = diff --git a/test/common/config/http_subscription_test_harness.h b/test/common/config/http_subscription_test_harness.h index 9e64b0d944e2..89826a049b4b 100644 --- a/test/common/config/http_subscription_test_harness.h +++ b/test/common/config/http_subscription_test_harness.h @@ -93,8 +93,8 @@ class HttpSubscriptionTestHarness : public SubscriptionTestHarness { } expected_request += "\"resource_names\":[\"" + joined_cluster_names + "\"]"; } - expected_request += - ",\"type_url\":\"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment\""; + expected_request += ",\"type_url\":\"type.googleapis.com/" + "envoy.config.endpoint.v3.ClusterLoadAssignment\""; expected_request += "}"; EXPECT_EQ(expected_request, request->bodyAsString()); EXPECT_EQ(fmt::format_int(expected_request.size()).str(), @@ -129,7 +129,7 @@ class HttpSubscriptionTestHarness : public SubscriptionTestHarness { std::string response_json = "{\"version_info\":\"" + version + "\",\"resources\":["; for (const auto& cluster : cluster_names) { response_json += "{\"@type\":\"type.googleapis.com/" - "envoy.api.v2.ClusterLoadAssignment\",\"cluster_name\":\"" + + "envoy.config.endpoint.v3.ClusterLoadAssignment\",\"cluster_name\":\"" + cluster + "\"},"; } response_json.pop_back(); diff --git a/test/common/config/new_grpc_mux_impl_test.cc b/test/common/config/new_grpc_mux_impl_test.cc index 2bcf1ecd75de..f143530f5ae9 100644 --- a/test/common/config/new_grpc_mux_impl_test.cc +++ b/test/common/config/new_grpc_mux_impl_test.cc @@ -101,7 +101,7 @@ TEST_F(NewGrpcMuxImplTest, DiscoveryResponseNonexistentSub) { response->set_system_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->mutable_resource()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->mutable_resource()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "1")) .WillOnce(Invoke([&load_assignment](const std::vector& added_resources, const Protobuf::RepeatedPtrField&, @@ -204,7 +204,7 @@ TEST_F(NewGrpcMuxImplTest, V3ResourceResponseV2ResourceWatch) { envoy::config::cluster::v3::Cluster cluster; unexpected_response->set_type_url(Config::TypeUrl::get().Cluster); unexpected_response->set_system_version_info("0"); - unexpected_response->add_resources()->mutable_resource()->PackFrom(API_DOWNGRADE(cluster)); + unexpected_response->add_resources()->mutable_resource()->PackFrom(cluster); EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "0")).Times(0); grpc_mux_->onDiscoveryResponse(std::move(unexpected_response), control_plane_stats_); } @@ -252,7 +252,7 @@ TEST_F(NewGrpcMuxImplTest, V2ResourceResponseV3ResourceWatch) { response->set_system_version_info("1"); envoy::config::endpoint::v3::ClusterLoadAssignment load_assignment; load_assignment.set_cluster_name("x"); - response->add_resources()->mutable_resource()->PackFrom(API_DOWNGRADE(load_assignment)); + response->add_resources()->mutable_resource()->PackFrom(load_assignment); // Send response that contains resource with v3 type url. response->set_type_url(v2_type_url); EXPECT_CALL(callbacks_, onConfigUpdate(_, _, "1")) diff --git a/test/common/http/header_utility_test.cc b/test/common/http/header_utility_test.cc index 47c39a36311e..9119f9cc5420 100644 --- a/test/common/http/header_utility_test.cc +++ b/test/common/http/header_utility_test.cc @@ -145,7 +145,8 @@ exact_match: value EXPECT_EQ("value", header_data.value_); } -TEST(HeaderDataConstructorTest, RegexMatchSpecifier) { +TEST(HeaderDataConstructorTest, DEPRECATED_FEATURE_TEST(RegexMatchSpecifier)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( name: test-header regex_match: value @@ -249,7 +250,8 @@ invert_match: true EXPECT_EQ(true, header_data.invert_match_); } -TEST(MatchHeadersTest, MayMatchOneOrMoreRequestHeader) { +TEST(MatchHeadersTest, DEPRECATED_FEATURE_TEST(MayMatchOneOrMoreRequestHeader)) { + TestDeprecatedV2Api _deprecated_v2_api; TestRequestHeaderMapImpl headers{{"some-header", "a"}, {"other-header", "b"}}; const std::string yaml = R"EOF( @@ -277,7 +279,6 @@ exact_match: a,b // Make sure that an exact match on "a,b" does in fact work. EXPECT_TRUE(HeaderUtility::matchHeaders(headers, header_data)); - TestScopedRuntime runtime; Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.reloadable_features.http_match_on_all_headers", "false"}}); // Flipping runtime to false should make "a,b" no longer match because we will match on the first @@ -370,7 +371,8 @@ invert_match: true EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } -TEST(MatchHeadersTest, HeaderRegexMatch) { +TEST(MatchHeadersTest, DEPRECATED_FEATURE_TEST(HeaderRegexMatch)) { + TestDeprecatedV2Api _deprecated_v2_api; TestRequestHeaderMapImpl matching_headers{{"match-header", "123"}}; TestRequestHeaderMapImpl unmatching_headers{{"match-header", "1234"}, {"match-header", "123.456"}}; @@ -404,7 +406,8 @@ name: match-header EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } -TEST(MatchHeadersTest, HeaderRegexInverseMatch) { +TEST(MatchHeadersTest, DEPRECATED_FEATURE_TEST(HeaderRegexInverseMatch)) { + TestDeprecatedV2Api _deprecated_v2_api; TestRequestHeaderMapImpl matching_headers{{"match-header", "1234"}, {"match-header", "123.456"}}; TestRequestHeaderMapImpl unmatching_headers{{"match-header", "123"}}; diff --git a/test/common/protobuf/BUILD b/test/common/protobuf/BUILD index 4aa4300922dc..bad3b85b77ab 100644 --- a/test/common/protobuf/BUILD +++ b/test/common/protobuf/BUILD @@ -35,6 +35,7 @@ envoy_cc_test( "//test/proto:sensitive_proto_cc_proto", "//test/test_common:environment_lib", "//test/test_common:logging_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/api/v2:pkg_cc_proto", "@envoy_api//envoy/api/v2/core:pkg_cc_proto", diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index e135905f361b..eee8bffdfb23 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -26,6 +26,7 @@ #include "test/proto/sensitive.pb.h" #include "test/test_common/environment.h" #include "test/test_common/logging.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "absl/container/node_hash_set.h" @@ -36,34 +37,32 @@ using namespace std::chrono_literals; namespace Envoy { -class RuntimeStatsHelper { +class RuntimeStatsHelper : public TestScopedRuntime { public: - RuntimeStatsHelper() - : api_(Api::createApiForTest(store_)), - runtime_deprecated_feature_use_(store_.counter("runtime.deprecated_feature_use")), + RuntimeStatsHelper(bool allow_deprecated_v2_api = false) + : runtime_deprecated_feature_use_(store_.counter("runtime.deprecated_feature_use")), deprecated_feature_seen_since_process_start_( store_.gauge("runtime.deprecated_feature_seen_since_process_start", Stats::Gauge::ImportMode::NeverImport)) { - envoy::config::bootstrap::v3::LayeredRuntime config; - config.add_layers()->mutable_admin_layer(); - loader_ = std::make_unique( - Runtime::LoaderPtr{new Runtime::LoaderImpl(dispatcher_, tls_, config, local_info_, store_, - generator_, validation_visitor_, *api_)}); + if (allow_deprecated_v2_api) { + Runtime::LoaderSingleton::getExisting()->mergeValues({ + {"envoy.reloadable_features.enable_deprecated_v2_api", "true"}, + {"envoy.features.enable_all_deprecated_features", "true"}, + }); + } } - Event::MockDispatcher dispatcher_; - NiceMock tls_; - Stats::TestUtil::TestStore store_; - Random::MockRandomGenerator generator_; - Api::ApiPtr api_; - std::unique_ptr loader_; Stats::Counter& runtime_deprecated_feature_use_; Stats::Gauge& deprecated_feature_seen_since_process_start_; - NiceMock local_info_; - NiceMock validation_visitor_; }; class ProtobufUtilityTest : public testing::Test, protected RuntimeStatsHelper {}; +// TODO(htuch): During/before the v2 removal, cleanup the various examples that explicitly refer to +// v2 API protos and replace with upgrade examples not tie to the concrete API. +class ProtobufV2ApiUtilityTest : public testing::Test, protected RuntimeStatsHelper { +public: + ProtobufV2ApiUtilityTest() : RuntimeStatsHelper(true) {} +}; TEST_F(ProtobufUtilityTest, ConvertPercentNaNDouble) { envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; @@ -267,7 +266,7 @@ TEST_F(ProtobufUtilityTest, LoadBinaryProtoFromFile) { EXPECT_TRUE(TestUtility::protoEqual(bootstrap, proto_from_file)); } -TEST_F(ProtobufUtilityTest, DEPRECATED_FEATURE_TEST(LoadBinaryV2ProtoFromFile)) { +TEST_F(ProtobufV2ApiUtilityTest, DEPRECATED_FEATURE_TEST(LoadBinaryV2ProtoFromFile)) { // Allow the use of v2.Bootstrap.runtime. Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.deprecated_features:envoy.config.bootstrap.v2.Bootstrap.runtime", "True "}}); @@ -370,7 +369,7 @@ TEST_F(ProtobufUtilityTest, LoadJsonFromFileNoBoosting) { EXPECT_TRUE(TestUtility::protoEqual(bootstrap, proto_from_file)); } -TEST_F(ProtobufUtilityTest, DEPRECATED_FEATURE_TEST(LoadV2TextProtoFromFile)) { +TEST_F(ProtobufV2ApiUtilityTest, DEPRECATED_FEATURE_TEST(LoadV2TextProtoFromFile)) { API_NO_BOOST(envoy::config::bootstrap::v2::Bootstrap) bootstrap; bootstrap.mutable_node()->set_build_version("foo"); @@ -1234,7 +1233,7 @@ TEST_F(ProtobufUtilityTest, AnyConvertAndValidateFailedValidation) { } // MessageUtility::unpackTo() with the wrong type throws. -TEST_F(ProtobufUtilityTest, UnpackToWrongType) { +TEST_F(ProtobufV2ApiUtilityTest, UnpackToWrongType) { ProtobufWkt::Duration source_duration; source_duration.set_seconds(42); ProtobufWkt::Any source_any; @@ -1268,7 +1267,7 @@ TEST_F(ProtobufUtilityTest, UnpackToSameVersion) { } // MessageUtility::unpackTo() with API message works across version. -TEST_F(ProtobufUtilityTest, UnpackToNextVersion) { +TEST_F(ProtobufV2ApiUtilityTest, UnpackToNextVersion) { API_NO_BOOST(envoy::api::v2::Cluster) source; source.set_drain_connections_on_host_removal(true); ProtobufWkt::Any source_any; @@ -1280,7 +1279,7 @@ TEST_F(ProtobufUtilityTest, UnpackToNextVersion) { } // Validate warning messages on v2 upgrades. -TEST_F(ProtobufUtilityTest, V2UpgradeWarningLogs) { +TEST_F(ProtobufV2ApiUtilityTest, V2UpgradeWarningLogs) { API_NO_BOOST(envoy::config::cluster::v3::Cluster) dst; // First attempt works. EXPECT_LOG_CONTAINS("warn", "Configuration does not parse cleanly as v3", @@ -1295,7 +1294,8 @@ TEST_F(ProtobufUtilityTest, V2UpgradeWarningLogs) { EXPECT_LOG_CONTAINS("warn", "Configuration does not parse cleanly as v3", MessageUtil::loadFromJson("{drain_connections_on_host_removal: false}", dst, ProtobufMessage::getNullValidationVisitor())); - // This is kind of terrible, but it's hard to do dependency injection at onVersionUpgradeWarn(). + // This is kind of terrible, but it's hard to do dependency injection at + // onVersionUpgradeDeprecation(). std::this_thread::sleep_for(5s); // NOLINT // We can log the original warning again. EXPECT_LOG_CONTAINS("warn", "Configuration does not parse cleanly as v3", @@ -1353,7 +1353,7 @@ TEST_F(ProtobufUtilityTest, LoadFromJsonNoBoosting) { } // MessageUtility::loadFromJson() with API message works across version. -TEST_F(ProtobufUtilityTest, LoadFromJsonNextVersion) { +TEST_F(ProtobufV2ApiUtilityTest, LoadFromJsonNextVersion) { { API_NO_BOOST(envoy::config::cluster::v3::Cluster) dst; MessageUtil::loadFromJson("{use_tcp_for_dns_lookups: true}", dst, diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 9ee70ea170fc..13659b472a85 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -141,9 +141,17 @@ Http::TestRequestHeaderMapImpl genHeaders(const std::string& host, const std::st envoy::config::route::v3::RouteConfiguration parseRouteConfigurationFromYaml(const std::string& yaml) { envoy::config::route::v3::RouteConfiguration route_config; + // Most tests should be v3 and not boost. + bool avoid_boosting = true; + // If we're under TestDeprecatedV2Api, allow boosting. + auto* runtime = Runtime::LoaderSingleton::getExisting(); + if (runtime != nullptr && runtime->threadsafeSnapshot()->runtimeFeatureEnabled( + "envoy.reloadable_features.enable_deprecated_v2_api")) { + avoid_boosting = false; + } // Load the file and keep the annotations (in case of an upgrade) to make sure - // validate() observes the upgrade - TestUtility::loadFromYaml(yaml, route_config, true); + // validate() observes the upgrade. + TestUtility::loadFromYaml(yaml, route_config, true, avoid_boosting); TestUtility::validate(route_config); return route_config; } @@ -316,6 +324,7 @@ class RouteMatcherTest : public testing::Test, public ConfigImplTestBase {}; // When removing legacy fields this test can be removed. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(TestLegacyRoutes)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: regex @@ -1242,6 +1251,7 @@ TEST_F(RouteMatcherTest, TestRoutesWithWildcardAndDefaultOnly) { // When deprecating regex: this test can be removed. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(TestRoutesWithInvalidRegexLegacy)) { + TestDeprecatedV2Api _deprecated_v2_api; std::string invalid_route = R"EOF( virtual_hosts: - name: regex @@ -1954,6 +1964,7 @@ TEST_F(RouteMatcherTest, HeaderMatchedRouting) { // Verify the fixes for https://github.com/envoyproxy/envoy/issues/2406 // When removing regex_match this test can be removed entirely. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(InvalidHeaderMatchedRoutingConfigLegacy)) { + TestDeprecatedV2Api _deprecated_v2_api; std::string value_with_regex_chars = R"EOF( virtual_hosts: - name: local_service @@ -2028,6 +2039,7 @@ TEST_F(RouteMatcherTest, InvalidHeaderMatchedRoutingConfig) { // When removing value: simply remove that section of the config and the relevant test. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(QueryParamMatchedRouting)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: local_service @@ -2145,6 +2157,7 @@ TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(QueryParamMatchedRouting)) { // When removing value: this test can be removed. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(InvalidQueryParamMatchedRoutingConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; std::string value_with_regex_chars = R"EOF( virtual_hosts: - name: local_service @@ -3166,6 +3179,7 @@ TEST_F(RouteMatcherTest, ClusterNotFoundResponseCodeConfig404) { // TODO(dereka) DEPRECATED_FEATURE_TEST can be removed when `request_mirror_policy` is removed. TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(Shadow)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: www2 @@ -3236,6 +3250,7 @@ TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(Shadow)) { } TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(ShadowPolicyAndPolicies)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: www2 @@ -3261,6 +3276,7 @@ class RouteConfigurationV2 : public testing::Test, public ConfigImplTestBase {}; // When removing runtime_key: this test can be removed. TEST_F(RouteConfigurationV2, DEPRECATED_FEATURE_TEST(RequestMirrorPolicy)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: mirror @@ -4023,7 +4039,7 @@ TEST_F(RouteMatcherTest, TestInvalidCharactersInHostRewrites) { routes: - match: { prefix: "/foo" } route: - host_rewrite: "new_host\ndroptable" + host_rewrite_literal: "new_host\ndroptable" cluster: www )EOF"; @@ -4040,7 +4056,7 @@ TEST_F(RouteMatcherTest, TestInvalidCharactersInAutoHostRewrites) { routes: - match: { prefix: "/foo" } route: - auto_host_rewrite_header: "x-host\ndroptable" + host_rewrite_header: "x-host\ndroptable" cluster: www )EOF"; @@ -5292,7 +5308,8 @@ TEST_F(BadHttpRouteConfigurationsTest, BadRouteEntryConfigMissingPathSpecifier) "RouteValidationError.Match: \\[\"value is required\"\\]"); } -TEST_F(BadHttpRouteConfigurationsTest, BadRouteEntryConfigPrefixAndRegex) { +TEST_F(BadHttpRouteConfigurationsTest, DEPRECATED_FEATURE_TEST(BadRouteEntryConfigPrefixAndRegex)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: www2 @@ -5340,7 +5357,8 @@ TEST_F(BadHttpRouteConfigurationsTest, BadRouteEntryConfigNoAction) { "caused by field: \"action\", reason: is required"); } -TEST_F(BadHttpRouteConfigurationsTest, BadRouteEntryConfigPathAndRegex) { +TEST_F(BadHttpRouteConfigurationsTest, DEPRECATED_FEATURE_TEST(BadRouteEntryConfigPathAndRegex)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: www2 @@ -5514,6 +5532,7 @@ TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(ExcludeVHRateLimits)) { // When allow_origin: and allow_origin_regex: are removed, simply remove them // and the relevant checks below. TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TestVHostCorsConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: "default" @@ -5631,6 +5650,7 @@ TEST_F(RoutePropertyTest, TestRouteCorsConfig) { // When allow-origin: is removed, this test can be removed. TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TTestVHostCorsLegacyConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: default @@ -5671,6 +5691,7 @@ TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TTestVHostCorsLegacyConfig)) { // When allow-origin: is removed, this test can be removed. TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TestRouteCorsLegacyConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: default @@ -5706,7 +5727,8 @@ TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TestRouteCorsLegacyConfig)) { EXPECT_EQ(cors_policy->allowCredentials(), true); } -TEST_F(RoutePropertyTest, TestBadCorsConfig) { +TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TestBadCorsConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: default @@ -7434,6 +7456,7 @@ class PerFilterConfigsTest : public testing::Test, public ConfigImplTestBase { }; TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(TypedConfigFilterError)) { + TestDeprecatedV2Api _deprecated_v2_api; { const std::string yaml = R"EOF( virtual_hosts: @@ -7474,6 +7497,7 @@ TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(TypedConfigFilterError)) { } TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(UnknownFilterStruct)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: bar @@ -7510,6 +7534,7 @@ TEST_F(PerFilterConfigsTest, UnknownFilterAny) { // Test that a trivially specified NamedHttpFilterConfigFactory ignores per_filter_config without // error. TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(DefaultFilterImplementationStruct)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: bar @@ -7542,6 +7567,7 @@ TEST_F(PerFilterConfigsTest, DefaultFilterImplementationAny) { } TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(RouteLocalConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: bar @@ -7580,6 +7606,7 @@ TEST_F(PerFilterConfigsTest, RouteLocalTypedConfig) { } TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(WeightedClusterConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: bar @@ -7626,6 +7653,7 @@ TEST_F(PerFilterConfigsTest, WeightedClusterTypedConfig) { } TEST_F(PerFilterConfigsTest, DEPRECATED_FEATURE_TEST(WeightedClusterFallthroughConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( virtual_hosts: - name: bar diff --git a/test/common/router/rds_impl_test.cc b/test/common/router/rds_impl_test.cc index 8adc57d7ac94..2bbb87733bf8 100644 --- a/test/common/router/rds_impl_test.cc +++ b/test/common/router/rds_impl_test.cc @@ -100,7 +100,7 @@ codec_type: auto stat_prefix: foo http_filters: - name: http_dynamo_filter - config: {} + typed_config: {} )EOF"; EXPECT_CALL(outer_init_manager_, add(_)); @@ -166,7 +166,7 @@ TEST_F(RdsImplTest, Basic) { "version_info": "1", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", "name": "foo_route_config", "virtual_hosts": null } @@ -196,7 +196,7 @@ TEST_F(RdsImplTest, Basic) { "version_info": "2", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", "name": "foo_route_config", "virtual_hosts": [ { @@ -248,7 +248,7 @@ TEST_F(RdsImplTest, FailureInvalidConfig) { "version_info": "1", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.RouteConfiguration", + "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", "name": "INVALID_NAME_FOR_route_config", "virtual_hosts": null } @@ -730,7 +730,7 @@ TEST_F(RouteConfigProviderManagerImplTest, ConfigDumpAfterConfigRejected) { const std::string response1_yaml = R"EOF( version_info: '1' resources: -- "@type": type.googleapis.com/envoy.api.v2.RouteConfiguration +- "@type": type.googleapis.com/envoy.config.route.v3.RouteConfiguration name: foo_route_config virtual_hosts: - name: integration diff --git a/test/common/router/router_upstream_log_test.cc b/test/common/router/router_upstream_log_test.cc index 64829ff6cec8..768d2f8eaad7 100644 --- a/test/common/router/router_upstream_log_test.cc +++ b/test/common/router/router_upstream_log_test.cc @@ -40,8 +40,9 @@ absl::optional testUpstreamLog() { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog - format: "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% %RESPONSE_CODE% + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + log_format: + text_format: "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %REQ(:AUTHORITY)% %UPSTREAM_HOST% %UPSTREAM_LOCAL_ADDRESS% %RESP(X-UPSTREAM-HEADER)% %TRAILER(X-TRAILER)%\n" path: "/dev/null" @@ -286,8 +287,9 @@ TEST_F(RouterUpstreamLogTest, LogTimestampsAndDurations) { const std::string yaml = R"EOF( name: accesslog typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog - format: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + log_format: + text_format: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% %DURATION% %RESPONSE_DURATION% %REQUEST_DURATION%" path: "/dev/null" )EOF"; diff --git a/test/common/router/scoped_rds_test.cc b/test/common/router/scoped_rds_test.cc index c0e71530f317..4618e47f4deb 100644 --- a/test/common/router/scoped_rds_test.cc +++ b/test/common/router/scoped_rds_test.cc @@ -697,7 +697,7 @@ codec_type: auto stat_prefix: foo http_filters: - name: http_dynamo_filter - config: + typed_config: scoped_routes: name: $0 scope_key_builder: diff --git a/test/common/tcp_proxy/BUILD b/test/common/tcp_proxy/BUILD index 9f8b01a83e27..769badd15355 100644 --- a/test/common/tcp_proxy/BUILD +++ b/test/common/tcp_proxy/BUILD @@ -34,6 +34,7 @@ envoy_cc_test( "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/mocks/upstream:host_mocks", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index f19c3bac4d45..bd3111fa7dc7 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -33,6 +33,7 @@ #include "test/mocks/stream_info/mocks.h" #include "test/mocks/tcp/mocks.h" #include "test/mocks/upstream/host.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -154,6 +155,7 @@ TEST(ConfigTest, DEPRECATED_FEATURE_TEST(EmptyRouteConfig)) { } TEST(ConfigTest, DEPRECATED_FEATURE_TEST(Routes)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( stat_prefix: name cluster: cluster @@ -366,6 +368,7 @@ TEST(ConfigTest, DEPRECATED_FEATURE_TEST(Routes)) { // Tests that a deprecated_v1 route gets the top-level endpoint selector. TEST(ConfigTest, DEPRECATED_FEATURE_TEST(RouteWithTopLevelMetadataMatchConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( stat_prefix: name cluster: cluster @@ -1955,6 +1958,7 @@ class TcpProxyRoutingTest : public testing::Test { }; TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(NonRoutableConnection)) { + TestDeprecatedV2Api _deprecated_v2_api; setup(false); const uint32_t total_cx = config_->stats().downstream_cx_total_.value(); @@ -1976,6 +1980,7 @@ TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(NonRoutableConnection)) { } TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(RoutableConnection)) { + TestDeprecatedV2Api _deprecated_v2_api; setup(false); const uint32_t total_cx = config_->stats().downstream_cx_total_.value(); @@ -1998,6 +2003,7 @@ TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(RoutableConnection)) { // Test that the tcp proxy uses the cluster from FilterState if set TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(UseClusterFromPerConnectionCluster)) { + TestDeprecatedV2Api _deprecated_v2_api; setup(false); initializeFilter(); @@ -2015,6 +2021,7 @@ TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(UseClusterFromPerConnectionC // Test that the tcp proxy forwards the requested server name from FilterState if set TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(UpstreamServerName)) { + TestDeprecatedV2Api _deprecated_v2_api; setup(false); initializeFilter(); @@ -2045,6 +2052,7 @@ TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(UpstreamServerName)) { // Test that the tcp proxy override ALPN from FilterState if set TEST_F(TcpProxyRoutingTest, DEPRECATED_FEATURE_TEST(ApplicationProtocols)) { + TestDeprecatedV2Api _deprecated_v2_api; setup(false); initializeFilter(); diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 6837a9f5a0da..65eeb64c1070 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -46,6 +46,7 @@ envoy_cc_test( "//test/mocks/upstream:health_checker_mocks", "//test/mocks/upstream:load_balancer_context_mock", "//test/mocks/upstream:thread_aware_load_balancer_mocks", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", @@ -131,6 +132,7 @@ envoy_cc_benchmark_binary( "//test/mocks/server:instance_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_manager_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -403,6 +405,7 @@ envoy_cc_test( "//test/mocks/server:instance_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:cluster_manager_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", ], @@ -605,6 +608,7 @@ envoy_cc_test( "//test/mocks/upstream:health_checker_mocks", "//test/mocks/upstream:priority_set_mocks", "//test/test_common:registry_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 155e2b628857..2127623a9400 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -12,6 +12,7 @@ #include "test/mocks/upstream/health_checker.h" #include "test/mocks/upstream/load_balancer_context.h" #include "test/mocks/upstream/thread_aware_load_balancer.h" +#include "test/test_common/test_runtime.h" namespace Envoy { namespace Upstream { @@ -433,7 +434,8 @@ TEST_F(ClusterManagerImplTest, OriginalDstLbRestriction) { "'CLUSTER_PROVIDED' or 'ORIGINAL_DST_LB' is allowed with cluster type 'ORIGINAL_DST'"); } -TEST_F(ClusterManagerImplTest, OriginalDstLbRestriction2) { +TEST_F(ClusterManagerImplTest, DEPRECATED_FEATURE_TEST(OriginalDstLbRestriction2)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( static_resources: clusters: @@ -554,7 +556,8 @@ INSTANTIATE_TEST_SUITE_P(ClusterManagerSubsetInitializationTest, testing::ValuesIn(ClusterManagerSubsetInitializationTest::lbPolicies()), ClusterManagerSubsetInitializationTest::paramName); -TEST_F(ClusterManagerImplTest, SubsetLoadBalancerOriginalDstRestriction) { +TEST_F(ClusterManagerImplTest, DEPRECATED_FEATURE_TEST(SubsetLoadBalancerOriginalDstRestriction)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( static_resources: clusters: diff --git a/test/common/upstream/eds_speed_test.cc b/test/common/upstream/eds_speed_test.cc index 79de684a015b..50ee28cdd0a4 100644 --- a/test/common/upstream/eds_speed_test.cc +++ b/test/common/upstream/eds_speed_test.cc @@ -25,6 +25,7 @@ #include "test/mocks/server/instance.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "benchmark/benchmark.h" @@ -139,6 +140,7 @@ class EdsSpeedTest { num_hosts); } + TestDeprecatedV2Api _deprecated_v2_api_; State& state_; const bool v2_config_; const std::string type_url_; diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 811b7dc86ff0..925e913fc699 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -2688,6 +2688,7 @@ TEST_F(ProdHttpHealthCheckerTest, ProdHttpHealthCheckerH1HealthChecking) { } TEST_F(HttpHealthCheckerImplTest, DEPRECATED_FEATURE_TEST(Http1CodecClient)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( timeout: 1s interval: 1s @@ -2708,6 +2709,7 @@ TEST_F(HttpHealthCheckerImplTest, DEPRECATED_FEATURE_TEST(Http1CodecClient)) { } TEST_F(HttpHealthCheckerImplTest, DEPRECATED_FEATURE_TEST(Http2CodecClient)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( timeout: 1s interval: 1s diff --git a/test/common/upstream/original_dst_cluster_test.cc b/test/common/upstream/original_dst_cluster_test.cc index 4e95e2303882..643de67a77e5 100644 --- a/test/common/upstream/original_dst_cluster_test.cc +++ b/test/common/upstream/original_dst_cluster_test.cc @@ -25,6 +25,7 @@ #include "test/mocks/server/instance.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -143,7 +144,8 @@ TEST_F(OriginalDstClusterTest, BadConfigWithLoadAssignment) { "ORIGINAL_DST clusters must have no load assignment or hosts configured"); } -TEST_F(OriginalDstClusterTest, BadConfigWithDeprecatedHosts) { +TEST_F(OriginalDstClusterTest, DEPRECATED_FEATURE_TEST(BadConfigWithDeprecatedHosts)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( name: name connect_timeout: 0.25s diff --git a/test/common/upstream/transport_socket_matcher_test.cc b/test/common/upstream/transport_socket_matcher_test.cc index b5e495c8e592..b564192f860e 100644 --- a/test/common/upstream/transport_socket_matcher_test.cc +++ b/test/common/upstream/transport_socket_matcher_test.cc @@ -110,7 +110,8 @@ name: "enableFooSocket" hasSidecar: "true" transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "abc" )EOF"}); @@ -125,7 +126,8 @@ name: "sidecar_socket" sidecar: "true" transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "sidecar")EOF", R"EOF( name: "http_socket" @@ -133,7 +135,8 @@ name: "http_socket" protocol: "http" transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "http" )EOF"}); @@ -161,7 +164,8 @@ name: "sidecar_http_socket" protocol: "http" transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "sidecar_http" )EOF", R"EOF( @@ -170,7 +174,8 @@ name: "sidecar_socket" sidecar: "true" transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "sidecar" )EOF"}); envoy::config::core::v3::Metadata metadata; @@ -188,7 +193,8 @@ name: "match_all" match: {} transport_socket: name: "foo" - config: + typed_config: + "@type": type.googleapis.com/envoy.config.core.v3.Node id: "match_all" )EOF"}); envoy::config::core::v3::Metadata metadata; diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index 336f40d86d91..a796379301d8 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -38,6 +38,7 @@ #include "test/mocks/upstream/health_checker.h" #include "test/mocks/upstream/priority_set.h" #include "test/test_common/registry.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -2397,7 +2398,9 @@ TEST_F(ClusterInfoImplTest, TypedExtensionProtocolOptionsForUnknownFilter) { } // This test case can't be converted for V3 API as it is specific for extension_protocol_options -TEST_F(ClusterInfoImplTest, OneofExtensionProtocolOptionsForUnknownFilter) { +TEST_F(ClusterInfoImplTest, + DEPRECATED_FEATURE_TEST(OneofExtensionProtocolOptionsForUnknownFilter)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( name: name connect_timeout: 0.25s diff --git a/test/config/utility.cc b/test/config/utility.cc index 171651259497..54a8247dfb18 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -109,7 +109,7 @@ std::string ConfigHelper::tcpProxyConfig() { filters: name: tcp typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp_stats cluster: cluster_0 )EOF"); @@ -128,7 +128,7 @@ std::string ConfigHelper::httpProxyConfig() { filters: name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: config_test delayed_close_timeout: nanos: 100 @@ -140,7 +140,7 @@ std::string ConfigHelper::httpProxyConfig() { filter: not_health_check_filter: {{}} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: {} route_config: virtual_hosts: @@ -167,7 +167,7 @@ std::string ConfigHelper::quicHttpProxyConfig() { filters: name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: config_test http_filters: name: envoy.filters.http.router @@ -177,7 +177,7 @@ std::string ConfigHelper::quicHttpProxyConfig() { filter: not_health_check_filter: {{}} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: {} route_config: virtual_hosts: @@ -199,7 +199,7 @@ std::string ConfigHelper::defaultBufferFilter() { return R"EOF( name: buffer typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.buffer.v2.Buffer + "@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer max_request_bytes : 5242880 )EOF"; } @@ -208,7 +208,7 @@ std::string ConfigHelper::smallBufferFilter() { return R"EOF( name: buffer typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.buffer.v2.Buffer + "@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer max_request_bytes : 1024 )EOF"; } @@ -217,7 +217,7 @@ std::string ConfigHelper::defaultHealthCheckFilter() { return R"EOF( name: health_check typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck pass_through_mode: false )EOF"; } @@ -226,7 +226,7 @@ std::string ConfigHelper::defaultSquashFilter() { return R"EOF( name: squash typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.squash.v2.Squash + "@type": type.googleapis.com/envoy.extensions.filters.http.squash.v3.Squash cluster: squash attachment_template: spec: @@ -259,6 +259,7 @@ std::string ConfigHelper::discoveredClustersBootstrap(const std::string& api_typ port_value: 0 dynamic_resources: cds_config: + resource_api_version: V3 api_config_source: api_type: {} grpc_services: @@ -288,7 +289,7 @@ std::string ConfigHelper::discoveredClustersBootstrap(const std::string& api_typ filters: name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: config_test http_filters: name: envoy.filters.http.router @@ -477,7 +478,7 @@ ConfigHelper::buildListener(const std::string& name, const std::string& route_co filters: - name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: {} codec_type: HTTP2 rds: @@ -617,6 +618,11 @@ void ConfigHelper::addRuntimeOverride(const std::string& key, const std::string& (*static_layer->mutable_fields())[std::string(key)] = ValueUtil::stringValue(std::string(value)); } +void ConfigHelper::enableDeprecatedV2Api() { + addRuntimeOverride("envoy.reloadable_features.enable_deprecated_v2_api", "true"); + addRuntimeOverride("envoy.features.enable_all_deprecated_features", "true"); +} + void ConfigHelper::setNewCodecs() { addRuntimeOverride("envoy.reloadable_features.new_codec_behavior", "true"); } diff --git a/test/config/utility.h b/test/config/utility.h index 51b1a8ef152b..0f7fc73b433e 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -242,6 +242,9 @@ class ConfigHelper { // Add this key value pair to the static runtime. void addRuntimeOverride(const std::string& key, const std::string& value); + // Enable deprecated v2 API resources via the runtime. + void enableDeprecatedV2Api(); + // Add filter_metadata to a cluster with the given name void addClusterFilterMetadata(absl::string_view metadata_yaml, absl::string_view cluster_name = "cluster_0"); diff --git a/test/config_test/BUILD b/test/config_test/BUILD index 792ba3d60320..6f17e5bcd689 100644 --- a/test/config_test/BUILD +++ b/test/config_test/BUILD @@ -64,6 +64,7 @@ envoy_cc_test( "//source/common/config:api_version_lib", "//test/test_common:environment_lib", "//test/test_common:logging_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v2:pkg_cc_proto", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", diff --git a/test/config_test/deprecated_configs_test.cc b/test/config_test/deprecated_configs_test.cc index dbb10707912e..c0c68b9dd1d5 100644 --- a/test/config_test/deprecated_configs_test.cc +++ b/test/config_test/deprecated_configs_test.cc @@ -6,6 +6,7 @@ #include "test/config_test/config_test.h" #include "test/test_common/environment.h" #include "test/test_common/logging.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -28,11 +29,14 @@ TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapTextProtoDepr const std::string filename = TestEnvironment::writeStringToFileForTest("proto.pb_text", bootstrap_text); - // Loading as previous version should work (after upgrade) - API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; - EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", - ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); - EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + { + TestDeprecatedV2Api _deprecated_v2_api; + // Loading as previous version should work (after upgrade) + API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; + EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", + ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); + EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + } // Loading as current version should fail API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v3_from_file; @@ -80,11 +84,14 @@ TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapBinaryProtoDe const std::string filename = TestEnvironment::writeStringToFileForTest("proto.pb", bootstrap_binary_str); - // Loading as previous version should work (after upgrade) - API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; - EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", - ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); - EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + { + TestDeprecatedV2Api _deprecated_v2_api; + // Loading as previous version should work (after upgrade) + API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; + EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", + ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); + EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + } // Loading as current version should fail API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v3_from_file; @@ -111,11 +118,14 @@ TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapBinaryProtoDe HasSubstr("Illegal use of hidden_envoy_deprecated_ V2 field " "'envoy.config.core.v3.Node.hidden_envoy_deprecated_build_version'")); - // Loading binary proto v3 with hidden-deprecated field with boosting will - // succeed as it cannot differentiate between v2 with the deprecated field and - // v3 with hidden_envoy_deprecated field - ConfigTest::loadVersionedBootstrapFile(filename_v3, proto_v3_from_file); - EXPECT_EQ("foo", proto_v3_from_file.node().hidden_envoy_deprecated_build_version()); + { + TestDeprecatedV2Api _deprecated_v2_api; + // Loading binary proto v3 with hidden-deprecated field with boosting will + // succeed as it cannot differentiate between v2 with the deprecated field and + // v3 with hidden_envoy_deprecated field + ConfigTest::loadVersionedBootstrapFile(filename_v3, proto_v3_from_file); + EXPECT_EQ("foo", proto_v3_from_file.node().hidden_envoy_deprecated_build_version()); + } } // A deprecated field can be used in previous version yaml and upgraded. @@ -131,11 +141,14 @@ TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapYamlDeprecate const std::string filename = TestEnvironment::writeStringToFileForTest( "proto.yaml", MessageUtil::getYamlStringFromMessage(bootstrap, false, false)); - // Loading as previous version should work (after upgrade) - API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; - EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", - ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); - EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + { + TestDeprecatedV2Api _deprecated_v2_api; + // Loading as previous version should work (after upgrade) + API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v2_from_file; + EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.api.v2.core.Node.build_version'", + ConfigTest::loadVersionedBootstrapFile(filename, proto_v2_from_file, 2)); + EXPECT_EQ("foo", proto_v2_from_file.node().hidden_envoy_deprecated_build_version()); + } // Loading as current version should fail API_NO_BOOST(envoy::config::bootstrap::v3::Bootstrap) proto_v3_from_file; @@ -171,6 +184,7 @@ TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapYamlDeprecate // A deprecated field can be used in previous version json and upgraded. TEST(DeprecatedConfigsTest, DEPRECATED_FEATURE_TEST(LoadV2BootstrapJsonDeprecatedField)) { + TestDeprecatedV2Api _deprecated_v2_api; API_NO_BOOST(envoy::config::bootstrap::v2::Bootstrap) bootstrap = TestUtility::parseYaml(R"EOF( node: diff --git a/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc b/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc index b635e7084fd3..a54dcaa7ec7d 100644 --- a/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc +++ b/test/extensions/access_loggers/grpc/http_grpc_access_log_integration_test.cc @@ -137,7 +137,6 @@ TEST_P(AccessLogIntegrationTest, BasicAccessLogFlow) { cluster: cluster_name locality: zone: zone_name - build_version: {} user_agent_name: "envoy" log_name: foo http_logs: @@ -156,8 +155,7 @@ TEST_P(AccessLogIntegrationTest, BasicAccessLogFlow) { value: 404 response_code_details: "route_not_found" response_headers_bytes: 54 -)EOF", - VersionInfo::version()))); +)EOF"))); BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( lookupPort("http"), "GET", "/notfound", "", downstream_protocol_, version_); @@ -210,7 +208,6 @@ TEST_P(AccessLogIntegrationTest, BasicAccessLogFlow) { cluster: cluster_name locality: zone: zone_name - build_version: {} user_agent_name: "envoy" log_name: foo http_logs: @@ -229,8 +226,7 @@ TEST_P(AccessLogIntegrationTest, BasicAccessLogFlow) { value: 404 response_code_details: "route_not_found" response_headers_bytes: 54 -)EOF", - VersionInfo::version()))); +)EOF"))); cleanup(); } diff --git a/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc b/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc index 852f9867c7c2..203b7eb98068 100644 --- a/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc +++ b/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc @@ -147,15 +147,14 @@ TEST_P(TcpGrpcAccessLogIntegrationTest, BasicAccessLogFlow) { ASSERT_TRUE(waitForAccessLogConnection()); ASSERT_TRUE(waitForAccessLogStream()); - ASSERT_TRUE(waitForAccessLogRequest( - fmt::format(R"EOF( + ASSERT_TRUE( + waitForAccessLogRequest(fmt::format(R"EOF( identifier: node: id: node_name cluster: cluster_name locality: zone: zone_name - build_version: {} user_agent_name: "envoy" log_name: foo tcp_logs: @@ -181,11 +180,11 @@ TEST_P(TcpGrpcAccessLogIntegrationTest, BasicAccessLogFlow) { received_bytes: 3 sent_bytes: 5 )EOF", - VersionInfo::version(), Network::Test::getLoopbackAddressString(ipVersion()), - Network::Test::getLoopbackAddressString(ipVersion()), - Network::Test::getLoopbackAddressString(ipVersion()), - Network::Test::getLoopbackAddressString(ipVersion()), - Network::Test::getLoopbackAddressString(ipVersion())))); + Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion())))); cleanup(); } diff --git a/test/extensions/clusters/aggregate/cluster_integration_test.cc b/test/extensions/clusters/aggregate/cluster_integration_test.cc index b99d0a3974d5..623bc60c8149 100644 --- a/test/extensions/clusters/aggregate/cluster_integration_test.cc +++ b/test/extensions/clusters/aggregate/cluster_integration_test.cc @@ -38,6 +38,7 @@ const std::string& config() { port_value: 0 dynamic_resources: cds_config: + resource_api_version: V3 api_config_source: api_type: GRPC grpc_services: @@ -64,7 +65,7 @@ const std::string& config() { cluster_type: name: envoy.clusters.aggregate typed_config: - "@type": type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig clusters: - cluster_1 - cluster_2 @@ -78,7 +79,7 @@ const std::string& config() { filters: name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: config_test http_filters: name: envoy.filters.http.router diff --git a/test/extensions/clusters/aggregate/cluster_test.cc b/test/extensions/clusters/aggregate/cluster_test.cc index e98d79524a9f..c45de45e3778 100644 --- a/test/extensions/clusters/aggregate/cluster_test.cc +++ b/test/extensions/clusters/aggregate/cluster_test.cc @@ -155,7 +155,7 @@ class AggregateClusterTest : public testing::Test { cluster_type: name: envoy.clusters.aggregate typed_config: - "@type": type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig clusters: - primary - secondary diff --git a/test/extensions/clusters/aggregate/cluster_update_test.cc b/test/extensions/clusters/aggregate/cluster_update_test.cc index 8755ba8a29ef..b706e38dfbfc 100644 --- a/test/extensions/clusters/aggregate/cluster_update_test.cc +++ b/test/extensions/clusters/aggregate/cluster_update_test.cc @@ -67,7 +67,7 @@ class AggregateClusterUpdateTest : public testing::Test { cluster_type: name: envoy.clusters.aggregate typed_config: - "@type": type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig clusters: - primary - secondary @@ -253,7 +253,7 @@ TEST_F(AggregateClusterUpdateTest, InitializeAggregateClusterAfterOtherClusters) cluster_type: name: envoy.clusters.aggregate typed_config: - "@type": type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig clusters: - primary - secondary diff --git a/test/extensions/clusters/dynamic_forward_proxy/BUILD b/test/extensions/clusters/dynamic_forward_proxy/BUILD index 9572e4a947d7..116fe24dfc52 100644 --- a/test/extensions/clusters/dynamic_forward_proxy/BUILD +++ b/test/extensions/clusters/dynamic_forward_proxy/BUILD @@ -29,6 +29,7 @@ envoy_extension_cc_test( "//test/mocks/upstream:load_balancer_context_mock", "//test/mocks/upstream:load_balancer_mocks", "//test/test_common:environment_lib", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/clusters/dynamic_forward_proxy/v3:pkg_cc_proto", ], diff --git a/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc b/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc index 6908428c3c10..4bce356d487a 100644 --- a/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc +++ b/test/extensions/clusters/dynamic_forward_proxy/cluster_test.cc @@ -16,6 +16,7 @@ #include "test/mocks/upstream/load_balancer.h" #include "test/mocks/upstream/load_balancer_context.h" #include "test/test_common/environment.h" +#include "test/test_common/test_runtime.h" using testing::AtLeast; using testing::DoAll; @@ -137,7 +138,7 @@ connect_timeout: 0.25s cluster_type: name: dynamic_forward_proxy typed_config: - "@type": type.googleapis.com/envoy.config.cluster.dynamic_forward_proxy.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig dns_cache_config: name: foo dns_lookup_family: AUTO @@ -232,6 +233,7 @@ class ClusterFactoryTest : public testing::Test { // Verify that using 'sni' causes a failure. TEST_F(ClusterFactoryTest, DEPRECATED_FEATURE_TEST(InvalidSNI)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml_config = TestEnvironment::substitute(R"EOF( name: name connect_timeout: 0.25s @@ -256,6 +258,7 @@ connect_timeout: 0.25s // Verify that using 'verify_subject_alt_name' causes a failure. TEST_F(ClusterFactoryTest, DEPRECATED_FEATURE_TEST(InvalidVerifySubjectAltName)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml_config = TestEnvironment::substitute(R"EOF( name: name connect_timeout: 0.25s @@ -285,7 +288,7 @@ connect_timeout: 0.25s cluster_type: name: dynamic_forward_proxy typed_config: - "@type": type.googleapis.com/envoy.config.cluster.dynamic_forward_proxy.v2alpha.ClusterConfig + "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig dns_cache_config: name: foo upstream_http_protocol_options: {} diff --git a/test/extensions/clusters/redis/redis_cluster_integration_test.cc b/test/extensions/clusters/redis/redis_cluster_integration_test.cc index 53f4d6454ec9..a45187a2d19a 100644 --- a/test/extensions/clusters/redis/redis_cluster_integration_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_integration_test.cc @@ -36,7 +36,7 @@ const std::string& listenerConfig() { filters: name: redis typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: redis_stats prefix_routes: catch_all_route: @@ -118,7 +118,7 @@ const std::string& testConfigWithAuth() { CONSTRUCT_ON_FIRST_USE(std::string, testConfig() + R"EOF( typed_extension_protocol_options: envoy.filters.network.redis_proxy: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProtocolOptions + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProtocolOptions auth_password: { inline_string: somepassword } )EOF"); } diff --git a/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc b/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc index d6bd8e2b698d..a180f9ef16a8 100644 --- a/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc +++ b/test/extensions/common/aws/aws_metadata_fetcher_integration_test.cc @@ -23,12 +23,12 @@ class AwsMetadataIntegrationTestBase : public ::testing::Test, public BaseIntegr filters: name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: metadata_test http_filters: - name: fault typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault delay: fixed_delay: seconds: {} diff --git a/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.h b/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.h index 06d8a457e264..e5460e289d16 100644 --- a/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.h +++ b/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_integration_test.h @@ -10,7 +10,7 @@ const std::string ADAPTIVE_CONCURRENCY_CONFIG = R"EOF( name: envoy.filters.http.adaptive_concurrency typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.adaptive_concurrency.v2alpha.AdaptiveConcurrency + "@type": type.googleapis.com/envoy.extensions.filters.http.adaptive_concurrency.v3.AdaptiveConcurrency gradient_controller_config: sample_aggregate_percentile: value: 50 diff --git a/test/extensions/filters/http/csrf/csrf_filter_integration_test.cc b/test/extensions/filters/http/csrf/csrf_filter_integration_test.cc index 6500bf77b61a..e3890805cdab 100644 --- a/test/extensions/filters/http/csrf/csrf_filter_integration_test.cc +++ b/test/extensions/filters/http/csrf/csrf_filter_integration_test.cc @@ -5,7 +5,7 @@ namespace { const std::string CSRF_ENABLED_CONFIG = R"EOF( name: csrf typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.csrf.v2.CsrfPolicy + "@type": type.googleapis.com/envoy.extensions.filters.http.csrf.v3.CsrfPolicy filter_enabled: default_value: numerator: 100 @@ -19,7 +19,7 @@ name: csrf const std::string CSRF_FILTER_ENABLED_CONFIG = R"EOF( name: csrf typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.csrf.v2.CsrfPolicy + "@type": type.googleapis.com/envoy.extensions.filters.http.csrf.v3.CsrfPolicy filter_enabled: default_value: numerator: 100 @@ -29,7 +29,7 @@ name: csrf const std::string CSRF_SHADOW_ENABLED_CONFIG = R"EOF( name: csrf typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.csrf.v2.CsrfPolicy + "@type": type.googleapis.com/envoy.extensions.filters.http.csrf.v3.CsrfPolicy filter_enabled: default_value: numerator: 0 @@ -43,7 +43,7 @@ name: csrf const std::string CSRF_DISABLED_CONFIG = R"EOF( name: csrf typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.csrf.v2.CsrfPolicy + "@type": type.googleapis.com/envoy.extensions.filters.http.csrf.v3.CsrfPolicy filter_enabled: default_value: numerator: 0 diff --git a/test/extensions/filters/http/fault/fault_filter_integration_test.cc b/test/extensions/filters/http/fault/fault_filter_integration_test.cc index 957667fad1ae..0a1a013b6bfc 100644 --- a/test/extensions/filters/http/fault/fault_filter_integration_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_integration_test.cc @@ -20,7 +20,7 @@ class FaultIntegrationTest : public Event::TestUsingSimulatedTime, R"EOF( name: fault typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault response_rate_limit: fixed_limit: limit_kbps: 1 @@ -32,7 +32,7 @@ name: fault R"EOF( name: fault typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault abort: header_abort: {} percentage: @@ -72,7 +72,7 @@ TEST_P(FaultIntegrationTestAllProtocols, NoFault) { R"EOF( name: fault typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault + "@type": type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault )EOF"; initializeFilter(filter_config); diff --git a/test/extensions/filters/http/fault/fault_filter_test.cc b/test/extensions/filters/http/fault/fault_filter_test.cc index e5ca23e2094d..f22c9ed88eaf 100644 --- a/test/extensions/filters/http/fault/fault_filter_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_test.cc @@ -46,7 +46,6 @@ class FaultFilterTest : public testing::Test { public: const std::string fixed_delay_and_abort_nodes_yaml = R"EOF( delay: - type: fixed percentage: numerator: 100 denominator: HUNDRED @@ -62,7 +61,6 @@ class FaultFilterTest : public testing::Test { const std::string fixed_delay_only_yaml = R"EOF( delay: - type: fixed percentage: numerator: 100 denominator: HUNDRED @@ -79,7 +77,6 @@ class FaultFilterTest : public testing::Test { const std::string fixed_delay_and_abort_yaml = R"EOF( delay: - type: fixed percentage: numerator: 100 denominator: HUNDRED @@ -100,7 +97,6 @@ class FaultFilterTest : public testing::Test { const std::string fixed_delay_and_abort_match_headers_yaml = R"EOF( delay: - type: fixed percentage: numerator: 100 denominator: HUNDRED @@ -118,7 +114,6 @@ class FaultFilterTest : public testing::Test { const std::string delay_with_upstream_cluster_yaml = R"EOF( delay: - type: fixed percentage: numerator: 100 denominator: HUNDRED @@ -208,7 +203,6 @@ TEST(FaultFilterBadConfigTest, BadDelayType) { TEST(FaultFilterBadConfigTest, BadDelayDuration) { const std::string yaml = R"EOF( delay: - type: fixed percentage: numerator: 50 denominator: HUNDRED @@ -221,7 +215,6 @@ TEST(FaultFilterBadConfigTest, BadDelayDuration) { TEST(FaultFilterBadConfigTest, MissingDelayDuration) { const std::string yaml = R"EOF( delay: - type: fixed percentage: numerator: 50 denominator: HUNDRED diff --git a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc index 473d9bfa655f..a792f8cea8ec 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc @@ -35,7 +35,7 @@ class GrpcJsonTranscoderIntegrationTest R"EOF( name: grpc_json_transcoder typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor : "{}" services : "bookstore.Bookstore" )EOF"; @@ -462,7 +462,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UnaryGetError1) { R"EOF( name: grpc_json_transcoder typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor : "{}" services : "bookstore.Bookstore" ignore_unknown_query_parameters : true @@ -486,7 +486,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UnaryErrorConvertedToJson) { R"EOF( name: grpc_json_transcoder typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor: "{}" services: "bookstore.Bookstore" convert_grpc_status: true @@ -511,7 +511,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, UnaryErrorInTrailerConvertedToJson) { R"EOF( name: grpc_json_transcoder typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor: "{}" services: "bookstore.Bookstore" convert_grpc_status: true @@ -536,7 +536,7 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, StreamingErrorConvertedToJson) { R"EOF( name: grpc_json_transcoder typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder + "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder proto_descriptor: "{}" services: "bookstore.Bookstore" convert_grpc_status: true diff --git a/test/extensions/filters/http/gzip/gzip_filter_integration_test.cc b/test/extensions/filters/http/gzip/gzip_filter_integration_test.cc index a7a0d8d00b70..bd0edc6d1d56 100644 --- a/test/extensions/filters/http/gzip/gzip_filter_integration_test.cc +++ b/test/extensions/filters/http/gzip/gzip_filter_integration_test.cc @@ -23,6 +23,7 @@ class GzipIntegrationTest : public testing::TestWithParamdecodeHeaders(headers, true)); EXPECT_FALSE(headers.has("accept-encoding")); } diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index 3b1cf0812b09..176ebaa445f8 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -140,6 +140,7 @@ envoy_extension_cc_test( ":mock_lib", ":test_common_lib", "//source/extensions/filters/http/jwt_authn:matchers_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/jwt_authn/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/jwt_authn/matcher_test.cc b/test/extensions/filters/http/jwt_authn/matcher_test.cc index e64245b6cfdb..78dfabecd733 100644 --- a/test/extensions/filters/http/jwt_authn/matcher_test.cc +++ b/test/extensions/filters/http/jwt_authn/matcher_test.cc @@ -6,6 +6,7 @@ #include "test/extensions/filters/http/jwt_authn/mock.h" #include "test/extensions/filters/http/jwt_authn/test_common.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" using envoy::extensions::filters::http::jwt_authn::v3::RequirementRule; @@ -40,6 +41,7 @@ TEST_F(MatcherTest, TestMatchPrefix) { } TEST_F(MatcherTest, TestMatchRegex) { + TestDeprecatedV2Api _deprecated_v2_api; const char config[] = R"(match: regex: "/[^c][au]t")"; RequirementRule rule; @@ -105,7 +107,8 @@ TEST_F(MatcherTest, TestMatchQuery) { prefix: "/" query_parameters: - name: foo - value: bar)"; + string_match: + exact: bar)"; RequirementRule rule; TestUtility::loadFromYaml(config, rule); MatcherConstPtr matcher = Matcher::create(rule); @@ -146,7 +149,8 @@ TEST_F(MatcherTest, TestMatchPathAndHeader) { path: "/boo" query_parameters: - name: foo - value: bar)"; + string_match: + exact: bar)"; RequirementRule rule; TestUtility::loadFromYaml(config, rule); MatcherConstPtr matcher = Matcher::create(rule); diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index 92f8e7e141a6..60ca43871e71 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -115,6 +115,8 @@ class LuaIntegrationTest : public testing::TestWithParamset_route_config_name(route_config_name); + hcm.mutable_rds()->mutable_config_source()->set_resource_api_version( + envoy::config::core::v3::ApiVersion::V3); envoy::config::core::v3::ApiConfigSource* rds_api_config_source = hcm.mutable_rds()->mutable_config_source()->mutable_api_config_source(); rds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); @@ -215,7 +217,7 @@ TEST_P(LuaIntegrationTest, CallMetadataDuringLocalReply) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_response(response_handle) local metadata = response_handle:metadata():get("foo.bar") @@ -240,7 +242,7 @@ TEST_P(LuaIntegrationTest, RequestAndResponse) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) request_handle:logTrace("log test") @@ -379,7 +381,7 @@ TEST_P(LuaIntegrationTest, UpstreamHttpCall) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) local headers, body = request_handle:httpCall( @@ -437,7 +439,7 @@ TEST_P(LuaIntegrationTest, UpstreamCallAndRespond) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) local headers, body = request_handle:httpCall( @@ -487,7 +489,7 @@ TEST_P(LuaIntegrationTest, UpstreamAsyncHttpCall) { R"EOF( name: envoy.filters.http.lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) local headers, body = request_handle:httpCall( @@ -537,7 +539,7 @@ TEST_P(LuaIntegrationTest, ChangeRoute) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) request_handle:headers():remove(":path") @@ -571,7 +573,7 @@ TEST_P(LuaIntegrationTest, SurviveMultipleCalls) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) request_handle:streamInfo():dynamicMetadata() @@ -607,7 +609,7 @@ TEST_P(LuaIntegrationTest, SignatureVerification) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function string.fromhex(str) return (str:gsub('..', function (cc) @@ -956,7 +958,7 @@ TEST_P(LuaIntegrationTest, RewriteResponseBuffer) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_response(response_handle) local content_length = response_handle:body():setBytes("ok") @@ -975,7 +977,7 @@ TEST_P(LuaIntegrationTest, RewriteChunkedBody) { R"EOF( name: lua typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.lua.v2.Lua + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_response(response_handle) response_handle:headers():replace("content-length", 2) diff --git a/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc b/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc index 89b73bbef081..6c2f397dbd20 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc @@ -13,7 +13,7 @@ namespace { const std::string RBAC_CONFIG = R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: policies: foo: @@ -26,7 +26,7 @@ name: rbac const std::string RBAC_CONFIG_WITH_DENY_ACTION = R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: action: DENY policies: @@ -40,7 +40,7 @@ name: rbac const std::string RBAC_CONFIG_WITH_PREFIX_MATCH = R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: policies: foo: @@ -53,7 +53,7 @@ name: rbac const std::string RBAC_CONFIG_WITH_PATH_EXACT_MATCH = R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: policies: foo: @@ -67,7 +67,7 @@ name: rbac const std::string RBAC_CONFIG_WITH_PATH_IGNORE_CASE_MATCH = R"EOF( name: rbac typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.rbac.v2.RBAC + "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: policies: foo: diff --git a/test/extensions/filters/http/tap/tap_filter_integration_test.cc b/test/extensions/filters/http/tap/tap_filter_integration_test.cc index ee6bf2f75484..6edcaa3f254e 100644 --- a/test/extensions/filters/http/tap/tap_filter_integration_test.cc +++ b/test/extensions/filters/http/tap/tap_filter_integration_test.cc @@ -140,7 +140,7 @@ class TapIntegrationTest : public testing::TestWithParammergeValues( {{"envoy.deprecated_features:envoy.config.filter.network.redis_proxy.v2.RedisProxy.cluster", "true"}, @@ -97,7 +97,7 @@ stat_prefix: foo TEST(RedisProxyFilterConfigFactoryTest, DEPRECATED_FEATURE_TEST(RedisProxyCorrectProtoLegacyCatchAllCluster)) { - TestScopedRuntime scoped_runtime; + TestDeprecatedV2Api _deprecated_v2_api; Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.deprecated_features:envoy.config.filter.network.redis_proxy.v2.RedisProxy." "PrefixRoutes.catch_all_cluster", diff --git a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc index d7345dbac788..821f2497d7f5 100644 --- a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc +++ b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc @@ -151,7 +151,7 @@ const std::string CONFIG_WITH_ROUTES_BASE = fmt::format(R"EOF( filters: name: redis typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: redis_stats settings: op_timeout: 5s @@ -210,7 +210,7 @@ const std::string CONFIG_WITH_ROUTES_AND_AUTH_PASSWORDS = fmt::format(R"EOF( type: STATIC typed_extension_protocol_options: envoy.filters.network.redis_proxy: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProtocolOptions + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProtocolOptions auth_password: {{ inline_string: cluster_0_password }} lb_policy: RANDOM load_assignment: @@ -227,7 +227,7 @@ const std::string CONFIG_WITH_ROUTES_AND_AUTH_PASSWORDS = fmt::format(R"EOF( lb_policy: RANDOM typed_extension_protocol_options: envoy.filters.network.redis_proxy: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProtocolOptions + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProtocolOptions auth_password: {{ inline_string: cluster_1_password }} load_assignment: cluster_name: cluster_1 @@ -242,7 +242,7 @@ const std::string CONFIG_WITH_ROUTES_AND_AUTH_PASSWORDS = fmt::format(R"EOF( type: STATIC typed_extension_protocol_options: envoy.filters.network.redis_proxy: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProtocolOptions + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProtocolOptions auth_password: {{ inline_string: cluster_2_password }} lb_policy: RANDOM load_assignment: @@ -264,7 +264,7 @@ const std::string CONFIG_WITH_ROUTES_AND_AUTH_PASSWORDS = fmt::format(R"EOF( filters: name: redis typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy stat_prefix: redis_stats settings: op_timeout: 5s diff --git a/test/extensions/filters/network/tcp_proxy/BUILD b/test/extensions/filters/network/tcp_proxy/BUILD index ad332adc27ac..e8df13157fa0 100644 --- a/test/extensions/filters/network/tcp_proxy/BUILD +++ b/test/extensions/filters/network/tcp_proxy/BUILD @@ -18,6 +18,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/network/tcp_proxy:config", "//test/mocks/server:factory_context_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/network/tcp_proxy/config_test.cc b/test/extensions/filters/network/tcp_proxy/config_test.cc index ff74cf1cb0f8..5dc16e512be8 100644 --- a/test/extensions/filters/network/tcp_proxy/config_test.cc +++ b/test/extensions/filters/network/tcp_proxy/config_test.cc @@ -6,6 +6,7 @@ #include "extensions/filters/network/tcp_proxy/config.h" #include "test/mocks/server/factory_context.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -71,6 +72,7 @@ INSTANTIATE_TEST_SUITE_P(IpList, RouteIpListConfigTest, ],)EOF")); TEST_P(RouteIpListConfigTest, DEPRECATED_FEATURE_TEST(TcpProxy)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string json_string = R"EOF( { "stat_prefix": "my_stat_prefix", diff --git a/test/extensions/filters/network/thrift_proxy/integration_test.cc b/test/extensions/filters/network/thrift_proxy/integration_test.cc index 0d520bb20da4..2766bf61d540 100644 --- a/test/extensions/filters/network/thrift_proxy/integration_test.cc +++ b/test/extensions/filters/network/thrift_proxy/integration_test.cc @@ -28,7 +28,7 @@ class ThriftConnManagerIntegrationTest filters: - name: thrift typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.thrift_proxy.v2alpha1.ThriftProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.thrift_proxy.v3.ThriftProxy stat_prefix: thrift_stats route_config: name: "routes" diff --git a/test/extensions/filters/network/thrift_proxy/translation_integration_test.cc b/test/extensions/filters/network/thrift_proxy/translation_integration_test.cc index 7b07ad7ab623..0c50426b0fef 100644 --- a/test/extensions/filters/network/thrift_proxy/translation_integration_test.cc +++ b/test/extensions/filters/network/thrift_proxy/translation_integration_test.cc @@ -29,7 +29,7 @@ class ThriftTranslationIntegrationTest filters: - name: thrift typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.thrift_proxy.v2alpha1.ThriftProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.thrift_proxy.v3.ThriftProxy stat_prefix: thrift_stats route_config: name: "routes" diff --git a/test/extensions/grpc_credentials/aws_iam/aws_iam_grpc_credentials_test.cc b/test/extensions/grpc_credentials/aws_iam/aws_iam_grpc_credentials_test.cc index 6001a7bfdacd..6a5852bb9a1a 100644 --- a/test/extensions/grpc_credentials/aws_iam/aws_iam_grpc_credentials_test.cc +++ b/test/extensions/grpc_credentials/aws_iam/aws_iam_grpc_credentials_test.cc @@ -69,14 +69,14 @@ class GrpcAwsIamClientIntegrationTest : public GrpcSslClientIntegrationTest { ABSL_FALLTHROUGH_INTENDED; case RegionLocation::NotProvided: config_yaml = fmt::format(R"EOF( - "@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.AwsIamConfig + "@type": type.googleapis.com/envoy.config.grpc_credential.v3.AwsIamConfig service_name: {} )EOF", service_name_); break; case RegionLocation::InConfig: config_yaml = fmt::format(R"EOF( - "@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.AwsIamConfig + "@type": type.googleapis.com/envoy.config.grpc_credential.v3.AwsIamConfig service_name: {} region: {} )EOF", diff --git a/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc b/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc index 1a2c90929e9e..416059bdb7e6 100644 --- a/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc +++ b/test/extensions/grpc_credentials/file_based_metadata/integration_test.cc @@ -41,7 +41,7 @@ class GrpcFileBasedMetadataClientIntegrationTest : public GrpcSslClientIntegrati TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcacert.pem")); if (!header_value_1_.empty()) { const std::string yaml1 = fmt::format(R"EOF( -"@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.FileBasedMetadataConfig +"@type": type.googleapis.com/envoy.config.grpc_credential.v3.FileBasedMetadataConfig secret_data: inline_string: {} header_key: {} @@ -56,7 +56,7 @@ header_prefix: {} if (!header_value_2_.empty()) { // uses default key/prefix const std::string yaml2 = fmt::format(R"EOF( -"@type": type.googleapis.com/envoy.config.grpc_credential.v2alpha.FileBasedMetadataConfig +"@type": type.googleapis.com/envoy.config.grpc_credential.v3.FileBasedMetadataConfig secret_data: inline_string: {} )EOF", diff --git a/test/extensions/health_checkers/redis/BUILD b/test/extensions/health_checkers/redis/BUILD index 5e91cd4f9cd8..6a134aebe2ba 100644 --- a/test/extensions/health_checkers/redis/BUILD +++ b/test/extensions/health_checkers/redis/BUILD @@ -30,6 +30,7 @@ envoy_extension_cc_test( "//test/mocks/upstream:host_mocks", "//test/mocks/upstream:host_set_mocks", "//test/mocks/upstream:priority_set_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/network/redis_proxy/v3:pkg_cc_proto", ], @@ -49,6 +50,7 @@ envoy_extension_cc_test( "//test/mocks/server:health_checker_factory_context_mocks", "//test/mocks/upstream:health_checker_mocks", "//test/mocks/upstream:priority_set_mocks", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/extensions/filters/network/redis_proxy/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/health_checkers/redis/config_test.cc b/test/extensions/health_checkers/redis/config_test.cc index b54620d614ab..5f4caf4a4f66 100644 --- a/test/extensions/health_checkers/redis/config_test.cc +++ b/test/extensions/health_checkers/redis/config_test.cc @@ -12,6 +12,7 @@ #include "test/mocks/server/health_checker_factory_context.h" #include "test/mocks/upstream/health_checker.h" #include "test/mocks/upstream/priority_set.h" +#include "test/test_common/test_runtime.h" namespace Envoy { namespace Extensions { @@ -22,6 +23,7 @@ namespace { using CustomRedisHealthChecker = Extensions::HealthCheckers::RedisHealthChecker::RedisHealthChecker; TEST(HealthCheckerFactoryTest, DEPRECATED_FEATURE_TEST(CreateRedisDeprecated)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( timeout: 1s interval: 1s @@ -71,6 +73,7 @@ TEST(HealthCheckerFactoryTest, CreateRedis) { } TEST(HealthCheckerFactoryTest, DEPRECATED_FEATURE_TEST(CreateRedisWithoutKeyDeprecated)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = R"EOF( timeout: 1s interval: 1s diff --git a/test/extensions/health_checkers/redis/redis_test.cc b/test/extensions/health_checkers/redis/redis_test.cc index a751b8be6428..47e37851b7fe 100644 --- a/test/extensions/health_checkers/redis/redis_test.cc +++ b/test/extensions/health_checkers/redis/redis_test.cc @@ -18,6 +18,7 @@ #include "test/mocks/upstream/host.h" #include "test/mocks/upstream/host_set.h" #include "test/mocks/upstream/priority_set.h" +#include "test/test_common/test_runtime.h" using testing::_; using testing::DoAll; @@ -573,6 +574,7 @@ TEST_F(RedisHealthCheckerTest, LogInitialFailure) { } TEST_F(RedisHealthCheckerTest, DEPRECATED_FEATURE_TEST(ExistsDeprecated)) { + TestDeprecatedV2Api _deprecated_v2_api; InSequence s; setupExistsHealthcheckDeprecated(false); diff --git a/test/extensions/tracers/datadog/config_test.cc b/test/extensions/tracers/datadog/config_test.cc index 52a44719367c..8c84b6c3f475 100644 --- a/test/extensions/tracers/datadog/config_test.cc +++ b/test/extensions/tracers/datadog/config_test.cc @@ -33,7 +33,7 @@ TEST(DatadogTracerConfigTest, DatadogHttpTracer) { http: name: datadog typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.DatadogConfig + "@type": type.googleapis.com/envoy.config.trace.v3.DatadogConfig collector_cluster: fake_cluster service_name: fake_file )EOF"; diff --git a/test/extensions/tracers/dynamic_ot/config_test.cc b/test/extensions/tracers/dynamic_ot/config_test.cc index a655b23e5f6c..e16cd0a05687 100644 --- a/test/extensions/tracers/dynamic_ot/config_test.cc +++ b/test/extensions/tracers/dynamic_ot/config_test.cc @@ -35,7 +35,8 @@ TEST(DynamicOtTracerConfigTest, DynamicOpentracingHttpTracer) { R"EOF( http: name: envoy.tracers.dynamic_ot - config: + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.DynamicOtConfig library: %s config: output_file: fake_file diff --git a/test/extensions/tracers/lightstep/config_test.cc b/test/extensions/tracers/lightstep/config_test.cc index e56ff7b0c507..711e8455c7da 100644 --- a/test/extensions/tracers/lightstep/config_test.cc +++ b/test/extensions/tracers/lightstep/config_test.cc @@ -33,7 +33,7 @@ TEST(LightstepTracerConfigTest, LightstepHttpTracer) { http: name: lightstep typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.LightstepConfig + "@type": type.googleapis.com/envoy.config.trace.v3.LightstepConfig collector_cluster: fake_cluster access_token_file: fake_file )EOF"; diff --git a/test/extensions/tracers/opencensus/config_test.cc b/test/extensions/tracers/opencensus/config_test.cc index 227ed0d353c8..9e2698fee03d 100644 --- a/test/extensions/tracers/opencensus/config_test.cc +++ b/test/extensions/tracers/opencensus/config_test.cc @@ -26,7 +26,7 @@ TEST(OpenCensusTracerConfigTest, InvalidStackdriverConfiguration) { http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig stackdriver_exporter_enabled: true stackdriver_grpc_service: envoy_grpc: @@ -49,7 +49,7 @@ TEST(OpenCensusTracerConfigTest, InvalidOcagentConfiguration) { http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig ocagent_exporter_enabled: true ocagent_grpc_service: envoy_grpc: @@ -87,7 +87,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerWithTypedConfig) { http: name: opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig trace_config: rate_limiting_sampler: qps: 123 @@ -129,7 +129,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerGrpc) { http: name: opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig trace_config: rate_limiting_sampler: qps: 123 @@ -177,7 +177,7 @@ TEST(OpenCensusTracerConfigTest, ShouldCreateAtMostOneOpenCensusTracer) { http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig trace_config: rate_limiting_sampler: qps: 123 @@ -230,7 +230,7 @@ TEST(OpenCensusTracerConfigTest, ShouldNotCacheInvalidConfiguration) { http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig ocagent_exporter_enabled: true ocagent_grpc_service: envoy_grpc: @@ -248,7 +248,7 @@ TEST(OpenCensusTracerConfigTest, ShouldNotCacheInvalidConfiguration) { http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig ocagent_exporter_enabled: true ocagent_grpc_service: google_grpc: @@ -279,7 +279,7 @@ TEST(OpenCensusTracerConfigTest, ShouldRejectSubsequentCreateAttemptsWithDiffere http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig trace_config: rate_limiting_sampler: qps: 123 @@ -296,7 +296,7 @@ TEST(OpenCensusTracerConfigTest, ShouldRejectSubsequentCreateAttemptsWithDiffere http: name: envoy.tracers.opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig trace_config: rate_limiting_sampler: qps: 321 @@ -317,7 +317,7 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerStackdriverGrpc) { http: name: opencensus typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.OpenCensusConfig + "@type": type.googleapis.com/envoy.config.trace.v3.OpenCensusConfig stackdriver_exporter_enabled: true stackdriver_grpc_service: google_grpc: diff --git a/test/extensions/tracers/xray/config_test.cc b/test/extensions/tracers/xray/config_test.cc index 9bf6a24c65bb..7008ef0a0c54 100644 --- a/test/extensions/tracers/xray/config_test.cc +++ b/test/extensions/tracers/xray/config_test.cc @@ -28,7 +28,7 @@ TEST(XRayTracerConfigTest, XRayHttpTracerWithTypedConfig) { http: name: xray typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2alpha.XRayConfig + "@type": type.googleapis.com/envoy.config.trace.v3.XRayConfig daemon_endpoint: protocol: UDP address: 127.0.0.1 @@ -62,7 +62,7 @@ TEST(XRayTracerConfigTest, XRayHttpTracerWithInvalidFileName) { http: name: xray typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2alpha.XRayConfig + "@type": type.googleapis.com/envoy.config.trace.v3.XRayConfig daemon_endpoint: protocol: UDP address: 127.0.0.1 @@ -88,7 +88,7 @@ TEST(XRayTracerConfigTest, ProtocolNotUDPThrows) { http: name: xray typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2alpha.XRayConfig + "@type": type.googleapis.com/envoy.config.trace.v3.XRayConfig daemon_endpoint: protocol: TCP address: 127.0.0.1 @@ -113,7 +113,7 @@ TEST(XRayTracerConfigTest, UsingNamedPortThrows) { http: name: xray typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2alpha.XRayConfig + "@type": type.googleapis.com/envoy.config.trace.v3.XRayConfig daemon_endpoint: protocol: UDP address: 127.0.0.1 diff --git a/test/extensions/tracers/zipkin/config_test.cc b/test/extensions/tracers/zipkin/config_test.cc index 0f62b8f7fd7f..a862017d9e74 100644 --- a/test/extensions/tracers/zipkin/config_test.cc +++ b/test/extensions/tracers/zipkin/config_test.cc @@ -30,7 +30,7 @@ TEST(ZipkinTracerConfigTest, ZipkinHttpTracer) { http: name: zipkin typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.ZipkinConfig + "@type": type.googleapis.com/envoy.config.trace.v3.ZipkinConfig collector_cluster: fake_cluster collector_endpoint: /api/v1/spans collector_endpoint_version: HTTP_JSON @@ -57,7 +57,7 @@ TEST(ZipkinTracerConfigTest, ZipkinHttpTracerWithTypedConfig) { http: name: zipkin typed_config: - "@type": type.googleapis.com/envoy.config.trace.v2.ZipkinConfig + "@type": type.googleapis.com/envoy.config.trace.v3.ZipkinConfig collector_cluster: fake_cluster collector_endpoint: /api/v2/spans collector_endpoint_version: HTTP_PROTO diff --git a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc index 5e605999b671..84f335c3c211 100644 --- a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc +++ b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc @@ -205,14 +205,6 @@ TEST_F(ZipkinDriverTest, AllowCollectorClusterToBeAddedViaApi) { setup(zipkin_config, true); } -TEST_F(ZipkinDriverTest, FlushSeveralSpans) { - expectValidFlushSeveralSpans("HTTP_JSON_V1", "application/json"); -} - -TEST_F(ZipkinDriverTest, FlushSeveralSpansHttpJsonV1) { - expectValidFlushSeveralSpans("HTTP_JSON_V1", "application/json"); -} - TEST_F(ZipkinDriverTest, FlushSeveralSpansHttpJson) { expectValidFlushSeveralSpans("HTTP_JSON", "application/json"); } @@ -222,7 +214,7 @@ TEST_F(ZipkinDriverTest, FlushSeveralSpansHttpProto) { } TEST_F(ZipkinDriverTest, FlushOneSpanReportFailure) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); Http::MockAsyncClientRequest request(&cm_.async_client_); Http::AsyncClient::Callbacks* callback; @@ -268,7 +260,7 @@ TEST_F(ZipkinDriverTest, SkipReportIfCollectorClusterHasBeenRemoved) { EXPECT_CALL(cm_, addThreadLocalClusterUpdateCallbacks_(_)) .WillOnce(DoAll(SaveArgAddress(&cluster_update_callbacks), Return(nullptr))); - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.zipkin.min_flush_spans", 5)) .WillRepeatedly(Return(1)); @@ -386,7 +378,7 @@ TEST_F(ZipkinDriverTest, SkipReportIfCollectorClusterHasBeenRemoved) { } TEST_F(ZipkinDriverTest, CancelInflightRequestsOnDestruction) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); StrictMock request1(&cm_.async_client_), request2(&cm_.async_client_), request3(&cm_.async_client_), request4(&cm_.async_client_); @@ -446,7 +438,7 @@ TEST_F(ZipkinDriverTest, CancelInflightRequestsOnDestruction) { } TEST_F(ZipkinDriverTest, FlushSpansTimer) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); const absl::optional timeout(std::chrono::seconds(5)); EXPECT_CALL(cm_.async_client_, @@ -473,7 +465,7 @@ TEST_F(ZipkinDriverTest, FlushSpansTimer) { } TEST_F(ZipkinDriverTest, NoB3ContextSampledTrue) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID).empty()); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID).empty()); @@ -487,7 +479,7 @@ TEST_F(ZipkinDriverTest, NoB3ContextSampledTrue) { } TEST_F(ZipkinDriverTest, NoB3ContextSampledFalse) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID).empty()); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID).empty()); @@ -501,7 +493,7 @@ TEST_F(ZipkinDriverTest, NoB3ContextSampledFalse) { } TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleTrue) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -517,7 +509,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleTrue) { } TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleFalse) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -533,7 +525,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleFalse) { } TEST_F(ZipkinDriverTest, PropagateB3NotSampled) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID).empty()); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID).empty()); @@ -554,7 +546,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NotSampled) { } TEST_F(ZipkinDriverTest, PropagateB3NotSampledWithFalse) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID).empty()); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID).empty()); @@ -576,7 +568,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NotSampledWithFalse) { } TEST_F(ZipkinDriverTest, PropagateB3SampledWithTrue) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID).empty()); EXPECT_TRUE(request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID).empty()); @@ -598,7 +590,7 @@ TEST_F(ZipkinDriverTest, PropagateB3SampledWithTrue) { } TEST_F(ZipkinDriverTest, PropagateB3SampleFalse) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -614,7 +606,7 @@ TEST_F(ZipkinDriverTest, PropagateB3SampleFalse) { } TEST_F(ZipkinDriverTest, ZipkinSpanTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); // ==== // Test effective setTag() @@ -701,7 +693,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); const std::string trace_id = Hex::uint64ToHex(generateRandom64()); const std::string span_id = Hex::uint64ToHex(generateRandom64()); @@ -725,7 +717,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersEmptyParentSpanTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); // Root span so have same trace and span id const std::string id = Hex::uint64ToHex(generateRandom64()); @@ -745,7 +737,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersEmptyParentSpanTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3Headers128TraceIdTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); const uint64_t trace_id_high = generateRandom64(); const uint64_t trace_id_low = generateRandom64(); @@ -773,7 +765,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3Headers128TraceIdTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidTraceIdB3HeadersTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, std::string("xyz")); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_SPAN_ID, @@ -787,7 +779,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidTraceIdB3HeadersTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidSpanIdB3HeadersTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -801,7 +793,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidSpanIdB3HeadersTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidParentIdB3HeadersTest) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -816,7 +808,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidParentIdB3HeadersTest) { } TEST_F(ZipkinDriverTest, ExplicitlySetSampledFalse) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_, {Tracing::Reason::Sampling, true}); @@ -833,7 +825,7 @@ TEST_F(ZipkinDriverTest, ExplicitlySetSampledFalse) { } TEST_F(ZipkinDriverTest, ExplicitlySetSampledTrue) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_, {Tracing::Reason::Sampling, false}); @@ -850,7 +842,7 @@ TEST_F(ZipkinDriverTest, ExplicitlySetSampledTrue) { } TEST_F(ZipkinDriverTest, DuplicatedHeader) { - setupValidDriver("HTTP_JSON_V1"); + setupValidDriver("HTTP_JSON"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_SPAN_ID, diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index b265710464f1..403a08b61c3a 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -1015,7 +1015,8 @@ TEST_P(SslSocketTest, GetUriWithUriSan) { validation_context: trusted_ca: filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" - verify_subject_alt_name: "spiffe://lyft.com/test-team" + match_subject_alt_names: + exact: "spiffe://lyft.com/test-team" )EOF"; TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam()); @@ -1030,7 +1031,8 @@ TEST_P(SslSocketTest, Ipv4San) { validation_context: trusted_ca: filename: "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem" - verify_subject_alt_name: "127.0.0.1" + match_subject_alt_names: + exact: "127.0.0.1" )EOF"; const std::string server_ctx_yaml = R"EOF( @@ -1053,7 +1055,8 @@ TEST_P(SslSocketTest, Ipv6San) { validation_context: trusted_ca: filename: "{{ test_rundir }}/test/config/integration/certs/upstreamcacert.pem" - verify_subject_alt_name: "::1" + match_subject_alt_names: + exact: "::1" )EOF"; const std::string server_ctx_yaml = R"EOF( @@ -1455,7 +1458,8 @@ TEST_P(SslSocketTest, FailedClientAuthSanVerificationNoClientCert) { validation_context: trusted_ca: filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" - verify_subject_alt_name: "example.com" + match_subject_alt_names: + exact: "example.com" )EOF"; TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam()); @@ -1482,7 +1486,8 @@ TEST_P(SslSocketTest, FailedClientAuthSanVerification) { validation_context: trusted_ca: filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" - verify_subject_alt_name: "example.com" + match_subject_alt_names: + exact: "example.com" )EOF"; TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam()); diff --git a/test/integration/BUILD b/test/integration/BUILD index 8cf3bfcda25e..db3e796099c6 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1240,7 +1240,7 @@ envoy_cc_test( ":http_protocol_integration_lib", "//source/extensions/filters/network/tcp_proxy:config", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", - "@envoy_api//envoy/config/filter/network/tcp_proxy/v2:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], ) diff --git a/test/integration/ads_integration.cc b/test/integration/ads_integration.cc index 6a0db9004244..fc3d044807dd 100644 --- a/test/integration/ads_integration.cc +++ b/test/integration/ads_integration.cc @@ -75,7 +75,7 @@ AdsIntegrationTest::buildRedisListener(const std::string& name, const std::strin filters: - name: redis typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.redis_proxy.v3.RedisProxy settings: op_timeout: 1s stat_prefix: {} @@ -128,6 +128,9 @@ void AdsIntegrationTest::initializeAds(const bool rate_limiting) { ads_cluster->mutable_transport_socket()->mutable_typed_config()->PackFrom(context); }); setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); + if (api_version_ == envoy::config::core::v3::ApiVersion::V2 && !fatal_by_default_v2_override_) { + config_helper_.enableDeprecatedV2Api(); + } HttpIntegrationTest::initialize(); if (xds_stream_ == nullptr) { createXdsConnection(); diff --git a/test/integration/ads_integration.h b/test/integration/ads_integration.h index 8c9a8e0ab3e6..804b3b3f315e 100644 --- a/test/integration/ads_integration.h +++ b/test/integration/ads_integration.h @@ -18,7 +18,7 @@ namespace Envoy { class AdsIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, public HttpIntegrationTest { public: AdsIntegrationTest(const envoy::config::core::v3::ApiVersion api_version); - AdsIntegrationTest() : AdsIntegrationTest(envoy::config::core::v3::ApiVersion::V2) {} + AdsIntegrationTest() : AdsIntegrationTest(envoy::config::core::v3::ApiVersion::V3) {} void TearDown() override; @@ -56,7 +56,10 @@ class AdsIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, public Ht envoy::admin::v3::ListenersConfigDump getListenersConfigDump(); envoy::admin::v3::RoutesConfigDump getRoutesConfigDump(); + // If API version is v2, fatal-by-default is disabled unless fatal_by_default_v2_override_ is set. envoy::config::core::v3::ApiVersion api_version_; + // Set to force fatal-by-default v2 even if API version is v2. + bool fatal_by_default_v2_override_{false}; }; } // namespace Envoy diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index 053dfcbc9868..3fc55beb56e2 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -34,6 +34,140 @@ TEST_P(AdsIntegrationTest, Basic) { testBasicFlow(); } +// Basic CDS/EDS update that warms and makes active a single cluster. +TEST_P(AdsIntegrationTest, BasicClusterInitialWarming) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + const auto eds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse( + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); + sendDiscoveryResponse( + eds_type_url, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1", false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); +} + +// Update the only warming cluster. Verify that the new cluster is still warming and the cluster +// manager as a whole is not initialized. +TEST_P(AdsIntegrationTest, ClusterInitializationUpdateTheOnlyWarmingCluster) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + const auto eds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse( + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + // Update lb policy to MAGLEV so that cluster update is not skipped due to the same hash. + sendDiscoveryResponse( + cds_type_url, {buildCluster("cluster_0", "MAGLEV")}, {buildCluster("cluster_0", "MAGLEV")}, + {}, "2", false); + EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); + sendDiscoveryResponse( + eds_type_url, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1", false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); +} + +// Primary cluster is warming during cluster initialization. Update the cluster with immediate ready +// config and verify that all the clusters are initialized. +TEST_P(AdsIntegrationTest, TestPrimaryClusterWarmClusterInitialization) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + auto loopback = Network::Test::getLoopbackAddressString(ipVersion()); + addFakeUpstream(FakeHttpConnection::Type::HTTP2); + auto port = fake_upstreams_.back()->localAddress()->ip()->port(); + + // This cluster will be blocked since endpoint name cannot be resolved. + auto warming_cluster = ConfigHelper::buildStaticCluster("fake_cluster", port, loopback); + // Below endpoint accepts request but never return. The health check hangs 1 hour which covers the + // test running. + auto blocking_health_check = TestUtility::parseYaml(R"EOF( + timeout: 3600s + interval: 3600s + unhealthy_threshold: 2 + healthy_threshold: 2 + tcp_health_check: + send: + text: '01' + receive: + - text: '02' + )EOF"); + *warming_cluster.add_health_checks() = blocking_health_check; + + // Active cluster has the same name with warming cluster but has no blocking health check. + auto active_cluster = ConfigHelper::buildStaticCluster("fake_cluster", port, loopback); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse(cds_type_url, {warming_cluster}, + {warming_cluster}, {}, "1", false); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_.back()->waitForRawConnection(fake_upstream_connection)); + + // fake_cluster is in warming. + test_server_->waitForGaugeGe("cluster_manager.warming_clusters", 1); + + // Now replace the warming cluster by the config which will turn ready immediately. + sendDiscoveryResponse(cds_type_url, {active_cluster}, + {active_cluster}, {}, "2", false); + + // All clusters are ready. + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); +} + +// Two cluster warming, update one of them. Verify that the clusters are eventually initialized. +TEST_P(AdsIntegrationTest, ClusterInitializationUpdateOneOfThe2Warming) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + const auto eds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V3); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + sendDiscoveryResponse( + cds_type_url, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0"), buildCluster("cluster_1")}, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0"), buildCluster("cluster_1")}, + {}, "1", false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); + + // Update lb policy to MAGLEV so that cluster update is not skipped due to the same hash. + sendDiscoveryResponse( + cds_type_url, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0", "MAGLEV"), buildCluster("cluster_1")}, + {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), + buildCluster("cluster_0", "MAGLEV"), buildCluster("cluster_1")}, + {}, "2", false); + EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0", "cluster_1"}, + {"cluster_0", "cluster_1"}, {})); + sendDiscoveryResponse( + eds_type_url, + {buildClusterLoadAssignment("cluster_0"), buildClusterLoadAssignment("cluster_1")}, + {buildClusterLoadAssignment("cluster_0"), buildClusterLoadAssignment("cluster_1")}, {}, "1", + false); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); +} // Validate basic config delivery and upgrade with RateLimiting. TEST_P(AdsIntegrationTest, BasicWithRateLimiting) { initializeAds(true); @@ -736,7 +870,7 @@ class AdsFailIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, : HttpIntegrationTest(Http::CodecClient::Type::HTTP2, ipVersion(), ConfigHelper::adsBootstrap( sotwOrDelta() == Grpc::SotwOrDelta::Sotw ? "GRPC" : "DELTA_GRPC", - envoy::config::core::v3::ApiVersion::V2)) { + envoy::config::core::v3::ApiVersion::V3)) { create_xds_upstream_ = true; use_lds_ = false; sotw_or_delta_ = sotwOrDelta(); @@ -777,7 +911,7 @@ class AdsConfigIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, : HttpIntegrationTest(Http::CodecClient::Type::HTTP2, ipVersion(), ConfigHelper::adsBootstrap( sotwOrDelta() == Grpc::SotwOrDelta::Sotw ? "GRPC" : "DELTA_GRPC", - envoy::config::core::v3::ApiVersion::V2)) { + envoy::config::core::v3::ApiVersion::V3)) { create_xds_upstream_ = true; use_lds_ = false; sotw_or_delta_ = sotwOrDelta(); @@ -939,7 +1073,7 @@ class AdsClusterFromFileIntegrationTest : public Grpc::DeltaSotwIntegrationParam : HttpIntegrationTest(Http::CodecClient::Type::HTTP2, ipVersion(), ConfigHelper::adsBootstrap( sotwOrDelta() == Grpc::SotwOrDelta::Sotw ? "GRPC" : "DELTA_GRPC", - envoy::config::core::v3::ApiVersion::V2)) { + envoy::config::core::v3::ApiVersion::V3)) { create_xds_upstream_ = true; use_lds_ = false; sotw_or_delta_ = sotwOrDelta(); @@ -973,6 +1107,7 @@ class AdsClusterFromFileIntegrationTest : public Grpc::DeltaSotwIntegrationParam ads_eds_cluster->set_type(envoy::config::cluster::v3::Cluster::EDS); auto* eds_cluster_config = ads_eds_cluster->mutable_eds_cluster_config(); auto* eds_config = eds_cluster_config->mutable_eds_config(); + eds_config->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); eds_config->mutable_ads(); }); setUpstreamProtocol(FakeHttpConnection::Type::HTTP2); @@ -1033,6 +1168,7 @@ class AdsIntegrationTestWithRtds : public AdsIntegrationTest { rtds_layer->set_name("ads_rtds_layer"); auto* rtds_config = rtds_layer->mutable_rtds_config(); rtds_config->mutable_ads(); + rtds_config->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); auto* ads_config = bootstrap.mutable_dynamic_resources()->mutable_ads_config(); ads_config->set_set_node_on_first_message_only(true); @@ -1081,6 +1217,8 @@ class AdsIntegrationTestWithRtdsAndSecondaryClusters : public AdsIntegrationTest eds_cluster->set_type(envoy::config::cluster::v3::Cluster::EDS); auto* eds_cluster_config = eds_cluster->mutable_eds_cluster_config(); eds_cluster_config->mutable_eds_config()->mutable_ads(); + eds_cluster_config->mutable_eds_config()->set_resource_api_version( + envoy::config::core::v3::ApiVersion::V3); }); AdsIntegrationTestWithRtds::initialize(); } @@ -1123,184 +1261,85 @@ TEST_P(AdsIntegrationTestWithRtdsAndSecondaryClusters, Basic) { testBasicFlow(); } -// Check if EDS cluster defined in file is loaded before ADS request and used as xDS server -class AdsClusterV3Test : public AdsIntegrationTest { +// Some v2 ADS integration tests, these validate basic v2 support but are not complete, they reflect +// tests that have historically been worth validating on both v2 and v3. They will be removed in Q1. +class AdsClusterV2Test : public AdsIntegrationTest { public: - AdsClusterV3Test() : AdsIntegrationTest(envoy::config::core::v3::ApiVersion::V3) {} + AdsClusterV2Test() : AdsIntegrationTest(envoy::config::core::v3::ApiVersion::V2) {} }; -INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, AdsClusterV3Test, +INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, AdsClusterV2Test, DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS); -TEST_P(AdsClusterV3Test, BasicClusterInitialWarming) { +// Basic CDS/EDS update that warms and makes active a single cluster (v2 API). +TEST_P(AdsClusterV2Test, BasicClusterInitialWarming) { initialize(); const auto cds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); const auto eds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); sendDiscoveryResponse( - cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", true); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); sendDiscoveryResponse( eds_type_url, {buildClusterLoadAssignment("cluster_0")}, - {buildClusterLoadAssignment("cluster_0")}, {}, "1", false); + {buildClusterLoadAssignment("cluster_0")}, {}, "1", true); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); } -// Update the only warming cluster. Verify that the new cluster is still warming and the cluster -// manager as a whole is not initialized. -TEST_P(AdsClusterV3Test, ClusterInitializationUpdateTheOnlyWarmingCluster) { +// If we attempt to use v2 APIs by default, the configuration should be rejected. +TEST_P(AdsClusterV2Test, RejectV2ConfigByDefault) { + fatal_by_default_v2_override_ = true; initialize(); const auto cds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); - const auto eds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); sendDiscoveryResponse( - cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); - // Update lb policy to MAGLEV so that cluster update is not skipped due to the same hash. - sendDiscoveryResponse( - cds_type_url, {buildCluster("cluster_0", "MAGLEV")}, {buildCluster("cluster_0", "MAGLEV")}, - {}, "2", false); - EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); - sendDiscoveryResponse( - eds_type_url, {buildClusterLoadAssignment("cluster_0")}, - {buildClusterLoadAssignment("cluster_0")}, {}, "1", false); - - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); -} - -// Primary cluster is warming during cluster initialization. Update the cluster with immediate ready -// config and verify that all the clusters are initialized. -TEST_P(AdsClusterV3Test, TestPrimaryClusterWarmClusterInitialization) { - initialize(); - const auto cds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); - auto loopback = Network::Test::getLoopbackAddressString(ipVersion()); - addFakeUpstream(FakeHttpConnection::Type::HTTP2); - auto port = fake_upstreams_.back()->localAddress()->ip()->port(); - - // This cluster will be blocked since endpoint name cannot be resolved. - auto warming_cluster = ConfigHelper::buildStaticCluster("fake_cluster", port, loopback); - // Below endpoint accepts request but never return. The health check hangs 1 hour which covers the - // test running. - auto blocking_health_check = TestUtility::parseYaml(R"EOF( - timeout: 3600s - interval: 3600s - unhealthy_threshold: 2 - healthy_threshold: 2 - tcp_health_check: - send: - text: '01' - receive: - - text: '02' - )EOF"); - *warming_cluster.add_health_checks() = blocking_health_check; - - // Active cluster has the same name with warming cluster but has no blocking health check. - auto active_cluster = ConfigHelper::buildStaticCluster("fake_cluster", port, loopback); - - EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); - sendDiscoveryResponse(cds_type_url, {warming_cluster}, - {warming_cluster}, {}, "1", false); - - FakeRawConnectionPtr fake_upstream_connection; - ASSERT_TRUE(fake_upstreams_.back()->waitForRawConnection(fake_upstream_connection)); - - // fake_cluster is in warming. - test_server_->waitForGaugeGe("cluster_manager.warming_clusters", 1); - - // Now replace the warming cluster by the config which will turn ready immediately. - sendDiscoveryResponse(cds_type_url, {active_cluster}, - {active_cluster}, {}, "2", false); - - // All clusters are ready. - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 2); -} - -// Two cluster warming, update one of them. Verify that the clusters are eventually initialized. -TEST_P(AdsClusterV3Test, ClusterInitializationUpdateOneOfThe2Warming) { - initialize(); - const auto cds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); - const auto eds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); - - EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); - sendDiscoveryResponse( - cds_type_url, - {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), - buildCluster("cluster_0"), buildCluster("cluster_1")}, - {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), - buildCluster("cluster_0"), buildCluster("cluster_1")}, - {}, "1", false); - - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); - - // Update lb policy to MAGLEV so that cluster update is not skipped due to the same hash. - sendDiscoveryResponse( - cds_type_url, - {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), - buildCluster("cluster_0", "MAGLEV"), buildCluster("cluster_1")}, - {ConfigHelper::buildStaticCluster("primary_cluster", 8000, "127.0.0.1"), - buildCluster("cluster_0", "MAGLEV"), buildCluster("cluster_1")}, - {}, "2", false); - EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0", "cluster_1"}, - {"cluster_0", "cluster_1"}, {})); - sendDiscoveryResponse( - eds_type_url, - {buildClusterLoadAssignment("cluster_0"), buildClusterLoadAssignment("cluster_1")}, - {buildClusterLoadAssignment("cluster_0"), buildClusterLoadAssignment("cluster_1")}, {}, "1", - false); - - test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); - test_server_->waitForGaugeGe("cluster_manager.active_clusters", 4); + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", true); + test_server_->waitForCounterGe("cluster_manager.cds.update_rejected", 1); } // Verify CDS is paused during cluster warming. -TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { +TEST_P(AdsClusterV2Test, CdsPausedDuringWarming) { initialize(); const auto cds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); const auto eds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); const auto lds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); const auto rds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); // Send initial configuration, validate we can process a request. EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); sendDiscoveryResponse( - cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", false); + cds_type_url, {buildCluster("cluster_0")}, {buildCluster("cluster_0")}, {}, "1", true); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"cluster_0"}, {"cluster_0"}, {})); sendDiscoveryResponse( eds_type_url, {buildClusterLoadAssignment("cluster_0")}, - {buildClusterLoadAssignment("cluster_0")}, {}, "1", false); + {buildClusterLoadAssignment("cluster_0")}, {}, "1", true); EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "1", {}, {}, {})); EXPECT_TRUE(compareDiscoveryRequest(lds_type_url, "", {}, {}, {})); sendDiscoveryResponse( lds_type_url, {buildListener("listener_0", "route_config_0")}, - {buildListener("listener_0", "route_config_0")}, {}, "1", false); + {buildListener("listener_0", "route_config_0")}, {}, "1", true); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "1", {"cluster_0"}, {}, {})); EXPECT_TRUE( compareDiscoveryRequest(rds_type_url, "", {"route_config_0"}, {"route_config_0"}, {})); sendDiscoveryResponse( rds_type_url, {buildRouteConfig("route_config_0", "cluster_0")}, - {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1", false); + {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1", true); EXPECT_TRUE(compareDiscoveryRequest(lds_type_url, "1", {}, {}, {})); EXPECT_TRUE(compareDiscoveryRequest(rds_type_url, "1", {"route_config_0"}, {}, {})); @@ -1311,7 +1350,7 @@ TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { // Send the first warming cluster. sendDiscoveryResponse( cds_type_url, {buildCluster("warming_cluster_1")}, {buildCluster("warming_cluster_1")}, - {"cluster_0"}, "2", false); + {"cluster_0"}, "2", true); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); @@ -1321,7 +1360,7 @@ TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { // Send the second warming cluster. sendDiscoveryResponse( cds_type_url, {buildCluster("warming_cluster_2")}, {buildCluster("warming_cluster_2")}, {}, - "3", false); + "3", true); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); // We would've got a Cluster discovery request with version 2 here, had the CDS not been paused. @@ -1335,7 +1374,7 @@ TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { buildClusterLoadAssignment("warming_cluster_2")}, {buildClusterLoadAssignment("warming_cluster_1"), buildClusterLoadAssignment("warming_cluster_2")}, - {"cluster_0"}, "2", false); + {"cluster_0"}, "2", true); // Validate that clusters are warmed. test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); @@ -1353,7 +1392,7 @@ TEST_P(AdsClusterV3Test, CdsPausedDuringWarming) { } // Validates that the initial xDS request batches all resources referred to in static config -TEST_P(AdsClusterV3Test, XdsBatching) { +TEST_P(AdsClusterV2Test, XdsBatching) { config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { bootstrap.mutable_dynamic_resources()->clear_cds_config(); bootstrap.mutable_dynamic_resources()->clear_lds_config(); @@ -1373,9 +1412,9 @@ TEST_P(AdsClusterV3Test, XdsBatching) { const auto eds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); const auto rds_type_url = Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3); + envoy::config::core::v3::ApiVersion::V2); EXPECT_TRUE(compareDiscoveryRequest(eds_type_url, "", {"eds_cluster2", "eds_cluster"}, {"eds_cluster2", "eds_cluster"}, {}, true)); @@ -1383,7 +1422,7 @@ TEST_P(AdsClusterV3Test, XdsBatching) { eds_type_url, {buildClusterLoadAssignment("eds_cluster"), buildClusterLoadAssignment("eds_cluster2")}, {buildClusterLoadAssignment("eds_cluster"), buildClusterLoadAssignment("eds_cluster2")}, {}, - "1", false); + "1", true); EXPECT_TRUE(compareDiscoveryRequest(rds_type_url, "", {"route_config2", "route_config"}, {"route_config2", "route_config"}, {})); @@ -1393,7 +1432,7 @@ TEST_P(AdsClusterV3Test, XdsBatching) { buildRouteConfig("route_config", "dummy_cluster")}, {buildRouteConfig("route_config2", "eds_cluster2"), buildRouteConfig("route_config", "dummy_cluster")}, - {}, "1", false); + {}, "1", true); }; initialize(); diff --git a/test/integration/alpn_selection_integration_test.cc b/test/integration/alpn_selection_integration_test.cc index a576f51d3e16..3ca3964a049e 100644 --- a/test/integration/alpn_selection_integration_test.cc +++ b/test/integration/alpn_selection_integration_test.cc @@ -37,7 +37,7 @@ class AlpnSelectionIntegrationTest : public testing::Test, public HttpIntegratio R"EOF( name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: alpn_protocols: [ %s ] tls_certificates: diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index 354d309ef86f..b5f8ddb1cf84 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -259,7 +259,7 @@ void BaseIntegrationTest::createGeneratedApiTestServer( test_server_ = IntegrationTestServer::create( bootstrap_path, version_, on_server_ready_function_, on_server_init_function_, deterministic_, timeSystem(), *api_, defer_listener_finalization_, process_object_, validator_config, - concurrency_, drain_time_, drain_strategy_, use_real_stats_); + concurrency_, drain_time_, drain_strategy_, use_real_stats_, v2_bootstrap_); if (config_helper_.bootstrap().static_resources().listeners_size() > 0 && !defer_listener_finalization_) { diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index aee365f67a45..3fc85792b86d 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -153,7 +153,7 @@ class BaseIntegrationTest : protected Logger::Loggable { void sendDiscoveryResponse(const std::string& type_url, const std::vector& state_of_the_world, const std::vector& added_or_updated, const std::vector& removed, const std::string& version, - const bool api_downgrade = true) { + const bool api_downgrade = false) { if (sotw_or_delta_ == Grpc::SotwOrDelta::Sotw) { sendSotwDiscoveryResponse(type_url, state_of_the_world, version, api_downgrade); } else { @@ -189,7 +189,7 @@ class BaseIntegrationTest : protected Logger::Loggable { template void sendSotwDiscoveryResponse(const std::string& type_url, const std::vector& messages, - const std::string& version, const bool api_downgrade = true) { + const std::string& version, const bool api_downgrade = false) { API_NO_BOOST(envoy::api::v2::DiscoveryResponse) discovery_response; discovery_response.set_version_info(version); discovery_response.set_type_url(type_url); @@ -209,7 +209,7 @@ class BaseIntegrationTest : protected Logger::Loggable { void sendDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, const std::vector& removed, - const std::string& version, const bool api_downgrade = true) { + const std::string& version, const bool api_downgrade = false) { sendDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, xds_stream_, {}, api_downgrade); } @@ -218,7 +218,7 @@ class BaseIntegrationTest : protected Logger::Loggable { sendDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, const std::vector& removed, const std::string& version, FakeStreamPtr& stream, const std::vector& aliases = {}, - const bool api_downgrade = true) { + const bool api_downgrade = false) { auto response = createDeltaDiscoveryResponse(type_url, added_or_updated, removed, version, aliases, api_downgrade); stream->sendGrpcMessage(response); @@ -229,7 +229,7 @@ class BaseIntegrationTest : protected Logger::Loggable { createDeltaDiscoveryResponse(const std::string& type_url, const std::vector& added_or_updated, const std::vector& removed, const std::string& version, const std::vector& aliases, - const bool api_downgrade = true) { + const bool api_downgrade = false) { API_NO_BOOST(envoy::api::v2::DeltaDiscoveryResponse) response; response.set_system_version_info("system_version_info_this_is_a_test"); @@ -244,7 +244,7 @@ class BaseIntegrationTest : protected Logger::Loggable { temp_any.PackFrom(message); resource->mutable_resource()->PackFrom(message); } - resource->set_name(TestUtility::xdsResourceName(temp_any)); + resource->set_name(intResourceName(message)); resource->set_version(version); for (const auto& alias : aliases) { resource->add_aliases(alias); @@ -257,6 +257,17 @@ class BaseIntegrationTest : protected Logger::Loggable { } private: + std::string intResourceName(const envoy::config::listener::v3::Listener& m) { return m.name(); } + std::string intResourceName(const envoy::config::route::v3::RouteConfiguration& m) { + return m.name(); + } + std::string intResourceName(const envoy::config::cluster::v3::Cluster& m) { return m.name(); } + std::string intResourceName(const envoy::config::endpoint::v3::ClusterLoadAssignment& m) { + return m.cluster_name(); + } + std::string intResourceName(const envoy::config::route::v3::VirtualHost& m) { return m.name(); } + std::string intResourceName(const envoy::service::runtime::v3::Runtime& m) { return m.name(); } + Event::GlobalTimeSystem time_system_; public: @@ -438,6 +449,9 @@ class BaseIntegrationTest : protected Logger::Loggable { // This override exists for tests measuring stats memory. bool use_real_stats_{}; + // Use a v2 bootstrap. + bool v2_bootstrap_{false}; + private: // The type for the Envoy-to-backend connection FakeHttpConnection::Type upstream_protocol_{FakeHttpConnection::Type::HTTP1}; diff --git a/test/integration/hds_integration_test.cc b/test/integration/hds_integration_test.cc index 9f49ed87f0b2..cd8a5027c4c1 100644 --- a/test/integration/hds_integration_test.cc +++ b/test/integration/hds_integration_test.cc @@ -171,7 +171,7 @@ class HdsIntegrationTest : public Grpc::VersionedGrpcClientIntegrationParamTest, transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "%s" } diff --git a/test/integration/header_integration_test.cc b/test/integration/header_integration_test.cc index a7a837fe7e5e..40437b8ab8c1 100644 --- a/test/integration/header_integration_test.cc +++ b/test/integration/header_integration_test.cc @@ -217,6 +217,7 @@ class HeaderIntegrationTest type: EDS eds_cluster_config: eds_config: + resource_api_version: V3 api_config_source: api_type: GRPC grpc_services: @@ -382,7 +383,7 @@ class HeaderIntegrationTest discovery_response.set_type_url(Config::TypeUrl::get().ClusterLoadAssignment); auto cluster_load_assignment = - TestUtility::parseYaml(fmt::format( + TestUtility::parseYaml(fmt::format( R"EOF( cluster_name: cluster_0 endpoints: diff --git a/test/integration/http2_upstream_integration_test.cc b/test/integration/http2_upstream_integration_test.cc index 30a193b83bc3..7079efb41834 100644 --- a/test/integration/http2_upstream_integration_test.cc +++ b/test/integration/http2_upstream_integration_test.cc @@ -333,13 +333,13 @@ TEST_P(Http2UpstreamIntegrationTest, HittingEncoderFilterLimitForGrpc) { const std::string yaml_string = fmt::format(R"EOF( name: router typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.router.v2.Router + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router upstream_log: name: accesslog filter: not_health_check_filter: {{}} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog path: {} )EOF", Platform::null_device_path); @@ -458,13 +458,13 @@ TEST_P(Http2UpstreamIntegrationTest, ConfigureHttpOverGrpcLogs) { const std::string yaml_string = R"EOF( name: router typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.router.v2.Router + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router upstream_log: name: grpc_accesslog filter: not_health_check_filter: {} typed_config: - "@type": type.googleapis.com/envoy.config.accesslog.v2.HttpGrpcAccessLogConfig + "@type": type.googleapis.com/envoy.extensions.access_loggers.grpc.v3.HttpGrpcAccessLogConfig common_config: log_name: foo grpc_service: diff --git a/test/integration/integration_admin_test.cc b/test/integration/integration_admin_test.cc index c01716d33c24..6e12c3cf8f2f 100644 --- a/test/integration/integration_admin_test.cc +++ b/test/integration/integration_admin_test.cc @@ -562,6 +562,7 @@ TEST_P(StatsMatcherIntegrationTest, ExcludePrefixServerDot) { } TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(ExcludeRequests)) { + v2_bootstrap_ = true; stats_matcher_.mutable_exclusion_list()->add_patterns()->set_hidden_envoy_deprecated_regex( ".*requests.*"); initialize(); @@ -577,6 +578,7 @@ TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(ExcludeExact)) { } TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(ExcludeMultipleExact)) { + v2_bootstrap_ = true; stats_matcher_.mutable_exclusion_list()->add_patterns()->set_exact("server.concurrency"); stats_matcher_.mutable_exclusion_list()->add_patterns()->set_hidden_envoy_deprecated_regex( ".*live"); diff --git a/test/integration/listener_lds_integration_test.cc b/test/integration/listener_lds_integration_test.cc index 183956c12eae..2871798c7bf0 100644 --- a/test/integration/listener_lds_integration_test.cc +++ b/test/integration/listener_lds_integration_test.cc @@ -60,6 +60,8 @@ class ListenerIntegrationTest : public HttpIntegrationTest, http_connection_manager) { auto* rds_config = http_connection_manager.mutable_rds(); rds_config->set_route_config_name(route_table_name_); + rds_config->mutable_config_source()->set_resource_api_version( + envoy::config::core::v3::ApiVersion::V3); envoy::config::core::v3::ApiConfigSource* rds_api_config_source = rds_config->mutable_config_source()->mutable_api_config_source(); rds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); @@ -78,8 +80,9 @@ class ListenerIntegrationTest : public HttpIntegrationTest, listener_config_.set_name(listener_name_); ENVOY_LOG_MISC(error, "listener config: {}", listener_config_.DebugString()); bootstrap.mutable_static_resources()->mutable_listeners()->Clear(); - auto* lds_api_config_source = - bootstrap.mutable_dynamic_resources()->mutable_lds_config()->mutable_api_config_source(); + auto* lds_config_source = bootstrap.mutable_dynamic_resources()->mutable_lds_config(); + lds_config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); + auto* lds_api_config_source = lds_config_source->mutable_api_config_source(); lds_api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); envoy::config::core::v3::GrpcService* grpc_service = lds_api_config_source->add_grpc_services(); @@ -151,7 +154,7 @@ class ListenerIntegrationTest : public HttpIntegrationTest, for (const auto& listener_blob : listener_configs) { const auto listener_config = TestUtility::parseYaml(listener_blob); - response.add_resources()->PackFrom(API_DOWNGRADE(listener_config)); + response.add_resources()->PackFrom(listener_config); } ASSERT(lds_upstream_info_.stream_by_resource_name_[listener_name_] != nullptr); lds_upstream_info_.stream_by_resource_name_[listener_name_]->sendGrpcMessage(response); @@ -163,7 +166,7 @@ class ListenerIntegrationTest : public HttpIntegrationTest, response.set_type_url(Config::TypeUrl::get().RouteConfiguration); const auto route_configuration = TestUtility::parseYaml(route_config); - response.add_resources()->PackFrom(API_DOWNGRADE(route_configuration)); + response.add_resources()->PackFrom(route_configuration); ASSERT(rds_upstream_info_.stream_by_resource_name_[route_configuration.name()] != nullptr); rds_upstream_info_.stream_by_resource_name_[route_configuration.name()]->sendGrpcMessage( response); diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 480b5b2e7cf5..358ec1515cc0 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -175,7 +175,7 @@ TEST_P(ProtocolIntegrationTest, ComputedHealthCheck) { config_helper_.addFilter(R"EOF( name: health_check typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck pass_through_mode: false cluster_min_healthy_percentages: example_cluster_name: { value: 75 } @@ -196,7 +196,7 @@ TEST_P(ProtocolIntegrationTest, ModifyBuffer) { config_helper_.addFilter(R"EOF( name: health_check typed_config: - "@type": type.googleapis.com/envoy.config.filter.http.health_check.v2.HealthCheck + "@type": type.googleapis.com/envoy.extensions.filters.http.health_check.v3.HealthCheck pass_through_mode: false cluster_min_healthy_percentages: example_cluster_name: { value: 75 } diff --git a/test/integration/rtds_integration_test.cc b/test/integration/rtds_integration_test.cc index cbcda0cfae79..925cdf42b261 100644 --- a/test/integration/rtds_integration_test.cc +++ b/test/integration/rtds_integration_test.cc @@ -47,6 +47,7 @@ std::string tdsBootstrapConfig(absl::string_view api_type) { rtds_layer: name: some_rtds_layer rtds_config: + resource_api_version: V3 api_config_source: api_type: {} grpc_services: diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index 4e5bc1ddebf5..2a44fc58dc86 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -64,6 +64,7 @@ class SdsDynamicIntegrationBaseTest : public Grpc::GrpcClientIntegrationParamTes const std::string& secret_name) { secret_config->set_name(secret_name); auto* config_source = secret_config->mutable_sds_config(); + config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); auto* api_config_source = config_source->mutable_api_config_source(); api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); auto* grpc_service = api_config_source->add_grpc_services(); @@ -123,7 +124,7 @@ class SdsDynamicIntegrationBaseTest : public Grpc::GrpcClientIntegrationParamTes API_NO_BOOST(envoy::api::v2::DiscoveryResponse) discovery_response; discovery_response.set_version_info("1"); discovery_response.set_type_url(Config::TypeUrl::get().Secret); - discovery_response.add_resources()->PackFrom(API_DOWNGRADE(secret)); + discovery_response.add_resources()->PackFrom(secret); xds_stream_->sendGrpcMessage(discovery_response); } diff --git a/test/integration/sds_generic_secret_integration_test.cc b/test/integration/sds_generic_secret_integration_test.cc index ac466c6a40ec..b2b9f8d085ab 100644 --- a/test/integration/sds_generic_secret_integration_test.cc +++ b/test/integration/sds_generic_secret_integration_test.cc @@ -58,6 +58,7 @@ class SdsGenericSecretTestFilterConfig public: SdsGenericSecretTestFilterConfig() : Extensions::HttpFilters::Common::EmptyHttpFilterConfig("sds-generic-secret-test") { + config_source_.set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); auto* api_config_source = config_source_.mutable_api_config_source(); api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); auto* grpc_service = api_config_source->add_grpc_services(); @@ -121,7 +122,7 @@ class SdsGenericSecretIntegrationTest : public Grpc::GrpcClientIntegrationParamT API_NO_BOOST(envoy::api::v2::DiscoveryResponse) discovery_response; discovery_response.set_version_info("0"); discovery_response.set_type_url(Config::TypeUrl::get().Secret); - discovery_response.add_resources()->PackFrom(API_DOWNGRADE(secret)); + discovery_response.add_resources()->PackFrom(secret); xds_stream_->sendGrpcMessage(discovery_response); } diff --git a/test/integration/server.cc b/test/integration/server.cc index e6991ec0a71a..c957caf59b06 100644 --- a/test/integration/server.cc +++ b/test/integration/server.cc @@ -32,7 +32,7 @@ OptionsImpl createTestOptionsImpl(const std::string& config_path, const std::str Network::Address::IpVersion ip_version, FieldValidationConfig validation_config, uint32_t concurrency, std::chrono::seconds drain_time, - Server::DrainStrategy drain_strategy) { + Server::DrainStrategy drain_strategy, bool v2_bootstrap) { OptionsImpl test_options("cluster_name", "node_name", "zone_name", spdlog::level::info); test_options.setConfigPath(config_path); @@ -47,6 +47,9 @@ OptionsImpl createTestOptionsImpl(const std::string& config_path, const std::str test_options.setIgnoreUnknownFieldsDynamic(validation_config.ignore_unknown_dynamic_fields); test_options.setConcurrency(concurrency); test_options.setHotRestartDisabled(true); + if (v2_bootstrap) { + test_options.setBootstrapVersion(2); + } return test_options; } @@ -60,14 +63,15 @@ IntegrationTestServerPtr IntegrationTestServer::create( Event::TestTimeSystem& time_system, Api::Api& api, bool defer_listener_finalization, ProcessObjectOptRef process_object, Server::FieldValidationConfig validation_config, uint32_t concurrency, std::chrono::seconds drain_time, Server::DrainStrategy drain_strategy, - bool use_real_stats) { + bool use_real_stats, bool v2_bootstrap) { IntegrationTestServerPtr server{ std::make_unique(time_system, api, config_path, use_real_stats)}; if (server_ready_function != nullptr) { server->setOnServerReadyCb(server_ready_function); } server->start(version, on_server_init_function, deterministic, defer_listener_finalization, - process_object, validation_config, concurrency, drain_time, drain_strategy); + process_object, validation_config, concurrency, drain_time, drain_strategy, + v2_bootstrap); return server; } @@ -87,15 +91,15 @@ void IntegrationTestServer::start(const Network::Address::IpVersion version, ProcessObjectOptRef process_object, Server::FieldValidationConfig validator_config, uint32_t concurrency, std::chrono::seconds drain_time, - Server::DrainStrategy drain_strategy) { + Server::DrainStrategy drain_strategy, bool v2_bootstrap) { ENVOY_LOG(info, "starting integration test server"); ASSERT(!thread_); - thread_ = - api_.threadFactory().createThread([version, deterministic, process_object, validator_config, - concurrency, drain_time, drain_strategy, this]() -> void { - threadRoutine(version, deterministic, process_object, validator_config, concurrency, - drain_time, drain_strategy); - }); + thread_ = api_.threadFactory().createThread([version, deterministic, process_object, + validator_config, concurrency, drain_time, + drain_strategy, v2_bootstrap, this]() -> void { + threadRoutine(version, deterministic, process_object, validator_config, concurrency, drain_time, + drain_strategy, v2_bootstrap); + }); // If any steps need to be done prior to workers starting, do them now. E.g., xDS pre-init. // Note that there is no synchronization guaranteeing this happens either @@ -171,9 +175,10 @@ void IntegrationTestServer::threadRoutine(const Network::Address::IpVersion vers bool deterministic, ProcessObjectOptRef process_object, Server::FieldValidationConfig validation_config, uint32_t concurrency, std::chrono::seconds drain_time, - Server::DrainStrategy drain_strategy) { + Server::DrainStrategy drain_strategy, bool v2_bootstrap) { OptionsImpl options(Server::createTestOptionsImpl(config_path_, "", version, validation_config, - concurrency, drain_time, drain_strategy)); + concurrency, drain_time, drain_strategy, + v2_bootstrap)); Thread::MutexBasicLockable lock; Random::RandomGeneratorPtr random_generator; diff --git a/test/integration/server.h b/test/integration/server.h index 48995e8cb0b7..9d70d735bbb4 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -46,7 +46,8 @@ createTestOptionsImpl(const std::string& config_path, const std::string& config_ FieldValidationConfig validation_config = FieldValidationConfig(), uint32_t concurrency = 1, std::chrono::seconds drain_time = std::chrono::seconds(1), - Server::DrainStrategy drain_strategy = Server::DrainStrategy::Gradual); + Server::DrainStrategy drain_strategy = Server::DrainStrategy::Gradual, + bool v2_bootstrap = false); class TestComponentFactory : public ComponentFactory { public: @@ -396,7 +397,7 @@ class IntegrationTestServer : public Logger::Loggable, Server::FieldValidationConfig validation_config = Server::FieldValidationConfig(), uint32_t concurrency = 1, std::chrono::seconds drain_time = std::chrono::seconds(1), Server::DrainStrategy drain_strategy = Server::DrainStrategy::Gradual, - bool use_real_stats = false); + bool use_real_stats = false, bool v2_bootstrap = false); // Note that the derived class is responsible for tearing down the server in its // destructor. ~IntegrationTestServer() override; @@ -419,7 +420,8 @@ class IntegrationTestServer : public Logger::Loggable, std::function on_server_init_function, bool deterministic, bool defer_listener_finalization, ProcessObjectOptRef process_object, Server::FieldValidationConfig validation_config, uint32_t concurrency, - std::chrono::seconds drain_time, Server::DrainStrategy drain_strategy); + std::chrono::seconds drain_time, Server::DrainStrategy drain_strategy, + bool v2_bootstrap); void waitForCounterEq(const std::string& name, uint64_t value, std::chrono::milliseconds timeout = std::chrono::milliseconds::zero(), @@ -517,7 +519,8 @@ class IntegrationTestServer : public Logger::Loggable, void threadRoutine(const Network::Address::IpVersion version, bool deterministic, ProcessObjectOptRef process_object, Server::FieldValidationConfig validation_config, uint32_t concurrency, - std::chrono::seconds drain_time, Server::DrainStrategy drain_strategy); + std::chrono::seconds drain_time, Server::DrainStrategy drain_strategy, + bool v2_bootstrap); Event::TestTimeSystem& time_system_; Api::Api& api_; diff --git a/test/integration/tcp_conn_pool_integration_test.cc b/test/integration/tcp_conn_pool_integration_test.cc index 592747627de2..159b083a3937 100644 --- a/test/integration/tcp_conn_pool_integration_test.cc +++ b/test/integration/tcp_conn_pool_integration_test.cc @@ -117,7 +117,7 @@ class TcpConnPoolIntegrationTest : public testing::TestWithParammutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert(*config_blob); + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy)>(*config_blob); auto* access_log = tcp_proxy_config.add_access_log(); access_log->set_name("accesslog"); @@ -304,8 +305,8 @@ TEST_P(TcpProxyIntegrationTest, AccessLog) { runtime_filter->set_runtime_key("unused-key"); auto* percent_sampled = runtime_filter->mutable_percent_sampled(); percent_sampled->set_numerator(100); - percent_sampled->set_denominator( - envoy::type::FractionalPercent::DenominatorType::FractionalPercent_DenominatorType_HUNDRED); + percent_sampled->set_denominator(envoy::type::v3::FractionalPercent::DenominatorType:: + FractionalPercent_DenominatorType_HUNDRED); config_blob->PackFrom(tcp_proxy_config); }); initialize(); @@ -390,9 +391,10 @@ TEST_P(TcpProxyIntegrationTest, TestIdletimeoutWithNoData) { auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert(*config_blob); + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy)>(*config_blob); tcp_proxy_config.mutable_idle_timeout()->set_nanos( std::chrono::duration_cast(std::chrono::milliseconds(100)) .count()); @@ -413,9 +415,10 @@ TEST_P(TcpProxyIntegrationTest, TestIdletimeoutWithLargeOutstandingData) { auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert(*config_blob); + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy)>(*config_blob); tcp_proxy_config.mutable_idle_timeout()->set_nanos( std::chrono::duration_cast(std::chrono::milliseconds(500)) .count()); @@ -445,7 +448,8 @@ TEST_P(TcpProxyIntegrationTest, TestMaxDownstreamConnectionDurationWithNoData) { auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert( *config_blob); @@ -469,7 +473,8 @@ TEST_P(TcpProxyIntegrationTest, TestMaxDownstreamConnectionDurationWithLargeOuts auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert( *config_blob); diff --git a/test/integration/tcp_tunneling_integration_test.cc b/test/integration/tcp_tunneling_integration_test.cc index efc7222c90cb..d5056a28720c 100644 --- a/test/integration/tcp_tunneling_integration_test.cc +++ b/test/integration/tcp_tunneling_integration_test.cc @@ -1,7 +1,7 @@ #include #include "envoy/config/bootstrap/v3/bootstrap.pb.h" -#include "envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.pb.h" +#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" #include "test/integration/http_integration.h" #include "test/integration/http_protocol_integration.h" @@ -324,7 +324,7 @@ class TcpTunnelingIntegrationTest : public testing::TestWithParam void { - envoy::config::filter::network::tcp_proxy::v2::TcpProxy proxy_config; + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy proxy_config; proxy_config.set_stat_prefix("tcp_stats"); proxy_config.set_cluster("cluster_0"); proxy_config.mutable_tunneling_config()->set_hostname("host.com"); @@ -467,9 +467,10 @@ TEST_P(TcpTunnelingIntegrationTest, TestIdletimeoutWithLargeOutstandingData) { auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config(); ASSERT_TRUE( - config_blob->Is()); + config_blob + ->Is()); auto tcp_proxy_config = MessageUtil::anyConvert(*config_blob); + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy)>(*config_blob); tcp_proxy_config.mutable_idle_timeout()->set_nanos( std::chrono::duration_cast(std::chrono::milliseconds(500)) .count()); diff --git a/test/integration/transport_socket_match_integration_test.cc b/test/integration/transport_socket_match_integration_test.cc index 75d101beb702..3487d546c592 100644 --- a/test/integration/transport_socket_match_integration_test.cc +++ b/test/integration/transport_socket_match_integration_test.cc @@ -34,7 +34,7 @@ name: "tls_socket" transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "%s" } diff --git a/test/integration/version_integration_test.cc b/test/integration/version_integration_test.cc index b07d2c881abb..4888f5c7ceef 100644 --- a/test/integration/version_integration_test.cc +++ b/test/integration/version_integration_test.cc @@ -28,7 +28,7 @@ const char ExampleIpTaggingConfig[] = R"EOF( TEST_P(VersionIntegrationTest, DEPRECATED_FEATURE_TEST(IpTaggingV2StaticStructConfig)) { config_helper_.addFilter(absl::StrCat(R"EOF( name: envoy.filters.http.ip_tagging - config: + hidden_envoy_deprecated_config: )EOF", ExampleIpTaggingConfig)); @@ -41,6 +41,7 @@ TEST_P(VersionIntegrationTest, DEPRECATED_FEATURE_TEST(IpTaggingV2StaticStructCo // envoy.filters.http.ip_tagging from v2 TypedStruct config. TEST_P(VersionIntegrationTest, IpTaggingV2StaticTypedStructConfig) { + config_helper_.enableDeprecatedV2Api(); config_helper_.addFilter(absl::StrCat(R"EOF( name: ip_tagging typed_config: @@ -67,6 +68,7 @@ name: ip_tagging // envoy.filters.http.ip_tagging from v2 typed Any config. TEST_P(VersionIntegrationTest, IpTaggingV2StaticTypedConfig) { + config_helper_.enableDeprecatedV2Api(); config_helper_.addFilter(absl::StrCat(R"EOF( name: ip_tagging typed_config: diff --git a/test/integration/vhds_integration_test.cc b/test/integration/vhds_integration_test.cc index 9e9847804e58..b0f5c4207dc1 100644 --- a/test/integration/vhds_integration_test.cc +++ b/test/integration/vhds_integration_test.cc @@ -67,7 +67,7 @@ const std::string& config() { - filters: - name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: config_test http_filters: - name: envoy.filters.http.on_demand @@ -76,6 +76,7 @@ const std::string& config() { rds: route_config_name: my_route config_source: + resource_api_version: V3 api_config_source: api_type: GRPC grpc_services: @@ -100,6 +101,7 @@ const char RdsConfig[] = R"EOF( name: my_route vhds: config_source: + resource_api_version: V3 api_config_source: api_type: DELTA_GRPC grpc_services: @@ -117,6 +119,7 @@ name: my_route route: { cluster: my_service } vhds: config_source: + resource_api_version: V3 api_config_source: api_type: DELTA_GRPC grpc_services: @@ -367,8 +370,8 @@ class VhdsIntegrationTest : public HttpIntegrationTest, resource->set_name("my_route/vhost_1"); resource->set_version("4"); resource->mutable_resource()->PackFrom( - API_DOWNGRADE(TestUtility::parseYaml( - virtualHostYaml("my_route/vhost_1", "vhost_1, vhost.first")))); + TestUtility::parseYaml( + virtualHostYaml("my_route/vhost_1", "vhost_1, vhost.first"))); resource->add_aliases("my_route/vhost.first"); ret.set_nonce("test-nonce-0"); diff --git a/test/integration/xds_integration_test.cc b/test/integration/xds_integration_test.cc index 4e0dcaf73c07..5d62e6521d12 100644 --- a/test/integration/xds_integration_test.cc +++ b/test/integration/xds_integration_test.cc @@ -17,7 +17,7 @@ namespace { using testing::HasSubstr; -// This is a minimal litmus test for the v2 xDS APIs. +// This is a minimal litmus test for the v3 xDS APIs. class XdsIntegrationTest : public testing::TestWithParam, public HttpIntegrationTest { public: @@ -113,7 +113,7 @@ class LdsInplaceUpdateTcpProxyIntegrationTest filters: - name: envoy.filters.network.tcp_proxy typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp_stats cluster: cluster_0 - filter_chain_match: @@ -121,7 +121,7 @@ class LdsInplaceUpdateTcpProxyIntegrationTest filters: - name: envoy.filters.network.tcp_proxy typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy stat_prefix: tcp_stats cluster: cluster_1 )EOF") {} @@ -471,17 +471,17 @@ TEST_P(LdsInplaceUpdateHttpIntegrationTest, ReloadConfigUpdatingDefaultFilterCha -> void { auto default_filter_chain = bootstrap.mutable_static_resources()->mutable_listeners(0)->mutable_default_filter_chain(); - default_filter_chain->set_name("default_filter_chain_v2"); + default_filter_chain->set_name("default_filter_chain_v3"); }); new_config_helper.setLds("1"); test_server_->waitForCounterGe("listener_manager.listener_in_place_updated", 1); test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); - auto codec_client_default_v2 = createHttpCodec("alpndefaultv2"); + auto codec_client_default_v3 = createHttpCodec("alpndefaultv3"); - Cleanup cleanup2([c_default_v2 = codec_client_default_v2.get()]() { c_default_v2->close(); }); + Cleanup cleanup2([c_default_v3 = codec_client_default_v3.get()]() { c_default_v3->close(); }); expectResponseHeaderConnectionClose(*codec_client_default, true); - expectResponseHeaderConnectionClose(*codec_client_default_v2, false); + expectResponseHeaderConnectionClose(*codec_client_default_v3, false); expectConnenctionServed(); } diff --git a/test/server/BUILD b/test/server/BUILD index 1f0c7018deda..c26a2ebcc87a 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -60,6 +60,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/server:instance_mocks", "//test/test_common:environment_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", @@ -258,6 +259,7 @@ envoy_cc_test( "//source/server:active_raw_udp_listener_config", "//test/test_common:network_utility_lib", "//test/test_common:registry_lib", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", diff --git a/test/server/api_listener_test.cc b/test/server/api_listener_test.cc index ff9fa0d02fd0..f337d985a5ea 100644 --- a/test/server/api_listener_test.cc +++ b/test/server/api_listener_test.cc @@ -40,7 +40,7 @@ name: test_api_listener port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router @@ -100,7 +100,7 @@ name: test_api_listener port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index 37fdca915316..4058746d0a21 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -121,9 +121,9 @@ envoy_cc_test_library( "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", - "@envoy_api//envoy/config/filter/network/http_connection_manager/v2:pkg_cc_proto", "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", ], ) diff --git a/test/server/config_validation/xds_fuzz.cc b/test/server/config_validation/xds_fuzz.cc index f55905f1fb67..3784e14af2ec 100644 --- a/test/server/config_validation/xds_fuzz.cc +++ b/test/server/config_validation/xds_fuzz.cc @@ -386,7 +386,7 @@ envoy::admin::v3::ListenersConfigDump XdsFuzzTest::getListenersConfigDump() { return dynamic_cast(*message_ptr); } -std::vector XdsFuzzTest::getRoutesConfigDump() { +std::vector XdsFuzzTest::getRoutesConfigDump() { auto map = test_server_->server().admin().getConfigTracker().getCallbacksMap(); // There is no route config dump before envoy has a route. @@ -399,9 +399,9 @@ std::vector XdsFuzzTest::getRoutesConfigDump // Since the route config dump gives the RouteConfigurations as an Any, go through and cast them // back to RouteConfigurations. - std::vector dump_routes; + std::vector dump_routes; for (const auto& route : dump.dynamic_route_configs()) { - envoy::api::v2::RouteConfiguration dyn_route; + envoy::config::route::v3::RouteConfiguration dyn_route; route.route_config().UnpackTo(&dyn_route); dump_routes.push_back(dyn_route); } diff --git a/test/server/config_validation/xds_fuzz.h b/test/server/config_validation/xds_fuzz.h index b2fcbf063937..602c312ee8a0 100644 --- a/test/server/config_validation/xds_fuzz.h +++ b/test/server/config_validation/xds_fuzz.h @@ -63,7 +63,7 @@ class XdsFuzzTest : public HttpIntegrationTest { void verifyRoutes(); envoy::admin::v3::ListenersConfigDump getListenersConfigDump(); - std::vector getRoutesConfigDump(); + std::vector getRoutesConfigDump(); bool eraseListener(const std::string& listener_name); bool hasRoute(const std::string& route_name); diff --git a/test/server/config_validation/xds_fuzz_test.cc b/test/server/config_validation/xds_fuzz_test.cc index 207d1132589a..5b41d4a01bbb 100644 --- a/test/server/config_validation/xds_fuzz_test.cc +++ b/test/server/config_validation/xds_fuzz_test.cc @@ -12,7 +12,7 @@ DEFINE_PROTO_FUZZER(const test::server::config_validation::XdsTestCase& input) { ENVOY_LOG_MISC(debug, "ProtoValidationException: {}", e.what()); return; } - XdsFuzzTest test(input, envoy::config::core::v3::ApiVersion::V2); + XdsFuzzTest test(input, envoy::config::core::v3::ApiVersion::V3); test.replay(); } diff --git a/test/server/config_validation/xds_verifier.cc b/test/server/config_validation/xds_verifier.cc index 90b6a8821d8e..4107fd23eabe 100644 --- a/test/server/config_validation/xds_verifier.cc +++ b/test/server/config_validation/xds_verifier.cc @@ -20,7 +20,7 @@ XdsVerifier::XdsVerifier(test::server::config_validation::Config::SotwOrDelta so */ std::string XdsVerifier::getRoute(const envoy::config::listener::v3::Listener& listener) { envoy::config::listener::v3::Filter filter0 = listener.filter_chains()[0].filters()[0]; - envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager conn_man; + envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager conn_man; filter0.typed_config().UnpackTo(&conn_man); return conn_man.rds().route_config_name(); } diff --git a/test/server/config_validation/xds_verifier.h b/test/server/config_validation/xds_verifier.h index d19ad8f6c1ab..61d576ec1f40 100644 --- a/test/server/config_validation/xds_verifier.h +++ b/test/server/config_validation/xds_verifier.h @@ -2,9 +2,9 @@ #include "envoy/common/exception.h" #include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/config/endpoint/v3/endpoint.pb.h" -#include "envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.pb.h" #include "envoy/config/listener/v3/listener.pb.h" #include "envoy/config/route/v3/route.pb.h" +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" #include "common/common/assert.h" diff --git a/test/server/configuration_impl_test.cc b/test/server/configuration_impl_test.cc index ea6da93626b9..eeeba3585b77 100644 --- a/test/server/configuration_impl_test.cc +++ b/test/server/configuration_impl_test.cc @@ -20,6 +20,7 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/server/instance.h" #include "test/test_common/environment.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "fmt/printf.h" @@ -480,6 +481,7 @@ TEST(InitialImplTest, EmptyDeprecatedRuntime) { // A deprecated Runtime is transformed to the equivalent LayeredRuntime. TEST(InitialImplTest, DeprecatedRuntimeTranslation) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string bootstrap_yaml = R"EOF( runtime: symlink_root: /srv/runtime/current diff --git a/test/server/filter_chain_manager_impl_test.cc b/test/server/filter_chain_manager_impl_test.cc index 92fdec6d8997..bf009afbf27e 100644 --- a/test/server/filter_chain_manager_impl_test.cc +++ b/test/server/filter_chain_manager_impl_test.cc @@ -119,7 +119,7 @@ class FilterChainManagerImplTest : public testing::Test { transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } diff --git a/test/server/lds_api_test.cc b/test/server/lds_api_test.cc index 1cfeea582ca8..4f59dbf6723e 100644 --- a/test/server/lds_api_test.cc +++ b/test/server/lds_api_test.cc @@ -223,13 +223,13 @@ TEST_F(LdsApiTest, Basic) { "version_info": "0", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener1", "address": { "socket_address": { "address": "tcp://0.0.0.1", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] }, { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener2", "address": { "socket_address": { "address": "tcp://0.0.0.2", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] @@ -256,13 +256,13 @@ TEST_F(LdsApiTest, Basic) { "version_info": "1", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener1", "address": { "socket_address": { "address": "tcp://0.0.0.1", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] }, { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener3", "address": { "socket_address": { "address": "tcp://0.0.0.3", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] @@ -296,7 +296,7 @@ TEST_F(LdsApiTest, UpdateVersionOnListenerRemove) { "version_info": "0", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener1", "address": { "socket_address": { "address": "tcp://0.0.0.1", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] @@ -345,7 +345,7 @@ TEST_F(LdsApiTest, TlsConfigWithoutCaCert) { std::string response1_yaml = R"EOF( version_info: '1' resources: -- "@type": type.googleapis.com/envoy.api.v2.Listener +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener name: listener0 address: socket_address: @@ -368,7 +368,7 @@ version_info: '1' std::string response2_basic = R"EOF( version_info: '1' resources: -- "@type": type.googleapis.com/envoy.api.v2.Listener +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener name: listener-8080 address: socket_address: @@ -378,7 +378,7 @@ version_info: '1' - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: @@ -427,13 +427,13 @@ TEST_F(LdsApiTest, ReplacingListenerWithSameAddress) { "version_info": "0", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener1", "address": { "socket_address": { "address": "tcp://0.0.0.1", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] }, { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener2", "address": { "socket_address": { "address": "tcp://0.0.0.2", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] @@ -460,13 +460,13 @@ TEST_F(LdsApiTest, ReplacingListenerWithSameAddress) { "version_info": "1", "resources": [ { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener1", "address": { "socket_address": { "address": "tcp://0.0.0.1", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] }, { - "@type": "type.googleapis.com/envoy.api.v2.Listener", + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", "name": "listener3", "address": { "socket_address": { "address": "tcp://0.0.0.2", "port_value": 0 } }, "filter_chains": [ { "filters": null } ] diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 742602c958aa..3d21de26252e 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -29,6 +29,7 @@ #include "test/server/utility.h" #include "test/test_common/network_utility.h" #include "test/test_common/registry.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "absl/strings/escaping.h" @@ -213,7 +214,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsTransportSocket) { transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: @@ -240,6 +241,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsTransportSocket) { TEST_F(ListenerManagerImplWithRealFiltersTest, DEPRECATED_FEATURE_TEST(TlsTransportSocketLegacyConfig)) { + TestDeprecatedV2Api _deprecated_v2_api; const std::string yaml = TestEnvironment::substitute(R"EOF( address: socket_address: @@ -286,7 +288,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, DEPRECATED_FEATURE_TEST(TlsContex transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: @@ -2154,7 +2156,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithDestinationP transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2200,7 +2202,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithDestinationI transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2246,7 +2248,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithServerNamesM transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2293,7 +2295,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithTransportPro transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2336,7 +2338,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithApplicationP transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2382,7 +2384,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourceTypeMa transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2443,7 +2445,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourceIpMatc transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2503,7 +2505,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourceIpv6Ma transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2542,7 +2544,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourcePortMa transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2588,7 +2590,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainWithSourceType transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2599,7 +2601,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainWithSourceType transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" } @@ -2609,7 +2611,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainWithSourceType transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } @@ -2676,7 +2678,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" } @@ -2686,7 +2688,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2696,7 +2698,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } @@ -2762,7 +2764,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" } @@ -2772,7 +2774,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2782,7 +2784,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } @@ -2848,7 +2850,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNam transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" } @@ -2861,7 +2863,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNam transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2874,7 +2876,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNam transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem" } @@ -2949,7 +2951,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithTransport transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -2994,7 +2996,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithApplicati transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3044,7 +3046,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithMultipleR transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3103,7 +3105,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDifferent transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3116,7 +3118,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDifferent transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3147,7 +3149,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3160,7 +3162,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -3435,7 +3437,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInline) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { inline_string: ")EOF", @@ -3463,7 +3465,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateChainInlinePrivateK - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - private_key: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_key.pem" } @@ -3486,7 +3488,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateIncomplete) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_chain.pem" } @@ -3509,7 +3511,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidCertificateC - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { inline_string: "invalid" } @@ -3533,7 +3535,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidIntermediate - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { inline_string: ")EOF", @@ -3555,7 +3557,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidPrivateKey) - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_chain.pem" } @@ -3575,7 +3577,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidTrustedCA) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_chain.pem" } @@ -3600,7 +3602,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, Metadata) { filters: - name: http typed_config: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: metadata_test route_config: virtual_hosts: @@ -3983,7 +3985,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, CRLFilename) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4010,7 +4012,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, CRLInline) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4036,7 +4038,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, InvalidCRLInline) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4059,7 +4061,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, CRLWithNoCA) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4081,7 +4083,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, VerifySanWithNoCA) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4107,7 +4109,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, VerifyIgnoreExpirationWithNoCA) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4131,7 +4133,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, VerifyIgnoreExpirationWithCA) { - transport_socket: name: tls typed_config: - "@type": type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem" } @@ -4162,7 +4164,7 @@ name: test_api_listener port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router @@ -4191,7 +4193,7 @@ name: test_api_listener port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router @@ -4220,7 +4222,7 @@ name: test_api_listener port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router @@ -4243,7 +4245,7 @@ name: test_api_listener_2 port_value: 1234 api_listener: api_listener: - "@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: hcm route_config: name: api_router diff --git a/test/server/server_test.cc b/test/server/server_test.cc index 5249a38f9828..d7abaf788c1d 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -662,22 +662,50 @@ TEST_P(ServerInstanceImplTest, LoadsBootstrapFromPbText) { EXPECT_EQ("bootstrap_id", server_->localInfo().node().id()); } -// Validate that bootstrap v2 pb_text with deprecated fields loads. -TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(LoadsV2BootstrapFromPbText)) { - EXPECT_LOG_CONTAINS( - "trace", "Configuration does not parse cleanly as v3", - initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.pb_text")); +// Validate that bootstrap v2 is rejected when --bootstrap-version is not set. +TEST_P(ServerInstanceImplTest, + DEPRECATED_FEATURE_TEST(FailToLoadV2BootstrapWithoutExplicitVersion)) { + EXPECT_THROW_WITH_REGEX( + initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.pb_text"), + DeprecatedMajorVersionException, + "Support for v2 will be removed from Envoy at the start of Q1 2021. You may make use of v2 " + "in Q3 2020 by setting"); +} + +// Validate that bootstrap v2 pb_text with deprecated fields loads when --bootstrap-version is set. +TEST_P(ServerInstanceImplTest, + DEPRECATED_FEATURE_TEST(LoadsV2BootstrapWithExplicitVersionFromPbText)) { + options_.bootstrap_version_ = 2; + initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.pb_text"); EXPECT_FALSE(server_->localInfo().node().hidden_envoy_deprecated_build_version().empty()); } -// Validate that bootstrap v2 YAML with deprecated fields loads. -TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(LoadsV2BootstrapFromYaml)) { +// Validate that bootstrap v2 pb_text with deprecated fields fails to load when +// --bootstrap-version is not set. +TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(FailToLoadV2BootstrapFromPbText)) { + EXPECT_THROW_WITH_REGEX( + initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.pb_text"), + EnvoyException, "The v2 xDS major version is deprecated and disabled by default."); +} + +// Validate that bootstrap v2 YAML with deprecated fields loads when --bootstrap-version is set. +TEST_P(ServerInstanceImplTest, + DEPRECATED_FEATURE_TEST(LoadsV2BootstrapWithExplicitVersionFromYaml)) { + options_.bootstrap_version_ = 2; EXPECT_LOG_CONTAINS( "trace", "Configuration does not parse cleanly as v3", initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.yaml")); EXPECT_FALSE(server_->localInfo().node().hidden_envoy_deprecated_build_version().empty()); } +// Validate that bootstrap v2 YAML with deprecated fields fails to load when +// --bootstrap-version is not set. +TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(FailsToLoadV2BootstrapFromYaml)) { + EXPECT_THROW_WITH_REGEX( + initialize("test/server/test_data/server/valid_v2_but_invalid_v3_bootstrap.yaml"), + EnvoyException, "The v2 xDS major version is deprecated and disabled by default."); +} + // Validate that bootstrap v3 pb_text with new fields loads fails if V2 config is specified. TEST_P(ServerInstanceImplTest, FailToLoadV3ConfigWhenV2SelectedFromPbText) { options_.bootstrap_version_ = 2; @@ -849,6 +877,7 @@ TEST_P(ServerInstanceImplTest, BootstrapRtdsThroughAdsViaEdsFails) { } TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(InvalidLegacyBootstrapRuntime)) { + options_.bootstrap_version_ = 2; EXPECT_THROW_WITH_MESSAGE( initialize("test/server/test_data/server/invalid_legacy_runtime_bootstrap.yaml"), EnvoyException, "Invalid runtime entry value for foo"); @@ -1109,6 +1138,7 @@ TEST_P(ServerInstanceImplTest, NoHttpTracing) { TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(ZipkinHttpTracingEnabled)) { options_.service_cluster_name_ = "some_cluster_name"; options_.service_node_name_ = "some_node_name"; + options_.bootstrap_version_ = 2; EXPECT_NO_THROW(initialize("test/server/test_data/server/zipkin_tracing_deprecated_config.yaml")); EXPECT_EQ("zipkin", server_->httpContext().defaultTracingConfig().http().name()); } diff --git a/test/server/test_data/server/zipkin_tracing_deprecated_config.yaml b/test/server/test_data/server/zipkin_tracing_deprecated_config.yaml index e79bd34b52e7..3de1e923fbfc 100644 --- a/test/server/test_data/server/zipkin_tracing_deprecated_config.yaml +++ b/test/server/test_data/server/zipkin_tracing_deprecated_config.yaml @@ -10,3 +10,8 @@ tracing: collector_cluster: zipkin collector_endpoint: "/api/v1/spans" collector_endpoint_version: HTTP_JSON +layered_runtime: + layers: + - name: static_layer + static_layer: + envoy.reloadable_features.enable_deprecated_v2_api: true diff --git a/test/test_common/resources.h b/test/test_common/resources.h index 1f0d762b68e8..e6508837f3cf 100644 --- a/test/test_common/resources.h +++ b/test/test_common/resources.h @@ -12,18 +12,20 @@ namespace Config { */ class TypeUrlValues { public: - const std::string Listener{"type.googleapis.com/envoy.api.v2.Listener"}; - const std::string Cluster{"type.googleapis.com/envoy.api.v2.Cluster"}; - const std::string ClusterLoadAssignment{"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"}; - const std::string Secret{"type.googleapis.com/envoy.api.v2.auth.Secret"}; - const std::string RouteConfiguration{"type.googleapis.com/envoy.api.v2.RouteConfiguration"}; - const std::string VirtualHost{"type.googleapis.com/envoy.api.v2.route.VirtualHost"}; + const std::string Listener{"type.googleapis.com/envoy.config.listener.v3.Listener"}; + const std::string Cluster{"type.googleapis.com/envoy.config.cluster.v3.Cluster"}; + const std::string ClusterLoadAssignment{ + "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"}; + const std::string Secret{"type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret"}; + const std::string RouteConfiguration{ + "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"}; + const std::string VirtualHost{"type.googleapis.com/envoy.config.route.v3.VirtualHost"}; const std::string ScopedRouteConfiguration{ - "type.googleapis.com/envoy.api.v2.ScopedRouteConfiguration"}; - const std::string Runtime{"type.googleapis.com/envoy.service.discovery.v2.Runtime"}; + "type.googleapis.com/envoy.config.route.v3.ScopedRouteConfiguration"}; + const std::string Runtime{"type.googleapis.com/envoy.service.runtime.v3.Runtime"}; }; using TypeUrl = ConstSingleton; } // namespace Config -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/test/test_common/test_runtime.h b/test/test_common/test_runtime.h index 08e0c441efc3..4a4ae54c8632 100644 --- a/test/test_common/test_runtime.h +++ b/test/test_common/test_runtime.h @@ -40,10 +40,10 @@ class TestScopedRuntime { generator_, validation_visitor_, *api_)); } -private: +protected: Event::MockDispatcher dispatcher_; testing::NiceMock tls_; - Stats::IsolatedStoreImpl store_; + Stats::TestUtil::TestStore store_; Random::MockRandomGenerator generator_; Api::ApiPtr api_; testing::NiceMock local_info_; @@ -51,4 +51,14 @@ class TestScopedRuntime { std::unique_ptr loader_; }; +class TestDeprecatedV2Api : public TestScopedRuntime { +public: + TestDeprecatedV2Api() { + Runtime::LoaderSingleton::getExisting()->mergeValues({ + {"envoy.reloadable_features.enable_deprecated_v2_api", "true"}, + {"envoy.features.enable_all_deprecated_features", "true"}, + }); + } +}; + } // namespace Envoy diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index 928cb440c5f2..adb3e8b4de7c 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -249,55 +249,6 @@ std::vector TestUtility::listFiles(const std::string& path, bool re return file_names; } -std::string TestUtility::xdsResourceName(const ProtobufWkt::Any& resource) { - if (resource.type_url() == Config::TypeUrl::get().Listener) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::TypeUrl::get().RouteConfiguration) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::TypeUrl::get().Cluster) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::TypeUrl::get().ClusterLoadAssignment) { - return TestUtility::anyConvert(resource) - .cluster_name(); - } - if (resource.type_url() == Config::TypeUrl::get().VirtualHost) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::TypeUrl::get().Runtime) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource) - .cluster_name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource).name(); - } - if (resource.type_url() == Config::getTypeUrl( - envoy::config::core::v3::ApiVersion::V3)) { - return TestUtility::anyConvert(resource).name(); - } - throw EnvoyException( - absl::StrCat("xdsResourceName does not know about type URL ", resource.type_url())); -} - std::string TestUtility::addLeftAndRightPadding(absl::string_view to_pad, int desired_length) { int line_fill_len = desired_length - to_pad.length(); int first_half_len = line_fill_len / 2; diff --git a/test/test_common/utility.h b/test/test_common/utility.h index 3dc15c8ccac0..1e0d25cd57f9 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -438,13 +438,6 @@ class TestUtility { return AssertionSuccess(); } - /** - * Returns the closest thing to a sensible "name" field for the given xDS resource. - * @param resource the resource to extract the name of. - * @return the resource's name. - */ - static std::string xdsResourceName(const ProtobufWkt::Any& resource); - /** * Returns a "novel" IPv4 loopback address, if available. * For many tests, we want a loopback address other than 127.0.0.1 where possible. For some diff --git a/test/tools/router_check/BUILD b/test/tools/router_check/BUILD index 5eaeef6cebba..1da1d8c084d2 100644 --- a/test/tools/router_check/BUILD +++ b/test/tools/router_check/BUILD @@ -38,6 +38,7 @@ envoy_cc_test_library( "//source/exe:platform_impl_lib", "//test/mocks/server:instance_mocks", "//test/test_common:printers_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", diff --git a/test/tools/router_check/router_check.cc b/test/tools/router_check/router_check.cc index 5e03f132e3a7..fea89d3eca98 100644 --- a/test/tools/router_check/router_check.cc +++ b/test/tools/router_check/router_check.cc @@ -4,6 +4,7 @@ #include "exe/platform_impl.h" +#include "test/test_common/test_runtime.h" #include "test/tools/router_check/router.h" int main(int argc, char* argv[]) { @@ -12,6 +13,8 @@ int main(int argc, char* argv[]) { const bool enforce_coverage = options.failUnder() != 0.0; // We need this to ensure WSAStartup is called on Windows Envoy::PlatformImpl platform_impl_; + // Until we remove v2 API, the tool will warn but not fail. + Envoy::TestDeprecatedV2Api _deprecated_v2_api; try { Envoy::RouterCheckTool checktool = From 6609d20fc3721c90970cde99802527a6500c9117 Mon Sep 17 00:00:00 2001 From: Lizan Zhou Date: Wed, 11 Nov 2020 15:15:42 -0800 Subject: [PATCH 072/117] bazelci: exclude wasm (#13974) Signed-off-by: Lizan Zhou --- .bazelci/presubmit.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index dcef9acbef46..1d034283e680 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -10,6 +10,7 @@ tasks: test_flags: - "--config=remote-clang-libc++" - "--config=remote-ci" + - "--define=wasm=disabled" - "--jobs=75" coverage: name: "Coverage" From de8334227416ebc16a344e3b3abab2998cb34eeb Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 11 Nov 2020 19:00:17 -0500 Subject: [PATCH 073/117] api: add unified matching API (#13926) * api: add unified matching API Signed-off-by: Snow Pettersen --- api/envoy/config/common/matcher/v3/BUILD | 2 + .../config/common/matcher/v3/matcher.proto | 122 ++++++++++++++ api/envoy/config/common/matcher/v4alpha/BUILD | 2 + .../common/matcher/v4alpha/matcher.proto | 149 ++++++++++++++++++ .../envoy/config/common/matcher/v3/BUILD | 2 + .../config/common/matcher/v3/matcher.proto | 122 ++++++++++++++ .../envoy/config/common/matcher/v4alpha/BUILD | 2 + .../common/matcher/v4alpha/matcher.proto | 149 ++++++++++++++++++ 8 files changed, 550 insertions(+) diff --git a/api/envoy/config/common/matcher/v3/BUILD b/api/envoy/config/common/matcher/v3/BUILD index 482d7fe987f2..2f90ace882d9 100644 --- a/api/envoy/config/common/matcher/v3/BUILD +++ b/api/envoy/config/common/matcher/v3/BUILD @@ -6,7 +6,9 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/config/core/v3:pkg", "//envoy/config/route/v3:pkg", + "//envoy/type/matcher/v3:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", ], ) diff --git a/api/envoy/config/common/matcher/v3/matcher.proto b/api/envoy/config/common/matcher/v3/matcher.proto index 119740547aaf..b9b69519a4b9 100644 --- a/api/envoy/config/common/matcher/v3/matcher.proto +++ b/api/envoy/config/common/matcher/v3/matcher.proto @@ -2,7 +2,9 @@ syntax = "proto3"; package envoy.config.common.matcher.v3; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/route/v3/route_components.proto"; +import "envoy/type/matcher/v3/value.proto"; import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; @@ -16,6 +18,126 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Unified Matcher API] +// A matcher, which may traverse a matching tree in order to result in a match action. +// During matching, the tree will be traversed until a match is found, or if no match +// is found the action specified by the most specific on_no_match will be evaluated. +// As an on_no_match might result in another matching tree being evaluated, this process +// might repeat several times until the final OnMatch (or no match) is decided. +message Matcher { + // What to do if a match is successful. + message OnMatch { + oneof on_match { + option (validate.required) = true; + + // Nested matcher to evaluate. + // If the nested matcher does not match and does not specify + // on_no_match, then this matcher is considered not to have + // matched, even if a predicate at this level or above returned + // true. + Matcher matcher = 1; + + // Protocol-specific action to take. + core.v3.TypedExtensionConfig action = 2; + } + } + + // A linear list of field matchers. + // The field matchers are evaluated in order, and the first match + // wins. + message MatcherList { + // Predicate to determine if a match is successful. + message Predicate { + // Predicate for a single input field. + message SinglePredicate { + // Protocol-specific specification of input field to match on. + core.v3.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + oneof matcher { + option (validate.required) = true; + + // Use existing infrastructure for actually matching the + // value. + type.matcher.v3.ValueMatcher value_match = 2; + + // Extension for custom matching logic. + core.v3.TypedExtensionConfig custom_match = 3; + } + } + + // A list of two or more matchers. Used to allow using a list within a oneof. + message PredicateList { + repeated Predicate predicate = 1 [(validate.rules).repeated = {min_items: 2}]; + } + + oneof match_type { + option (validate.required) = true; + + // A single predicate to evaluate. + SinglePredicate single_predicate = 1; + + // A list of predicates to be OR-ed together. + PredicateList or_matcher = 2; + + // A list of predicates to be AND-ed together. + PredicateList and_matcher = 3; + } + } + + // An individual matcher. + message FieldMatcher { + // Determines if the match succeeds. + Predicate predicate = 1 [(validate.rules).message = {required: true}]; + + // What to do if the match succeeds. + OnMatch on_match = 2 [(validate.rules).message = {required: true}]; + } + + // A list of matchers. First match wins. + repeated FieldMatcher matchers = 1 [(validate.rules).repeated = {min_items: 1}]; + } + + message MatcherTree { + // A map of configured matchers. Used to allow using a map within a oneof. + message MatchMap { + map map = 1 [(validate.rules).map = {min_pairs: 1}]; + } + + // Protocol-specific specification of input field to match on. + core.v3.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + // Exact or prefix match maps in which to look up the input value. + // If the lookup succeeds, the match is considered successful, and + // the corresponding OnMatch is used. + oneof tree_type { + option (validate.required) = true; + + MatchMap exact_match_map = 2; + + // Longest matching prefix wins. + MatchMap prefix_match_map = 3; + + // Extension for custom matching logic. + core.v3.TypedExtensionConfig custom_match = 4; + } + } + + oneof matcher_type { + option (validate.required) = true; + + // A linear list of matchers to evaluate. + MatcherList matcher_list = 1; + + // A match tree to evaluate. + MatcherTree matcher_tree = 2; + } + + // Optional OnMatch to use if the matcher failed. + // If specified, the OnMatch is used, and the matcher is considered + // to have matched. + // If not specified, the matcher is considered not to have matched. + OnMatch on_no_match = 3; +} + // Match configuration. This is a recursive structure which allows complex nested match // configurations to be built using various logical operators. // [#next-free-field: 11] diff --git a/api/envoy/config/common/matcher/v4alpha/BUILD b/api/envoy/config/common/matcher/v4alpha/BUILD index 42a738be2e89..8c0f8a2e08d8 100644 --- a/api/envoy/config/common/matcher/v4alpha/BUILD +++ b/api/envoy/config/common/matcher/v4alpha/BUILD @@ -7,7 +7,9 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ "//envoy/config/common/matcher/v3:pkg", + "//envoy/config/core/v4alpha:pkg", "//envoy/config/route/v4alpha:pkg", + "//envoy/type/matcher/v4alpha:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", ], ) diff --git a/api/envoy/config/common/matcher/v4alpha/matcher.proto b/api/envoy/config/common/matcher/v4alpha/matcher.proto index 3be0d2aea3a8..fc60fce40f79 100644 --- a/api/envoy/config/common/matcher/v4alpha/matcher.proto +++ b/api/envoy/config/common/matcher/v4alpha/matcher.proto @@ -2,7 +2,9 @@ syntax = "proto3"; package envoy.config.common.matcher.v4alpha; +import "envoy/config/core/v4alpha/extension.proto"; import "envoy/config/route/v4alpha/route_components.proto"; +import "envoy/type/matcher/v4alpha/value.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -15,6 +17,153 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Unified Matcher API] +// A matcher, which may traverse a matching tree in order to result in a match action. +// During matching, the tree will be traversed until a match is found, or if no match +// is found the action specified by the most specific on_no_match will be evaluated. +// As an on_no_match might result in another matching tree being evaluated, this process +// might repeat several times until the final OnMatch (or no match) is decided. +message Matcher { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher"; + + // What to do if a match is successful. + message OnMatch { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.OnMatch"; + + oneof on_match { + option (validate.required) = true; + + // Nested matcher to evaluate. + // If the nested matcher does not match and does not specify + // on_no_match, then this matcher is considered not to have + // matched, even if a predicate at this level or above returned + // true. + Matcher matcher = 1; + + // Protocol-specific action to take. + core.v4alpha.TypedExtensionConfig action = 2; + } + } + + // A linear list of field matchers. + // The field matchers are evaluated in order, and the first match + // wins. + message MatcherList { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList"; + + // Predicate to determine if a match is successful. + message Predicate { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate"; + + // Predicate for a single input field. + message SinglePredicate { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate.SinglePredicate"; + + // Protocol-specific specification of input field to match on. + core.v4alpha.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + oneof matcher { + option (validate.required) = true; + + // Use existing infrastructure for actually matching the + // value. + type.matcher.v4alpha.ValueMatcher value_match = 2; + + // Extension for custom matching logic. + core.v4alpha.TypedExtensionConfig custom_match = 3; + } + } + + // A list of two or more matchers. Used to allow using a list within a oneof. + message PredicateList { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate.PredicateList"; + + repeated Predicate predicate = 1 [(validate.rules).repeated = {min_items: 2}]; + } + + oneof match_type { + option (validate.required) = true; + + // A single predicate to evaluate. + SinglePredicate single_predicate = 1; + + // A list of predicates to be OR-ed together. + PredicateList or_matcher = 2; + + // A list of predicates to be AND-ed together. + PredicateList and_matcher = 3; + } + } + + // An individual matcher. + message FieldMatcher { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.FieldMatcher"; + + // Determines if the match succeeds. + Predicate predicate = 1 [(validate.rules).message = {required: true}]; + + // What to do if the match succeeds. + OnMatch on_match = 2 [(validate.rules).message = {required: true}]; + } + + // A list of matchers. First match wins. + repeated FieldMatcher matchers = 1 [(validate.rules).repeated = {min_items: 1}]; + } + + message MatcherTree { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherTree"; + + // A map of configured matchers. Used to allow using a map within a oneof. + message MatchMap { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherTree.MatchMap"; + + map map = 1 [(validate.rules).map = {min_pairs: 1}]; + } + + // Protocol-specific specification of input field to match on. + core.v4alpha.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + // Exact or prefix match maps in which to look up the input value. + // If the lookup succeeds, the match is considered successful, and + // the corresponding OnMatch is used. + oneof tree_type { + option (validate.required) = true; + + MatchMap exact_match_map = 2; + + // Longest matching prefix wins. + MatchMap prefix_match_map = 3; + + // Extension for custom matching logic. + core.v4alpha.TypedExtensionConfig custom_match = 4; + } + } + + oneof matcher_type { + option (validate.required) = true; + + // A linear list of matchers to evaluate. + MatcherList matcher_list = 1; + + // A match tree to evaluate. + MatcherTree matcher_tree = 2; + } + + // Optional OnMatch to use if the matcher failed. + // If specified, the OnMatch is used, and the matcher is considered + // to have matched. + // If not specified, the matcher is considered not to have matched. + OnMatch on_no_match = 3; +} + // Match configuration. This is a recursive structure which allows complex nested match // configurations to be built using various logical operators. // [#next-free-field: 11] diff --git a/generated_api_shadow/envoy/config/common/matcher/v3/BUILD b/generated_api_shadow/envoy/config/common/matcher/v3/BUILD index 482d7fe987f2..2f90ace882d9 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v3/BUILD +++ b/generated_api_shadow/envoy/config/common/matcher/v3/BUILD @@ -6,7 +6,9 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/config/core/v3:pkg", "//envoy/config/route/v3:pkg", + "//envoy/type/matcher/v3:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", ], ) diff --git a/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto b/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto index 119740547aaf..b9b69519a4b9 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto +++ b/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto @@ -2,7 +2,9 @@ syntax = "proto3"; package envoy.config.common.matcher.v3; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/route/v3/route_components.proto"; +import "envoy/type/matcher/v3/value.proto"; import "udpa/annotations/migrate.proto"; import "udpa/annotations/status.proto"; @@ -16,6 +18,126 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Unified Matcher API] +// A matcher, which may traverse a matching tree in order to result in a match action. +// During matching, the tree will be traversed until a match is found, or if no match +// is found the action specified by the most specific on_no_match will be evaluated. +// As an on_no_match might result in another matching tree being evaluated, this process +// might repeat several times until the final OnMatch (or no match) is decided. +message Matcher { + // What to do if a match is successful. + message OnMatch { + oneof on_match { + option (validate.required) = true; + + // Nested matcher to evaluate. + // If the nested matcher does not match and does not specify + // on_no_match, then this matcher is considered not to have + // matched, even if a predicate at this level or above returned + // true. + Matcher matcher = 1; + + // Protocol-specific action to take. + core.v3.TypedExtensionConfig action = 2; + } + } + + // A linear list of field matchers. + // The field matchers are evaluated in order, and the first match + // wins. + message MatcherList { + // Predicate to determine if a match is successful. + message Predicate { + // Predicate for a single input field. + message SinglePredicate { + // Protocol-specific specification of input field to match on. + core.v3.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + oneof matcher { + option (validate.required) = true; + + // Use existing infrastructure for actually matching the + // value. + type.matcher.v3.ValueMatcher value_match = 2; + + // Extension for custom matching logic. + core.v3.TypedExtensionConfig custom_match = 3; + } + } + + // A list of two or more matchers. Used to allow using a list within a oneof. + message PredicateList { + repeated Predicate predicate = 1 [(validate.rules).repeated = {min_items: 2}]; + } + + oneof match_type { + option (validate.required) = true; + + // A single predicate to evaluate. + SinglePredicate single_predicate = 1; + + // A list of predicates to be OR-ed together. + PredicateList or_matcher = 2; + + // A list of predicates to be AND-ed together. + PredicateList and_matcher = 3; + } + } + + // An individual matcher. + message FieldMatcher { + // Determines if the match succeeds. + Predicate predicate = 1 [(validate.rules).message = {required: true}]; + + // What to do if the match succeeds. + OnMatch on_match = 2 [(validate.rules).message = {required: true}]; + } + + // A list of matchers. First match wins. + repeated FieldMatcher matchers = 1 [(validate.rules).repeated = {min_items: 1}]; + } + + message MatcherTree { + // A map of configured matchers. Used to allow using a map within a oneof. + message MatchMap { + map map = 1 [(validate.rules).map = {min_pairs: 1}]; + } + + // Protocol-specific specification of input field to match on. + core.v3.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + // Exact or prefix match maps in which to look up the input value. + // If the lookup succeeds, the match is considered successful, and + // the corresponding OnMatch is used. + oneof tree_type { + option (validate.required) = true; + + MatchMap exact_match_map = 2; + + // Longest matching prefix wins. + MatchMap prefix_match_map = 3; + + // Extension for custom matching logic. + core.v3.TypedExtensionConfig custom_match = 4; + } + } + + oneof matcher_type { + option (validate.required) = true; + + // A linear list of matchers to evaluate. + MatcherList matcher_list = 1; + + // A match tree to evaluate. + MatcherTree matcher_tree = 2; + } + + // Optional OnMatch to use if the matcher failed. + // If specified, the OnMatch is used, and the matcher is considered + // to have matched. + // If not specified, the matcher is considered not to have matched. + OnMatch on_no_match = 3; +} + // Match configuration. This is a recursive structure which allows complex nested match // configurations to be built using various logical operators. // [#next-free-field: 11] diff --git a/generated_api_shadow/envoy/config/common/matcher/v4alpha/BUILD b/generated_api_shadow/envoy/config/common/matcher/v4alpha/BUILD index 42a738be2e89..8c0f8a2e08d8 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v4alpha/BUILD +++ b/generated_api_shadow/envoy/config/common/matcher/v4alpha/BUILD @@ -7,7 +7,9 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ "//envoy/config/common/matcher/v3:pkg", + "//envoy/config/core/v4alpha:pkg", "//envoy/config/route/v4alpha:pkg", + "//envoy/type/matcher/v4alpha:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", ], ) diff --git a/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto b/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto index 3be0d2aea3a8..fc60fce40f79 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto +++ b/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto @@ -2,7 +2,9 @@ syntax = "proto3"; package envoy.config.common.matcher.v4alpha; +import "envoy/config/core/v4alpha/extension.proto"; import "envoy/config/route/v4alpha/route_components.proto"; +import "envoy/type/matcher/v4alpha/value.proto"; import "udpa/annotations/status.proto"; import "udpa/annotations/versioning.proto"; @@ -15,6 +17,153 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // [#protodoc-title: Unified Matcher API] +// A matcher, which may traverse a matching tree in order to result in a match action. +// During matching, the tree will be traversed until a match is found, or if no match +// is found the action specified by the most specific on_no_match will be evaluated. +// As an on_no_match might result in another matching tree being evaluated, this process +// might repeat several times until the final OnMatch (or no match) is decided. +message Matcher { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher"; + + // What to do if a match is successful. + message OnMatch { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.OnMatch"; + + oneof on_match { + option (validate.required) = true; + + // Nested matcher to evaluate. + // If the nested matcher does not match and does not specify + // on_no_match, then this matcher is considered not to have + // matched, even if a predicate at this level or above returned + // true. + Matcher matcher = 1; + + // Protocol-specific action to take. + core.v4alpha.TypedExtensionConfig action = 2; + } + } + + // A linear list of field matchers. + // The field matchers are evaluated in order, and the first match + // wins. + message MatcherList { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList"; + + // Predicate to determine if a match is successful. + message Predicate { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate"; + + // Predicate for a single input field. + message SinglePredicate { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate.SinglePredicate"; + + // Protocol-specific specification of input field to match on. + core.v4alpha.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + oneof matcher { + option (validate.required) = true; + + // Use existing infrastructure for actually matching the + // value. + type.matcher.v4alpha.ValueMatcher value_match = 2; + + // Extension for custom matching logic. + core.v4alpha.TypedExtensionConfig custom_match = 3; + } + } + + // A list of two or more matchers. Used to allow using a list within a oneof. + message PredicateList { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.Predicate.PredicateList"; + + repeated Predicate predicate = 1 [(validate.rules).repeated = {min_items: 2}]; + } + + oneof match_type { + option (validate.required) = true; + + // A single predicate to evaluate. + SinglePredicate single_predicate = 1; + + // A list of predicates to be OR-ed together. + PredicateList or_matcher = 2; + + // A list of predicates to be AND-ed together. + PredicateList and_matcher = 3; + } + } + + // An individual matcher. + message FieldMatcher { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherList.FieldMatcher"; + + // Determines if the match succeeds. + Predicate predicate = 1 [(validate.rules).message = {required: true}]; + + // What to do if the match succeeds. + OnMatch on_match = 2 [(validate.rules).message = {required: true}]; + } + + // A list of matchers. First match wins. + repeated FieldMatcher matchers = 1 [(validate.rules).repeated = {min_items: 1}]; + } + + message MatcherTree { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherTree"; + + // A map of configured matchers. Used to allow using a map within a oneof. + message MatchMap { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.common.matcher.v3.Matcher.MatcherTree.MatchMap"; + + map map = 1 [(validate.rules).map = {min_pairs: 1}]; + } + + // Protocol-specific specification of input field to match on. + core.v4alpha.TypedExtensionConfig input = 1 [(validate.rules).message = {required: true}]; + + // Exact or prefix match maps in which to look up the input value. + // If the lookup succeeds, the match is considered successful, and + // the corresponding OnMatch is used. + oneof tree_type { + option (validate.required) = true; + + MatchMap exact_match_map = 2; + + // Longest matching prefix wins. + MatchMap prefix_match_map = 3; + + // Extension for custom matching logic. + core.v4alpha.TypedExtensionConfig custom_match = 4; + } + } + + oneof matcher_type { + option (validate.required) = true; + + // A linear list of matchers to evaluate. + MatcherList matcher_list = 1; + + // A match tree to evaluate. + MatcherTree matcher_tree = 2; + } + + // Optional OnMatch to use if the matcher failed. + // If specified, the OnMatch is used, and the matcher is considered + // to have matched. + // If not specified, the matcher is considered not to have matched. + OnMatch on_no_match = 3; +} + // Match configuration. This is a recursive structure which allows complex nested match // configurations to be built using various logical operators. // [#next-free-field: 11] From e4eec652eb88a3bbe60ebe5cc49338df6f7ac0bf Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Nov 2020 01:21:51 +0000 Subject: [PATCH 074/117] examples: Add WebSocket sandbox (#13846) Signed-off-by: Ryan Northey --- ci/do_ci.sh | 2 +- docs/root/start/sandboxes/index.rst | 1 + docs/root/start/sandboxes/websocket.rst | 156 ++++++++++++++++++ examples/websocket/Dockerfile-proxy-ws | 5 + examples/websocket/Dockerfile-proxy-wss | 5 + .../Dockerfile-proxy-wss-passthrough | 5 + examples/websocket/README.md | 2 + examples/websocket/docker-compose.yaml | 35 ++++ examples/websocket/envoy-ws.yaml | 42 +++++ examples/websocket/envoy-wss-passthrough.yaml | 28 ++++ examples/websocket/envoy-wss.yaml | 109 ++++++++++++ examples/websocket/verify.sh | 54 ++++++ 12 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 docs/root/start/sandboxes/websocket.rst create mode 100644 examples/websocket/Dockerfile-proxy-ws create mode 100644 examples/websocket/Dockerfile-proxy-wss create mode 100644 examples/websocket/Dockerfile-proxy-wss-passthrough create mode 100644 examples/websocket/README.md create mode 100644 examples/websocket/docker-compose.yaml create mode 100644 examples/websocket/envoy-ws.yaml create mode 100644 examples/websocket/envoy-wss-passthrough.yaml create mode 100644 examples/websocket/envoy-wss.yaml create mode 100755 examples/websocket/verify.sh diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 8a9e68182115..a117499646f9 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -448,7 +448,7 @@ elif [[ "$CI_TARGET" == "verify_examples" ]]; then done docker images sudo apt-get update -y - sudo apt-get install -y -qq --no-install-recommends redis-tools + sudo apt-get install -y -qq --no-install-recommends expect redis-tools export DOCKER_NO_PULL=1 umask 027 chmod -R o-rwx examples/ diff --git a/docs/root/start/sandboxes/index.rst b/docs/root/start/sandboxes/index.rst index aac98b800966..28d5ecfb3a55 100644 --- a/docs/root/start/sandboxes/index.rst +++ b/docs/root/start/sandboxes/index.rst @@ -30,5 +30,6 @@ features. The following sandboxes are available: redis tls wasm-cc + websocket zipkin_tracing skywalking_tracing diff --git a/docs/root/start/sandboxes/websocket.rst b/docs/root/start/sandboxes/websocket.rst new file mode 100644 index 000000000000..4128936af15a --- /dev/null +++ b/docs/root/start/sandboxes/websocket.rst @@ -0,0 +1,156 @@ +.. _install_sandboxes_websocket: + +WebSockets +========== + +.. sidebar:: Requirements + + `openssl `_ + Used to create a TLS certificate for the websocket. + +This example walks through some of the ways that Envoy can be configured to proxy WebSockets. + +It demonstrates terminating a WebSocket connection with and without ``TLS``, and provides some basic examples +of proxying to encrypted and non-encrypted upstream sockets. + +.. warning:: + + For the sake of simplicity, the examples provided here do not authenticate any client certificates, + or validate any of the provided certificates. + + When using ``TLS``, you are strongly encouraged to :ref:`validate ` + all certificates wherever possible. + + You should also :ref:`authenticate clients ` + where you control both sides of the connection, or relevant protocols are available. + +.. include:: _include/docker-env-setup.rst + +Change directory to ``examples/websocket`` in the Envoy repository. + +Step 3: Create a certificate file for wss +***************************************** + +.. code-block:: console + + $ pwd + envoy/examples/websocket + $ mkdir -p certs + $ openssl req -batch -new -x509 -nodes -keyout certs/key.pem -out certs/cert.pem + Generating a RSA private key + ..................................................................................................................+++++ + ......+++++ + writing new private key to 'certs/key.pem' + ----- + $ openssl pkcs12 -export -passout pass: -out certs/output.pkcs12 -inkey certs/key.pem -in certs/cert.pem + +Step 4: Build and start the sandbox +*********************************** + +This starts three proxies listening on ``localhost`` ports ``10000-30000``. + +It also starts two upstream services, one ``ws`` and one ``wss``. + +The upstream services listen on the internal Docker network on ports ``80`` and ``443`` respectively. + +The socket servers are very trivial implementations, that simply output ``[ws] HELO`` and +``[wss] HELO`` in response to any input. + +.. code-block:: console + + $ docker-compose pull + $ docker-compose up --build -d + $ docker-compose ps + + Name Command State Ports + --------------------------------------------------------------------------------------------------- + websocket_proxy-ws_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:10000->10000/tcp + websocket_proxy-wss_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:20000->10000/tcp + websocket_proxy-wss-passthrough_1 /docker-entrypoint.sh /usr ... Up 0.0.0.0:30000->10000/tcp + websocket_service-ws_1 websocat -E ws-listen:0.0. ... Up + websocket_service-wss_1 websocat wss-listen:0.0.0. ... Up + +Step 5: Test proxying ``ws`` -> ``ws`` +************************************** + +The proxy listening on port ``10000`` terminates the WebSocket connection without ``TLS`` and then proxies +to an upstream socket, also without ``TLS``. + +In order for Envoy to terminate the WebSocket connection, the +:ref:`upgrade_configs ` +in :ref:`HttpConnectionManager ` +must be set, as can be seen in the provided :download:`ws -> ws configuration <_include/websocket/envoy-ws.yaml>`: + +.. literalinclude:: _include/websocket/envoy-ws.yaml + :language: yaml + :lines: 1-29 + :linenos: + :emphasize-lines: 13-14 + +You can start an interactive session with the socket as follows: + +.. code-block:: console + + $ docker run -ti --network=host solsson/websocat ws://localhost:10000 + HELO + [ws] HELO + GOODBYE + [ws] HELO + +Type ``Ctrl-c`` to exit the socket session. + +Step 6: Test proxying ``wss`` -> ``wss`` +**************************************** + +The proxy listening on port ``20000`` terminates the WebSocket connection with ``TLS`` and then proxies +to an upstream ``TLS`` WebSocket. + +In addition to the +:ref:`upgrade_configs ` +in :ref:`HttpConnectionManager `, +the :download:`wss -> wss configuration <_include/websocket/envoy-wss.yaml>` adds a ``TLS`` +:ref:`transport_socket ` to both the +:ref:`listener ` and the +:ref:`cluster `. + +You can start an interactive session with the socket as follows: + +.. code-block:: console + + $ docker run -ti --network=host solsson/websocat --insecure wss://localhost:20000 + HELO + [wss] HELO + GOODBYE + [wss] HELO + +Type ``Ctrl-c`` to exit the socket session. + +Step 7: Test proxying ``wss`` passthrough +***************************************** + +The proxy listening on port ``30000`` passes through all ``TCP`` traffic to an upstream ``TLS`` WebSocket. + +The :download:`wss passthrough configuration <_include/websocket/envoy-wss-passthrough.yaml>` requires no ``TLS`` +or ``HTTP`` setup, and instead uses a simple +:ref:`tcp_proxy `. + +You can start an interactive session with the socket as follows: + +.. code-block:: console + + $ docker run -ti --network=host solsson/websocat --insecure wss://localhost:30000 + HELO + [wss] HELO + GOODBYE + [wss] HELO + +Type ``Ctrl-c`` to exit the socket session. + +.. seealso:: + + :ref:`Securing Envoy quick start guide ` + Outline of key concepts for securing Envoy. + + :ref:`Double proxy sandbox ` + An example of securing traffic between proxies with validation and + mutual authentication using ``mTLS`` with non-``HTTP`` traffic. diff --git a/examples/websocket/Dockerfile-proxy-ws b/examples/websocket/Dockerfile-proxy-ws new file mode 100644 index 000000000000..a8d9d19ed1b8 --- /dev/null +++ b/examples/websocket/Dockerfile-proxy-ws @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-ws.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/websocket/Dockerfile-proxy-wss b/examples/websocket/Dockerfile-proxy-wss new file mode 100644 index 000000000000..c0114fb559b4 --- /dev/null +++ b/examples/websocket/Dockerfile-proxy-wss @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-wss.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/websocket/Dockerfile-proxy-wss-passthrough b/examples/websocket/Dockerfile-proxy-wss-passthrough new file mode 100644 index 000000000000..66e4eb5d2629 --- /dev/null +++ b/examples/websocket/Dockerfile-proxy-wss-passthrough @@ -0,0 +1,5 @@ +FROM envoyproxy/envoy-dev:latest + +COPY ./envoy-wss-passthrough.yaml /etc/envoy.yaml +RUN chmod go+r /etc/envoy.yaml +CMD ["/usr/local/bin/envoy", "-c /etc/envoy.yaml", "-l", "debug"] diff --git a/examples/websocket/README.md b/examples/websocket/README.md new file mode 100644 index 000000000000..8473888649c3 --- /dev/null +++ b/examples/websocket/README.md @@ -0,0 +1,2 @@ +To learn about this sandbox and for instructions on how to run it please head over +to the [Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/websocket.html). diff --git a/examples/websocket/docker-compose.yaml b/examples/websocket/docker-compose.yaml new file mode 100644 index 000000000000..0435bfddaec2 --- /dev/null +++ b/examples/websocket/docker-compose.yaml @@ -0,0 +1,35 @@ +version: "3.7" +services: + + proxy-ws: + build: + context: . + dockerfile: Dockerfile-proxy-ws + ports: + - "10000:10000" + + proxy-wss-wss: + build: + context: . + dockerfile: Dockerfile-proxy-wss + ports: + - "20000:10000" + + proxy-wss-passthrough: + build: + context: . + dockerfile: Dockerfile-proxy-wss-passthrough + ports: + - "30000:10000" + + service-ws: + image: solsson/websocat + hostname: service-ws + command: -E ws-listen:0.0.0.0:80 literalreply:'[ws] HELO' + + service-wss: + image: solsson/websocat + hostname: service-wss + command: wss-listen:0.0.0.0:443 literalreply:"[wss] HELO" --pkcs12-der /certs/output.pkcs12 + volumes: + - ./certs/output.pkcs12:/certs/output.pkcs12 diff --git a/examples/websocket/envoy-ws.yaml b/examples/websocket/envoy-ws.yaml new file mode 100644 index 000000000000..b04013066a9c --- /dev/null +++ b/examples/websocket/envoy-ws.yaml @@ -0,0 +1,42 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_ws_to_ws + upgrade_configs: + - upgrade_type: websocket + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service_ws + http_filters: + - name: envoy.filters.http.router + + clusters: + - name: service_ws + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service_ws + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-ws + port_value: 80 diff --git a/examples/websocket/envoy-wss-passthrough.yaml b/examples/websocket/envoy-wss-passthrough.yaml new file mode 100644 index 000000000000..4da386464106 --- /dev/null +++ b/examples/websocket/envoy-wss-passthrough.yaml @@ -0,0 +1,28 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.tcp_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy + cluster: service_wss_passthrough + stat_prefix: wss_passthrough + + clusters: + - name: service_wss_passthrough + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service_wss_passthrough + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-wss + port_value: 443 diff --git a/examples/websocket/envoy-wss.yaml b/examples/websocket/envoy-wss.yaml new file mode 100644 index 000000000000..9993916e1851 --- /dev/null +++ b/examples/websocket/envoy-wss.yaml @@ -0,0 +1,109 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_wss_to_wss + upgrade_configs: + - upgrade_type: websocket + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service_wss + http_filters: + - name: envoy.filters.http.router + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + # The following self-signed certificate pair is generated using: + # $ openssl req -x509 -newkey rsa:2048 -keyout a/front-proxy-key.pem -out a/front-proxy-crt.pem -days 3650 -nodes -subj '/CN=front-envoy' + # + # Instead of feeding it as an inline_string, certificate pair can also be fed to Envoy + # via filename. Reference: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#config-core-v3-datasource. + # + # Or in a dynamic configuration scenario, certificate pair can be fetched remotely via + # Secret Discovery Service (SDS). Reference: https://www.envoyproxy.io/docs/envoy/latest/configuration/security/secret. + certificate_chain: + inline_string: | + -----BEGIN CERTIFICATE----- + MIICqDCCAZACCQCquzpHNpqBcDANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtm + cm9udC1lbnZveTAeFw0yMDA3MDgwMTMxNDZaFw0zMDA3MDYwMTMxNDZaMBYxFDAS + BgNVBAMMC2Zyb250LWVudm95MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + AQEAthnYkqVQBX+Wg7aQWyCCb87hBce1hAFhbRM8Y9dQTqxoMXZiA2n8G089hUou + oQpEdJgitXVS6YMFPFUUWfwcqxYAynLK4X5im26Yfa1eO8La8sZUS+4Bjao1gF5/ + VJxSEo2yZ7fFBo8M4E44ZehIIocipCRS+YZehFs6dmHoq/MGvh2eAHIa+O9xssPt + ofFcQMR8rwBHVbKy484O10tNCouX4yUkyQXqCRy6HRu7kSjOjNKSGtjfG+h5M8bh + 10W7ZrsJ1hWhzBulSaMZaUY3vh5ngpws1JATQVSK1Jm/dmMRciwlTK7KfzgxHlSX + 58ENpS7yPTISkEICcLbXkkKGEQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCmj6Hg + vwOxWz0xu+6fSfRL6PGJUGq6wghCfUvjfwZ7zppDUqU47fk+yqPIOzuGZMdAqi7N + v1DXkeO4A3hnMD22Rlqt25vfogAaZVToBeQxCPd/ALBLFrvLUFYuSlS3zXSBpQqQ + Ny2IKFYsMllz5RSROONHBjaJOn5OwqenJ91MPmTAG7ujXKN6INSBM0PjX9Jy4Xb9 + zT+I85jRDQHnTFce1WICBDCYidTIvJtdSSokGSuy4/xyxAAc/BpZAfOjBQ4G1QRe + 9XwOi790LyNUYFJVyeOvNJwveloWuPLHb9idmY5YABwikUY6QNcXwyHTbRCkPB2I + m+/R4XnmL4cKQ+5Z + -----END CERTIFICATE----- + private_key: + inline_string: | + -----BEGIN PRIVATE KEY----- + MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2GdiSpVAFf5aD + tpBbIIJvzuEFx7WEAWFtEzxj11BOrGgxdmIDafwbTz2FSi6hCkR0mCK1dVLpgwU8 + VRRZ/ByrFgDKcsrhfmKbbph9rV47wtryxlRL7gGNqjWAXn9UnFISjbJnt8UGjwzg + Tjhl6EgihyKkJFL5hl6EWzp2Yeir8wa+HZ4Achr473Gyw+2h8VxAxHyvAEdVsrLj + zg7XS00Ki5fjJSTJBeoJHLodG7uRKM6M0pIa2N8b6HkzxuHXRbtmuwnWFaHMG6VJ + oxlpRje+HmeCnCzUkBNBVIrUmb92YxFyLCVMrsp/ODEeVJfnwQ2lLvI9MhKQQgJw + tteSQoYRAgMBAAECggEAeDGdEkYNCGQLe8pvg8Z0ccoSGpeTxpqGrNEKhjfi6NrB + NwyVav10iq4FxEmPd3nobzDPkAftfvWc6hKaCT7vyTkPspCMOsQJ39/ixOk+jqFx + lNa1YxyoZ9IV2DIHR1iaj2Z5gB367PZUoGTgstrbafbaNY9IOSyojCIO935ubbcx + DWwL24XAf51ez6sXnI8V5tXmrFlNXhbhJdH8iIxNyM45HrnlUlOk0lCK4gmLJjy9 + 10IS2H2Wh3M5zsTpihH1JvM56oAH1ahrhMXs/rVFXXkg50yD1KV+HQiEbglYKUxO + eMYtfaY9i2CuLwhDnWp3oxP3HfgQQhD09OEN3e0IlQKBgQDZ/3poG9TiMZSjfKqL + xnCABMXGVQsfFWNC8THoW6RRx5Rqi8q08yJrmhCu32YKvccsOljDQJQQJdQO1g09 + e/adJmCnTrqxNtjPkX9txV23Lp6Ak7emjiQ5ICu7iWxrcO3zf7hmKtj7z+av8sjO + mDI7NkX5vnlE74nztBEjp3eC0wKBgQDV2GeJV028RW3b/QyP3Gwmax2+cKLR9PKR + nJnmO5bxAT0nQ3xuJEAqMIss/Rfb/macWc2N/6CWJCRT6a2vgy6xBW+bqG6RdQMB + xEZXFZl+sSKhXPkc5Wjb4lQ14YWyRPrTjMlwez3k4UolIJhJmwl+D7OkMRrOUERO + EtUvc7odCwKBgBi+nhdZKWXveM7B5N3uzXBKmmRz3MpPdC/yDtcwJ8u8msUpTv4R + JxQNrd0bsIqBli0YBmFLYEMg+BwjAee7vXeDFq+HCTv6XMva2RsNryCO4yD3I359 + XfE6DJzB8ZOUgv4Dvluie3TB2Y6ZQV/p+LGt7G13yG4hvofyJYvlg3RPAoGAcjDg + +OH5zLN2eqah8qBN0CYa9/rFt0AJ19+7/smLTJ7QvQq4g0gwS1couplcCEnNGWiK + 72y1n/ckvvplmPeAE19HveMvR9UoCeV5ej86fACy8V/oVpnaaLBvL2aCMjPLjPP9 + DWeCIZp8MV86cvOrGfngf6kJG2qZTueXl4NAuwkCgYEArKkhlZVXjwBoVvtHYmN2 + o+F6cGMlRJTLhNc391WApsgDZfTZSdeJsBsvvzS/Nc0burrufJg0wYioTlpReSy4 + ohhtprnQQAddfjHP7rh2LGt+irFzhdXXQ1ybGaGM9D764KUNCXLuwdly0vzXU4HU + q5sGxGrC1RECGB5Zwx2S2ZY= + -----END PRIVATE KEY----- + + clusters: + - name: service_wss + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service_wss + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: service-wss + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext diff --git a/examples/websocket/verify.sh b/examples/websocket/verify.sh new file mode 100755 index 000000000000..68b463b077ff --- /dev/null +++ b/examples/websocket/verify.sh @@ -0,0 +1,54 @@ +#!/bin/bash -e +# +# Requirements: expect +# + +export NAME=tls +export MANUAL=true + +# shellcheck source=examples/verify-common.sh +. "$(dirname "${BASH_SOURCE[0]}")/../verify-common.sh" + + +interact_ws () { + local port="$1" \ + protocol="$2" \ + backend="$3" \ + insecure="" + if [ "$protocol" == "wss" ]; then + insecure="--insecure" + fi + expect < ws" +interact_ws 10000 ws ws + +run_log "Interact with web socket wss -> wss" +interact_ws 20000 wss wss + +run_log "Interact with web socket wss passthrough" +interact_ws 30000 wss wss From 4a14f1ce2dd92db2eb5673cf3cac4ebc9c9796b7 Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 11 Nov 2020 21:58:06 -0500 Subject: [PATCH 075/117] api: mark matching API as WIP (#13990) Signed-off-by: Snow Pettersen --- api/envoy/config/common/matcher/v3/matcher.proto | 3 +++ api/envoy/config/common/matcher/v4alpha/matcher.proto | 3 +++ .../envoy/config/common/matcher/v3/matcher.proto | 3 +++ .../envoy/config/common/matcher/v4alpha/matcher.proto | 3 +++ 4 files changed, 12 insertions(+) diff --git a/api/envoy/config/common/matcher/v3/matcher.proto b/api/envoy/config/common/matcher/v3/matcher.proto index b9b69519a4b9..8a1cb4839c4a 100644 --- a/api/envoy/config/common/matcher/v3/matcher.proto +++ b/api/envoy/config/common/matcher/v3/matcher.proto @@ -23,6 +23,9 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // is found the action specified by the most specific on_no_match will be evaluated. // As an on_no_match might result in another matching tree being evaluated, this process // might repeat several times until the final OnMatch (or no match) is decided. +// +// This API is a work in progress. +// [#not-implemented-hide:] message Matcher { // What to do if a match is successful. message OnMatch { diff --git a/api/envoy/config/common/matcher/v4alpha/matcher.proto b/api/envoy/config/common/matcher/v4alpha/matcher.proto index fc60fce40f79..15859474e6e1 100644 --- a/api/envoy/config/common/matcher/v4alpha/matcher.proto +++ b/api/envoy/config/common/matcher/v4alpha/matcher.proto @@ -22,6 +22,9 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // is found the action specified by the most specific on_no_match will be evaluated. // As an on_no_match might result in another matching tree being evaluated, this process // might repeat several times until the final OnMatch (or no match) is decided. +// +// This API is a work in progress. +// [#not-implemented-hide:] message Matcher { option (udpa.annotations.versioning).previous_message_type = "envoy.config.common.matcher.v3.Matcher"; diff --git a/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto b/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto index b9b69519a4b9..8a1cb4839c4a 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto +++ b/generated_api_shadow/envoy/config/common/matcher/v3/matcher.proto @@ -23,6 +23,9 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // is found the action specified by the most specific on_no_match will be evaluated. // As an on_no_match might result in another matching tree being evaluated, this process // might repeat several times until the final OnMatch (or no match) is decided. +// +// This API is a work in progress. +// [#not-implemented-hide:] message Matcher { // What to do if a match is successful. message OnMatch { diff --git a/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto b/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto index fc60fce40f79..15859474e6e1 100644 --- a/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto +++ b/generated_api_shadow/envoy/config/common/matcher/v4alpha/matcher.proto @@ -22,6 +22,9 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // is found the action specified by the most specific on_no_match will be evaluated. // As an on_no_match might result in another matching tree being evaluated, this process // might repeat several times until the final OnMatch (or no match) is decided. +// +// This API is a work in progress. +// [#not-implemented-hide:] message Matcher { option (udpa.annotations.versioning).previous_message_type = "envoy.config.common.matcher.v3.Matcher"; From 3b4d165c169d9bcfa23382114d21cdcc7895f575 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Thu, 12 Nov 2020 14:17:33 +0900 Subject: [PATCH 076/117] wasm: add wasmtime runtime (#13932) Commit Message: wasm: Add Wasmtime as a new runtime Additional Description: add a new wasm runtime named Wasmtime Risk Level: low Testing: run in `bazel.compile_time_options` build target as WAVM. Docs Changes: Docs will be provided in subsequent PRs. Release Notes: enable Wasmtime as a Wasm filter runtime optionally. ref: https://github.com/proxy-wasm/proxy-wasm-cpp-host/pull/73 Signed-off-by: mathetake Co-authored-by: Piotr Sikora --- bazel/BUILD | 10 ++-- bazel/envoy_build_system.bzl | 4 +- bazel/envoy_select.bzl | 11 ++-- bazel/external/proxy_wasm_cpp_host.BUILD | 59 ++++++++----------- bazel/external/wasm-c-api.BUILD | 19 ++++++ bazel/external/wasmtime.BUILD | 32 ++++++++++ bazel/repositories.bzl | 14 +++++ bazel/repositories_extra.bzl | 2 + bazel/repository_locations.bzl | 54 ++++++++++++++++- ci/do_ci.sh | 16 ++--- examples/wasm-cc/BUILD | 2 +- source/extensions/common/wasm/wasm_vm.cc | 10 ++++ .../extensions/common/wasm/well_known_names.h | 2 + .../access_loggers/wasm/config_test.cc | 3 + test/extensions/bootstrap/wasm/config_test.cc | 3 + test/extensions/bootstrap/wasm/wasm_test.cc | 26 ++++++-- test/extensions/common/wasm/wasm_test.cc | 3 + .../filters/http/wasm/config_test.cc | 11 +++- .../filters/http/wasm/wasm_filter_test.cc | 5 +- .../filters/network/wasm/config_test.cc | 3 + .../filters/network/wasm/wasm_filter_test.cc | 3 + .../stats_sinks/wasm/config_test.cc | 3 + .../stats_sinks/wasm/wasm_stat_sink_test.cc | 3 + tools/spelling/spelling_dictionary.txt | 1 + 24 files changed, 230 insertions(+), 69 deletions(-) create mode 100644 bazel/external/wasm-c-api.BUILD create mode 100644 bazel/external/wasmtime.BUILD diff --git a/bazel/BUILD b/bazel/BUILD index d03b931018a3..e4def8093f52 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -315,11 +315,6 @@ config_setting( ) # TODO: consider converting WAVM VM support to an extension (https://github.com/envoyproxy/envoy/issues/12574) -config_setting( - name = "wasm_all", - values = {"define": "wasm=enabled"}, -) - config_setting( name = "wasm_wavm", values = {"define": "wasm=wavm"}, @@ -330,6 +325,11 @@ config_setting( values = {"define": "wasm=v8"}, ) +config_setting( + name = "wasm_wasmtime", + values = {"define": "wasm=wasmtime"}, +) + config_setting( name = "wasm_none", values = {"define": "wasm=disabled"}, diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl index e95329095dca..283cf130af22 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -20,8 +20,8 @@ load( _envoy_select_hot_restart = "envoy_select_hot_restart", _envoy_select_new_codecs_in_integration_tests = "envoy_select_new_codecs_in_integration_tests", _envoy_select_wasm = "envoy_select_wasm", - _envoy_select_wasm_all_v8_wavm_none = "envoy_select_wasm_all_v8_wavm_none", _envoy_select_wasm_v8 = "envoy_select_wasm_v8", + _envoy_select_wasm_wasmtime = "envoy_select_wasm_wasmtime", _envoy_select_wasm_wavm = "envoy_select_wasm_wavm", ) load( @@ -181,8 +181,8 @@ envoy_select_boringssl = _envoy_select_boringssl envoy_select_google_grpc = _envoy_select_google_grpc envoy_select_hot_restart = _envoy_select_hot_restart envoy_select_wasm = _envoy_select_wasm -envoy_select_wasm_all_v8_wavm_none = _envoy_select_wasm_all_v8_wavm_none envoy_select_wasm_wavm = _envoy_select_wasm_wavm +envoy_select_wasm_wasmtime = _envoy_select_wasm_wasmtime envoy_select_wasm_v8 = _envoy_select_wasm_v8 envoy_select_new_codecs_in_integration_tests = _envoy_select_new_codecs_in_integration_tests diff --git a/bazel/envoy_select.bzl b/bazel/envoy_select.bzl index 5a33e4da515d..8cbecb26075c 100644 --- a/bazel/envoy_select.bzl +++ b/bazel/envoy_select.bzl @@ -41,6 +41,7 @@ def envoy_select_wasm(xs): def envoy_select_wasm_v8(xs): return select({ + "@envoy//bazel:wasm_wasmtime": [], "@envoy//bazel:wasm_wavm": [], "@envoy//bazel:wasm_none": [], "//conditions:default": xs, @@ -48,18 +49,14 @@ def envoy_select_wasm_v8(xs): def envoy_select_wasm_wavm(xs): return select({ - "@envoy//bazel:wasm_all": xs, "@envoy//bazel:wasm_wavm": xs, "//conditions:default": [], }) -def envoy_select_wasm_all_v8_wavm_none(xs1, xs2, xs3, xs4): +def envoy_select_wasm_wasmtime(xs): return select({ - "@envoy//bazel:wasm_all": xs1, - "@envoy//bazel:wasm_v8": xs2, - "@envoy//bazel:wasm_wavm": xs3, - "@envoy//bazel:wasm_none": xs4, - "//conditions:default": xs2, + "@envoy//bazel:wasm_wasmtime": xs, + "//conditions:default": [], }) # Select the given values if use legacy codecs in test is on in the current build. diff --git a/bazel/external/proxy_wasm_cpp_host.BUILD b/bazel/external/proxy_wasm_cpp_host.BUILD index 1b3f0829d7b2..6157654ed5ea 100644 --- a/bazel/external/proxy_wasm_cpp_host.BUILD +++ b/bazel/external/proxy_wasm_cpp_host.BUILD @@ -1,8 +1,8 @@ load("@rules_cc//cc:defs.bzl", "cc_library") load( "@envoy//bazel:envoy_build_system.bzl", - "envoy_select_wasm_all_v8_wavm_none", "envoy_select_wasm_v8", + "envoy_select_wasm_wasmtime", "envoy_select_wasm_wavm", ) @@ -21,38 +21,23 @@ cc_library( cc_library( name = "lib", # Note that the select cannot appear in the glob. - srcs = envoy_select_wasm_all_v8_wavm_none( - glob( - [ - "src/**/*.h", - "src/**/*.cc", - ], - ), - glob( - [ - "src/**/*.h", - "src/**/*.cc", - ], - exclude = ["src/wavm/*"], - ), - glob( - [ - "src/**/*.h", - "src/**/*.cc", - ], - exclude = ["src/v8/*"], - ), - glob( - [ - "src/**/*.h", - "src/**/*.cc", - ], - exclude = [ - "src/wavm/*", - "src/v8/*", - ], - ), - ), + srcs = glob( + [ + "src/**/*.h", + "src/**/*.cc", + ], + exclude = [ + "src/v8/*.cc", + "src/wavm/*.cc", + "src/wasmtime/*.cc", + ], + ) + envoy_select_wasm_v8(glob([ + "src/v8/*.cc", + ])) + envoy_select_wasm_wavm(glob([ + "src/wavm/*.cc", + ])) + envoy_select_wasm_wasmtime(glob([ + "src/wasmtime/*.cc", + ])), copts = envoy_select_wasm_wavm([ '-DWAVM_API=""', "-Wno-non-virtual-dtor", @@ -68,9 +53,11 @@ cc_library( "//external:zlib", "@proxy_wasm_cpp_sdk//:api_lib", "@proxy_wasm_cpp_sdk//:common_lib", - ] + envoy_select_wasm_wavm([ - "@envoy//bazel/foreign_cc:wavm", - ]) + envoy_select_wasm_v8([ + ] + envoy_select_wasm_v8([ "//external:wee8", + ]) + envoy_select_wasm_wavm([ + "@envoy//bazel/foreign_cc:wavm", + ]) + envoy_select_wasm_wasmtime([ + "@com_github_wasm_c_api//:wasmtime_lib", ]), ) diff --git a/bazel/external/wasm-c-api.BUILD b/bazel/external/wasm-c-api.BUILD new file mode 100644 index 000000000000..abff294cf9c3 --- /dev/null +++ b/bazel/external/wasm-c-api.BUILD @@ -0,0 +1,19 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +licenses(["notice"]) # Apache 2 + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "wasmtime_lib", + hdrs = [ + "include/wasm.h", + ], + defines = [ + "ENVOY_WASM_WASMTIME", + ], + include_prefix = "wasmtime", + deps = [ + "@com_github_wasmtime//:rust_c_api", + ], +) diff --git a/bazel/external/wasmtime.BUILD b/bazel/external/wasmtime.BUILD new file mode 100644 index 000000000000..edb2ab30367e --- /dev/null +++ b/bazel/external/wasmtime.BUILD @@ -0,0 +1,32 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") +load("@io_bazel_rules_rust//rust:rust.bzl", "rust_library") + +licenses(["notice"]) # Apache 2 + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "helpers_lib", + srcs = [ + "crates/runtime/src/helpers.c", + ], + visibility = ["//visibility:private"], +) + +rust_library( + name = "rust_c_api", + srcs = glob(["crates/c-api/src/**/*.rs"]), + crate_root = "crates/c-api/src/lib.rs", + crate_type = "staticlib", + edition = "2018", + proc_macro_deps = [ + "@proxy_wasm_cpp_host//bazel/cargo:wasmtime_c_api_macros", + ], + deps = [ + ":helpers_lib", + "@proxy_wasm_cpp_host//bazel/cargo:anyhow", + "@proxy_wasm_cpp_host//bazel/cargo:env_logger", + "@proxy_wasm_cpp_host//bazel/cargo:once_cell", + "@proxy_wasm_cpp_host//bazel/cargo:wasmtime", + ], +) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 3df2774717ed..557657b89431 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -182,6 +182,8 @@ def envoy_dependencies(skip_targets = []): _org_llvm_llvm() _com_github_wavm_wavm() + _com_github_wasmtime() + _com_github_wasm_c_api() switched_rules_by_language( name = "com_google_googleapis_imports", @@ -864,6 +866,18 @@ def _com_github_wavm_wavm(): actual = "@envoy//bazel/foreign_cc:wavm", ) +def _com_github_wasmtime(): + external_http_archive( + name = "com_github_wasmtime", + build_file = "@envoy//bazel/external:wasmtime.BUILD", + ) + +def _com_github_wasm_c_api(): + external_http_archive( + name = "com_github_wasm_c_api", + build_file = "@envoy//bazel/external:wasm-c-api.BUILD", + ) + def _kafka_deps(): # This archive contains Kafka client source code. # We are using request/response message format files to generate parser code. diff --git a/bazel/repositories_extra.bzl b/bazel/repositories_extra.bzl index 70fe69b6fa40..dbfc5221d054 100644 --- a/bazel/repositories_extra.bzl +++ b/bazel/repositories_extra.bzl @@ -1,5 +1,6 @@ load("@rules_python//python:repositories.bzl", "py_repositories") load("@rules_python//python:pip.bzl", "pip3_import", "pip_repositories") +load("@proxy_wasm_cpp_host//bazel/cargo:crates.bzl", "proxy_wasm_cpp_host_raze__fetch_remote_crates") # Python dependencies. def _python_deps(): @@ -100,3 +101,4 @@ def _python_deps(): # Envoy deps that rely on a first stage of dependency loading in envoy_dependencies(). def envoy_dependencies_extra(): _python_deps() + proxy_wasm_cpp_host_raze__fetch_remote_crates() diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index d55dc3c74462..ecb74393ffea 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -615,6 +615,46 @@ REPOSITORY_LOCATIONS_SPEC = dict( ], cpe = "cpe:2.3:a:webassembly_virtual_machine_project:webassembly_virtual_machine:*", ), + com_github_wasmtime = dict( + project_name = "wasmtime", + project_desc = "A standalone runtime for WebAssembly", + project_url = "https://github.com/bytecodealliance/wasmtime", + version = "0.21.0", + sha256 = "7874feb1026bbef06796bd5ab80e73f15b8e83752bde8dc93994f5bc039a4952", + strip_prefix = "wasmtime-{version}", + urls = ["https://github.com/bytecodealliance/wasmtime/archive/v{version}.tar.gz"], + release_date = "2020-11-05", + use_category = ["dataplane_ext"], + extensions = [ + "envoy.access_loggers.wasm", + "envoy.bootstrap.wasm", + "envoy.filters.http.wasm", + "envoy.filters.network.wasm", + "envoy.stat_sinks.wasm", + ], + cpe = "cpe:2.3:a:webassembly_virtual_machine_project:webassembly_virtual_machine:*", + ), + com_github_wasm_c_api = dict( + project_name = "wasm-c-api", + project_desc = "WebAssembly C and C++ API", + project_url = "https://github.com/WebAssembly/wasm-c-api", + # this is the submodule's specific commit used by wasmtime + # https://github.com/bytecodealliance/wasmtime/tree/v0.21.0/crates/c-api + version = "d9a80099d496b5cdba6f3fe8fc77586e0e505ddc", + sha256 = "aea8cd095e9937f1e14f2c93e026317b197eb2345e7a817fe3932062eb7b792c", + strip_prefix = "wasm-c-api-{version}", + urls = ["https://github.com/WebAssembly/wasm-c-api/archive/{version}.tar.gz"], + release_date = "2019-11-14", + use_category = ["dataplane_ext"], + extensions = [ + "envoy.access_loggers.wasm", + "envoy.bootstrap.wasm", + "envoy.filters.http.wasm", + "envoy.filters.network.wasm", + "envoy.stat_sinks.wasm", + ], + cpe = "N/A", + ), io_opencensus_cpp = dict( project_name = "OpenCensus C++", project_desc = "OpenCensus tracing library", @@ -830,8 +870,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host", - version = "b7d3d13d75bb6b50f192252258bb9583bf723fa4", - sha256 = "ae639f94a80adbe915849bccb32346720c59a579db09918e44aefafed6cbb100", + version = "4741d2f1cd5eb250f66d0518238c333353259d56", + sha256 = "30fc4becfcc5a95ac875fc5a0658a91aa7ddedd763b52d7810c13ed35d9d81aa", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -864,8 +904,16 @@ REPOSITORY_LOCATIONS_SPEC = dict( sha256 = "426a7712af597d90301dcc29e63d62de5c2e09fb347692e89abb775ec97c74fe", strip_prefix = "rules_rust-{version}", urls = ["https://github.com/bazelbuild/rules_rust/archive/{version}.tar.gz"], - use_category = ["test_only"], + use_category = ["dataplane_ext"], + extensions = [ + "envoy.access_loggers.wasm", + "envoy.bootstrap.wasm", + "envoy.filters.http.wasm", + "envoy.filters.network.wasm", + "envoy.stat_sinks.wasm", + ], release_date = "2020-10-21", + cpe = "N/A", ), rules_antlr = dict( project_name = "ANTLR Rules for Bazel", diff --git a/ci/do_ci.sh b/ci/do_ci.sh index a117499646f9..4e3de2a2f2a5 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -280,7 +280,6 @@ elif [[ "$CI_TARGET" == "bazel.compile_time_options" ]]; then "--define" "boringssl=fips" "--define" "log_debug_assert_in_release=enabled" "--define" "quiche=enabled" - "--define" "wasm=wavm" "--define" "path_normalization_by_default=true" "--define" "deprecated_features=disabled" "--define" "use_new_codecs_in_integration_tests=false" @@ -297,19 +296,22 @@ elif [[ "$CI_TARGET" == "bazel.compile_time_options" ]]; then TEST_TARGETS=("@envoy//test/...") fi # Building all the dependencies from scratch to link them against libc++. - echo "Building and testing ${TEST_TARGETS[*]}" - bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only + echo "Building and testing with wasm=wasmtime: ${TEST_TARGETS[*]}" + bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wasmtime "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only + + echo "Building and testing with wasm=wavm: ${TEST_TARGETS[*]}" + bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wavm "${COMPILE_TIME_OPTIONS[@]}" -c dbg "${TEST_TARGETS[@]}" --test_tag_filters=-nofips --build_tests_only # Legacy codecs "--define legacy_codecs_in_integration_tests=true" should also be tested in # integration tests with asan. - bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" "${COMPILE_TIME_OPTIONS[@]}" -c dbg @envoy//test/integration/... --config=clang-asan --build_tests_only + bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wavm "${COMPILE_TIME_OPTIONS[@]}" -c dbg @envoy//test/integration/... --config=clang-asan --build_tests_only # "--define log_debug_assert_in_release=enabled" must be tested with a release build, so run only # these tests under "-c opt" to save time in CI. - bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" "${COMPILE_TIME_OPTIONS[@]}" -c opt @envoy//test/common/common:assert_test @envoy//test/server:server_test + bazel_with_collection test "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wavm "${COMPILE_TIME_OPTIONS[@]}" -c opt @envoy//test/common/common:assert_test @envoy//test/server:server_test - echo "Building binary..." - bazel build "${BAZEL_BUILD_OPTIONS[@]}" "${COMPILE_TIME_OPTIONS[@]}" -c dbg @envoy//source/exe:envoy-static --build_tag_filters=-nofips + echo "Building binary with wasm=wavm..." + bazel build "${BAZEL_BUILD_OPTIONS[@]}" --define wasm=wavm "${COMPILE_TIME_OPTIONS[@]}" -c dbg @envoy//source/exe:envoy-static --build_tag_filters=-nofips collect_build_profile build exit 0 elif [[ "$CI_TARGET" == "bazel.api" ]]; then diff --git a/examples/wasm-cc/BUILD b/examples/wasm-cc/BUILD index 3d139a68e0ea..a6f1975445ca 100644 --- a/examples/wasm-cc/BUILD +++ b/examples/wasm-cc/BUILD @@ -11,7 +11,7 @@ envoy_package() selects.config_setting_group( name = "include_wasm_config", - match_all = ["//bazel:x86", "//bazel:wasm_all"], + match_all = ["//bazel:x86", "//bazel:wasm_v8"], ) filegroup( diff --git a/source/extensions/common/wasm/wasm_vm.cc b/source/extensions/common/wasm/wasm_vm.cc index 9f888b18f8f5..5b482e6bb847 100644 --- a/source/extensions/common/wasm/wasm_vm.cc +++ b/source/extensions/common/wasm/wasm_vm.cc @@ -17,6 +17,9 @@ #if defined(ENVOY_WASM_WAVM) #include "include/proxy-wasm/wavm.h" #endif +#if defined(ENVOY_WASM_WASMTIME) +#include "include/proxy-wasm/wasmtime.h" +#endif using ContextBase = proxy_wasm::ContextBase; using Word = proxy_wasm::Word; @@ -82,6 +85,13 @@ WasmVmPtr createWasmVm(absl::string_view runtime, const Stats::ScopeSharedPtr& s auto wasm = proxy_wasm::createWavmVm(); wasm->integration() = getWasmExtension()->createEnvoyWasmVmIntegration(scope, runtime, "wavm"); return wasm; +#endif +#if defined(ENVOY_WASM_WASMTIME) + } else if (runtime == WasmRuntimeNames::get().Wasmtime) { + auto wasm = proxy_wasm::createWasmtimeVm(); + wasm->integration() = + getWasmExtension()->createEnvoyWasmVmIntegration(scope, runtime, "wasmtime"); + return wasm; #endif } else { ENVOY_LOG_TO_LOGGER( diff --git a/source/extensions/common/wasm/well_known_names.h b/source/extensions/common/wasm/well_known_names.h index 5fb8602bf831..3904868bba88 100644 --- a/source/extensions/common/wasm/well_known_names.h +++ b/source/extensions/common/wasm/well_known_names.h @@ -15,6 +15,8 @@ namespace Wasm { */ class WasmRuntimeValues { public: + // Wasmtime (https://github.com/bytecodealliance/wasmtime). + const std::string Wasmtime = "envoy.wasm.runtime.wasmtime"; // WAVM (https://github.com/WAVM/WAVM) Wasm VM. const std::string Wavm = "envoy.wasm.runtime.wavm"; // Null sandbox: modules must be compiled into envoy and registered name is given in the diff --git a/test/extensions/access_loggers/wasm/config_test.cc b/test/extensions/access_loggers/wasm/config_test.cc index 02a71c9132b7..744f074fdb8c 100644 --- a/test/extensions/access_loggers/wasm/config_test.cc +++ b/test/extensions/access_loggers/wasm/config_test.cc @@ -47,6 +47,9 @@ auto testing_values = testing::Values( #endif #if defined(ENVOY_WASM_WAVM) "wavm", +#endif +#if defined(ENVOY_WASM_WASMTIME) + "wasmtime", #endif "null"); INSTANTIATE_TEST_SUITE_P(Runtimes, WasmAccessLogConfigTest, testing_values); diff --git a/test/extensions/bootstrap/wasm/config_test.cc b/test/extensions/bootstrap/wasm/config_test.cc index 6fb99261a3f8..bd5d4b947166 100644 --- a/test/extensions/bootstrap/wasm/config_test.cc +++ b/test/extensions/bootstrap/wasm/config_test.cc @@ -75,6 +75,9 @@ auto testing_values = testing::Values( #endif #if defined(ENVOY_WASM_WAVM) "wavm", +#endif +#if defined(ENVOY_WASM_WASMTIME) + "wasmtime", #endif "null"); INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFactoryTest, testing_values); diff --git a/test/extensions/bootstrap/wasm/wasm_test.cc b/test/extensions/bootstrap/wasm/wasm_test.cc index 2befcbd97cba..fdfdd9536557 100644 --- a/test/extensions/bootstrap/wasm/wasm_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_test.cc @@ -73,7 +73,7 @@ class WasmTestBase { std::shared_ptr wasm_; }; -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) +#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME) class WasmTest : public WasmTestBase, public testing::TestWithParam { public: void createWasm() { WasmTestBase::createWasm(GetParam()); } @@ -85,13 +85,20 @@ auto testing_values = testing::Values( #if defined(ENVOY_WASM_V8) "v8" #endif -#if defined(ENVOY_WASM_V8) && defined(ENVOY_WASM_WAVM) +#if defined(ENVOY_WASM_V8) && (defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME)) , #endif #if defined(ENVOY_WASM_WAVM) "wavm" #endif +#if (defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM)) && defined(ENVOY_WASM_WASMTIME) + , +#endif +#if defined(ENVOY_WASM_WASMTIME) + "wasmtime" +#endif ); + INSTANTIATE_TEST_SUITE_P(Runtimes, WasmTest, testing_values); #endif @@ -117,11 +124,14 @@ auto testing_null_values = testing::Values( #endif #if defined(ENVOY_WASM_WAVM) "wavm", +#endif +#if defined(ENVOY_WASM_WASMTIME) + "wasmtime", #endif "null"); INSTANTIATE_TEST_SUITE_P(Runtimes, WasmNullTest, testing_null_values); -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) +#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME) class WasmTestMatrix : public WasmTestBase, public testing::TestWithParam> { public: @@ -145,11 +155,17 @@ INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmTestMatrix, #if defined(ENVOY_WASM_V8) "v8" #endif -#if defined(ENVOY_WASM_V8) && defined(ENVOY_WASM_WAVM) +#if defined(ENVOY_WASM_V8) && (defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME)) , #endif #if defined(ENVOY_WASM_WAVM) "wavm" +#endif +#if (defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM)) && defined(ENVOY_WASM_WASMTIME) + , +#endif +#if defined(ENVOY_WASM_WASMTIME) + "wasmtime" #endif ), testing::Values("cpp", "rust"))); @@ -188,7 +204,7 @@ TEST_P(WasmTestMatrix, Logging) { } #endif -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) +#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME) TEST_P(WasmTest, BadSignature) { createWasm(); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index 0c84105cd778..110f7f720eec 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -97,6 +97,9 @@ auto test_values = testing::Values( #endif #if defined(ENVOY_WASM_WAVM) "wavm", +#endif +#if defined(ENVOY_WASM_WASMTIME) + "wasmtime", #endif "null"); INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonTest, test_values); diff --git a/test/extensions/filters/http/wasm/config_test.cc b/test/extensions/filters/http/wasm/config_test.cc index 6b41185f7913..552aa61a9387 100644 --- a/test/extensions/filters/http/wasm/config_test.cc +++ b/test/extensions/filters/http/wasm/config_test.cc @@ -29,7 +29,7 @@ using Common::Wasm::WasmException; namespace HttpFilters { namespace Wasm { -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) +#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME) class WasmFilterConfigTest : public Event::TestUsingSimulatedTime, public testing::TestWithParam { protected: @@ -71,13 +71,20 @@ auto testing_values = testing::Values( #if defined(ENVOY_WASM_V8) "v8" #endif -#if defined(ENVOY_WASM_V8) && defined(ENVOY_WASM_WAVM) +#if defined(ENVOY_WASM_V8) && (defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME)) , #endif #if defined(ENVOY_WASM_WAVM) "wavm" #endif +#if (defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM)) && defined(ENVOY_WASM_WASMTIME) + , +#endif +#if defined(ENVOY_WASM_WASMTIME) + "wasmtime" +#endif ); + INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFilterConfigTest, testing_values); TEST_P(WasmFilterConfigTest, JsonLoadFromFileWasm) { diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index ea010b9ae71d..9d3cda60b6ec 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -102,6 +102,9 @@ auto testing_values = testing::Values( #endif #if defined(ENVOY_WASM_WAVM) std::make_tuple("wavm", "cpp"), std::make_tuple("wavm", "rust"), +#endif +#if defined(ENVOY_WASM_WASMTIME) + std::make_tuple("wasmtime", "cpp"), std::make_tuple("wasmtime", "rust"), #endif std::make_tuple("null", "cpp")); INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, testing_values); @@ -1208,7 +1211,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamOpenAtShutdown) { // Test metadata access including CEL expressions. // TODO: re-enable this on Windows if and when the CEL `Antlr` parser compiles on Windows. -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) +#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME) TEST_P(WasmHttpFilterTest, Metadata) { setupTest("", "metadata"); setupFilter(); diff --git a/test/extensions/filters/network/wasm/config_test.cc b/test/extensions/filters/network/wasm/config_test.cc index 58d17c177fb7..f1507071ef78 100644 --- a/test/extensions/filters/network/wasm/config_test.cc +++ b/test/extensions/filters/network/wasm/config_test.cc @@ -64,6 +64,9 @@ auto testing_values = testing::Values( #endif #if defined(ENVOY_WASM_WAVM) "wavm", +#endif +#if defined(ENVOY_WASM_WASMTIME) + "wasmtime", #endif "null"); INSTANTIATE_TEST_SUITE_P(Runtimes, WasmNetworkFilterConfigTest, testing_values); diff --git a/test/extensions/filters/network/wasm/wasm_filter_test.cc b/test/extensions/filters/network/wasm/wasm_filter_test.cc index 6bf1ca8151e6..dd3a2e29a0c2 100644 --- a/test/extensions/filters/network/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/network/wasm/wasm_filter_test.cc @@ -90,6 +90,9 @@ auto testing_values = testing::Values( #endif #if defined(ENVOY_WASM_WAVM) std::make_tuple("wavm", "cpp"), std::make_tuple("wavm", "rust"), +#endif +#if defined(ENVOY_WASM_WASMTIME) + std::make_tuple("wasmtime", "cpp"), std::make_tuple("wasmtime", "rust"), #endif std::make_tuple("null", "cpp")); INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmNetworkFilterTest, testing_values); diff --git a/test/extensions/stats_sinks/wasm/config_test.cc b/test/extensions/stats_sinks/wasm/config_test.cc index 1e115dd2f946..d9b1263215af 100644 --- a/test/extensions/stats_sinks/wasm/config_test.cc +++ b/test/extensions/stats_sinks/wasm/config_test.cc @@ -73,6 +73,9 @@ auto testing_values = testing::Values( #endif #if defined(ENVOY_WASM_WAVM) "wavm", +#endif +#if defined(ENVOY_WASM_WASMTIME) + "wasmtime", #endif "null"); INSTANTIATE_TEST_SUITE_P(Runtimes, WasmStatSinkConfigTest, testing_values); diff --git a/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc b/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc index acd4df85dbde..716925bfd12f 100644 --- a/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc +++ b/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc @@ -62,6 +62,9 @@ auto testing_values = testing::Values( #endif #if defined(ENVOY_WASM_WAVM) "wavm", +#endif +#if defined(ENVOY_WASM_WASMTIME) + "wasmtime", #endif "null"); INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonContextTest, testing_values); diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 53d6dc677a7d..b0c7a7c98d5e 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1205,6 +1205,7 @@ virtualize vptr wakeup wakeups +wasmtime websocket wepoll whitespace From 489806d941f1028718f62218d2744589613637b1 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Nov 2020 06:22:02 +0000 Subject: [PATCH 077/117] configs: Separate and add stdout access logger to demo config in Docker container (#13935) This will make it easier to document request access logging based on a demo config Signed-off-by: Ryan Northey --- configs/BUILD | 8 ++++-- configs/Dockerfile | 2 +- configs/envoy-demo.yaml | 63 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 configs/envoy-demo.yaml diff --git a/configs/BUILD b/configs/BUILD index 9e2b73195368..064ce28fd94c 100644 --- a/configs/BUILD +++ b/configs/BUILD @@ -31,11 +31,15 @@ filegroup( "using_deprecated_config.yaml", "**/*.template.yaml", "freebind/freebind.yaml", + "envoy-demo.yaml", ], ) + select({ - "//bazel:apple": [], + "//bazel:apple": ["envoy-demo.yaml"], "//bazel:windows_x86_64": [], - "//conditions:default": ["freebind/freebind.yaml"], + "//conditions:default": [ + "envoy-demo.yaml", + "freebind/freebind.yaml", + ], }), ) diff --git a/configs/Dockerfile b/configs/Dockerfile index bae9778af9f3..8167a41d4dd4 100644 --- a/configs/Dockerfile +++ b/configs/Dockerfile @@ -3,5 +3,5 @@ FROM envoyproxy/envoy-dev:latest RUN apt-get update -COPY envoyproxy_io_proxy.yaml /etc/envoy.yaml +COPY envoy-demo.yaml /etc/envoy.yaml CMD /usr/local/bin/envoy -c /etc/envoy.yaml diff --git a/configs/envoy-demo.yaml b/configs/envoy-demo.yaml new file mode 100644 index 000000000000..287e7faa42f5 --- /dev/null +++ b/configs/envoy-demo.yaml @@ -0,0 +1,63 @@ +admin: + access_log_path: /tmp/admin_access.log + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 9901 +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + # For the demo config in the Docker container we use: + # - system logs -> `/dev/stderr` + # - (listener) access_logs -> `/dev/stdout` + path: /dev/stdout + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + host_rewrite_literal: www.envoyproxy.io + cluster: service_envoyproxy_io + http_filters: + - name: envoy.filters.http.router + clusters: + - name: service_envoyproxy_io + connect_timeout: 30s + type: LOGICAL_DNS + # Comment out the following line to test on v6 networks + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: service_envoyproxy_io + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: www.envoyproxy.io + port_value: 443 + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + sni: www.envoyproxy.io From 5cc781173f2fb6d987bd5d5634b1e1fb3324a745 Mon Sep 17 00:00:00 2001 From: Kuat Date: Thu, 12 Nov 2020 01:41:17 -0800 Subject: [PATCH 078/117] deps: replace third party statusor with absl (#13988) Signed-off-by: Kuat Yessenov --- source/common/common/BUILD | 2 +- source/common/common/statusor.h | 2 +- .../filters/http/cdn_loop/filter.cc | 2 +- .../filters/http/cdn_loop/parser.cc | 18 +- .../extensions/filters/http/cdn_loop/utils.cc | 3 +- .../filters/http/cdn_loop/parser_fuzz_test.cc | 2 +- test/test_common/status_utility.h | 2 +- third_party/statusor/BUILD | 33 -- third_party/statusor/statusor.cc | 43 -- third_party/statusor/statusor.h | 352 -------------- third_party/statusor/statusor_internals.h | 244 ---------- third_party/statusor/statusor_test.cc | 441 ------------------ 12 files changed, 16 insertions(+), 1128 deletions(-) delete mode 100644 third_party/statusor/BUILD delete mode 100644 third_party/statusor/statusor.cc delete mode 100644 third_party/statusor/statusor.h delete mode 100644 third_party/statusor/statusor_internals.h delete mode 100644 third_party/statusor/statusor_test.cc diff --git a/source/common/common/BUILD b/source/common/common/BUILD index 8c645fd32a91..89e41ae0c7b1 100644 --- a/source/common/common/BUILD +++ b/source/common/common/BUILD @@ -405,6 +405,6 @@ envoy_cc_library( name = "statusor_lib", hdrs = ["statusor.h"], deps = [ - "//third_party/statusor:statusor_lib", + "@com_google_absl//absl/status:statusor", ], ) diff --git a/source/common/common/statusor.h b/source/common/common/statusor.h index bb7201d3c4b6..474eb1548060 100644 --- a/source/common/common/statusor.h +++ b/source/common/common/statusor.h @@ -1,6 +1,6 @@ #pragma once -#include "third_party/statusor/statusor.h" +#include "absl/status/statusor.h" /** * Facility for returning either a valid value or an error in a form of Envoy::Status. diff --git a/source/extensions/filters/http/cdn_loop/filter.cc b/source/extensions/filters/http/cdn_loop/filter.cc index 9cc81ca0a928..38158011502e 100644 --- a/source/extensions/filters/http/cdn_loop/filter.cc +++ b/source/extensions/filters/http/cdn_loop/filter.cc @@ -33,7 +33,7 @@ Http::FilterHeadersStatus CdnLoopFilter::decodeHeaders(Http::RequestHeaderMap& h header_entry != nullptr) { if (StatusOr count = countCdnLoopOccurrences(header_entry->value().getStringView(), cdn_id_); - !count) { + !count.ok()) { decoder_callbacks_->sendLocalReply(Http::Code::BadRequest, ParseErrorMessage, nullptr, absl::nullopt, ParseErrorDetails); return Http::FilterHeadersStatus::StopIteration; diff --git a/source/extensions/filters/http/cdn_loop/parser.cc b/source/extensions/filters/http/cdn_loop/parser.cc index e686406c2fd3..6f37ac5eb3fa 100644 --- a/source/extensions/filters/http/cdn_loop/parser.cc +++ b/source/extensions/filters/http/cdn_loop/parser.cc @@ -118,7 +118,7 @@ StatusOr parseQuotedString(const ParseContext& input) { continue; } else if (context.peek() == '\\') { if (StatusOr quoted_pair_context = parseQuotedPair(context); - !quoted_pair_context) { + !quoted_pair_context.ok()) { return quoted_pair_context.status(); } else { context.setNext(*quoted_pair_context); @@ -222,13 +222,13 @@ StatusOr parseCdnId(const ParseContext& input) { // Optimization: dispatch on the next character to avoid the StrFormat in the // error path of an IPv6 parser when the value has a token (and vice versa). if (context.peek() == '[') { - if (StatusOr ipv6 = parsePlausibleIpV6(context); !ipv6) { + if (StatusOr ipv6 = parsePlausibleIpV6(context); !ipv6.ok()) { return ipv6.status(); } else { context.setNext(*ipv6); } } else { - if (StatusOr token = parseToken(context); !token) { + if (StatusOr token = parseToken(context); !token.ok()) { return token.status(); } else { context.setNext(*token); @@ -260,7 +260,7 @@ StatusOr parseCdnId(const ParseContext& input) { StatusOr parseParameter(const ParseContext& input) { ParseContext context = input; - if (StatusOr parsed_token = parseToken(context); !parsed_token) { + if (StatusOr parsed_token = parseToken(context); !parsed_token.ok()) { return parsed_token.status(); } else { context.setNext(*parsed_token); @@ -286,13 +286,13 @@ StatusOr parseParameter(const ParseContext& input) { // error path of an quoted string parser when the next item is a token (and // vice versa). if (context.peek() == '"') { - if (StatusOr value_quote = parseQuotedString(context); !value_quote) { + if (StatusOr value_quote = parseQuotedString(context); !value_quote.ok()) { return value_quote.status(); } else { return *value_quote; } } else { - if (StatusOr value_token = parseToken(context); !value_token) { + if (StatusOr value_token = parseToken(context); !value_token.ok()) { return value_token.status(); } else { return *value_token; @@ -303,7 +303,7 @@ StatusOr parseParameter(const ParseContext& input) { StatusOr parseCdnInfo(const ParseContext& input) { absl::string_view cdn_id; ParseContext context = input; - if (StatusOr parsed_id = parseCdnId(input); !parsed_id) { + if (StatusOr parsed_id = parseCdnId(input); !parsed_id.ok()) { return parsed_id.status(); } else { context.setNext(parsed_id->context()); @@ -320,7 +320,7 @@ StatusOr parseCdnInfo(const ParseContext& input) { context.setNext(skipOptionalWhitespace(context)); - if (StatusOr parameter = parseParameter(context); !parameter) { + if (StatusOr parameter = parseParameter(context); !parameter.ok()) { return parameter.status(); } else { context.setNext(*parameter); @@ -348,7 +348,7 @@ StatusOr parseCdnInfoList(const ParseContext& input) { continue; } - if (StatusOr parsed_cdn_info = parseCdnInfo(context); !parsed_cdn_info) { + if (StatusOr parsed_cdn_info = parseCdnInfo(context); !parsed_cdn_info.ok()) { return parsed_cdn_info.status(); } else { cdn_infos.push_back(parsed_cdn_info->cdnId()); diff --git a/source/extensions/filters/http/cdn_loop/utils.cc b/source/extensions/filters/http/cdn_loop/utils.cc index 2ea1a6a1945d..e7c478509910 100644 --- a/source/extensions/filters/http/cdn_loop/utils.cc +++ b/source/extensions/filters/http/cdn_loop/utils.cc @@ -19,7 +19,8 @@ StatusOr countCdnLoopOccurrences(absl::string_view header, absl::string_vie return absl::InvalidArgumentError("cdn_id cannot be empty"); } - if (absl::StatusOr parsed = Parser::parseCdnInfoList(header); parsed) { + if (absl::StatusOr parsed = Parser::parseCdnInfoList(header); + parsed.ok()) { return std::count(parsed->cdnIds().begin(), parsed->cdnIds().end(), cdn_id); } else { return parsed.status(); diff --git a/test/extensions/filters/http/cdn_loop/parser_fuzz_test.cc b/test/extensions/filters/http/cdn_loop/parser_fuzz_test.cc index 8fa6c16085b6..7d5f53f8bbc4 100644 --- a/test/extensions/filters/http/cdn_loop/parser_fuzz_test.cc +++ b/test/extensions/filters/http/cdn_loop/parser_fuzz_test.cc @@ -17,7 +17,7 @@ using Envoy::Extensions::HttpFilters::CdnLoop::Parser::ParsedCdnInfoList; DEFINE_FUZZER(const uint8_t* buf, size_t len) { absl::string_view input(reinterpret_cast(buf), len); StatusOr list = parseCdnInfoList(ParseContext(input)); - if (list) { + if (list.ok()) { // If we successfully parse input, we should make sure that cdn_ids we find appear in the input // string in order. size_t start = 0; diff --git a/test/test_common/status_utility.h b/test/test_common/status_utility.h index 5a48a7c2e0f3..7611830b26af 100644 --- a/test/test_common/status_utility.h +++ b/test/test_common/status_utility.h @@ -13,7 +13,7 @@ namespace StatusHelpers { // StatusOr status(3); // EXPECT_THAT(status, IsOkAndHolds(3)); MATCHER_P(IsOkAndHolds, expected, "") { - if (!arg) { + if (!arg.ok()) { *result_listener << "which has unexpected status: " << arg.status(); return false; } diff --git a/third_party/statusor/BUILD b/third_party/statusor/BUILD deleted file mode 100644 index fe17659a5378..000000000000 --- a/third_party/statusor/BUILD +++ /dev/null @@ -1,33 +0,0 @@ -licenses(["notice"]) # Apache 2 - -load( - "//bazel:envoy_build_system.bzl", - "envoy_cc_library", - "envoy_cc_test", - "envoy_package", -) - -envoy_package() - -envoy_cc_library( - name = "statusor_lib", - srcs = [ - "statusor.cc", - ], - hdrs = [ - "statusor.h", - "statusor_internals.h", - ], - external_deps = [ - "abseil_status", - ], - deps = ["//source/common/common:assert_lib"], -) - -envoy_cc_test( - name = "statusor_test", - srcs = ["statusor_test.cc"], - deps = [ - ":statusor_lib", - ], -) diff --git a/third_party/statusor/statusor.cc b/third_party/statusor/statusor.cc deleted file mode 100644 index 20105404217a..000000000000 --- a/third_party/statusor/statusor.cc +++ /dev/null @@ -1,43 +0,0 @@ -/** - * IMPORTANT: this file is a fork of the soon to be open-source absl::StatusOr class. - * When the absl::StatusOr lands this file will be removed. - */ - -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "third_party/statusor/statusor.h" - -#include - -#include "common/common/assert.h" - -namespace absl { - -namespace internal_statusor { - -void Helper::HandleInvalidStatusCtorArg(absl::Status* status) { - const char* kMessage = "An OK status is not a valid constructor argument to StatusOr"; - ASSERT(false, kMessage); - // In optimized builds, we will fall back to ::util::error::INTERNAL. - *status = absl::Status(absl::StatusCode::kInternal, kMessage); -} - -void Helper::Crash(const absl::Status&) { abort(); } - -} // namespace internal_statusor - -} // namespace Envoy diff --git a/third_party/statusor/statusor.h b/third_party/statusor/statusor.h deleted file mode 100644 index d8a7d48329e0..000000000000 --- a/third_party/statusor/statusor.h +++ /dev/null @@ -1,352 +0,0 @@ -/** - * IMPORTANT: this file is a fork of the soon to be open-source absl::StatusOr class. - * When the absl::StatusOr lands this file will be removed. - */ - -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// StatusOr is the union of a Status object and a T -// object. StatusOr models the concept of an object that is either a -// usable value, or an error Status explaining why such a value is -// not present. To this end, StatusOr does not allow its Status -// value to be OkStatus(). -// -// The primary use-case for StatusOr is as the return value of a -// function which may fail. -// -// Example usage of a StatusOr: -// -// StatusOr result = DoBigCalculationThatCouldFail(); -// if (result) { -// result->DoSomethingCool(); -// } else { -// GOOGLE_LOG(ERROR) << result.status(); -// } -// -// Example that is guaranteed crash if the result holds no value: -// -// StatusOr result = DoBigCalculationThatCouldFail(); -// const Foo& foo = result.value(); -// foo.DoSomethingCool(); -// -// Example usage of a StatusOr>: -// -// StatusOr> result = FooFactory::MakeNewFoo(arg); -// if (!result) { -// GOOGLE_LOG(ERROR) << result.status(); -// } else if (*result == nullptr) { -// GOOGLE_LOG(ERROR) << "Unexpected null pointer"; -// } else { -// (*result)->DoSomethingCool(); -// } -// -// Example factory implementation returning StatusOr: -// -// StatusOr FooFactory::MakeFoo(int arg) { -// if (arg <= 0) { -// return ::cel_base::Status(::cel_base::INVALID_ARGUMENT, -// "Arg must be positive"); -// } -// return Foo(arg); -// } -// - -#include -#include -#include -#include - -#include "third_party/statusor/statusor_internals.h" - -#include "absl/base/attributes.h" -#include "absl/base/macros.h" - -namespace absl { - -// Returned StatusOr objects may not be ignored. -template class ABSL_MUST_USE_RESULT StatusOr; - -template -class StatusOr : private internal_statusor::StatusOrData, - private internal_statusor::TraitsBase::value, - std::is_move_constructible::value> { - template friend class StatusOr; - - typedef internal_statusor::StatusOrData Base; - -public: - using element_type = T; - - // Constructs a new StatusOr with Status::UNKNOWN status. This is marked - // 'explicit' to try to catch cases like 'return {};', where people think - // StatusOr> will be initialized with an empty vector, - // instead of a Status::UNKNOWN status. - explicit StatusOr(); - - // StatusOr will be copy constructible/assignable if T is copy - // constructible. - StatusOr(const StatusOr&) = default; - StatusOr& operator=(const StatusOr&) = default; - - // StatusOr will be move constructible/assignable if T is move - // constructible. - StatusOr(StatusOr&&) = default; - StatusOr& operator=(StatusOr&&) = default; - - // Conversion copy/move constructor, T must be convertible from U. - // These should not participate in overload resolution if U - // is not convertible to T. - template StatusOr(const StatusOr& other); - template StatusOr(StatusOr&& other); - - // Conversion copy/move assignment operator, T must be convertible from U. - template StatusOr& operator=(const StatusOr& other); - template StatusOr& operator=(StatusOr&& other); - - // Constructs a new StatusOr with the given value. After calling this - // constructor, this->ok() will be true and the contained value may be - // retrieved with value(), operator*(), or operator->(). - // - // NOTE: Not explicit - we want to use StatusOr as a return type - // so it is convenient and sensible to be able to do 'return T()' - // when the return type is StatusOr. - // - // REQUIRES: T is copy constructible. - StatusOr(const T& value); - - // Constructs a new StatusOr with the given non-ok status. After calling this - // constructor, this->ok() will be false and calls to value() will - // CHECK-fail. - // - // NOTE: Not explicit - we want to use StatusOr as a return - // value, so it is convenient and sensible to be able to do 'return - // Status()' when the return type is StatusOr. - // - // REQUIRES: !status.ok(). This requirement is checked by ASSERT. - // In optimized builds, passing OkStatus() here will have the effect - // of passing INTERNAL as a fallback. - StatusOr(const absl::Status& status); - StatusOr& operator=(const absl::Status& status); - - // Similar to the `const T&` overload. - // - // REQUIRES: T is move constructible. - StatusOr(T&& value); - - // RValue versions of the operations declared above. - StatusOr(absl::Status&& status); - StatusOr& operator=(absl::Status&& status); - - // Returns this->ok() - explicit operator bool() const { return ok(); } - - // Returns this->status().ok() - ABSL_MUST_USE_RESULT bool ok() const { return this->status_.ok(); } - - // Returns a reference to our status. If this contains a T, then - // returns OkStatus(). - const absl::Status& status() const&; - absl::Status status() &&; - - // Returns a reference to our current value, or ASSERT-fails if !this->ok(). If - // you have already checked the status using this->ok() or operator bool(), - // then you probably want to use operator*() or operator->() to access the - // current value instead of value(). - // - // Note: for value types that are cheap to copy, prefer simple code: - // - // T value = status_or.value(); - // - // Otherwise, if the value type is expensive to copy, but can be left - // in the StatusOr, simply assign to a reference: - // - // T& value = status_or.value(); // or `const T&` - // - // Otherwise, if the value type supports an efficient move, it can be - // used as follows: - // - // T value = std::move(status_or).value(); - // - // The std::move on status_or instead of on the whole expression enables - // warnings about possible uses of the status_or object after the move. - - const T& value() const&; - T& value() &; - const T&& value() const&&; - T&& value() &&; - - // Returns a reference to the current value. - // - // REQUIRES: this->ok() == true, otherwise the behavior is undefined. - // - // Use this->ok() or `operator bool()` to verify that there is a current - // value. Alternatively, see value() for a similar API that guarantees - // ASSERT-failing if there is no current value. - const T& operator*() const&; - T& operator*() &; - const T&& operator*() const&&; - T&& operator*() &&; - - // Returns a pointer to the current value. - // - // REQUIRES: this->ok() == true, otherwise the behavior is undefined. - // - // Use this->ok() or `operator bool()` to verify that there is a current - // value. - const T* operator->() const; - T* operator->(); - - // Returns a copy of the current value if this->ok() == true. Otherwise - // returns a default value. - template T value_or(U&& default_value) const&; - template T value_or(U&& default_value) &&; - - // Ignores any errors. This method does nothing except potentially suppress - // complaints from any tools that are checking that errors are not dropped on - // the floor. - void IgnoreError() const; -}; - -//////////////////////////////////////////////////////////////////////////////// -// Implementation details for StatusOr - -template -StatusOr::StatusOr() : Base(absl::Status(absl::StatusCode::kUnknown, "")) {} - -template StatusOr::StatusOr(const T& value) : Base(value) {} - -template StatusOr::StatusOr(const absl::Status& status) : Base(status) {} - -template StatusOr& StatusOr::operator=(const absl::Status& status) { - this->Assign(status); - return *this; -} - -template StatusOr::StatusOr(T&& value) : Base(std::move(value)) {} - -template StatusOr::StatusOr(absl::Status&& status) : Base(std::move(status)) {} - -template StatusOr& StatusOr::operator=(absl::Status&& status) { - this->Assign(std::move(status)); - return *this; -} - -template -template -inline StatusOr::StatusOr(const StatusOr& other) - : Base(static_cast::Base&>(other)) {} - -template -template -inline StatusOr& StatusOr::operator=(const StatusOr& other) { - if (other.ok()) - this->Assign(other.value()); - else - this->Assign(other.status()); - return *this; -} - -template -template -inline StatusOr::StatusOr(StatusOr&& other) - : Base(static_cast::Base&&>(other)) {} - -template -template -inline StatusOr& StatusOr::operator=(StatusOr&& other) { - if (other.ok()) { - this->Assign(std::move(other).value()); - } else { - this->Assign(std::move(other).status()); - } - return *this; -} - -template const absl::Status& StatusOr::status() const& { return this->status_; } -template absl::Status StatusOr::status() && { - return ok() ? absl::OkStatus() : std::move(this->status_); -} - -template const T& StatusOr::value() const& { - this->EnsureOk(); - return this->data_; -} - -template T& StatusOr::value() & { - this->EnsureOk(); - return this->data_; -} - -template const T&& StatusOr::value() const&& { - this->EnsureOk(); - return std::move(this->data_); -} - -template T&& StatusOr::value() && { - this->EnsureOk(); - return std::move(this->data_); -} - -template const T& StatusOr::operator*() const& { - this->EnsureOk(); - return this->data_; -} - -template T& StatusOr::operator*() & { - this->EnsureOk(); - return this->data_; -} - -template const T&& StatusOr::operator*() const&& { - this->EnsureOk(); - return std::move(this->data_); -} - -template T&& StatusOr::operator*() && { - this->EnsureOk(); - return std::move(this->data_); -} - -template const T* StatusOr::operator->() const { - this->EnsureOk(); - return &this->data_; -} - -template T* StatusOr::operator->() { - this->EnsureOk(); - return &this->data_; -} - -template template T StatusOr::value_or(U&& default_value) const& { - if (ok()) { - return this->data_; - } - return std::forward(default_value); -} - -template template T StatusOr::value_or(U&& default_value) && { - if (ok()) { - return std::move(this->data_); - } - return std::forward(default_value); -} - -template void StatusOr::IgnoreError() const { - // no-op -} - -} // namespace absl diff --git a/third_party/statusor/statusor_internals.h b/third_party/statusor/statusor_internals.h deleted file mode 100644 index ffe3f91af2c7..000000000000 --- a/third_party/statusor/statusor_internals.h +++ /dev/null @@ -1,244 +0,0 @@ -/** - * IMPORTANT: this file is a fork of the soon to be open-source absl::StatusOr class. - * When the absl::StatusOr lands this file will be removed. - */ - -/* - * Copyright 2019 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include "absl/base/attributes.h" -#include "absl/meta/type_traits.h" -#include "absl/status/status.h" - -namespace absl { - -namespace internal_statusor { - -class Helper { -public: - // Move type-agnostic error handling to the .cc. - static void HandleInvalidStatusCtorArg(absl::Status*); - ABSL_ATTRIBUTE_NORETURN static void Crash(const absl::Status& status); -}; - -// Construct an instance of T in `p` through placement new, passing Args... to -// the constructor. -// This abstraction is here mostly for the gcc performance fix. -template void PlacementNew(void* p, Args&&... args) { -#if defined(__GNUC__) && !defined(__clang__) - // Teach gcc that 'p' cannot be null, fixing code size issues. - if (p == nullptr) - __builtin_unreachable(); -#endif - new (p) T(std::forward(args)...); -} - -// Helper base class to hold the data and all operations. -// We move all this to a base class to allow mixing with the appropriate -// TraitsBase specialization. -template class StatusOrData { - template friend class StatusOrData; - -public: - StatusOrData() = delete; - - StatusOrData(const StatusOrData& other) { - if (other.ok()) { - MakeValue(other.data_); - MakeStatus(); - } else { - MakeStatus(other.status_); - } - } - - StatusOrData(StatusOrData&& other) noexcept { - if (other.ok()) { - MakeValue(std::move(other.data_)); - MakeStatus(); - } else { - MakeStatus(std::move(other.status_)); - } - } - - template StatusOrData(const StatusOrData& other) { - if (other.ok()) { - MakeValue(other.data_); - MakeStatus(); - } else { - MakeStatus(other.status_); - } - } - - template StatusOrData(StatusOrData&& other) { - if (other.ok()) { - MakeValue(std::move(other.data_)); - MakeStatus(); - } else { - MakeStatus(std::move(other.status_)); - } - } - - explicit StatusOrData(const T& value) : data_(value) { MakeStatus(); } - explicit StatusOrData(T&& value) : data_(std::move(value)) { MakeStatus(); } - - explicit StatusOrData(const absl::Status& status) : status_(status) { EnsureNotOk(); } - explicit StatusOrData(absl::Status&& status) : status_(std::move(status)) { EnsureNotOk(); } - - StatusOrData& operator=(const StatusOrData& other) { - if (this == &other) - return *this; - if (other.ok()) - Assign(other.data_); - else - Assign(other.status_); - return *this; - } - - StatusOrData& operator=(StatusOrData&& other) { - if (this == &other) - return *this; - if (other.ok()) - Assign(std::move(other.data_)); - else - Assign(std::move(other.status_)); - return *this; - } - - ~StatusOrData() { - if (ok()) { - status_.~Status(); - data_.~T(); - } else { - status_.~Status(); - } - } - - void Assign(const T& value) { - if (ok()) { - data_.~T(); - MakeValue(value); - } else { - MakeValue(value); - status_ = absl::OkStatus(); - } - } - - void Assign(T&& value) { - if (ok()) { - data_.~T(); - MakeValue(std::move(value)); - } else { - MakeValue(std::move(value)); - status_ = absl::OkStatus(); - } - } - - void Assign(const absl::Status& status) { - Clear(); - status_ = status; - EnsureNotOk(); - } - - void Assign(absl::Status&& status) { - Clear(); - status_ = std::move(status); - EnsureNotOk(); - } - - bool ok() const { return status_.ok(); } - -protected: - // status_ will always be active after the constructor. - // We make it a union to be able to initialize exactly how we need without - // waste. - // E/g. in the copy constructor we use the default constructor of - // Status in the ok() path to avoid an extra Ref call. - union { - absl::Status status_; - }; - - // data_ is active iff status_.ok()==true - struct Dummy {}; - union { - // When T is const, we need some non-const object we can cast to void* for - // the placement new. dummy_ is that object. - Dummy dummy_; - T data_; - }; - - void Clear() { - if (ok()) - data_.~T(); - } - - void EnsureOk() const { - if (!ok()) - Helper::Crash(status_); - } - - void EnsureNotOk() { - if (ok()) - Helper::HandleInvalidStatusCtorArg(&status_); - } - - // Construct the value (i.e. data_) through placement new with the passed - // argument. - template void MakeValue(Arg&& arg) { - internal_statusor::PlacementNew(&dummy_, std::forward(arg)); - } - - // Construct the status (i.e. status_) through placement new with the passed - // argument. - template void MakeStatus(Args&&... args) { - internal_statusor::PlacementNew(&status_, std::forward(args)...); - } -}; - -// Helper base class to allow implicitly deleted constructors and assignment -// operations in StatusOr. -// TraitsBase will explicitly delete what it can't support and StatusOr will -// inherit that behavior implicitly. -template struct TraitsBase { - TraitsBase() = default; - TraitsBase(const TraitsBase&) = default; - TraitsBase(TraitsBase&&) = default; - TraitsBase& operator=(const TraitsBase&) = default; - TraitsBase& operator=(TraitsBase&&) = default; -}; - -template <> struct TraitsBase { - TraitsBase() = default; - TraitsBase(const TraitsBase&) = delete; - TraitsBase(TraitsBase&&) = default; - TraitsBase& operator=(const TraitsBase&) = delete; - TraitsBase& operator=(TraitsBase&&) = default; -}; - -template <> struct TraitsBase { - TraitsBase() = default; - TraitsBase(const TraitsBase&) = delete; - TraitsBase(TraitsBase&&) = delete; - TraitsBase& operator=(const TraitsBase&) = delete; - TraitsBase& operator=(TraitsBase&&) = delete; -}; - -} // namespace internal_statusor - -} // namespace absl diff --git a/third_party/statusor/statusor_test.cc b/third_party/statusor/statusor_test.cc deleted file mode 100644 index b627c518476e..000000000000 --- a/third_party/statusor/statusor_test.cc +++ /dev/null @@ -1,441 +0,0 @@ -/** - * IMPORTANT: this file is a fork of the soon to be open-source absl::StatusOr class. - * When the absl::StatusOr lands this file will be removed. - */ - -/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -==============================================================================*/ - -// Unit tests for StatusOr - -#include -#include - -#include "third_party/statusor/statusor.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace absl { -namespace { - - -class Base1 { - public: - virtual ~Base1() {} - int pad_; -}; - -class Base2 { - public: - virtual ~Base2() {} - int yetotherpad_; -}; - -class Derived : public Base1, public Base2 { - public: - ~Derived() override {} - int evenmorepad_; -}; - -class CopyNoAssign { - public: - explicit CopyNoAssign(int value) : foo_(value) {} - CopyNoAssign(const CopyNoAssign& other) : foo_(other.foo_) {} - int foo_; - - private: - const CopyNoAssign& operator=(const CopyNoAssign&); -}; - -class NoDefaultConstructor { - public: - explicit NoDefaultConstructor(int foo); -}; - -static_assert(!std::is_default_constructible(), - "Should not be default-constructible."); - -StatusOr> ReturnUniquePtr() { - // Uses implicit constructor from T&& - return std::unique_ptr(new int(0)); -} - -TEST(StatusOr, ElementType) { - static_assert(std::is_same::element_type, int>(), ""); - static_assert(std::is_same::element_type, char>(), ""); -} - -TEST(StatusOr, NullPointerStatusOr) { - // As a very special case, null-plain-pointer StatusOr used to be an - // error. Test that it no longer is. - StatusOr null_status(nullptr); - EXPECT_TRUE(null_status.ok()); - EXPECT_EQ(null_status.value(), nullptr); -} - -TEST(StatusOr, TestNoDefaultConstructorInitialization) { - // Explicitly initialize it with an error code. - StatusOr statusor(absl::CancelledError("")); - EXPECT_FALSE(statusor.ok()); - EXPECT_EQ(statusor.status().code(), absl::StatusCode::kCancelled); - - // Default construction of StatusOr initializes it with an UNKNOWN error code. - StatusOr statusor2; - EXPECT_FALSE(statusor2.ok()); - EXPECT_EQ(statusor2.status().code(), absl::StatusCode::kUnknown); -} - -TEST(StatusOr, TestMoveOnlyInitialization) { - StatusOr> thing(ReturnUniquePtr()); - ASSERT_TRUE(thing.ok()); - EXPECT_EQ(0, *thing.value()); - int* previous = thing.value().get(); - - thing = ReturnUniquePtr(); - EXPECT_TRUE(thing.ok()); - EXPECT_EQ(0, *thing.value()); - EXPECT_NE(previous, thing.value().get()); -} - -TEST(StatusOr, TestMoveOnlyStatusCtr) { - StatusOr> thing(absl::CancelledError("")); - ASSERT_FALSE(thing.ok()); -} - -TEST(StatusOr, TestMoveOnlyValueExtraction) { - StatusOr> thing(ReturnUniquePtr()); - ASSERT_TRUE(thing.ok()); - std::unique_ptr ptr = std::move(thing).value(); - EXPECT_EQ(0, *ptr); - - thing = std::move(ptr); - ptr = std::move(thing.value()); - EXPECT_EQ(0, *ptr); -} - -TEST(StatusOr, TestMoveOnlyConversion) { - StatusOr> const_thing(ReturnUniquePtr()); - EXPECT_TRUE(const_thing.ok()); - EXPECT_EQ(0, *const_thing.value()); - - // Test rvalue converting assignment - const int* const_previous = const_thing.value().get(); - const_thing = ReturnUniquePtr(); - EXPECT_TRUE(const_thing.ok()); - EXPECT_EQ(0, *const_thing.value()); - EXPECT_NE(const_previous, const_thing.value().get()); -} - -TEST(StatusOr, TestMoveOnlyVector) { - // Sanity check that StatusOr works in vector. - std::vector>> vec; - vec.push_back(ReturnUniquePtr()); - vec.resize(2); - auto another_vec = std::move(vec); - EXPECT_EQ(0, *another_vec[0].value()); - EXPECT_EQ(absl::StatusCode::kUnknown, another_vec[1].status().code()); -} - -TEST(StatusOr, TestMoveWithValuesAndErrors) { - StatusOr status_or(std::string(1000, '0')); - StatusOr value1(std::string(1000, '1')); - StatusOr value2(std::string(1000, '2')); - StatusOr error1(Status(absl::StatusCode::kUnknown, "error1")); - StatusOr error2(Status(absl::StatusCode::kUnknown, "error2")); - - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '0'), status_or.value()); - - // Overwrite the value in status_or with another value. - status_or = std::move(value1); - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '1'), status_or.value()); - - // Overwrite the value in status_or with an error. - status_or = std::move(error1); - ASSERT_FALSE(status_or.ok()); - EXPECT_EQ("error1", status_or.status().message()); - - // Overwrite the error in status_or with another error. - status_or = std::move(error2); - ASSERT_FALSE(status_or.ok()); - EXPECT_EQ("error2", status_or.status().message()); - - // Overwrite the error with a value. - status_or = std::move(value2); - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '2'), status_or.value()); -} - -TEST(StatusOr, TestCopyWithValuesAndErrors) { - StatusOr status_or(std::string(1000, '0')); - StatusOr value1(std::string(1000, '1')); - StatusOr value2(std::string(1000, '2')); - StatusOr error1(Status(absl::StatusCode::kUnknown, "error1")); - StatusOr error2(Status(absl::StatusCode::kUnknown, "error2")); - - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '0'), status_or.value()); - - // Overwrite the value in status_or with another value. - status_or = value1; - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '1'), status_or.value()); - - // Overwrite the value in status_or with an error. - status_or = error1; - ASSERT_FALSE(status_or.ok()); - EXPECT_EQ("error1", status_or.status().message()); - - // Overwrite the error in status_or with another error. - status_or = error2; - ASSERT_FALSE(status_or.ok()); - EXPECT_EQ("error2", status_or.status().message()); - - // Overwrite the error with a value. - status_or = value2; - ASSERT_TRUE(status_or.ok()); - EXPECT_EQ(std::string(1000, '2'), status_or.value()); - - // Verify original values unchanged. - EXPECT_EQ(std::string(1000, '1'), value1.value()); - EXPECT_EQ("error1", error1.status().message()); - EXPECT_EQ("error2", error2.status().message()); - EXPECT_EQ(std::string(1000, '2'), value2.value()); -} - -TEST(StatusOr, TestDefaultCtor) { - StatusOr thing; - EXPECT_FALSE(thing.ok()); - EXPECT_EQ(thing.status().code(), absl::StatusCode::kUnknown); -} - -TEST(StatusOrDeathTest, TestDefaultCtorValue) { - StatusOr thing; - EXPECT_DEATH(thing.value(), ""); - - const StatusOr thing2; - EXPECT_DEATH(thing.value(), ""); -} - -TEST(StatusOr, TestStatusCtor) { - StatusOr thing(Status(absl::StatusCode::kCancelled, "")); - EXPECT_FALSE(thing.ok()); - EXPECT_EQ(thing.status().code(), absl::StatusCode::kCancelled); -} - -TEST(StatusOr, TestValueCtor) { - const int kI = 4; - const StatusOr thing(kI); - EXPECT_TRUE(thing.ok()); - EXPECT_EQ(kI, thing.value()); -} - -TEST(StatusOr, TestCopyCtorStatusOk) { - const int kI = 4; - const StatusOr original(kI); - const StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); - EXPECT_EQ(original.value(), copy.value()); -} - -TEST(StatusOr, TestCopyCtorStatusNotOk) { - StatusOr original(Status(absl::StatusCode::kCancelled, "")); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); -} - -TEST(StatusOr, TestCopyCtorNonAssignable) { - const int kI = 4; - CopyNoAssign value(kI); - StatusOr original(value); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); - EXPECT_EQ(original.value().foo_, copy.value().foo_); -} - -TEST(StatusOr, TestCopyCtorStatusOKConverting) { - const int kI = 4; - StatusOr original(kI); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); - EXPECT_DOUBLE_EQ(original.value(), copy.value()); -} - -TEST(StatusOr, TestCopyCtorStatusNotOkConverting) { - StatusOr original(Status(absl::StatusCode::kCancelled, "")); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); -} - -TEST(StatusOr, TestAssignmentStatusOk) { - const int kI = 4; - StatusOr source(kI); - StatusOr target; - target = source; - EXPECT_EQ(target.status(), source.status()); - EXPECT_EQ(source.value(), target.value()); -} - -TEST(StatusOr, TestAssignmentStatusNotOk) { - StatusOr source(Status(absl::StatusCode::kCancelled, "")); - StatusOr target; - target = source; - EXPECT_EQ(target.status(), source.status()); -} - -TEST(StatusOr, TestStatus) { - StatusOr good(4); - EXPECT_TRUE(good.ok()); - StatusOr bad(Status(absl::StatusCode::kCancelled, "")); - EXPECT_FALSE(bad.ok()); - EXPECT_EQ(bad.status(), Status(absl::StatusCode::kCancelled, "")); -} - -TEST(StatusOr, TestValue) { - const int kI = 4; - StatusOr thing(kI); - EXPECT_EQ(kI, thing.value()); -} - -TEST(StatusOr, TestValueConst) { - const int kI = 4; - const StatusOr thing(kI); - EXPECT_EQ(kI, thing.value()); -} - -TEST(StatusOrDeathTest, TestValueNotOk) { - StatusOr thing(Status(absl::StatusCode::kCancelled, "cancelled")); - EXPECT_DEATH(thing.value(), ""); -} - -TEST(StatusOrDeathTest, TestValueNotOkConst) { - const StatusOr thing(Status(absl::StatusCode::kUnknown, "")); - EXPECT_DEATH(thing.value(), ""); -} - -TEST(StatusOr, TestPointerDefaultCtor) { - StatusOr thing; - EXPECT_FALSE(thing.ok()); - EXPECT_EQ(thing.status().code(), absl::StatusCode::kUnknown); -} - -TEST(StatusOrDeathTest, TestPointerDefaultCtorValue) { - StatusOr thing; - EXPECT_DEATH(thing.value(), ""); -} - -TEST(StatusOr, TestPointerStatusCtor) { - StatusOr thing(Status(absl::StatusCode::kCancelled, "")); - EXPECT_FALSE(thing.ok()); - EXPECT_EQ(thing.status(), Status(absl::StatusCode::kCancelled, "")); -} - -TEST(StatusOr, TestPointerValueCtor) { - const int kI = 4; - StatusOr thing(&kI); - EXPECT_TRUE(thing.ok()); - EXPECT_EQ(&kI, thing.value()); -} - -TEST(StatusOr, TestPointerCopyCtorStatusOk) { - const int kI = 0; - StatusOr original(&kI); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); - EXPECT_EQ(original.value(), copy.value()); -} - -TEST(StatusOr, TestPointerCopyCtorStatusNotOk) { - StatusOr original(Status(absl::StatusCode::kCancelled, "")); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); -} - -TEST(StatusOr, TestPointerCopyCtorStatusOKConverting) { - Derived derived; - StatusOr original(&derived); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); - EXPECT_EQ(static_cast(original.value()), - copy.value()); -} - -TEST(StatusOr, TestPointerCopyCtorStatusNotOkConverting) { - StatusOr original(Status(absl::StatusCode::kCancelled, "")); - StatusOr copy(original); - EXPECT_EQ(copy.status(), original.status()); -} - -TEST(StatusOr, TestPointerAssignmentStatusOk) { - const int kI = 0; - StatusOr source(&kI); - StatusOr target; - target = source; - EXPECT_EQ(target.status(), source.status()); - EXPECT_EQ(source.value(), target.value()); -} - -TEST(StatusOr, TestPointerAssignmentStatusNotOk) { - StatusOr source(Status(absl::StatusCode::kCancelled, "")); - StatusOr target; - target = source; - EXPECT_EQ(target.status(), source.status()); -} - -TEST(StatusOr, TestPointerStatus) { - const int kI = 0; - StatusOr good(&kI); - EXPECT_TRUE(good.ok()); - StatusOr bad(Status(absl::StatusCode::kCancelled, "")); - EXPECT_EQ(bad.status(), Status(absl::StatusCode::kCancelled, "")); -} - -TEST(StatusOr, TestPointerValue) { - const int kI = 0; - StatusOr thing(&kI); - EXPECT_EQ(&kI, thing.value()); -} - -TEST(StatusOr, TestPointerValueConst) { - const int kI = 0; - const StatusOr thing(&kI); - EXPECT_EQ(&kI, thing.value()); -} - -// NOTE(tucker): StatusOr does not support this kind -// of resize op. -// TEST(StatusOr, StatusOrVectorOfUniquePointerCanResize) { -// using EvilType = std::vector>; -// static_assert(std::is_copy_constructible::value, ""); -// std::vector> v(5); -// v.reserve(v.capacity() + 10); -// } - -TEST(StatusOrDeathTest, TestPointerValueNotOk) { - StatusOr thing(Status(absl::StatusCode::kCancelled, "cancelled")); - EXPECT_DEATH(thing.value(), ""); -} - -TEST(StatusOrDeathTest, TestPointerValueNotOkConst) { - const StatusOr thing(Status(absl::StatusCode::kCancelled, "cancelled")); - EXPECT_DEATH(thing.value(), ""); -} - -// Benchmarks were removed as we not intend to change forked code. - -} // namespace -} // namespace absl From bdd1045d8a89b508f2b0a9d88c45bc3d1aa3620d Mon Sep 17 00:00:00 2001 From: Elena Cokova Date: Thu, 12 Nov 2020 09:51:51 -0500 Subject: [PATCH 079/117] router: call chargeStats() in recreateStream() when handling internal redirects (#13585) Commit Message: Call chargeStats when handling an internal redirect to correctly identify the requests as 3xx when logging stats. Without this change, internal redirects are classified as 0xx since the response code is never set on the underlying stream_info. Additional Description: The call to chargeStats() is generally made in encodeHeaders(), but that is skipped when handling internal redirects (the stream is recreated instead). Risk Level: Low Testing: Unit tests to confirm that most existing behavior is unchanged, and integration test to check that the stats are logged as expected. Docs Changes: N/A Release Notes: N/A Signed-off-by: Elena Cokova --- include/envoy/http/filter.h | 31 +++--- source/common/http/async_client_impl.h | 2 +- source/common/http/conn_manager_impl.cc | 8 ++ source/common/http/conn_manager_impl.h | 2 +- source/common/http/filter_manager.cc | 16 +++- source/common/http/filter_manager.h | 7 +- source/common/router/router.cc | 2 +- .../http/on_demand/on_demand_update.cc | 2 +- test/common/http/conn_manager_impl_test_2.cc | 2 +- test/common/router/router_test.cc | 22 ++--- .../http/on_demand/on_demand_filter_test.cc | 4 +- test/integration/redirect_integration_test.cc | 94 +++++++++++++++++-- test/mocks/http/mocks.h | 3 +- 13 files changed, 154 insertions(+), 41 deletions(-) diff --git a/include/envoy/http/filter.h b/include/envoy/http/filter.h index bd46d32f49d4..f9710a45eb0a 100644 --- a/include/envoy/http/filter.h +++ b/include/envoy/http/filter.h @@ -495,19 +495,24 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks { */ virtual uint32_t decoderBufferLimit() PURE; - // Takes a stream, and acts as if the headers are newly arrived. - // On success, this will result in a creating a new filter chain and likely upstream request - // associated with the original downstream stream. - // On failure, if the preconditions outlined below are not met, the caller is - // responsible for handling or terminating the original stream. - // - // This is currently limited to - // - streams which are completely read - // - streams which do not have a request body. - // - // Note that HttpConnectionManager sanitization will *not* be performed on the - // recreated stream, as it is assumed that sanitization has already been done. - virtual bool recreateStream() PURE; + /** + * Takes a stream, and acts as if the headers are newly arrived. + * On success, this will result in a creating a new filter chain and likely + * upstream request associated with the original downstream stream. On + * failure, if the preconditions outlined below are not met, the caller is + * responsible for handling or terminating the original stream. + * + * This is currently limited to + * - streams which are completely read + * - streams which do not have a request body. + * + * Note that HttpConnectionManager sanitization will *not* be performed on the + * recreated stream, as it is assumed that sanitization has already been done. + * + * @param original_response_headers Headers used for logging in the access logs and for charging + * stats. Ignored if null. + */ + virtual bool recreateStream(const ResponseHeaderMap* original_response_headers) PURE; /** * Adds socket options to be applied to any connections used for upstream requests. Note that diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 8ec5261e3852..31e43a663601 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -411,7 +411,7 @@ class AsyncStreamImpl : public AsyncClient::Stream, void removeDownstreamWatermarkCallbacks(DownstreamWatermarkCallbacks&) override {} void setDecoderBufferLimit(uint32_t) override {} uint32_t decoderBufferLimit() override { return 0; } - bool recreateStream() override { return false; } + bool recreateStream(const ResponseHeaderMap*) override { return false; } const ScopeTrackedObject& scope() override { return *this; } void addUpstreamSocketOptions(const Network::Socket::OptionsSharedPtr&) override {} Network::Socket::OptionsSharedPtr getUpstreamSocketOptions() const override { return {}; } diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 4589054b1c11..8327872bec97 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -792,6 +792,14 @@ void ConnectionManagerImpl::ActiveStream::chargeStats(const ResponseHeaderMap& h } } + // No response is sent back downstream for internal redirects, so don't charge downstream stats. + const absl::optional& response_code_details = + filter_manager_.streamInfo().responseCodeDetails(); + if (response_code_details.has_value() && + response_code_details == Envoy::StreamInfo::ResponseCodeDetails::get().InternalRedirect) { + return; + } + connection_manager_.stats_.named_.downstream_rq_completed_.inc(); connection_manager_.listener_stats_.downstream_rq_completed_.inc(); if (CodeUtility::is1xx(response_code)) { diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index ebc4e59a3563..4922a19c392d 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -158,7 +158,6 @@ class ConnectionManagerImpl : Logger::Loggable, ActiveStream(ConnectionManagerImpl& connection_manager, uint32_t buffer_limit); void completeRequest(); - void chargeStats(const ResponseHeaderMap& headers); const Network::Connection* connection(); void sendLocalReply(bool is_grpc_request, Code code, absl::string_view body, const std::function& modify_headers, @@ -228,6 +227,7 @@ class ConnectionManagerImpl : Logger::Loggable, void setResponseTrailers(Http::ResponseTrailerMapPtr&& response_trailers) override { response_trailers_ = std::move(response_trailers); } + void chargeStats(const ResponseHeaderMap& headers) override; // TODO(snowp): Create shared OptRef/OptConstRef helpers Http::RequestHeaderMapOptRef requestHeaders() override { diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index e866101e4a26..36aa653a9006 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -1,5 +1,7 @@ #include "common/http/filter_manager.h" +#include "envoy/http/header_map.h" + #include "common/common/enum_to_int.h" #include "common/common/scope_tracker.h" #include "common/http/codes.h" @@ -1253,7 +1255,7 @@ void ActiveStreamDecoderFilter::setDecoderBufferLimit(uint32_t limit) { uint32_t ActiveStreamDecoderFilter::decoderBufferLimit() { return parent_.buffer_limit_; } -bool ActiveStreamDecoderFilter::recreateStream() { +bool ActiveStreamDecoderFilter::recreateStream(const ResponseHeaderMap* headers) { // Because the filter's and the HCM view of if the stream has a body and if // the stream is complete may differ, re-check bytesReceived() to make sure // there was no body from the HCM's point of view. @@ -1264,6 +1266,18 @@ bool ActiveStreamDecoderFilter::recreateStream() { parent_.stream_info_.setResponseCodeDetails( StreamInfo::ResponseCodeDetails::get().InternalRedirect); + if (headers != nullptr) { + // The call to setResponseHeaders is needed to ensure that the headers are properly logged in + // access logs before the stream is destroyed. Since the function expects a ResponseHeaderPtr&&, + // ownership of the headers must be passed. This cannot happen earlier in the flow (such as in + // the call to setupRedirect) because at that point it is still possible for the headers to be + // used in a different logical branch. We work around this by creating a copy and passing + // ownership of the copy instead. + ResponseHeaderMapPtr headers_copy = createHeaderMap(*headers); + parent_.filter_manager_callbacks_.setResponseHeaders(std::move(headers_copy)); + parent_.filter_manager_callbacks_.chargeStats(*headers); + } + parent_.filter_manager_callbacks_.recreateStream(parent_.stream_info_.filter_state_); return true; diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index 507e3f4a8684..9f1de48ef3a4 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -184,7 +184,7 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase, removeDownstreamWatermarkCallbacks(DownstreamWatermarkCallbacks& watermark_callbacks) override; void setDecoderBufferLimit(uint32_t limit) override; uint32_t decoderBufferLimit() override; - bool recreateStream() override; + bool recreateStream(const Http::ResponseHeaderMap* original_response_headers) override; void addUpstreamSocketOptions(const Network::Socket::OptionsSharedPtr& options) override; @@ -331,6 +331,11 @@ class FilterManagerCallbacks { */ virtual void setResponseTrailers(ResponseTrailerMapPtr&& response_trailers) PURE; + /** + * Updates response code stats based on the details in the headers. + */ + virtual void chargeStats(const ResponseHeaderMap& headers) PURE; + // TODO(snowp): We should consider moving filter access to headers/trailers to happen via the // callbacks instead of via the encode/decode callbacks on the filters. diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 684b03226a44..62634c95aee4 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -1421,7 +1421,7 @@ bool Filter::setupRedirect(const Http::ResponseHeaderMap& headers, !callbacks_->decodingBuffer() && // Redirects with body not yet supported. location != nullptr && convertRequestHeadersForInternalRedirect(*downstream_headers_, *location) && - callbacks_->recreateStream()) { + callbacks_->recreateStream(&headers)) { cluster_->stats().upstream_internal_redirect_succeeded_total_.inc(); return true; } diff --git a/source/extensions/filters/http/on_demand/on_demand_update.cc b/source/extensions/filters/http/on_demand/on_demand_update.cc index ef75595fb7ff..2e392846f3b0 100644 --- a/source/extensions/filters/http/on_demand/on_demand_update.cc +++ b/source/extensions/filters/http/on_demand/on_demand_update.cc @@ -61,7 +61,7 @@ void OnDemandRouteUpdate::onRouteConfigUpdateCompletion(bool route_exists) { if (route_exists && // route can be resolved after an on-demand // VHDS update !callbacks_->decodingBuffer() && // Redirects with body not yet supported. - callbacks_->recreateStream()) { + callbacks_->recreateStream(/*headers=*/nullptr)) { return; } diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 6a7ed3ab088a..566c5aeb7a3b 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -2908,7 +2908,7 @@ TEST_F(HttpConnectionManagerImplTest, ConnectionFilterState) { Buffer::OwnedImpl fake_input; conn_manager_->onData(fake_input, false); - decoder_filters_[0]->callbacks_->recreateStream(); + decoder_filters_[0]->callbacks_->recreateStream(nullptr); conn_manager_->onData(fake_input, false); // The connection life time data should have been written to the connection filter state. diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 11dd4f5ee2f5..20ada9e6c527 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -4513,7 +4513,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWhenReachingMaxInternalRedirect) { setNumPreviousRedirect(3); sendRequest(); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); @@ -4532,7 +4532,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWithEmptyLocation) { redirect_headers_->setLocation(""); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); @@ -4550,7 +4550,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWithInvalidLocation) { redirect_headers_->setLocation("h"); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); @@ -4567,7 +4567,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWithoutCompleteRequest) { sendRequest(false); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); @@ -4585,7 +4585,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWithoutLocation) { redirect_headers_->removeLocation(); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); Buffer::OwnedImpl data("1234567890"); @@ -4602,7 +4602,7 @@ TEST_F(RouterTest, InternalRedirectRejectedWithBody) { Buffer::InstancePtr body_data(new Buffer::OwnedImpl("random_fake_data")); EXPECT_CALL(callbacks_, decodingBuffer()).WillOnce(Return(body_data.get())); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); Buffer::OwnedImpl data("1234567890"); @@ -4620,7 +4620,7 @@ TEST_F(RouterTest, CrossSchemeRedirectRejectedByPolicy) { redirect_headers_->setLocation("https://www.foo.com"); EXPECT_CALL(callbacks_, decodingBuffer()).Times(1); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), true); EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ @@ -4644,7 +4644,7 @@ TEST_F(RouterTest, InternalRedirectRejectedByPredicate) { .WillOnce(Return(std::vector({mock_predicate}))); EXPECT_CALL(*mock_predicate, acceptTargetRoute(_, _, _, _)).WillOnce(Return(false)); ON_CALL(*mock_predicate, name()).WillByDefault(Return("mock_predicate")); - EXPECT_CALL(callbacks_, recreateStream()).Times(0); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(0); response_decoder_->decodeHeaders(std::move(redirect_headers_), true); EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ @@ -4667,7 +4667,7 @@ TEST_F(RouterTest, HttpInternalRedirectSucceeded) { EXPECT_CALL(callbacks_, decodingBuffer()).Times(1); EXPECT_CALL(callbacks_, clearRouteCache()).Times(1); - EXPECT_CALL(callbacks_, recreateStream()).Times(1).WillOnce(Return(true)); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(1).WillOnce(Return(true)); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ .counter("upstream_internal_redirect_succeeded_total") @@ -4692,7 +4692,7 @@ TEST_F(RouterTest, HttpsInternalRedirectSucceeded) { EXPECT_CALL(connection_, ssl()).Times(1).WillOnce(Return(ssl_connection)); EXPECT_CALL(callbacks_, decodingBuffer()).Times(1); EXPECT_CALL(callbacks_, clearRouteCache()).Times(1); - EXPECT_CALL(callbacks_, recreateStream()).Times(1).WillOnce(Return(true)); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(1).WillOnce(Return(true)); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ .counter("upstream_internal_redirect_succeeded_total") @@ -4715,7 +4715,7 @@ TEST_F(RouterTest, CrossSchemeRedirectAllowedByPolicy) { isCrossSchemeRedirectAllowed()) .WillOnce(Return(true)); EXPECT_CALL(callbacks_, clearRouteCache()).Times(1); - EXPECT_CALL(callbacks_, recreateStream()).Times(1).WillOnce(Return(true)); + EXPECT_CALL(callbacks_, recreateStream(_)).Times(1).WillOnce(Return(true)); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ .counter("upstream_internal_redirect_succeeded_total") diff --git a/test/extensions/filters/http/on_demand/on_demand_filter_test.cc b/test/extensions/filters/http/on_demand/on_demand_filter_test.cc index 2df0f265d24e..d2978064b2d9 100644 --- a/test/extensions/filters/http/on_demand/on_demand_filter_test.cc +++ b/test/extensions/filters/http/on_demand/on_demand_filter_test.cc @@ -91,14 +91,14 @@ TEST_F(OnDemandFilterTest, TestOnRouteConfigUpdateCompletionContinuesDecodingWit TEST_F(OnDemandFilterTest, OnRouteConfigUpdateCompletionContinuesDecodingIfRedirectFails) { EXPECT_CALL(decoder_callbacks_, continueDecoding()); EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillOnce(Return(nullptr)); - EXPECT_CALL(decoder_callbacks_, recreateStream()).WillOnce(Return(false)); + EXPECT_CALL(decoder_callbacks_, recreateStream(_)).WillOnce(Return(false)); filter_->onRouteConfigUpdateCompletion(true); } // tests onRouteConfigUpdateCompletion() when route was resolved TEST_F(OnDemandFilterTest, OnRouteConfigUpdateCompletionRestartsActiveStream) { EXPECT_CALL(decoder_callbacks_, decodingBuffer()).WillOnce(Return(nullptr)); - EXPECT_CALL(decoder_callbacks_, recreateStream()).WillOnce(Return(true)); + EXPECT_CALL(decoder_callbacks_, recreateStream(_)).WillOnce(Return(true)); filter_->onRouteConfigUpdateCompletion(true); } diff --git a/test/integration/redirect_integration_test.cc b/test/integration/redirect_integration_test.cc index 0504e33ad338..c0c9d1f1b307 100644 --- a/test/integration/redirect_integration_test.cc +++ b/test/integration/redirect_integration_test.cc @@ -8,12 +8,13 @@ namespace Envoy { -using testing::HasSubstr; +using ::testing::HasSubstr; namespace { constexpr char HandleThreeHopLocationFormat[] = "http://handle.internal.redirect.max.three.hop/path{}"; -} +constexpr char kTestHeaderKey[] = "test-header"; +} // namespace class RedirectIntegrationTest : public HttpProtocolIntegrationTest { public: @@ -81,14 +82,20 @@ class RedirectIntegrationTest : public HttpProtocolIntegrationTest { return new_stream; } - Http::TestResponseHeaderMapImpl redirect_response_{ - {":status", "302"}, {"content-length", "0"}, {"location", "http://authority2/new/url"}}; - + Http::TestResponseHeaderMapImpl redirect_response_{{":status", "302"}, + {"content-length", "0"}, + {"location", "http://authority2/new/url"}, + // Test header added to confirm that response + // headers are populated for internal redirects + {kTestHeaderKey, "test-header-value"}}; + Envoy::Http::LowerCaseString test_header_key_{kTestHeaderKey}; std::vector upstream_connections_; }; // By default if internal redirects are not configured, redirects are proxied. TEST_P(RedirectIntegrationTest, RedirectNotConfigured) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); + // Use base class initialize. HttpProtocolIntegrationTest::initialize(); @@ -96,10 +103,16 @@ TEST_P(RedirectIntegrationTest, RedirectNotConfigured) { auto response = sendRequestAndWaitForResponse(default_request_headers_, 0, redirect_response_, 0); EXPECT_TRUE(response->complete()); EXPECT_EQ("302", response->headers().getStatusValue()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } // Now test a route with redirects configured on in pass-through mode. TEST_P(RedirectIntegrationTest, InternalRedirectPassedThrough) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -109,10 +122,15 @@ TEST_P(RedirectIntegrationTest, InternalRedirectPassedThrough) { EXPECT_EQ( 0, test_server_->counter("cluster.cluster_0.upstream_internal_redirect_failed_total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } TEST_P(RedirectIntegrationTest, BasicInternalRedirect) { - useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%"); + useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); // Validate that header sanitization is only called once. config_helper_.addConfigModifier( [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& @@ -143,10 +161,17 @@ TEST_P(RedirectIntegrationTest, BasicInternalRedirect) { EXPECT_EQ("200", response->headers().getStatusValue()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_internal_redirect_succeeded_total") ->value()); - EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("internal_redirect")); + // 302 was never returned downstream + EXPECT_EQ(0, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_2xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + // No test header + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), HasSubstr("200 via_upstream -\n")); } TEST_P(RedirectIntegrationTest, InternalRedirectWithThreeHopLimit) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); // Validate that header sanitization is only called once. config_helper_.addConfigModifier( [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& @@ -184,9 +209,21 @@ TEST_P(RedirectIntegrationTest, InternalRedirectWithThreeHopLimit) { EXPECT_EQ( 1, test_server_->counter("http.config_test.passthrough_internal_redirect_too_many_redirects") ->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 2), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 3), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } TEST_P(RedirectIntegrationTest, InternalRedirectToDestinationWithBody) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); // Validate that header sanitization is only called once. config_helper_.addConfigModifier( [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& @@ -225,9 +262,17 @@ TEST_P(RedirectIntegrationTest, InternalRedirectToDestinationWithBody) { EXPECT_EQ("200", response->headers().getStatusValue()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_internal_redirect_succeeded_total") ->value()); + // 302 was never returned downstream + EXPECT_EQ(0, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_2xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + // No test header + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), HasSubstr("200 via_upstream -\n")); } TEST_P(RedirectIntegrationTest, InternalRedirectPreventedByPreviousRoutesPredicate) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); auto handle_prevent_repeated_target = config_helper_.createVirtualHost("handle.internal.redirect.no.repeated.target"); auto* internal_redirect_policy = handle_prevent_repeated_target.mutable_routes(0) @@ -278,9 +323,19 @@ TEST_P(RedirectIntegrationTest, InternalRedirectPreventedByPreviousRoutesPredica EXPECT_EQ( 1, test_server_->counter("http.config_test.passthrough_internal_redirect_predicate")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 2), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } TEST_P(RedirectIntegrationTest, InternalRedirectPreventedByAllowListedRoutesPredicate) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); auto handle_allow_listed_redirect_route = config_helper_.createVirtualHost("handle.internal.redirect.only.allow.listed.target"); auto* internal_redirect_policy = handle_allow_listed_redirect_route.mutable_routes(0) @@ -336,9 +391,19 @@ TEST_P(RedirectIntegrationTest, InternalRedirectPreventedByAllowListedRoutesPred EXPECT_EQ( 1, test_server_->counter("http.config_test.passthrough_internal_redirect_predicate")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 2), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } TEST_P(RedirectIntegrationTest, InternalRedirectPreventedBySafeCrossSchemePredicate) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); auto handle_safe_cross_scheme_route = config_helper_.createVirtualHost( "handle.internal.redirect.only.allow.safe.cross.scheme.redirect"); auto* internal_redirect_policy = handle_safe_cross_scheme_route.mutable_routes(0) @@ -396,9 +461,19 @@ TEST_P(RedirectIntegrationTest, InternalRedirectPreventedBySafeCrossSchemePredic EXPECT_EQ( 1, test_server_->counter("http.config_test.passthrough_internal_redirect_predicate")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 1), + HasSubstr("302 internal_redirect test-header-value\n")); + EXPECT_THAT(waitForAccessLog(access_log_name_, 2), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } TEST_P(RedirectIntegrationTest, InvalidRedirect) { + useAccessLog("%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); initialize(); redirect_response_.setLocation("invalid_url"); @@ -412,6 +487,11 @@ TEST_P(RedirectIntegrationTest, InvalidRedirect) { EXPECT_EQ( 1, test_server_->counter("cluster.cluster_0.upstream_internal_redirect_failed_total")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_THAT(waitForAccessLog(access_log_name_), + HasSubstr("302 via_upstream test-header-value\n")); + EXPECT_EQ("test-header-value", + response->headers().get(test_header_key_)[0]->value().getStringView()); } INSTANTIATE_TEST_SUITE_P(Protocols, RedirectIntegrationTest, diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index ce9eb19fb646..407956f8aa93 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -58,6 +58,7 @@ class MockFilterManagerCallbacks : public FilterManagerCallbacks { MOCK_METHOD(void, encodeData, (Buffer::Instance&, bool)); MOCK_METHOD(void, encodeTrailers, (ResponseTrailerMap&)); MOCK_METHOD(void, encodeMetadata, (MetadataMapVector&)); + MOCK_METHOD(void, chargeStats, (const ResponseHeaderMap&)); MOCK_METHOD(void, setRequestTrailers, (RequestTrailerMapPtr &&)); MOCK_METHOD(void, setContinueHeaders, (ResponseHeaderMapPtr &&)); MOCK_METHOD(void, setResponseHeaders_, (ResponseHeaderMap&)); @@ -201,7 +202,7 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, MOCK_METHOD(void, removeDownstreamWatermarkCallbacks, (DownstreamWatermarkCallbacks&)); MOCK_METHOD(void, setDecoderBufferLimit, (uint32_t)); MOCK_METHOD(uint32_t, decoderBufferLimit, ()); - MOCK_METHOD(bool, recreateStream, ()); + MOCK_METHOD(bool, recreateStream, (const ResponseHeaderMap* headers)); MOCK_METHOD(void, addUpstreamSocketOptions, (const Network::Socket::OptionsSharedPtr& options)); MOCK_METHOD(Network::Socket::OptionsSharedPtr, getUpstreamSocketOptions, (), (const)); From 1d2591619a3494e43f5dbb6b370c2f0374b2d003 Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Thu, 12 Nov 2020 20:46:57 +0530 Subject: [PATCH 080/117] Added DurationTimeout as response flag for HCM. (#13671) Commit Message: Added DurationTimeout as a response flag for HCM. Additional Description: In #12495, "DurationTimeout" as a response flag produced at the TCP connection timeout. The timeout flag should have the same behavior for TCP and HTTP connection-level. Risk Level: Testing: Docs Changes: Release Notes: Platform Specific Features: Fixes #13142 Signed-off-by: Manish Kumar --- source/common/http/conn_manager_impl.cc | 4 ++-- test/common/http/conn_manager_impl_test_2.cc | 22 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 8327872bec97..79235b97fe33 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -470,13 +470,13 @@ void ConnectionManagerImpl::onIdleTimeout() { } } -// TODO(#13142): Add DurationTimeout response flag for HCM. void ConnectionManagerImpl::onConnectionDurationTimeout() { ENVOY_CONN_LOG(debug, "max connection duration reached", read_callbacks_->connection()); stats_.named_.downstream_cx_max_duration_reached_.inc(); if (!codec_) { // Attempt to write out buffered data one last time and issue a local close if successful. - doConnectionClose(Network::ConnectionCloseType::FlushWrite, absl::nullopt, + doConnectionClose(Network::ConnectionCloseType::FlushWrite, + StreamInfo::ResponseFlag::DurationTimeout, StreamInfo::ResponseCodeDetails::get().DurationTimeout); } else if (drain_state_ == DrainState::NotDraining) { startDrainSequence(); diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 566c5aeb7a3b..57bcd5d0697e 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -309,6 +309,28 @@ TEST_F(HttpConnectionManagerImplTest, IdleTimeout) { EXPECT_EQ(1U, stats_.named_.downstream_cx_idle_timeout_.value()); } +TEST_F(HttpConnectionManagerImplTest, ConnectionDurationResponseFlag) { + // Not used in the test. + delete codec_; + + max_connection_duration_ = (std::chrono::milliseconds(10)); + Event::MockTimer* connection_duration_timer = setUpTimer(); + EXPECT_CALL(*connection_duration_timer, enableTimer(_, _)); + setup(false, ""); + + EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::FlushWrite)); + filter_callbacks_.connection_.streamInfo().setResponseFlag( + StreamInfo::ResponseFlag::DurationTimeout); + EXPECT_CALL(*connection_duration_timer, disableTimer()); + + connection_duration_timer->invokeCallback(); + + EXPECT_TRUE(filter_callbacks_.connection_.streamInfo().hasResponseFlag( + StreamInfo::ResponseFlag::DurationTimeout)); + + EXPECT_EQ(1U, stats_.named_.downstream_cx_max_duration_reached_.value()); +} + TEST_F(HttpConnectionManagerImplTest, ConnectionDurationNoCodec) { // Not used in the test. delete codec_; From 36da8fe9a02eccaca0edac1ccaad504a702b1985 Mon Sep 17 00:00:00 2001 From: Alex Konradi Date: Thu, 12 Nov 2020 14:40:51 -0500 Subject: [PATCH 081/117] server: split thread_local_object.h and overload_manager.h (#13999) Move the interfaces declared in these files into separate files to allow the Dispatcher to reference the ThreadLocalOverloadState without creating circular dependencies. Signed-off-by: Alex Konradi --- include/envoy/runtime/BUILD | 2 +- include/envoy/runtime/runtime.h | 2 +- include/envoy/server/BUILD | 18 ++---- include/envoy/server/factory_context.h | 2 +- include/envoy/server/instance.h | 2 +- include/envoy/server/overload/BUILD | 29 +++++++++ .../server/{ => overload}/overload_manager.h | 55 +--------------- .../overload/thread_local_overload_state.h | 63 +++++++++++++++++++ include/envoy/server/worker.h | 2 +- include/envoy/thread_local/BUILD | 12 +++- include/envoy/thread_local/thread_local.h | 23 +------ .../envoy/thread_local/thread_local_object.h | 33 ++++++++++ source/common/grpc/BUILD | 2 +- source/common/grpc/google_async_client_impl.h | 2 +- source/common/http/BUILD | 2 +- source/common/http/conn_manager_impl.h | 2 +- source/common/memory/BUILD | 2 +- source/common/memory/heap_shrinker.h | 2 +- source/extensions/common/wasm/BUILD | 1 + source/extensions/common/wasm/wasm.h | 2 +- .../filters/http/admission_control/BUILD | 1 + .../thread_local_controller.h | 2 +- source/server/BUILD | 2 +- source/server/admin/admin.h | 2 +- source/server/overload_manager_impl.cc | 1 - source/server/overload_manager_impl.h | 2 +- test/mocks/server/BUILD | 4 +- test/mocks/server/main.h | 2 +- test/mocks/server/overload_manager.h | 3 +- test/server/overload_manager_impl_test.cc | 2 +- 30 files changed, 169 insertions(+), 110 deletions(-) create mode 100644 include/envoy/server/overload/BUILD rename include/envoy/server/{ => overload}/overload_manager.h (57%) create mode 100644 include/envoy/server/overload/thread_local_overload_state.h create mode 100644 include/envoy/thread_local/thread_local_object.h diff --git a/include/envoy/runtime/BUILD b/include/envoy/runtime/BUILD index b80d180dedaa..f28a5af6a50e 100644 --- a/include/envoy/runtime/BUILD +++ b/include/envoy/runtime/BUILD @@ -17,7 +17,7 @@ envoy_cc_library( ], deps = [ "//include/envoy/stats:stats_interface", - "//include/envoy/thread_local:thread_local_interface", + "//include/envoy/thread_local:thread_local_object", "//source/common/common:assert_lib", "//source/common/singleton:threadsafe_singleton", "@envoy_api//envoy/type/v3:pkg_cc_proto", diff --git a/include/envoy/runtime/runtime.h b/include/envoy/runtime/runtime.h index 72aa2e90fb28..27264a1f9e58 100644 --- a/include/envoy/runtime/runtime.h +++ b/include/envoy/runtime/runtime.h @@ -9,7 +9,7 @@ #include "envoy/common/pure.h" #include "envoy/stats/store.h" -#include "envoy/thread_local/thread_local.h" +#include "envoy/thread_local/thread_local_object.h" #include "envoy/type/v3/percent.pb.h" #include "common/common/assert.h" diff --git a/include/envoy/server/BUILD b/include/envoy/server/BUILD index 30b3d46b1c56..a9f7854ba1c2 100644 --- a/include/envoy/server/BUILD +++ b/include/envoy/server/BUILD @@ -127,7 +127,7 @@ envoy_cc_library( "//include/envoy/local_info:local_info_interface", "//include/envoy/runtime:runtime_interface", "//include/envoy/secret:secret_manager_interface", - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/ssl:context_manager_interface", "//include/envoy/thread_local:thread_local_interface", "//include/envoy/tracing:http_tracer_interface", @@ -151,8 +151,8 @@ envoy_cc_library( name = "worker_interface", hdrs = ["worker.h"], deps = [ - ":overload_manager_interface", "//include/envoy/server:guarddog_interface", + "//include/envoy/server/overload:overload_manager_interface", ], ) @@ -183,7 +183,7 @@ envoy_cc_library( "//include/envoy/local_info:local_info_interface", "//include/envoy/network:drain_decision_interface", "//include/envoy/runtime:runtime_interface", - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/singleton:manager_interface", "//include/envoy/thread_local:thread_local_interface", "//include/envoy/tracing:http_tracer_interface", @@ -207,8 +207,8 @@ envoy_cc_library( "//include/envoy/config:typed_config_interface", "//include/envoy/http:codes_interface", "//include/envoy/http:filter_interface", - "//include/envoy/server:overload_manager_interface", "//include/envoy/server:transport_socket_config_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/singleton:manager_interface", "//include/envoy/thread_local:thread_local_interface", "//include/envoy/tracing:http_tracer_interface", @@ -302,16 +302,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "overload_manager_interface", - hdrs = ["overload_manager.h"], - deps = [ - "//include/envoy/event:timer_interface", - "//include/envoy/thread_local:thread_local_interface", - "//source/common/singleton:const_singleton", - ], -) - envoy_cc_library( name = "tracer_config_interface", hdrs = ["tracer_config.h"], diff --git a/include/envoy/server/factory_context.h b/include/envoy/server/factory_context.h index 7d496a6d2eb4..01ffee80f763 100644 --- a/include/envoy/server/factory_context.h +++ b/include/envoy/server/factory_context.h @@ -19,7 +19,7 @@ #include "envoy/server/admin.h" #include "envoy/server/drain_manager.h" #include "envoy/server/lifecycle_notifier.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/server/process_context.h" #include "envoy/singleton/manager.h" #include "envoy/stats/scope.h" diff --git a/include/envoy/server/instance.h b/include/envoy/server/instance.h index 8bba884e8b47..338479f5ea2a 100644 --- a/include/envoy/server/instance.h +++ b/include/envoy/server/instance.h @@ -23,7 +23,7 @@ #include "envoy/server/lifecycle_notifier.h" #include "envoy/server/listener_manager.h" #include "envoy/server/options.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/ssl/context_manager.h" #include "envoy/thread_local/thread_local.h" #include "envoy/tracing/http_tracer.h" diff --git a/include/envoy/server/overload/BUILD b/include/envoy/server/overload/BUILD new file mode 100644 index 000000000000..990a306c7fd4 --- /dev/null +++ b/include/envoy/server/overload/BUILD @@ -0,0 +1,29 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_library( + name = "overload_manager_interface", + hdrs = ["overload_manager.h"], + deps = [ + ":thread_local_overload_state", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/thread_local:thread_local_interface", + "//source/common/singleton:const_singleton", + ], +) + +envoy_cc_library( + name = "thread_local_overload_state", + hdrs = ["thread_local_overload_state.h"], + deps = [ + "//include/envoy/event:timer_interface", + "//include/envoy/thread_local:thread_local_object", + ], +) diff --git a/include/envoy/server/overload_manager.h b/include/envoy/server/overload/overload_manager.h similarity index 57% rename from include/envoy/server/overload_manager.h rename to include/envoy/server/overload/overload_manager.h index 47e9fe0af51e..a4b42a7c9fad 100644 --- a/include/envoy/server/overload_manager.h +++ b/include/envoy/server/overload/overload_manager.h @@ -3,64 +3,13 @@ #include #include "envoy/common/pure.h" -#include "envoy/event/timer.h" -#include "envoy/thread_local/thread_local.h" +#include "envoy/event/dispatcher.h" +#include "envoy/server/overload/thread_local_overload_state.h" -#include "common/common/macros.h" #include "common/singleton/const_singleton.h" namespace Envoy { namespace Server { - -/** - * Tracks the state of an overload action. The state is a number between 0 and 1 that represents the - * level of saturation. The values are categorized in two groups: - * - Saturated (value = 1): indicates that an overload action is active because at least one of its - * triggers has reached saturation. - * - Scaling (0 <= value < 1): indicates that an overload action is not saturated. - */ -class OverloadActionState { -public: - static constexpr OverloadActionState inactive() { return OverloadActionState(0); } - - static constexpr OverloadActionState saturated() { return OverloadActionState(1.0); } - - explicit constexpr OverloadActionState(float value) - : action_value_(std::min(1.0f, std::max(0.0f, value))) {} - - float value() const { return action_value_; } - bool isSaturated() const { return action_value_ == 1; } - -private: - float action_value_; -}; - -/** - * Callback invoked when an overload action changes state. - */ -using OverloadActionCb = std::function; - -enum class OverloadTimerType { - // Timers created with this type will never be scaled. This should only be used for testing. - UnscaledRealTimerForTest, - // The amount of time an HTTP connection to a downstream client can remain idle (no streams). This - // corresponds to the HTTP_DOWNSTREAM_CONNECTION_IDLE TimerType in overload.proto. - HttpDownstreamIdleConnectionTimeout, -}; - -/** - * Thread-local copy of the state of each configured overload action. - */ -class ThreadLocalOverloadState : public ThreadLocal::ThreadLocalObject { -public: - // Get a thread-local reference to the value for the given action key. - virtual const OverloadActionState& getState(const std::string& action) PURE; - - // Get a scaled timer whose minimum corresponds to the configured value for the given timer type. - virtual Event::TimerPtr createScaledTimer(OverloadTimerType timer_type, - Event::TimerCb callback) PURE; -}; - /** * Well-known overload action names. */ diff --git a/include/envoy/server/overload/thread_local_overload_state.h b/include/envoy/server/overload/thread_local_overload_state.h new file mode 100644 index 000000000000..6c94fab5cd42 --- /dev/null +++ b/include/envoy/server/overload/thread_local_overload_state.h @@ -0,0 +1,63 @@ + +#pragma once + +#include + +#include "envoy/common/pure.h" +#include "envoy/event/timer.h" +#include "envoy/thread_local/thread_local_object.h" + +namespace Envoy { +namespace Server { + +/** + * Tracks the state of an overload action. The state is a number between 0 and 1 that represents the + * level of saturation. The values are categorized in two groups: + * - Saturated (value = 1): indicates that an overload action is active because at least one of its + * triggers has reached saturation. + * - Scaling (0 <= value < 1): indicates that an overload action is not saturated. + */ +class OverloadActionState { +public: + static constexpr OverloadActionState inactive() { return OverloadActionState(0); } + + static constexpr OverloadActionState saturated() { return OverloadActionState(1.0); } + + explicit constexpr OverloadActionState(float value) + : action_value_(std::min(1.0f, std::max(0.0f, value))) {} + + float value() const { return action_value_; } + bool isSaturated() const { return action_value_ == 1; } + +private: + float action_value_; +}; + +/** + * Callback invoked when an overload action changes state. + */ +using OverloadActionCb = std::function; + +enum class OverloadTimerType { + // Timers created with this type will never be scaled. This should only be used for testing. + UnscaledRealTimerForTest, + // The amount of time an HTTP connection to a downstream client can remain idle (no streams). This + // corresponds to the HTTP_DOWNSTREAM_CONNECTION_IDLE TimerType in overload.proto. + HttpDownstreamIdleConnectionTimeout, +}; + +/** + * Thread-local copy of the state of each configured overload action. + */ +class ThreadLocalOverloadState : public ThreadLocal::ThreadLocalObject { +public: + // Get a thread-local reference to the value for the given action key. + virtual const OverloadActionState& getState(const std::string& action) PURE; + + // Get a scaled timer whose minimum corresponds to the configured value for the given timer type. + virtual Event::TimerPtr createScaledTimer(OverloadTimerType timer_type, + Event::TimerCb callback) PURE; +}; + +} // namespace Server +} // namespace Envoy diff --git a/include/envoy/server/worker.h b/include/envoy/server/worker.h index 9d6ed578cfbe..762cf83005e7 100644 --- a/include/envoy/server/worker.h +++ b/include/envoy/server/worker.h @@ -3,7 +3,7 @@ #include #include "envoy/server/guarddog.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" namespace Envoy { namespace Server { diff --git a/include/envoy/thread_local/BUILD b/include/envoy/thread_local/BUILD index 3b23de4e0175..9a5e61235494 100644 --- a/include/envoy/thread_local/BUILD +++ b/include/envoy/thread_local/BUILD @@ -11,5 +11,15 @@ envoy_package() envoy_cc_library( name = "thread_local_interface", hdrs = ["thread_local.h"], - deps = ["//include/envoy/event:dispatcher_interface"], + deps = [ + ":thread_local_object", + "//include/envoy/event:dispatcher_interface", + "//source/common/common:assert_lib", + ], +) + +envoy_cc_library( + name = "thread_local_object", + hdrs = ["thread_local_object.h"], + deps = ["//source/common/common:assert_lib"], ) diff --git a/include/envoy/thread_local/thread_local.h b/include/envoy/thread_local/thread_local.h index 4993b118d1eb..8ff2ca99c8c0 100644 --- a/include/envoy/thread_local/thread_local.h +++ b/include/envoy/thread_local/thread_local.h @@ -7,30 +7,13 @@ #include "envoy/common/optref.h" #include "envoy/common/pure.h" #include "envoy/event/dispatcher.h" +#include "envoy/thread_local/thread_local_object.h" + +#include "common/common/assert.h" namespace Envoy { namespace ThreadLocal { -/** - * All objects that are stored via the ThreadLocal interface must derive from this type. - */ -class ThreadLocalObject { -public: - virtual ~ThreadLocalObject() = default; - - /** - * Return the object casted to a concrete type. See getTyped() below for comments on the casts. - */ - template T& asType() { - ASSERT(dynamic_cast(this) != nullptr); - return *static_cast(this); - } -}; - -using ThreadLocalObjectSharedPtr = std::shared_ptr; - -template class TypedSlot; - /** * An individual allocated TLS slot. When the slot is destroyed the stored thread local will * be freed on each thread. diff --git a/include/envoy/thread_local/thread_local_object.h b/include/envoy/thread_local/thread_local_object.h new file mode 100644 index 000000000000..e305102f7118 --- /dev/null +++ b/include/envoy/thread_local/thread_local_object.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +#include "common/common/assert.h" + +namespace Envoy { +namespace ThreadLocal { + +/** + * All objects that are stored via the ThreadLocal interface must derive from this type. + */ +class ThreadLocalObject { +public: + virtual ~ThreadLocalObject() = default; + + /** + * Return the object casted to a concrete type. See getTyped() below for comments on the casts. + */ + template T& asType() { + ASSERT(dynamic_cast(this) != nullptr); + return *static_cast(this); + } +}; + +using ThreadLocalObjectSharedPtr = std::shared_ptr; + +template class TypedSlot; + +} // namespace ThreadLocal +} // namespace Envoy diff --git a/source/common/grpc/BUILD b/source/common/grpc/BUILD index 3837cadc79d0..79b0eea9b9f3 100644 --- a/source/common/grpc/BUILD +++ b/source/common/grpc/BUILD @@ -171,7 +171,7 @@ envoy_cc_library( "//include/envoy/api:api_interface", "//include/envoy/grpc:google_grpc_creds_interface", "//include/envoy/thread:thread_interface", - "//include/envoy/thread_local:thread_local_interface", + "//include/envoy/thread_local:thread_local_object", "//source/common/common:base64_lib", "//source/common/common:empty_string", "//source/common/common:linked_object", diff --git a/source/common/grpc/google_async_client_impl.h b/source/common/grpc/google_async_client_impl.h index a0e27c5e5efd..c3ea7f4c12d4 100644 --- a/source/common/grpc/google_async_client_impl.h +++ b/source/common/grpc/google_async_client_impl.h @@ -10,7 +10,7 @@ #include "envoy/grpc/async_client.h" #include "envoy/stats/scope.h" #include "envoy/thread/thread.h" -#include "envoy/thread_local/thread_local.h" +#include "envoy/thread_local/thread_local_object.h" #include "envoy/tracing/http_tracer.h" #include "common/common/linked_object.h" diff --git a/source/common/http/BUILD b/source/common/http/BUILD index e5a2d719d369..01798486ffef 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -216,7 +216,7 @@ envoy_cc_library( "//include/envoy/router:rds_interface", "//include/envoy/router:scopes_interface", "//include/envoy/runtime:runtime_interface", - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/ssl:connection_interface", "//include/envoy/stats:stats_interface", "//include/envoy/stats:stats_macros", diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 4922a19c392d..828f8ceb30e4 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -26,7 +26,7 @@ #include "envoy/router/rds.h" #include "envoy/router/scopes.h" #include "envoy/runtime/runtime.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/ssl/connection.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" diff --git a/source/common/memory/BUILD b/source/common/memory/BUILD index 45cc04041baa..1fe67e534e49 100644 --- a/source/common/memory/BUILD +++ b/source/common/memory/BUILD @@ -35,7 +35,7 @@ envoy_cc_library( deps = [ ":utils_lib", "//include/envoy/event:dispatcher_interface", - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/stats:stats_interface", "//source/common/stats:symbol_table_lib", ], diff --git a/source/common/memory/heap_shrinker.h b/source/common/memory/heap_shrinker.h index 6c4a88bfbbb2..9b1930f2bb06 100644 --- a/source/common/memory/heap_shrinker.h +++ b/source/common/memory/heap_shrinker.h @@ -1,7 +1,7 @@ #pragma once #include "envoy/event/dispatcher.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats.h" diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index 02eb727951c1..f620531ced2c 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -32,6 +32,7 @@ envoy_cc_library( "//include/envoy/http:codes_interface", "//include/envoy/http:filter_interface", "//include/envoy/server:lifecycle_notifier_interface", + "//include/envoy/thread_local:thread_local_object", "//include/envoy/upstream:cluster_manager_interface", "//source/common/config:datasource_lib", "//source/common/singleton:const_singleton", diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index a812d1a1a522..08cf6ed636c0 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -11,7 +11,7 @@ #include "envoy/server/lifecycle_notifier.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats.h" -#include "envoy/thread_local/thread_local.h" +#include "envoy/thread_local/thread_local_object.h" #include "envoy/upstream/cluster_manager.h" #include "common/common/assert.h" diff --git a/source/extensions/filters/http/admission_control/BUILD b/source/extensions/filters/http/admission_control/BUILD index 07acbda5fe58..9dab0fc8f6bc 100644 --- a/source/extensions/filters/http/admission_control/BUILD +++ b/source/extensions/filters/http/admission_control/BUILD @@ -25,6 +25,7 @@ envoy_cc_extension( deps = [ "//include/envoy/http:filter_interface", "//include/envoy/runtime:runtime_interface", + "//include/envoy/thread_local:thread_local_object", "//source/common/common:cleanup_lib", "//source/common/http:codes_lib", "//source/common/runtime:runtime_lib", diff --git a/source/extensions/filters/http/admission_control/thread_local_controller.h b/source/extensions/filters/http/admission_control/thread_local_controller.h index 11f938758177..fde56131ecd8 100644 --- a/source/extensions/filters/http/admission_control/thread_local_controller.h +++ b/source/extensions/filters/http/admission_control/thread_local_controller.h @@ -3,7 +3,7 @@ #include "envoy/common/pure.h" #include "envoy/common/time.h" #include "envoy/http/codes.h" -#include "envoy/thread_local/thread_local.h" +#include "envoy/thread_local/thread_local_object.h" namespace Envoy { namespace Extensions { diff --git a/source/server/BUILD b/source/server/BUILD index 5e4ee63d9fae..c3404818a217 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -254,7 +254,7 @@ envoy_cc_library( srcs = ["overload_manager_impl.cc"], hdrs = ["overload_manager_impl.h"], deps = [ - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//include/envoy/stats:stats_interface", "//include/envoy/thread_local:thread_local_interface", "//source/common/common:logger_lib", diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index feddd8855c4b..de79c60b1992 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -14,7 +14,7 @@ #include "envoy/server/admin.h" #include "envoy/server/instance.h" #include "envoy/server/listener_manager.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/upstream/outlier_detection.h" #include "envoy/upstream/resource_manager.h" diff --git a/source/server/overload_manager_impl.cc b/source/server/overload_manager_impl.cc index db5ced97e41d..f399bb96ecfe 100644 --- a/source/server/overload_manager_impl.cc +++ b/source/server/overload_manager_impl.cc @@ -5,7 +5,6 @@ #include "envoy/common/exception.h" #include "envoy/config/overload/v3/overload.pb.h" #include "envoy/config/overload/v3/overload.pb.validate.h" -#include "envoy/server/overload_manager.h" #include "envoy/stats/scope.h" #include "common/common/fmt.h" diff --git a/source/server/overload_manager_impl.h b/source/server/overload_manager_impl.h index 55f0ee7517b0..b86162fb0224 100644 --- a/source/server/overload_manager_impl.h +++ b/source/server/overload_manager_impl.h @@ -8,7 +8,7 @@ #include "envoy/event/dispatcher.h" #include "envoy/event/scaled_range_timer_manager.h" #include "envoy/protobuf/message_validator.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/server/resource_monitor.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats.h" diff --git a/test/mocks/server/BUILD b/test/mocks/server/BUILD index d38b00d01c0c..7214ca820371 100644 --- a/test/mocks/server/BUILD +++ b/test/mocks/server/BUILD @@ -161,7 +161,7 @@ envoy_cc_mock( srcs = ["overload_manager.cc"], hdrs = ["overload_manager.h"], deps = [ - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", "//test/mocks/event:event_mocks", ], ) @@ -207,7 +207,7 @@ envoy_cc_mock( hdrs = ["main.h"], deps = [ "//include/envoy/server:configuration_interface", - "//include/envoy/server:overload_manager_interface", + "//include/envoy/server/overload:overload_manager_interface", ], ) diff --git a/test/mocks/server/main.h b/test/mocks/server/main.h index 59f6259250d7..17e00013c27d 100644 --- a/test/mocks/server/main.h +++ b/test/mocks/server/main.h @@ -6,7 +6,7 @@ #include #include "envoy/server/configuration.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "gmock/gmock.h" diff --git a/test/mocks/server/overload_manager.h b/test/mocks/server/overload_manager.h index 1dc9f4dc4b23..03fe54aa3232 100644 --- a/test/mocks/server/overload_manager.h +++ b/test/mocks/server/overload_manager.h @@ -2,7 +2,8 @@ #include -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" +#include "envoy/server/overload/thread_local_overload_state.h" #include "gmock/gmock.h" diff --git a/test/server/overload_manager_impl_test.cc b/test/server/overload_manager_impl_test.cc index e2e9ffb80f65..a134cfab6075 100644 --- a/test/server/overload_manager_impl_test.cc +++ b/test/server/overload_manager_impl_test.cc @@ -1,7 +1,7 @@ #include #include "envoy/config/overload/v3/overload.pb.h" -#include "envoy/server/overload_manager.h" +#include "envoy/server/overload/overload_manager.h" #include "envoy/server/resource_monitor.h" #include "envoy/server/resource_monitor_config.h" From 36c41915d0b82ca05058c1ce483c45dee860beed Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Nov 2020 19:53:52 +0000 Subject: [PATCH 082/117] docs: Update quick-start logging info (#13993) Signed-off-by: Ryan Northey --- .../quick-start/_include/envoy-demo.yaml | 5 ++++ docs/root/start/quick-start/run-envoy.rst | 23 +++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/root/start/quick-start/_include/envoy-demo.yaml b/docs/root/start/quick-start/_include/envoy-demo.yaml index 99bb743a97c5..6419deef60c5 100644 --- a/docs/root/start/quick-start/_include/envoy-demo.yaml +++ b/docs/root/start/quick-start/_include/envoy-demo.yaml @@ -12,6 +12,11 @@ static_resources: typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/stdout http_filters: - name: envoy.filters.http.router route_config: diff --git a/docs/root/start/quick-start/run-envoy.rst b/docs/root/start/quick-start/run-envoy.rst index fa1030004cbf..d9db5a40691f 100644 --- a/docs/root/start/quick-start/run-envoy.rst +++ b/docs/root/start/quick-start/run-envoy.rst @@ -252,7 +252,6 @@ This can be overridden using :option:`--log-path`. $ mkdir logs $ envoy -c envoy-demo.yaml --log-path logs/custom.log - ... .. tab:: Docker @@ -266,12 +265,28 @@ This can be overridden using :option:`--log-path`. envoyproxy/|envoy_docker_image| \ -c /etc/envoy/envoy.yaml \ --log-path logs/custom.log - ... :ref:`Access log ` paths can be set for the :ref:`admin interface `, and for configured :ref:`listeners `. +The :download:`demo configuration <_include/envoy-demo.yaml>` is configured with a +:ref:`listener ` that logs access +to ``/dev/stdout``: + +.. literalinclude:: _include/envoy-demo.yaml + :language: yaml + :linenos: + :lineno-start: 12 + :lines: 12-22 + :emphasize-lines: 4-8 + +The default configuration in the Envoy Docker container also logs access in this way. + +Logging to ``/dev/stderr`` and ``/dev/stdout`` for system and access logs respectively can +be useful when running Envoy inside a container as the streams can be separated, and logging requires no +additional files or directories to be mounted. + Some Envoy :ref:`filters and extensions ` may also have additional logging capabilities. Envoy can be configured to log to :ref:`different formats `, and to @@ -288,8 +303,6 @@ Debugging Envoy The log level for Envoy system logs can be set using the :option:`-l or --log-level <--log-level>` option. -The default is ``info``. - The available log levels are: - ``trace`` @@ -300,6 +313,8 @@ The available log levels are: - ``critical`` - ``off`` +The default is ``info``. + You can also set the log level for specific components using the :option:`--component-log-level` option. The following example inhibits all logging except for the ``upstream`` and ``connection`` components, From 3c8e56a19b13a8b447748ee410b3a63739d007dd Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Thu, 12 Nov 2020 11:58:40 -0800 Subject: [PATCH 083/117] wasm: allow execution of multiple instances of the same plugin. (#13753) Signed-off-by: Piotr Sikora Co-authored-by: mathetake --- bazel/repository_locations.bzl | 4 +- .../extensions/access_loggers/wasm/config.cc | 26 +++------ .../wasm/wasm_access_log_impl.h | 21 ++++---- source/extensions/bootstrap/wasm/config.cc | 12 ++--- source/extensions/bootstrap/wasm/config.h | 15 ++++-- source/extensions/common/wasm/context.cc | 12 ++--- source/extensions/common/wasm/context.h | 2 + source/extensions/common/wasm/wasm.cc | 36 +++++++++---- source/extensions/common/wasm/wasm.h | 34 +++++++++--- .../extensions/common/wasm/wasm_extension.cc | 8 +++ .../extensions/common/wasm/wasm_extension.h | 4 ++ .../filters/http/wasm/wasm_filter.cc | 24 ++------- .../filters/http/wasm/wasm_filter.h | 14 ++--- .../filters/network/wasm/wasm_filter.cc | 19 ++----- .../filters/network/wasm/wasm_filter.h | 19 +++---- source/extensions/stat_sinks/wasm/config.cc | 6 +-- .../stat_sinks/wasm/wasm_stat_sink_impl.h | 17 +++--- .../bootstrap/wasm/test_data/logging_cpp.cc | 5 +- test/extensions/bootstrap/wasm/wasm_test.cc | 4 +- .../common/wasm/test_data/test_cpp.cc | 4 ++ test/extensions/common/wasm/wasm_test.cc | 43 +++++++++------ .../filters/http/wasm/wasm_filter_test.cc | 53 ++++++++++--------- .../filters/network/wasm/config_test.cc | 2 +- .../filters/network/wasm/wasm_filter_test.cc | 2 +- test/test_common/wasm_base.h | 12 +++-- 25 files changed, 222 insertions(+), 176 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index ecb74393ffea..7607f13a46e3 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -870,8 +870,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host", - version = "4741d2f1cd5eb250f66d0518238c333353259d56", - sha256 = "30fc4becfcc5a95ac875fc5a0658a91aa7ddedd763b52d7810c13ed35d9d81aa", + version = "eceb02d5b7772ec1cd78a4d35356e57d2e6d59bb", + sha256 = "ae9d9b87d21d95647ebda197d130b37bddc5c6ee3e6630909a231fd55fcc9069", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], diff --git a/source/extensions/access_loggers/wasm/config.cc b/source/extensions/access_loggers/wasm/config.cc index 718adb0fad93..8ca765442e9c 100644 --- a/source/extensions/access_loggers/wasm/config.cc +++ b/source/extensions/access_loggers/wasm/config.cc @@ -23,8 +23,6 @@ WasmAccessLogFactory::createAccessLogInstance(const Protobuf::Message& proto_con const auto& config = MessageUtil::downcastAndValidate< const envoy::extensions::access_loggers::wasm::v3::WasmAccessLog&>( proto_config, context.messageValidationVisitor()); - auto access_log = - std::make_shared(config.config().root_id(), nullptr, std::move(filter)); // Create a base WASM to verify that the code loads before setting/cloning the for the // individual threads. @@ -35,25 +33,15 @@ WasmAccessLogFactory::createAccessLogInstance(const Protobuf::Message& proto_con envoy::config::core::v3::TrafficDirection::UNSPECIFIED, context.localInfo(), nullptr /* listener_metadata */); - auto callback = [access_log, &context, plugin](Common::Wasm::WasmHandleSharedPtr base_wasm) { - auto tls_slot = context.threadLocal().allocateSlot(); + auto access_log = std::make_shared(plugin, nullptr, std::move(filter)); + auto callback = [access_log, &context, plugin](Common::Wasm::WasmHandleSharedPtr base_wasm) { // NB: the Slot set() call doesn't complete inline, so all arguments must outlive this call. - tls_slot->set( - [base_wasm, - plugin](Event::Dispatcher& dispatcher) -> std::shared_ptr { - if (!base_wasm) { - // There is no way to prevent the connection at this point. The user could choose to use - // an HTTP Wasm plugin and only handle onLog() which would correctly close the - // connection in onRequestHeaders(). - if (!plugin->fail_open_) { - ENVOY_LOG(critical, "Plugin configured to fail closed failed to load"); - } - return nullptr; - } - return std::static_pointer_cast( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, dispatcher)); - }); + auto tls_slot = + ThreadLocal::TypedSlot::makeUnique(context.threadLocal()); + tls_slot->set([base_wasm, plugin](Event::Dispatcher& dispatcher) { + return Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, dispatcher); + }); access_log->setTlsSlot(std::move(tls_slot)); }; diff --git a/source/extensions/access_loggers/wasm/wasm_access_log_impl.h b/source/extensions/access_loggers/wasm/wasm_access_log_impl.h index 5a7654b97bee..94910ef56712 100644 --- a/source/extensions/access_loggers/wasm/wasm_access_log_impl.h +++ b/source/extensions/access_loggers/wasm/wasm_access_log_impl.h @@ -12,13 +12,15 @@ namespace Extensions { namespace AccessLoggers { namespace Wasm { -using Envoy::Extensions::Common::Wasm::WasmHandle; +using Envoy::Extensions::Common::Wasm::PluginHandle; +using Envoy::Extensions::Common::Wasm::PluginSharedPtr; class WasmAccessLog : public AccessLog::Instance { public: - WasmAccessLog(absl::string_view root_id, ThreadLocal::SlotPtr tls_slot, + WasmAccessLog(const PluginSharedPtr& plugin, ThreadLocal::TypedSlotPtr&& tls_slot, AccessLog::FilterPtr filter) - : root_id_(root_id), tls_slot_(std::move(tls_slot)), filter_(std::move(filter)) {} + : plugin_(plugin), tls_slot_(std::move(tls_slot)), filter_(std::move(filter)) {} + void log(const Http::RequestHeaderMap* request_headers, const Http::ResponseHeaderMap* response_headers, const Http::ResponseTrailerMap* response_trailers, @@ -30,20 +32,21 @@ class WasmAccessLog : public AccessLog::Instance { } } - if (tls_slot_->get()) { - tls_slot_->getTyped().wasm()->log(root_id_, request_headers, response_headers, - response_trailers, stream_info); + auto handle = tls_slot_->get(); + if (handle.has_value()) { + handle->wasm()->log(plugin_, request_headers, response_headers, response_trailers, + stream_info); } } - void setTlsSlot(ThreadLocal::SlotPtr tls_slot) { + void setTlsSlot(ThreadLocal::TypedSlotPtr&& tls_slot) { ASSERT(tls_slot_ == nullptr); tls_slot_ = std::move(tls_slot); } private: - std::string root_id_; - ThreadLocal::SlotPtr tls_slot_; + PluginSharedPtr plugin_; + ThreadLocal::TypedSlotPtr tls_slot_; AccessLog::FilterPtr filter_; }; diff --git a/source/extensions/bootstrap/wasm/config.cc b/source/extensions/bootstrap/wasm/config.cc index 3cc0068b9a16..0e8f4caa99ac 100644 --- a/source/extensions/bootstrap/wasm/config.cc +++ b/source/extensions/bootstrap/wasm/config.cc @@ -37,18 +37,18 @@ void WasmFactory::createWasm(const envoy::extensions::wasm::v3::WasmService& con } if (singleton) { // Return a Wasm VM which will be stored as a singleton by the Server. - cb(std::make_unique( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, context.dispatcher()))); + cb(std::make_unique(plugin, Common::Wasm::getOrCreateThreadLocalPlugin( + base_wasm, plugin, context.dispatcher()))); return; } // Per-thread WASM VM. // NB: the Slot set() call doesn't complete inline, so all arguments must outlive this call. - auto tls_slot = context.threadLocal().allocateSlot(); + auto tls_slot = + ThreadLocal::TypedSlot::makeUnique(context.threadLocal()); tls_slot->set([base_wasm, plugin](Event::Dispatcher& dispatcher) { - return std::static_pointer_cast( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, dispatcher)); + return Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, dispatcher); }); - cb(std::make_unique(std::move(tls_slot))); + cb(std::make_unique(plugin, std::move(tls_slot))); }; if (!Common::Wasm::createWasm( diff --git a/source/extensions/bootstrap/wasm/config.h b/source/extensions/bootstrap/wasm/config.h index e70306746389..b8f3850ef621 100644 --- a/source/extensions/bootstrap/wasm/config.h +++ b/source/extensions/bootstrap/wasm/config.h @@ -16,14 +16,21 @@ namespace Extensions { namespace Bootstrap { namespace Wasm { +using Envoy::Extensions::Common::Wasm::PluginHandle; +using Envoy::Extensions::Common::Wasm::PluginHandleSharedPtr; +using Envoy::Extensions::Common::Wasm::PluginSharedPtr; + class WasmService { public: - WasmService(Common::Wasm::WasmHandleSharedPtr singleton) : singleton_(std::move(singleton)) {} - WasmService(ThreadLocal::SlotPtr tls_slot) : tls_slot_(std::move(tls_slot)) {} + WasmService(PluginSharedPtr plugin, PluginHandleSharedPtr singleton) + : plugin_(plugin), singleton_(std::move(singleton)) {} + WasmService(PluginSharedPtr plugin, ThreadLocal::TypedSlotPtr&& tls_slot) + : plugin_(plugin), tls_slot_(std::move(tls_slot)) {} private: - Common::Wasm::WasmHandleSharedPtr singleton_; - ThreadLocal::SlotPtr tls_slot_; + PluginSharedPtr plugin_; + PluginHandleSharedPtr singleton_; + ThreadLocal::TypedSlotPtr tls_slot_; }; using WasmServicePtr = std::unique_ptr; diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index e6e4f8ae0f05..f5d0f3183a18 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -810,8 +810,8 @@ BufferInterface* Context::getBuffer(WasmBufferType type) { case WasmBufferType::VmConfiguration: return buffer_.set(wasm()->vm_configuration()); case WasmBufferType::PluginConfiguration: - if (plugin_) { - return buffer_.set(plugin_->plugin_configuration_); + if (temp_plugin_) { + return buffer_.set(temp_plugin_->plugin_configuration_); } return nullptr; case WasmBufferType::HttpRequestBody: @@ -1182,18 +1182,18 @@ bool Context::validateConfiguration(absl::string_view configuration, if (!wasm()->validate_configuration_) { return true; } - plugin_ = plugin_base; + temp_plugin_ = plugin_base; auto result = wasm() ->validate_configuration_(this, id_, static_cast(configuration.size())) .u64_ != 0; - plugin_.reset(); + temp_plugin_.reset(); return result; } absl::string_view Context::getConfiguration() { - if (plugin_) { - return plugin_->plugin_configuration_; + if (temp_plugin_) { + return temp_plugin_->plugin_configuration_; } else { return wasm()->vm_configuration(); } diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index e288c1e50602..657a0331addd 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -31,6 +31,7 @@ using proxy_wasm::ContextBase; using proxy_wasm::Pairs; using proxy_wasm::PairsWithStringValues; using proxy_wasm::PluginBase; +using proxy_wasm::PluginHandleBase; using proxy_wasm::SharedQueueDequeueToken; using proxy_wasm::SharedQueueEnqueueToken; using proxy_wasm::WasmBase; @@ -45,6 +46,7 @@ using GrpcService = envoy::config::core::v3::GrpcService; class Wasm; +using PluginHandleBaseSharedPtr = std::shared_ptr; using WasmHandleBaseSharedPtr = std::shared_ptr; // Opaque context object. diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index ab2a45f0aaf7..1b0e8e513be2 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -243,16 +243,16 @@ ContextBase* Wasm::createRootContext(const std::shared_ptr& plugin) ContextBase* Wasm::createVmContext() { return new Context(this); } -void Wasm::log(absl::string_view root_id, const Http::RequestHeaderMap* request_headers, +void Wasm::log(const PluginSharedPtr& plugin, const Http::RequestHeaderMap* request_headers, const Http::ResponseHeaderMap* response_headers, const Http::ResponseTrailerMap* response_trailers, const StreamInfo::StreamInfo& stream_info) { - auto context = getRootContext(root_id); + auto context = getRootContext(plugin, true); context->log(request_headers, response_headers, response_trailers, stream_info); } -void Wasm::onStatsUpdate(absl::string_view root_id, Envoy::Stats::MetricSnapshot& snapshot) { - auto context = getRootContext(root_id); +void Wasm::onStatsUpdate(const PluginSharedPtr& plugin, Envoy::Stats::MetricSnapshot& snapshot) { + auto context = getRootContext(plugin, true); context->onStatsUpdate(snapshot); } @@ -281,6 +281,14 @@ getCloneFactory(WasmExtension* wasm_extension, Event::Dispatcher& dispatcher, }; } +static proxy_wasm::PluginHandleFactory getPluginFactory(WasmExtension* wasm_extension) { + auto wasm_plugin_factory = wasm_extension->pluginFactory(); + return [wasm_plugin_factory](WasmHandleBaseSharedPtr base_wasm, + absl::string_view plugin_key) -> std::shared_ptr { + return wasm_plugin_factory(std::static_pointer_cast(base_wasm), plugin_key); + }; +} + WasmEvent toWasmEvent(const std::shared_ptr& wasm) { if (!wasm) { return WasmEvent::UnableToCreateVM; @@ -474,13 +482,21 @@ bool createWasm(const VmConfig& vm_config, const PluginSharedPtr& plugin, create_root_context_for_testing); } -WasmHandleSharedPtr getOrCreateThreadLocalWasm(const WasmHandleSharedPtr& base_wasm, - const PluginSharedPtr& plugin, - Event::Dispatcher& dispatcher, - CreateContextFn create_root_context_for_testing) { - return std::static_pointer_cast(proxy_wasm::getOrCreateThreadLocalWasm( +PluginHandleSharedPtr +getOrCreateThreadLocalPlugin(const WasmHandleSharedPtr& base_wasm, const PluginSharedPtr& plugin, + Event::Dispatcher& dispatcher, + CreateContextFn create_root_context_for_testing) { + if (!base_wasm) { + if (!plugin->fail_open_) { + ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::wasm), critical, + "Plugin configured to fail closed failed to load"); + } + return nullptr; + } + return std::static_pointer_cast(proxy_wasm::getOrCreateThreadLocalPlugin( std::static_pointer_cast(base_wasm), plugin, - getCloneFactory(getWasmExtension(), dispatcher, create_root_context_for_testing))); + getCloneFactory(getWasmExtension(), dispatcher, create_root_context_for_testing), + getPluginFactory(getWasmExtension()))); } } // namespace Wasm diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index 08cf6ed636c0..8ade0d66e63d 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -54,8 +54,8 @@ class Wasm : public WasmBase, Logger::Loggable { Upstream::ClusterManager& clusterManager() const { return cluster_manager_; } Event::Dispatcher& dispatcher() { return dispatcher_; } - Context* getRootContext(absl::string_view root_id) { - return static_cast(WasmBase::getRootContext(root_id)); + Context* getRootContext(const std::shared_ptr& plugin, bool allow_closed) { + return static_cast(WasmBase::getRootContext(plugin, allow_closed)); } void setTimerPeriod(uint32_t root_context_id, std::chrono::milliseconds period) override; virtual void tickHandler(uint32_t root_context_id); @@ -72,12 +72,13 @@ class Wasm : public WasmBase, Logger::Loggable { void getFunctions() override; // AccessLog::Instance - void log(absl::string_view root_id, const Http::RequestHeaderMap* request_headers, + void log(const PluginSharedPtr& plugin, const Http::RequestHeaderMap* request_headers, const Http::ResponseHeaderMap* response_headers, const Http::ResponseTrailerMap* response_trailers, const StreamInfo::StreamInfo& stream_info); - void onStatsUpdate(absl::string_view root_id, Envoy::Stats::MetricSnapshot& snapshot); + void onStatsUpdate(const PluginSharedPtr& plugin, Envoy::Stats::MetricSnapshot& snapshot); + virtual std::string buildVersion() { return BUILD_VERSION_NUMBER; } void initializeLifecycle(Server::ServerLifecycleNotifier& lifecycle_notifier); @@ -136,6 +137,23 @@ class WasmHandle : public WasmHandleBase, public ThreadLocal::ThreadLocalObject WasmSharedPtr wasm_; }; +using WasmHandleSharedPtr = std::shared_ptr; + +class PluginHandle : public PluginHandleBase, public ThreadLocal::ThreadLocalObject { +public: + explicit PluginHandle(const WasmHandleSharedPtr& wasm_handle, absl::string_view plugin_key) + : PluginHandleBase(std::static_pointer_cast(wasm_handle), plugin_key), + wasm_handle_(wasm_handle) {} + + WasmSharedPtr& wasm() { return wasm_handle_->wasm(); } + WasmHandleSharedPtr& wasmHandleForTest() { return wasm_handle_; } + +private: + WasmHandleSharedPtr wasm_handle_; +}; + +using PluginHandleSharedPtr = std::shared_ptr; + using CreateWasmCallback = std::function; // Returns false if createWasm failed synchronously. This is necessary because xDS *MUST* report @@ -150,10 +168,10 @@ bool createWasm(const VmConfig& vm_config, const PluginSharedPtr& plugin, CreateWasmCallback&& callback, CreateContextFn create_root_context_for_testing = nullptr); -WasmHandleSharedPtr -getOrCreateThreadLocalWasm(const WasmHandleSharedPtr& base_wasm, const PluginSharedPtr& plugin, - Event::Dispatcher& dispatcher, - CreateContextFn create_root_context_for_testing = nullptr); +PluginHandleSharedPtr +getOrCreateThreadLocalPlugin(const WasmHandleSharedPtr& base_wasm, const PluginSharedPtr& plugin, + Event::Dispatcher& dispatcher, + CreateContextFn create_root_context_for_testing = nullptr); void clearCodeCacheForTesting(); std::string anyToBytes(const ProtobufWkt::Any& any); diff --git a/source/extensions/common/wasm/wasm_extension.cc b/source/extensions/common/wasm/wasm_extension.cc index c75168f1761c..1917fa792a82 100644 --- a/source/extensions/common/wasm/wasm_extension.cc +++ b/source/extensions/common/wasm/wasm_extension.cc @@ -46,6 +46,14 @@ EnvoyWasm::createEnvoyWasmVmIntegration(const Stats::ScopeSharedPtr& scope, return std::make_unique(scope, runtime, short_runtime); } +PluginHandleExtensionFactory EnvoyWasm::pluginFactory() { + return [](const WasmHandleSharedPtr& base_wasm, + absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { + return std::static_pointer_cast( + std::make_shared(base_wasm, plugin_key)); + }; +} + WasmHandleExtensionFactory EnvoyWasm::wasmFactory() { return [](const VmConfig vm_config, const Stats::ScopeSharedPtr& scope, Upstream::ClusterManager& cluster_manager, Event::Dispatcher& dispatcher, diff --git a/source/extensions/common/wasm/wasm_extension.h b/source/extensions/common/wasm/wasm_extension.h index 5d41a58bb337..22ae373162f2 100644 --- a/source/extensions/common/wasm/wasm_extension.h +++ b/source/extensions/common/wasm/wasm_extension.h @@ -31,6 +31,8 @@ class EnvoyWasmVmIntegration; using WasmHandleSharedPtr = std::shared_ptr; using CreateContextFn = std::function& plugin)>; +using PluginHandleExtensionFactory = std::function; using WasmHandleExtensionFactory = std::function { virtual std::unique_ptr createEnvoyWasmVmIntegration(const Stats::ScopeSharedPtr& scope, absl::string_view runtime, absl::string_view short_runtime) = 0; + virtual PluginHandleExtensionFactory pluginFactory() = 0; virtual WasmHandleExtensionFactory wasmFactory() = 0; virtual WasmHandleExtensionCloneFactory wasmCloneFactory() = 0; enum class WasmEvent : int { @@ -100,6 +103,7 @@ class EnvoyWasm : public WasmExtension { std::unique_ptr createEnvoyWasmVmIntegration(const Stats::ScopeSharedPtr& scope, absl::string_view runtime, absl::string_view short_runtime) override; + PluginHandleExtensionFactory pluginFactory() override; WasmHandleExtensionFactory wasmFactory() override; WasmHandleExtensionCloneFactory wasmCloneFactory() override; void onEvent(WasmEvent event, const PluginSharedPtr& plugin) override; diff --git a/source/extensions/filters/http/wasm/wasm_filter.cc b/source/extensions/filters/http/wasm/wasm_filter.cc index c62b06c4102d..90713ba01989 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.cc +++ b/source/extensions/filters/http/wasm/wasm_filter.cc @@ -1,14 +1,5 @@ #include "extensions/filters/http/wasm/wasm_filter.h" -#include "envoy/http/codes.h" - -#include "common/buffer/buffer_impl.h" -#include "common/common/assert.h" -#include "common/common/enum_to_int.h" -#include "common/http/header_map_impl.h" -#include "common/http/message_impl.h" -#include "common/http/utility.h" - namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -16,7 +7,8 @@ namespace Wasm { FilterConfig::FilterConfig(const envoy::extensions::filters::http::wasm::v3::Wasm& config, Server::Configuration::FactoryContext& context) - : tls_slot_(context.threadLocal().allocateSlot()) { + : tls_slot_( + ThreadLocal::TypedSlot::makeUnique(context.threadLocal())) { plugin_ = std::make_shared( config.config().name(), config.config().root_id(), config.config().vm_config().vm_id(), config.config().vm_config().runtime(), @@ -26,15 +18,9 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::wasm::v3::Was auto plugin = plugin_; auto callback = [plugin, this](const Common::Wasm::WasmHandleSharedPtr& base_wasm) { // NB: the Slot set() call doesn't complete inline, so all arguments must outlive this call. - tls_slot_->set( - [base_wasm, - plugin](Event::Dispatcher& dispatcher) -> std::shared_ptr { - if (!base_wasm) { - return nullptr; - } - return std::static_pointer_cast( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, dispatcher)); - }); + tls_slot_->set([base_wasm, plugin](Event::Dispatcher& dispatcher) { + return Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, dispatcher); + }); }; if (!Common::Wasm::createWasm( diff --git a/source/extensions/filters/http/wasm/wasm_filter.h b/source/extensions/filters/http/wasm/wasm_filter.h index 36bfd1503b77..956862e5f09b 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.h +++ b/source/extensions/filters/http/wasm/wasm_filter.h @@ -16,8 +16,9 @@ namespace HttpFilters { namespace Wasm { using Envoy::Extensions::Common::Wasm::Context; +using Envoy::Extensions::Common::Wasm::PluginHandle; +using Envoy::Extensions::Common::Wasm::PluginSharedPtr; using Envoy::Extensions::Common::Wasm::Wasm; -using Envoy::Extensions::Common::Wasm::WasmHandle; class FilterConfig : Logger::Loggable { public: @@ -26,22 +27,23 @@ class FilterConfig : Logger::Loggable { std::shared_ptr createFilter() { Wasm* wasm = nullptr; - if (tls_slot_->get()) { - wasm = tls_slot_->getTyped().wasm().get(); + auto handle = tls_slot_->get(); + if (handle.has_value()) { + wasm = handle->wasm().get(); } if (plugin_->fail_open_ && (!wasm || wasm->isFailed())) { return nullptr; } if (wasm && !root_context_id_) { - root_context_id_ = wasm->getRootContext(plugin_->root_id_)->id(); + root_context_id_ = wasm->getRootContext(plugin_, false)->id(); } return std::make_shared(wasm, root_context_id_, plugin_); } private: uint32_t root_context_id_{0}; - Envoy::Extensions::Common::Wasm::PluginSharedPtr plugin_; - ThreadLocal::SlotPtr tls_slot_; + PluginSharedPtr plugin_; + ThreadLocal::TypedSlotPtr tls_slot_; Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; }; diff --git a/source/extensions/filters/network/wasm/wasm_filter.cc b/source/extensions/filters/network/wasm/wasm_filter.cc index 9d253b675abd..ccceeb9dc478 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.cc +++ b/source/extensions/filters/network/wasm/wasm_filter.cc @@ -1,9 +1,5 @@ #include "extensions/filters/network/wasm/wasm_filter.h" -#include "common/buffer/buffer_impl.h" -#include "common/common/assert.h" -#include "common/common/enum_to_int.h" - namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -11,7 +7,8 @@ namespace Wasm { FilterConfig::FilterConfig(const envoy::extensions::filters::network::wasm::v3::Wasm& config, Server::Configuration::FactoryContext& context) - : tls_slot_(context.threadLocal().allocateSlot()) { + : tls_slot_( + ThreadLocal::TypedSlot::makeUnique(context.threadLocal())) { plugin_ = std::make_shared( config.config().name(), config.config().root_id(), config.config().vm_config().vm_id(), config.config().vm_config().runtime(), @@ -21,15 +18,9 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::network::wasm::v3:: auto plugin = plugin_; auto callback = [plugin, this](Common::Wasm::WasmHandleSharedPtr base_wasm) { // NB: the Slot set() call doesn't complete inline, so all arguments must outlive this call. - tls_slot_->set( - [base_wasm, - plugin](Event::Dispatcher& dispatcher) -> std::shared_ptr { - if (!base_wasm) { - return nullptr; - } - return std::static_pointer_cast( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, dispatcher)); - }); + tls_slot_->set([base_wasm, plugin](Event::Dispatcher& dispatcher) { + return Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, dispatcher); + }); }; if (!Common::Wasm::createWasm( diff --git a/source/extensions/filters/network/wasm/wasm_filter.h b/source/extensions/filters/network/wasm/wasm_filter.h index 51adbcd7ac0c..6a6fe2584b2c 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.h +++ b/source/extensions/filters/network/wasm/wasm_filter.h @@ -16,8 +16,9 @@ namespace NetworkFilters { namespace Wasm { using Envoy::Extensions::Common::Wasm::Context; +using Envoy::Extensions::Common::Wasm::PluginHandle; +using Envoy::Extensions::Common::Wasm::PluginSharedPtr; using Envoy::Extensions::Common::Wasm::Wasm; -using Envoy::Extensions::Common::Wasm::WasmHandle; class FilterConfig : Logger::Loggable { public: @@ -26,25 +27,25 @@ class FilterConfig : Logger::Loggable { std::shared_ptr createFilter() { Wasm* wasm = nullptr; - if (tls_slot_->get()) { - wasm = tls_slot_->getTyped().wasm().get(); + auto handle = tls_slot_->get(); + if (handle.has_value()) { + wasm = handle->wasm().get(); } if (plugin_->fail_open_ && (!wasm || wasm->isFailed())) { return nullptr; } if (wasm && !root_context_id_) { - root_context_id_ = wasm->getRootContext(plugin_->root_id_)->id(); + root_context_id_ = wasm->getRootContext(plugin_, false)->id(); } return std::make_shared(wasm, root_context_id_, plugin_); } - Envoy::Extensions::Common::Wasm::Wasm* wasm() { - return tls_slot_->getTyped().wasm().get(); - } + + Wasm* wasmForTest() { return tls_slot_->get()->wasm().get(); } private: uint32_t root_context_id_{0}; - Envoy::Extensions::Common::Wasm::PluginSharedPtr plugin_; - ThreadLocal::SlotPtr tls_slot_; + PluginSharedPtr plugin_; + ThreadLocal::TypedSlotPtr tls_slot_; Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; }; diff --git a/source/extensions/stat_sinks/wasm/config.cc b/source/extensions/stat_sinks/wasm/config.cc index ba94937a3b3a..da07bbdd5880 100644 --- a/source/extensions/stat_sinks/wasm/config.cc +++ b/source/extensions/stat_sinks/wasm/config.cc @@ -22,14 +22,14 @@ WasmSinkFactory::createStatsSink(const Protobuf::Message& proto_config, MessageUtil::downcastAndValidate( proto_config, context.messageValidationContext().staticValidationVisitor()); - auto wasm_sink = std::make_unique(config.config().root_id(), nullptr); - auto plugin = std::make_shared( config.config().name(), config.config().root_id(), config.config().vm_config().vm_id(), config.config().vm_config().runtime(), Common::Wasm::anyToBytes(config.config().configuration()), config.config().fail_open(), envoy::config::core::v3::TrafficDirection::UNSPECIFIED, context.localInfo(), nullptr); + auto wasm_sink = std::make_unique(plugin, nullptr); + auto callback = [&wasm_sink, &context, plugin](Common::Wasm::WasmHandleSharedPtr base_wasm) { if (!base_wasm) { if (plugin->fail_open_) { @@ -40,7 +40,7 @@ WasmSinkFactory::createStatsSink(const Protobuf::Message& proto_config, return; } wasm_sink->setSingleton( - Common::Wasm::getOrCreateThreadLocalWasm(base_wasm, plugin, context.dispatcher())); + Common::Wasm::getOrCreateThreadLocalPlugin(base_wasm, plugin, context.dispatcher())); }; if (!Common::Wasm::createWasm( diff --git a/source/extensions/stat_sinks/wasm/wasm_stat_sink_impl.h b/source/extensions/stat_sinks/wasm/wasm_stat_sink_impl.h index 5f2a9b6e13f9..5b339a6c80e9 100644 --- a/source/extensions/stat_sinks/wasm/wasm_stat_sink_impl.h +++ b/source/extensions/stat_sinks/wasm/wasm_stat_sink_impl.h @@ -9,20 +9,21 @@ namespace Extensions { namespace StatSinks { namespace Wasm { -using Envoy::Extensions::Common::Wasm::WasmHandle; +using Envoy::Extensions::Common::Wasm::PluginHandleSharedPtr; +using Envoy::Extensions::Common::Wasm::PluginSharedPtr; class WasmStatSink : public Stats::Sink { public: - WasmStatSink(absl::string_view root_id, Common::Wasm::WasmHandleSharedPtr singleton) - : root_id_(root_id), singleton_(std::move(singleton)) {} + WasmStatSink(const PluginSharedPtr& plugin, PluginHandleSharedPtr singleton) + : plugin_(plugin), singleton_(singleton) {} void flush(Stats::MetricSnapshot& snapshot) override { - singleton_->wasm()->onStatsUpdate(root_id_, snapshot); + singleton_->wasm()->onStatsUpdate(plugin_, snapshot); } - void setSingleton(Common::Wasm::WasmHandleSharedPtr singleton) { + void setSingleton(PluginHandleSharedPtr singleton) { ASSERT(singleton != nullptr); - singleton_ = std::move(singleton); + singleton_ = singleton; } void onHistogramComplete(const Stats::Histogram& histogram, uint64_t value) override { @@ -31,8 +32,8 @@ class WasmStatSink : public Stats::Sink { } private: - std::string root_id_; - Common::Wasm::WasmHandleSharedPtr singleton_; + PluginSharedPtr plugin_; + PluginHandleSharedPtr singleton_; }; } // namespace Wasm diff --git a/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc b/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc index 70fde8f6ae19..9a5becfab1b3 100644 --- a/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc +++ b/test/extensions/bootstrap/wasm/test_data/logging_cpp.cc @@ -26,10 +26,7 @@ extern "C" PROXY_WASM_KEEPALIVE uint32_t proxy_on_configure(uint32_t, uint32_t c extern "C" PROXY_WASM_KEEPALIVE void proxy_on_context_create(uint32_t, uint32_t) {} -extern "C" PROXY_WASM_KEEPALIVE uint32_t proxy_on_vm_start(uint32_t, uint32_t) { - proxy_set_tick_period_milliseconds(10); - return 1; -} +extern "C" PROXY_WASM_KEEPALIVE uint32_t proxy_on_vm_start(uint32_t, uint32_t) { return 1; } extern "C" PROXY_WASM_KEEPALIVE void proxy_on_tick(uint32_t) { const char* root_id = nullptr; diff --git a/test/extensions/bootstrap/wasm/wasm_test.cc b/test/extensions/bootstrap/wasm/wasm_test.cc index fdfdd9536557..5384c66dcae8 100644 --- a/test/extensions/bootstrap/wasm/wasm_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_test.cc @@ -223,7 +223,7 @@ TEST_P(WasmTest, Segv) { auto context = static_cast(wasm_->start(plugin_)); EXPECT_CALL(*context, log_(spdlog::level::err, Eq("before badptr"))); EXPECT_FALSE(wasm_->configure(context, plugin_)); - wasm_->isFailed(); + EXPECT_TRUE(wasm_->isFailed()); } TEST_P(WasmTest, DivByZero) { @@ -235,7 +235,7 @@ TEST_P(WasmTest, DivByZero) { auto context = static_cast(wasm_->start(plugin_)); EXPECT_CALL(*context, log_(spdlog::level::err, Eq("before div by zero"))); context->onLog(); - wasm_->isFailed(); + EXPECT_TRUE(wasm_->isFailed()); } TEST_P(WasmTest, IntrinsicGlobals) { diff --git a/test/extensions/common/wasm/test_data/test_cpp.cc b/test/extensions/common/wasm/test_data/test_cpp.cc index 1d990901846a..f2003072ee8c 100644 --- a/test/extensions/common/wasm/test_data/test_cpp.cc +++ b/test/extensions/common/wasm/test_data/test_cpp.cc @@ -267,6 +267,10 @@ WASM_EXPORT(uint32_t, proxy_on_done, (uint32_t)) { return 0; } +WASM_EXPORT(void, proxy_on_tick, (uint32_t)) { + proxy_done(); +} + WASM_EXPORT(void, proxy_on_delete, (uint32_t)) { std::string message = "on_delete logging"; proxy_log(LogLevel::info, message.c_str(), message.size()); diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index 110f7f720eec..6d903abdbec2 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -237,8 +237,8 @@ TEST_P(WasmCommonTest, Logging) { wasm_handle.reset(); dispatcher->run(Event::Dispatcher::RunType::NonBlock); // This will fault on nullptr if wasm has been deleted. - plugin->plugin_configuration_ = "done"; - wasm_weak.lock()->configure(root_context, plugin); + wasm_weak.lock()->setTimerPeriod(root_context->id(), std::chrono::milliseconds(10)); + wasm_weak.lock()->tickHandler(root_context->id()); dispatcher->run(Event::Dispatcher::RunType::NonBlock); dispatcher->clearDeferredDeleteList(); } @@ -648,7 +648,7 @@ TEST_P(WasmCommonTest, VmCache) { auto root_id = ""; auto vm_id = ""; auto vm_configuration = "vm_cache"; - auto plugin_configuration = "init"; + auto plugin_configuration = "done"; auto plugin = std::make_shared( name, root_id, vm_id, GetParam(), plugin_configuration, false, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); @@ -692,7 +692,7 @@ TEST_P(WasmCommonTest, VmCache) { EXPECT_NE(wasm_handle2, nullptr); EXPECT_EQ(wasm_handle, wasm_handle2); - auto wasm_handle_local = getOrCreateThreadLocalWasm( + auto plugin_handle_local = getOrCreateThreadLocalPlugin( wasm_handle, plugin, [&dispatcher](const WasmHandleBaseSharedPtr& base_wasm) -> WasmHandleBaseSharedPtr { auto wasm = @@ -701,22 +701,24 @@ TEST_P(WasmCommonTest, VmCache) { nullptr, [](Wasm* wasm, const std::shared_ptr& plugin) -> ContextBase* { auto root_context = new TestContext(wasm, plugin); EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_vm_start vm_cache"))); - EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_configuration init"))); EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_done logging"))); EXPECT_CALL(*root_context, log_(spdlog::level::info, Eq("on_delete logging"))); return root_context; }); return std::make_shared(wasm); + }, + [](const WasmHandleBaseSharedPtr& base_wasm, + absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { + return std::make_shared(std::static_pointer_cast(base_wasm), + plugin_key); }); wasm_handle.reset(); wasm_handle2.reset(); - auto wasm = wasm_handle_local->wasm().get(); - wasm_handle_local.reset(); + auto wasm = plugin_handle_local->wasm(); + plugin_handle_local.reset(); dispatcher->run(Event::Dispatcher::RunType::NonBlock); - - plugin->plugin_configuration_ = "done"; wasm->configure(wasm->getContext(1), plugin); plugin.reset(); dispatcher->run(Event::Dispatcher::RunType::NonBlock); @@ -795,7 +797,7 @@ TEST_P(WasmCommonTest, RemoteCode) { EXPECT_NE(wasm_handle, nullptr); - auto wasm_handle_local = getOrCreateThreadLocalWasm( + auto plugin_handle_local = getOrCreateThreadLocalPlugin( wasm_handle, plugin, [&dispatcher](const WasmHandleBaseSharedPtr& base_wasm) -> WasmHandleBaseSharedPtr { auto wasm = @@ -809,11 +811,17 @@ TEST_P(WasmCommonTest, RemoteCode) { return root_context; }); return std::make_shared(wasm); + }, + [](const WasmHandleBaseSharedPtr& base_wasm, + absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { + return std::make_shared(std::static_pointer_cast(base_wasm), + plugin_key); }); wasm_handle.reset(); - auto wasm = wasm_handle_local->wasm().get(); - wasm_handle_local.reset(); + auto wasm = plugin_handle_local->wasm(); + plugin_handle_local.reset(); + dispatcher->run(Event::Dispatcher::RunType::NonBlock); wasm->configure(wasm->getContext(1), plugin); plugin.reset(); @@ -905,7 +913,7 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { dispatcher->run(Event::Dispatcher::RunType::NonBlock); EXPECT_NE(wasm_handle, nullptr); - auto wasm_handle_local = getOrCreateThreadLocalWasm( + auto plugin_handle_local = getOrCreateThreadLocalPlugin( wasm_handle, plugin, [&dispatcher](const WasmHandleBaseSharedPtr& base_wasm) -> WasmHandleBaseSharedPtr { auto wasm = @@ -919,11 +927,16 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { return root_context; }); return std::make_shared(wasm); + }, + [](const WasmHandleBaseSharedPtr& base_wasm, + absl::string_view plugin_key) -> PluginHandleBaseSharedPtr { + return std::make_shared(std::static_pointer_cast(base_wasm), + plugin_key); }); wasm_handle.reset(); - auto wasm = wasm_handle_local->wasm().get(); - wasm_handle_local.reset(); + auto wasm = plugin_handle_local->wasm(); + plugin_handle_local.reset(); dispatcher->run(Event::Dispatcher::RunType::NonBlock); wasm->configure(wasm->getContext(1), plugin); diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index 9d3cda60b6ec..a80dcd64a8e2 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -82,7 +82,7 @@ class WasmHttpFilterTest : public Common::Wasm::WasmHttpFilterTestBase< } setupBase(std::get<0>(GetParam()), code, createContextFn(), root_id, vm_configuration); } - void setupFilter(const std::string root_id = "") { setupFilterBase(root_id); } + void setupFilter() { setupFilterBase(); } void setupGrpcStreamTest(Grpc::RawAsyncStreamCallbacks*& callbacks); @@ -294,7 +294,7 @@ TEST_P(WasmHttpFilterTest, HeadersStopAndWatermark) { // Script that reads the body. TEST_P(WasmHttpFilterTest, BodyRequestReadBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody hello")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "ReadBody"}}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); @@ -306,7 +306,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestReadBody) { // Script that prepends and appends to the body. TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody prepend.hello.append")))); EXPECT_CALL(filter(), log_(spdlog::level::err, @@ -330,7 +330,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBody) { // Script that replaces the body. TEST_P(WasmHttpFilterTest, BodyRequestReplaceBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody replace")))).Times(2); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "ReplaceBody"}}; @@ -351,7 +351,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestReplaceBody) { // Script that removes the body. TEST_P(WasmHttpFilterTest, BodyRequestRemoveBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "RemoveBody"}}; @@ -364,7 +364,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestRemoveBody) { // Script that buffers the body. TEST_P(WasmHttpFilterTest, BodyRequestBufferBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "BufferBody"}}; @@ -407,7 +407,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestBufferBody) { // Script that prepends and appends to the buffered body. TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBufferedBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody prepend.hello.append")))); Http::TestRequestHeaderMapImpl request_headers{ @@ -421,7 +421,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestPrependAndAppendToBufferedBody) { // Script that replaces the buffered body. TEST_P(WasmHttpFilterTest, BodyRequestReplaceBufferedBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody replace")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "ReplaceBufferedBody"}}; @@ -434,7 +434,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestReplaceBufferedBody) { // Script that removes the buffered body. TEST_P(WasmHttpFilterTest, BodyRequestRemoveBufferedBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::err, Eq(absl::string_view("onBody ")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"x-test-operation", "RemoveBufferedBody"}}; @@ -447,7 +447,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestRemoveBufferedBody) { // Script that buffers the first part of the body and streams the rest TEST_P(WasmHttpFilterTest, BodyRequestBufferThenStreamBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); @@ -497,7 +497,7 @@ TEST_P(WasmHttpFilterTest, BodyRequestBufferThenStreamBody) { // Script that buffers the first part of the body and streams the rest TEST_P(WasmHttpFilterTest, BodyResponseBufferThenStreamBody) { setupTest("body"); - setupFilter("body"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, false)); @@ -580,7 +580,7 @@ TEST_P(WasmHttpFilterTest, AccessLogCreate) { TEST_P(WasmHttpFilterTest, AsyncCall) { setupTest("async_call"); - setupFilter("async_call"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; Http::MockAsyncClientRequest request(&cluster_manager_.async_client_); @@ -627,7 +627,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallBadCall) { return; } setupTest("async_call"); - setupFilter("async_call"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; Http::MockAsyncClientRequest request(&cluster_manager_.async_client_); @@ -647,7 +647,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallBadCall) { TEST_P(WasmHttpFilterTest, AsyncCallFailure) { setupTest("async_call"); - setupFilter("async_call"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; Http::MockAsyncClientRequest request(&cluster_manager_.async_client_); @@ -688,7 +688,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallFailure) { TEST_P(WasmHttpFilterTest, AsyncCallAfterDestroyed) { setupTest("async_call"); - setupFilter("async_call"); + setupFilter(); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; Http::MockAsyncClientRequest request(&cluster_manager_.async_client_); @@ -719,6 +719,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallAfterDestroyed) { // Destroy the Context, Plugin and VM. context_.reset(); plugin_.reset(); + plugin_handle_.reset(); wasm_.reset(); Http::ResponseMessagePtr response_message(new Http::ResponseMessageImpl( @@ -738,7 +739,7 @@ TEST_P(WasmHttpFilterTest, GrpcCall) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); NiceMock request; Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; Grpc::MockAsyncClientManager client_manager; @@ -793,7 +794,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallBadCall) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); Grpc::MockAsyncClientManager client_manager; auto client_factory = std::make_unique(); auto async_client = std::make_unique(); @@ -822,7 +823,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallFailure) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); NiceMock request; Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; Grpc::MockAsyncClientManager client_manager; @@ -884,7 +885,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallCancel) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); NiceMock request; Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; Grpc::MockAsyncClientManager client_manager; @@ -928,7 +929,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallClose) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); NiceMock request; Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; Grpc::MockAsyncClientManager client_manager; @@ -972,7 +973,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallAfterDestroyed) { return; } setupTest("grpc_call"); - setupFilter("grpc_call"); + setupFilter(); Grpc::MockAsyncRequest request; Grpc::RawAsyncRequestCallbacks* callbacks = nullptr; Grpc::MockAsyncClientManager client_manager; @@ -1013,6 +1014,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallAfterDestroyed) { // Destroy the Context, Plugin and VM. context_.reset(); plugin_.reset(); + plugin_handle_.reset(); wasm_.reset(); ProtobufWkt::Value value; @@ -1029,7 +1031,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallAfterDestroyed) { void WasmHttpFilterTest::setupGrpcStreamTest(Grpc::RawAsyncStreamCallbacks*& callbacks) { setupTest("grpc_stream"); - setupFilter("grpc_stream"); + setupFilter(); EXPECT_CALL(async_client_manager_, factoryForGrpcService(_, _, _)) .WillRepeatedly( @@ -1206,6 +1208,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamOpenAtShutdown) { // Destroy the Context, Plugin and VM. context_.reset(); plugin_.reset(); + plugin_handle_.reset(); wasm_.reset(); } @@ -1384,7 +1387,7 @@ TEST_P(WasmHttpFilterTest, SharedData) { TEST_P(WasmHttpFilterTest, SharedQueue) { setupTest("shared_queue"); - setupFilter("shared_queue"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onRequestHeaders enqueue Ok")))); EXPECT_CALL(filter(), log_(spdlog::level::warn, @@ -1423,7 +1426,7 @@ TEST_P(WasmHttpFilterTest, RootId1) { return; } setupTest("context1"); - setupFilter("context1"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders1 2")))); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter().decodeHeaders(request_headers, true)); @@ -1436,7 +1439,7 @@ TEST_P(WasmHttpFilterTest, RootId2) { return; } setupTest("context2"); - setupFilter("context2"); + setupFilter(); EXPECT_CALL(filter(), log_(spdlog::level::debug, Eq(absl::string_view("onRequestHeaders2 2")))); Http::TestRequestHeaderMapImpl request_headers; EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, diff --git a/test/extensions/filters/network/wasm/config_test.cc b/test/extensions/filters/network/wasm/config_test.cc index f1507071ef78..68541490a82c 100644 --- a/test/extensions/filters/network/wasm/config_test.cc +++ b/test/extensions/filters/network/wasm/config_test.cc @@ -183,7 +183,7 @@ TEST_P(WasmNetworkFilterConfigTest, FilterConfigFailOpen) { envoy::extensions::filters::network::wasm::v3::Wasm proto_config; TestUtility::loadFromYaml(yaml, proto_config); NetworkFilters::Wasm::FilterConfig filter_config(proto_config, context_); - filter_config.wasm()->fail(proxy_wasm::FailState::RuntimeError, ""); + filter_config.wasmForTest()->fail(proxy_wasm::FailState::RuntimeError, ""); EXPECT_EQ(filter_config.createFilter(), nullptr); } diff --git a/test/extensions/filters/network/wasm/wasm_filter_test.cc b/test/extensions/filters/network/wasm/wasm_filter_test.cc index dd3a2e29a0c2..517d37cee3b5 100644 --- a/test/extensions/filters/network/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/network/wasm/wasm_filter_test.cc @@ -58,7 +58,7 @@ class WasmNetworkFilterTest : public Common::Wasm::WasmNetworkFilterTestBase< "" /* root_id */, "" /* vm_configuration */, fail_open); } - void setupFilter() { setupFilterBase(""); } + void setupFilter() { setupFilterBase(); } TestFilter& filter() { return *static_cast(context_.get()); } diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index d049460d3e41..2cfc796084eb 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -80,12 +80,13 @@ template class WasmTestBase : public Base { lifecycle_notifier_, remote_data_provider_, [this](WasmHandleSharedPtr wasm) { wasm_ = wasm; }, create_root); if (wasm_) { - wasm_ = getOrCreateThreadLocalWasm( + plugin_handle_ = getOrCreateThreadLocalPlugin( wasm_, plugin_, dispatcher_, [this, create_root](Wasm* wasm, const std::shared_ptr& plugin) { root_context_ = static_cast(create_root(wasm, plugin)); return root_context_; }); + wasm_ = plugin_handle_->wasmHandleForTest(); } } @@ -101,6 +102,7 @@ template class WasmTestBase : public Base { NiceMock init_manager_; WasmHandleSharedPtr wasm_; PluginSharedPtr plugin_; + PluginHandleSharedPtr plugin_handle_; NiceMock ssl_; NiceMock connection_; NiceMock decoder_callbacks_; @@ -114,9 +116,9 @@ template class WasmTestBase : public Base { template class WasmHttpFilterTestBase : public WasmTestBase { public: - template void setupFilterBase(const std::string root_id = "") { + template void setupFilterBase() { auto wasm = WasmTestBase::wasm_ ? WasmTestBase::wasm_->wasm().get() : nullptr; - int root_context_id = wasm ? wasm->getRootContext(root_id)->id() : 0; + int root_context_id = wasm ? wasm->getRootContext(WasmTestBase::plugin_, false)->id() : 0; context_ = std::make_unique(wasm, root_context_id, WasmTestBase::plugin_); context_->setDecoderFilterCallbacks(decoder_callbacks_); context_->setEncoderFilterCallbacks(encoder_callbacks_); @@ -131,9 +133,9 @@ template class WasmHttpFilterTestBase : public W template class WasmNetworkFilterTestBase : public WasmTestBase { public: - template void setupFilterBase(const std::string root_id = "") { + template void setupFilterBase() { auto wasm = WasmTestBase::wasm_ ? WasmTestBase::wasm_->wasm().get() : nullptr; - int root_context_id = wasm ? wasm->getRootContext(root_id)->id() : 0; + int root_context_id = wasm ? wasm->getRootContext(WasmTestBase::plugin_, false)->id() : 0; context_ = std::make_unique(wasm, root_context_id, WasmTestBase::plugin_); context_->initializeReadFilterCallbacks(read_filter_callbacks_); context_->initializeWriteFilterCallbacks(write_filter_callbacks_); From 34b0d1a43890d2c3e5b965fbab31df2eddc5aee7 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 12 Nov 2020 20:36:49 +0000 Subject: [PATCH 084/117] docs: Minor cleanup of config render (#14002) Signed-off-by: Ryan Northey --- docs/root/start/quick-start/configuration-static.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/root/start/quick-start/configuration-static.rst b/docs/root/start/quick-start/configuration-static.rst index c9600c99bfd3..3b9a1516e24d 100644 --- a/docs/root/start/quick-start/configuration-static.rst +++ b/docs/root/start/quick-start/configuration-static.rst @@ -41,8 +41,8 @@ All paths are matched and routed to the ``service_envoyproxy_io`` .. literalinclude:: _include/envoy-demo.yaml :language: yaml :linenos: - :lines: 1-29 - :emphasize-lines: 3-27 + :lines: 1-34 + :emphasize-lines: 3-32 .. _start_quick_start_static_clusters: @@ -54,6 +54,6 @@ proxies over ``TLS`` to https://www.envoyproxy.io. .. literalinclude:: _include/envoy-demo.yaml :language: yaml - :lineno-start: 27 - :lines: 27-48 + :lineno-start: 32 + :lines: 32-53 :emphasize-lines: 3-22 From 2bbad812ca59d3e0759822923c3958ee29c59a4a Mon Sep 17 00:00:00 2001 From: Taylor Barrella Date: Thu, 12 Nov 2020 12:48:15 -0800 Subject: [PATCH 085/117] build: Fix some unused variable warnings (#13987) Signed-off-by: Taylor Barrella --- include/envoy/registry/registry.h | 1 + source/common/config/new_grpc_mux_impl.cc | 1 + source/common/init/manager_impl.cc | 1 + source/common/router/scoped_rds.cc | 1 + source/server/admin/config_dump_handler.cc | 3 +++ source/server/filter_chain_manager_impl.cc | 4 ++++ 6 files changed, 11 insertions(+) diff --git a/include/envoy/registry/registry.h b/include/envoy/registry/registry.h index b52686036074..b90e86ca52f3 100644 --- a/include/envoy/registry/registry.h +++ b/include/envoy/registry/registry.h @@ -346,6 +346,7 @@ template class FactoryRegistry : public Logger::Loggable>(); for (const auto& [factory_name, factory] : factories()) { + UNREFERENCED_PARAMETER(factory_name); if (factory == nullptr) { continue; } diff --git a/source/common/config/new_grpc_mux_impl.cc b/source/common/config/new_grpc_mux_impl.cc index e850c8504caa..dc8d5c3b14dc 100644 --- a/source/common/config/new_grpc_mux_impl.cc +++ b/source/common/config/new_grpc_mux_impl.cc @@ -91,6 +91,7 @@ void NewGrpcMuxImpl::onDiscoveryResponse( void NewGrpcMuxImpl::onStreamEstablished() { for (auto& [type_url, subscription] : subscriptions_) { + UNREFERENCED_PARAMETER(type_url); subscription->sub_state_.markStreamFresh(); } trySendDiscoveryRequests(); diff --git a/source/common/init/manager_impl.cc b/source/common/init/manager_impl.cc index 650203fabbea..1440dd726414 100644 --- a/source/common/init/manager_impl.cc +++ b/source/common/init/manager_impl.cc @@ -71,6 +71,7 @@ void ManagerImpl::dumpUnreadyTargets(envoy::admin::v3::UnreadyTargetsDumps& unre auto& message = *unready_targets_dumps.mutable_unready_targets_dumps()->Add(); message.set_name(name_); for (const auto& [target_name, count] : target_names_count_) { + UNREFERENCED_PARAMETER(count); message.add_target_names(target_name); } } diff --git a/source/common/router/scoped_rds.cc b/source/common/router/scoped_rds.cc index d9ca4781e7b5..17613814efea 100644 --- a/source/common/router/scoped_rds.cc +++ b/source/common/router/scoped_rds.cc @@ -427,6 +427,7 @@ ScopedRdsConfigSubscription::detectUpdateConflictAndCleanupRemoved( absl::flat_hash_map scope_name_by_hash = scope_name_by_hash_; absl::erase_if(scope_name_by_hash, [&updated_or_removed_scopes](const auto& key_name) { auto const& [key, name] = key_name; + UNREFERENCED_PARAMETER(key); return updated_or_removed_scopes.contains(name); }); absl::flat_hash_map diff --git a/source/server/admin/config_dump_handler.cc b/source/server/admin/config_dump_handler.cc index dbfd13a01e2e..9e1d54e9d3e9 100644 --- a/source/server/admin/config_dump_handler.cc +++ b/source/server/admin/config_dump_handler.cc @@ -155,6 +155,7 @@ ConfigDumpHandler::addResourceToDump(envoy::admin::v3::ConfigDump& dump, } for (const auto& [name, callback] : callbacks_map) { + UNREFERENCED_PARAMETER(name); ProtobufTypes::MessagePtr message = callback(); ASSERT(message); @@ -200,6 +201,7 @@ void ConfigDumpHandler::addAllConfigToDump(envoy::admin::v3::ConfigDump& dump, } for (const auto& [name, callback] : callbacks_map) { + UNREFERENCED_PARAMETER(name); ProtobufTypes::MessagePtr message = callback(); ASSERT(message); @@ -220,6 +222,7 @@ ProtobufTypes::MessagePtr ConfigDumpHandler::dumpEndpointConfigs() const { auto endpoint_config_dump = std::make_unique(); for (const auto& [name, cluster_ref] : server_.clusterManager().clusters()) { + UNREFERENCED_PARAMETER(name); const Upstream::Cluster& cluster = cluster_ref.get(); Upstream::ClusterInfoConstSharedPtr cluster_info = cluster.info(); envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment; diff --git a/source/server/filter_chain_manager_impl.cc b/source/server/filter_chain_manager_impl.cc index 0e006561ce09..65edde071124 100644 --- a/source/server/filter_chain_manager_impl.cc +++ b/source/server/filter_chain_manager_impl.cc @@ -600,6 +600,7 @@ const Network::FilterChain* FilterChainManagerImpl::findFilterChainForSourceIpAn void FilterChainManagerImpl::convertIPsToTries() { for (auto& [destination_port, destination_ips_pair] : destination_ports_map_) { + UNREFERENCED_PARAMETER(destination_port); // These variables are used as we build up the destination CIDRs used for the trie. auto& [destination_ips_map, destination_ips_trie] = destination_ips_pair; std::vector>> @@ -613,8 +614,11 @@ void FilterChainManagerImpl::convertIPsToTries() { // We need to get access to all of the source IP strings so that we can convert them into // a trie like we did for the destination IPs above. for (auto& [server_name, transport_protocols_map] : *server_names_map_ptr) { + UNREFERENCED_PARAMETER(server_name); for (auto& [transport_protocol, application_protocols_map] : transport_protocols_map) { + UNREFERENCED_PARAMETER(transport_protocol); for (auto& [application_protocol, source_arrays] : application_protocols_map) { + UNREFERENCED_PARAMETER(application_protocol); for (auto& [source_ips_map, source_ips_trie] : source_arrays) { std::vector< std::pair>> From 15db921d299a7f99c8c13e345275decc7b8c0153 Mon Sep 17 00:00:00 2001 From: Dhi Aurrahman Date: Fri, 13 Nov 2020 03:49:58 +0700 Subject: [PATCH 086/117] log: Add "%_" to print message with escaped newlines (#13832) This patch adds a new custom flag `%_` to the log pattern to print the actual message to log, but with escaped newlines. Signed-off-by: Dhi Aurrahman --- docs/root/operations/cli.rst | 5 ++-- docs/root/version_history/current.rst | 1 + source/common/common/logger.cc | 17 +++++++++++- source/common/common/logger.h | 27 +++++++++++++++++++ test/common/common/logger_test.cc | 38 +++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 3 deletions(-) diff --git a/docs/root/operations/cli.rst b/docs/root/operations/cli.rst index cb2253321ae1..6ff2795ef031 100644 --- a/docs/root/operations/cli.rst +++ b/docs/root/operations/cli.rst @@ -126,6 +126,7 @@ following are the command line options that Envoy supports. The supported format flags are (with example output): :%v: The actual message to log ("some user text") + :%_: The actual message to log, but with escaped newlines (from (if using ``%v``) "some user text\nbelow", to "some user text\\nbelow") :%t: Thread id ("1232") :%P: Process id ("3456") :%n: Logger's name ("filter") @@ -188,8 +189,8 @@ following are the command line options that Envoy supports. *(optional)* Enables fine-grain logger with file level log control and runtime update at administration interface. If enabled, main log macros including `ENVOY_LOG`, `ENVOY_CONN_LOG`, `ENVOY_STREAM_LOG` and - `ENVOY_FLUSH_LOG` will use a per-file logger, and the usage doesn't need `Envoy::Logger::Loggable` any - more. The administration interface usage is similar. Please see `Administration interface + `ENVOY_FLUSH_LOG` will use a per-file logger, and the usage doesn't need `Envoy::Logger::Loggable` any + more. The administration interface usage is similar. Please see `Administration interface `_ for more detail. .. option:: --socket-path diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 3a4dd5dffb4c..7ad37b2efed6 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -55,6 +55,7 @@ New Features * http: added frame flood and abuse checks to the upstream HTTP/2 codec. This check is off by default and can be enabled by setting the `envoy.reloadable_features.upstream_http2_flood_checks` runtime key to true. * jwt_authn: added support for :ref:`per-route config `. * listener: added an optional :ref:`default filter chain `. If this field is supplied, and none of the :ref:`filter_chains ` matches, this default filter chain is used to serve the connection. +* log: added a new custom flag ``%_`` to the log pattern to print the actual message to log, but with escaped newlines. * lua: added `downstreamDirectRemoteAddress()` and `downstreamLocalAddress()` APIs to :ref:`streamInfo() `. * mongo_proxy: the list of commands to produce metrics for is now :ref:`configurable `. * network: added a :ref:`timeout ` for incoming connections completing transport-level negotiation, including TLS and ALTS hanshakes. diff --git a/source/common/common/logger.cc b/source/common/common/logger.cc index e0f79783f88f..c20dbcecbe84 100644 --- a/source/common/common/logger.cc +++ b/source/common/common/logger.cc @@ -202,7 +202,12 @@ void Registry::setLogLevel(spdlog::level::level_enum log_level) { void Registry::setLogFormat(const std::string& log_format) { for (Logger& logger : allLoggers()) { - logger.logger_->set_pattern(log_format); + auto formatter = std::make_unique(); + formatter + ->add_flag( + CustomFlagFormatter::EscapeMessageNewLine::Placeholder) + .set_pattern(log_format); + logger.logger_->set_formatter(std::move(formatter)); } } @@ -217,5 +222,15 @@ Logger* Registry::logger(const std::string& log_name) { return logger_to_return; } +namespace CustomFlagFormatter { + +void EscapeMessageNewLine::format(const spdlog::details::log_msg& msg, const std::tm&, + spdlog::memory_buf_t& dest) { + auto escaped = absl::StrReplaceAll(absl::string_view(msg.payload.data(), msg.payload.size()), + replacements()); + dest.append(escaped.data(), escaped.data() + escaped.size()); +} + +} // namespace CustomFlagFormatter } // namespace Logger } // namespace Envoy diff --git a/source/common/common/logger.h b/source/common/common/logger.h index c22b29b873db..72a919bf4d4d 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -329,6 +329,33 @@ template class Loggable { } }; +// Contains custom flags to introduce user defined flags in log pattern. Reference: +// https://github.com/gabime/spdlog#user-defined-flags-in-the-log-pattern. +namespace CustomFlagFormatter { + +/** + * When added to a formatter, this adds '_' as a user defined flag in the log pattern that escapes + * newlines. + */ +class EscapeMessageNewLine : public spdlog::custom_flag_formatter { +public: + void format(const spdlog::details::log_msg& msg, const std::tm& tm, + spdlog::memory_buf_t& dest) override; + + std::unique_ptr clone() const override { + return spdlog::details::make_unique(); + } + + constexpr static char Placeholder = '_'; + +private: + using ReplacementMap = absl::flat_hash_map; + const static ReplacementMap& replacements() { + CONSTRUCT_ON_FIRST_USE(ReplacementMap, ReplacementMap{{"\n", "\\n"}}); + } +}; + +} // namespace CustomFlagFormatter } // namespace Logger /** diff --git a/test/common/common/logger_test.cc b/test/common/common/logger_test.cc index 8e4d8839852d..dc1ec0ba110a 100644 --- a/test/common/common/logger_test.cc +++ b/test/common/common/logger_test.cc @@ -1,3 +1,4 @@ +#include #include #include "common/common/logger.h" @@ -43,5 +44,42 @@ TEST(LoggerEscapeTest, WhitespaceOnly) { TEST(LoggerEscapeTest, Empty) { EXPECT_EQ("", DelegatingLogSink::escapeLogLine("")); } +class LoggerCustomFlagsTest : public testing::Test { +public: + LoggerCustomFlagsTest() : logger_(Registry::getSink()) {} + + void expectLogMessage(const std::string& pattern, const std::string& message, + const std::string& expected) { + auto formatter = std::make_unique(); + formatter + ->add_flag( + CustomFlagFormatter::EscapeMessageNewLine::Placeholder) + .set_pattern(pattern); + logger_->set_formatter(std::move(formatter)); + + testing::internal::CaptureStderr(); + logger_->log(spdlog::details::log_msg("test", spdlog::level::info, message)); +#ifdef WIN32 + EXPECT_EQ(expected + "\r\n", testing::internal::GetCapturedStderr()); +#else + EXPECT_EQ(expected + "\n", testing::internal::GetCapturedStderr()); +#endif + } + +protected: + DelegatingLogSinkSharedPtr logger_; +}; + +TEST_F(LoggerCustomFlagsTest, LogMessageAsIs) { + // This uses "%v", the default flag for printing the actual text to log. + // https://github.com/gabime/spdlog/wiki/3.-Custom-formatting#pattern-flags. + expectLogMessage("%v", "\n\nmessage\n\n", "\n\nmessage\n\n"); +} + +TEST_F(LoggerCustomFlagsTest, LogMessageAsEscaped) { + // This uses "%_", the added custom flag that escapes newlines from the actual text to log. + expectLogMessage("%_", "\n\nmessage\n\n", "\\n\\nmessage\\n\\n"); +} + } // namespace Logger } // namespace Envoy From 9270506f9f2b6786c21719bed3a97a6e0e71a760 Mon Sep 17 00:00:00 2001 From: Wayne Zhang Date: Thu, 12 Nov 2020 12:50:39 -0800 Subject: [PATCH 087/117] jwt_authn: change allow_missing implementation under RequiresAny (#13839) Signed-off-by: Wayne Zhang --- .../filters/http/jwt_authn/verifier.cc | 129 ++++++++++-------- .../http/jwt_authn/all_verifier_test.cc | 97 ++++++++++--- .../http/jwt_authn/group_verifier_test.cc | 54 ++++++-- 3 files changed, 193 insertions(+), 87 deletions(-) diff --git a/source/extensions/filters/http/jwt_authn/verifier.cc b/source/extensions/filters/http/jwt_authn/verifier.cc index e8b613911e8d..cb920c1a5477 100644 --- a/source/extensions/filters/http/jwt_authn/verifier.cc +++ b/source/extensions/filters/http/jwt_authn/verifier.cc @@ -26,7 +26,7 @@ struct CompletionState { // number of completed inner verifier for an any/all verifier. std::size_t number_completed_children_{0}; // A valid error for a RequireAny - Status any_valid_error_{Status::Ok}; + Status status_; }; class ContextImpl : public Verifier::Context { @@ -82,6 +82,8 @@ class BaseVerifierImpl : public Logger::Loggable, public Verifi void completeWithStatus(Status status, ContextImpl& context) const { if (parent_ != nullptr) { + auto& completion_state = context.getCompletionState(this); + completion_state.status_ = status; return parent_->onComplete(status, context); } @@ -113,7 +115,7 @@ class ProviderVerifierImpl : public BaseVerifierImpl { ProviderVerifierImpl(const std::string& provider_name, const AuthFactory& factory, const JwtProvider& provider, const BaseVerifierImpl* parent) : BaseVerifierImpl(parent), auth_factory_(factory), extractor_(Extractor::create(provider)), - provider_name_(absl::make_optional(provider_name)) {} + provider_name_(provider_name) {} void verify(ContextSharedPtr context) const override { auto& ctximpl = static_cast(*context); @@ -140,7 +142,7 @@ class ProviderVerifierImpl : public BaseVerifierImpl { private: const AuthFactory& auth_factory_; const ExtractorConstPtr extractor_; - const absl::optional provider_name_; + const std::string provider_name_; }; class ProviderAndAudienceVerifierImpl : public ProviderVerifierImpl { @@ -227,9 +229,7 @@ class AllowMissingVerifierImpl : public BaseVerifierImpl { VerifierConstPtr innerCreate(const JwtRequirement& requirement, const Protobuf::Map& providers, - const AuthFactory& factory, - const std::vector parent_provider_names, - const BaseVerifierImpl* parent); + const AuthFactory& factory, const BaseVerifierImpl* parent); // Base verifier for requires all or any. class BaseGroupVerifierImpl : public BaseVerifierImpl { @@ -258,39 +258,30 @@ class AnyVerifierImpl : public BaseGroupVerifierImpl { const Protobuf::Map& providers, const BaseVerifierImpl* parent) : BaseGroupVerifierImpl(parent) { - const JwtRequirement* by_pass_type_requirement = nullptr; - std::vector used_providers; + for (const auto& it : or_list.requirements()) { - bool is_regular_requirement = true; switch (it.requires_type_case()) { - case JwtRequirement::RequiresTypeCase::kProviderName: - used_providers.emplace_back(it.provider_name()); - break; - case JwtRequirement::RequiresTypeCase::kProviderAndAudiences: - used_providers.emplace_back(it.provider_and_audiences().provider_name()); - break; case JwtRequirement::RequiresTypeCase::kAllowMissingOrFailed: + is_allow_missing_or_failed_ = true; + break; case JwtRequirement::RequiresTypeCase::kAllowMissing: - is_regular_requirement = false; - if (by_pass_type_requirement == nullptr || - by_pass_type_requirement->requires_type_case() == - JwtRequirement::RequiresTypeCase::kAllowMissing) { - // We need to keep only one by_pass_type_requirement. If both - // kAllowMissing and kAllowMissingOrFailed are set, use - // kAllowMissingOrFailed. - by_pass_type_requirement = ⁢ - } + is_allow_missing_ = true; + break; default: + verifiers_.emplace_back(innerCreate(it, providers, factory, this)); break; } - if (is_regular_requirement) { - verifiers_.emplace_back( - innerCreate(it, providers, factory, std::vector{}, this)); - } } - if (by_pass_type_requirement) { - verifiers_.emplace_back( - innerCreate(*by_pass_type_requirement, providers, factory, used_providers, this)); + + // RequiresAny only has one missing or failed requirement. + if (verifiers_.empty() && (is_allow_missing_or_failed_ || is_allow_missing_)) { + JwtRequirement requirement; + if (is_allow_missing_or_failed_) { + requirement.mutable_allow_missing_or_failed(); + } else { + requirement.mutable_allow_missing(); + } + verifiers_.emplace_back(innerCreate(requirement, providers, factory, this)); } } @@ -299,22 +290,45 @@ class AnyVerifierImpl : public BaseGroupVerifierImpl { if (completion_state.is_completed_) { return; } - // For RequireAny: usually it returns the error from the last provider. - // But if a Jwt is not for a provider, its auth returns JwtMissed or JwtUnknownIssuer. - // Such error should not be used as the final error if there are other valid errors. - if (status != Status::Ok && status != Status::JwtMissed && status != Status::JwtUnknownIssuer) { - completion_state.any_valid_error_ = status; - } - if (++completion_state.number_completed_children_ == verifiers_.size() || - Status::Ok == status) { + + // If any of children is OK, return OK + if (Status::Ok == status) { completion_state.is_completed_ = true; - Status final_status = status; - if (status != Status::Ok && completion_state.any_valid_error_ != Status::Ok) { - final_status = completion_state.any_valid_error_; + completeWithStatus(status, context); + return; + } + + // Then wait for all children to be done. + if (++completion_state.number_completed_children_ == verifiers_.size()) { + // Aggregate all children status into a final status. + // JwtMissing should be treated differently than other failure status + // since it simply means there is not Jwt token for the required provider. + // If there is a failure status other than JwtMissing in the children, + // it should be used as the final status. + Status final_status = Status::JwtMissed; + for (const auto& it : verifiers_) { + // If a Jwt is extracted from a location not specified by the required provider, + // the authenticator returns JwtUnknownIssuer. It should be treated the same as + // JwtMissed. + Status child_status = context.getCompletionState(it.get()).status_; + if (child_status != Status::JwtMissed && child_status != Status::JwtUnknownIssuer) { + final_status = child_status; + } } + + if (is_allow_missing_or_failed_) { + final_status = Status::Ok; + } else if (is_allow_missing_ && final_status == Status::JwtMissed) { + final_status = Status::Ok; + } + completion_state.is_completed_ = true; completeWithStatus(final_status, context); } } + +private: + bool is_allow_missing_or_failed_{false}; + bool is_allow_missing_{false}; }; // Requires all verifier @@ -326,8 +340,7 @@ class AllVerifierImpl : public BaseGroupVerifierImpl { const BaseVerifierImpl* parent) : BaseGroupVerifierImpl(parent) { for (const auto& it : and_list.requirements()) { - verifiers_.emplace_back( - innerCreate(it, providers, factory, std::vector{}, this)); + verifiers_.emplace_back(innerCreate(it, providers, factory, this)); } } @@ -354,21 +367,19 @@ class AllowAllVerifierImpl : public BaseVerifierImpl { } }; +JwtProviderList getAllProvidersAsList(const Protobuf::Map& providers) { + JwtProviderList list; + for (const auto& it : providers) { + list.emplace_back(&it.second); + } + return list; +} + VerifierConstPtr innerCreate(const JwtRequirement& requirement, const Protobuf::Map& providers, - const AuthFactory& factory, - const std::vector parent_provider_names, - const BaseVerifierImpl* parent) { + const AuthFactory& factory, const BaseVerifierImpl* parent) { std::string provider_name; std::vector audiences; - JwtProviderList parent_providers; - for (const auto& name : parent_provider_names) { - const auto& it = providers.find(name); - if (it == providers.end()) { - throw EnvoyException(fmt::format("Required provider ['{}'] is not configured.", name)); - } - parent_providers.emplace_back(&it->second); - } switch (requirement.requires_type_case()) { case JwtRequirement::RequiresTypeCase::kProviderName: provider_name = requirement.provider_name(); @@ -386,9 +397,11 @@ VerifierConstPtr innerCreate(const JwtRequirement& requirement, return std::make_unique(requirement.requires_all(), factory, providers, parent); case JwtRequirement::RequiresTypeCase::kAllowMissingOrFailed: - return std::make_unique(factory, parent_providers, parent); + return std::make_unique(factory, getAllProvidersAsList(providers), + parent); case JwtRequirement::RequiresTypeCase::kAllowMissing: - return std::make_unique(factory, parent_providers, parent); + return std::make_unique(factory, getAllProvidersAsList(providers), + parent); case JwtRequirement::RequiresTypeCase::REQUIRES_TYPE_NOT_SET: return std::make_unique(parent); default: @@ -416,7 +429,7 @@ ContextSharedPtr Verifier::createContext(Http::RequestHeaderMap& headers, VerifierConstPtr Verifier::create(const JwtRequirement& requirement, const Protobuf::Map& providers, const AuthFactory& factory) { - return innerCreate(requirement, providers, factory, std::vector{}, nullptr); + return innerCreate(requirement, providers, factory, nullptr); } } // namespace JwtAuthn diff --git a/test/extensions/filters/http/jwt_authn/all_verifier_test.cc b/test/extensions/filters/http/jwt_authn/all_verifier_test.cc index 14f6c0d67d12..2afa53ed0c98 100644 --- a/test/extensions/filters/http/jwt_authn/all_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/all_verifier_test.cc @@ -29,6 +29,7 @@ constexpr char kConfigTemplate[] = R"( - name: "x-example" value_prefix: "" forward_payload_header: "x-example-payload" + forward: true local_jwks: inline_string: "" other_provider: @@ -37,6 +38,7 @@ constexpr char kConfigTemplate[] = R"( - name: "x-other" value_prefix: "" forward_payload_header: "x-other-payload" + forward: true local_jwks: inline_string: "" rules: @@ -47,20 +49,18 @@ constexpr char kConfigTemplate[] = R"( constexpr char kExampleHeader[] = "x-example"; constexpr char kOtherHeader[] = "x-other"; -// Returns true if the jwt_header is empty, and the jwt_header payload exists. -// Based on the JWT provider setup for this test, this matcher is equivalent to JWT verification -// was success. +// Returns true if jwt_header payload exists. +// Payload is added only after verification was success. MATCHER_P(JwtOutputSuccess, jwt_header, "") { auto payload_header = absl::StrCat(jwt_header, "-payload"); - return !arg.has(std::string(jwt_header)) && arg.has(payload_header); + return arg.has(payload_header); } -// Returns true if the jwt_header exists, and the jwt_header payload is empty. -// Based on the JWT provider setup for this test, this matcher is equivalent to JWT verification -// was failed. +// Returns true if the jwt_header payload is empty. +// Payload is added only after verification was success. MATCHER_P(JwtOutputFailedOrIgnore, jwt_header, "") { auto payload_header = absl::StrCat(jwt_header, "-payload"); - return arg.has(std::string(jwt_header)) && !arg.has(payload_header); + return !arg.has(payload_header); } class AllVerifierTest : public testing::Test { @@ -143,9 +143,7 @@ TEST_F(AllowFailedInSingleRequirementTest, OneGoodJwt) { auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}}; context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); - // As requirement has nothing except allow_missing_or_failed, it will - // not try to check any token. - EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kExampleHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); } TEST_F(AllowFailedInSingleRequirementTest, TwoGoodJwts) { @@ -154,8 +152,8 @@ TEST_F(AllowFailedInSingleRequirementTest, TwoGoodJwts) { Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, OtherGoodToken}}; context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); - EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kExampleHeader)); - EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kOtherHeader)); } TEST_F(AllowFailedInSingleRequirementTest, GoodAndBadJwts) { @@ -164,7 +162,73 @@ TEST_F(AllowFailedInSingleRequirementTest, GoodAndBadJwts) { Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, ExpiredToken}}; context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); + EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); +} + +// The `allow_missing_or_failed` is defined in an OR-list of requirements by itself. +class SingleAllowMissingInOrListTest : public AllVerifierTest { +protected: + void SetUp() override { + AllVerifierTest::SetUp(); + const char allow_missing_yaml[] = R"( +requires_any: + requirements: + - allow_missing: {} +)"; + modifyRequirement(allow_missing_yaml); + createVerifier(); + } +}; + +TEST_F(SingleAllowMissingInOrListTest, NoJwt) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + auto headers = Http::TestRequestHeaderMapImpl{}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); +} + +TEST_F(SingleAllowMissingInOrListTest, BadJwt) { + EXPECT_CALL(mock_cb_, onComplete(Status::JwtExpired)).Times(1); + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, ExpiredToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kExampleHeader)); +} + +TEST_F(SingleAllowMissingInOrListTest, MissingIssToken) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, ES256WithoutIssToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kExampleHeader)); +} + +TEST_F(SingleAllowMissingInOrListTest, OneGoodJwt) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); +} + +TEST_F(SingleAllowMissingInOrListTest, TwoGoodJwts) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + auto headers = + Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, OtherGoodToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kOtherHeader)); +} + +TEST_F(SingleAllowMissingInOrListTest, GoodAndBadJwts) { + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + auto headers = + Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, ExpiredToken}}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); } @@ -291,8 +355,7 @@ TEST_F(AllowFailedInAndListTest, TwoGoodJwts) { context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); - // The JWT in x-other is ignored. - EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kOtherHeader)); } class AllowFailedInAndOfOrListTest : public AllVerifierTest { @@ -467,7 +530,7 @@ TEST_F(AllowMissingInAndListTest, TwoGoodJwts) { context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); EXPECT_THAT(headers, JwtOutputSuccess(kExampleHeader)); - EXPECT_THAT(headers, JwtOutputFailedOrIgnore(kOtherHeader)); + EXPECT_THAT(headers, JwtOutputSuccess(kOtherHeader)); } class AllowMissingInAndOfOrListTest : public AllVerifierTest { @@ -526,7 +589,7 @@ TEST_F(AllowMissingInAndOfOrListTest, TwoGoodJwts) { } TEST_F(AllowMissingInAndOfOrListTest, GoodAndBadJwts) { - EXPECT_CALL(mock_cb_, onComplete(Status::JwtUnknownIssuer)).Times(1); + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); // Use the token with example.com issuer for x-other. auto headers = Http::TestRequestHeaderMapImpl{{kExampleHeader, GoodToken}, {kOtherHeader, GoodToken}}; diff --git a/test/extensions/filters/http/jwt_authn/group_verifier_test.cc b/test/extensions/filters/http/jwt_authn/group_verifier_test.cc index bd5d5d5ffa93..d9315ce42c70 100644 --- a/test/extensions/filters/http/jwt_authn/group_verifier_test.cc +++ b/test/extensions/filters/http/jwt_authn/group_verifier_test.cc @@ -544,8 +544,8 @@ TEST_F(GroupVerifierTest, TestAllInAnyBothRequireAllAreOk) { callbacks_["provider_4"](Status::Ok); } -// Test require any with additional allow all -TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowAll) { +// Test RequiresAny with two providers and allow_failed +TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowFailed) { TestUtility::loadFromYaml(RequiresAnyConfig, proto_config_); proto_config_.mutable_rules(0) ->mutable_requires() @@ -554,21 +554,51 @@ TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowAll) { ->mutable_allow_missing_or_failed(); createAsyncMockAuthsAndVerifier(std::vector{"example_provider", "other_provider"}); - auto mock_auth = std::make_unique(); - EXPECT_CALL(*mock_auth, doVerify(_, _, _, _, _)) - .WillOnce(Invoke([&](Http::HeaderMap&, Tracing::Span&, std::vector*, - SetPayloadCallback, AuthenticatorCallback callback) { - callbacks_[allowfailed] = std::move(callback); - })); - EXPECT_CALL(*mock_auth, onDestroy()).Times(1); - mock_auths_[allowfailed] = std::move(mock_auth); EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); auto headers = Http::TestRequestHeaderMapImpl{}; context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); verifier_->verify(context_); - callbacks_[allowfailed](Status::Ok); - // with requires any, if any inner verifier returns OK the whole any verifier should return OK. + callbacks_["example_provider"](Status::JwtMissed); + callbacks_["other_provider"](Status::JwtExpired); +} + +// Test RequiresAny with two providers and allow_missing, failed +TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowMissingButFailed) { + TestUtility::loadFromYaml(RequiresAnyConfig, proto_config_); + proto_config_.mutable_rules(0) + ->mutable_requires() + ->mutable_requires_any() + ->add_requirements() + ->mutable_allow_missing(); + + createAsyncMockAuthsAndVerifier(std::vector{"example_provider", "other_provider"}); + EXPECT_CALL(mock_cb_, onComplete(Status::JwtExpired)).Times(1); + + auto headers = Http::TestRequestHeaderMapImpl{}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + callbacks_["example_provider"](Status::JwtMissed); + callbacks_["other_provider"](Status::JwtExpired); +} + +// Test RequiresAny with two providers and allow_missing, but OK +TEST_F(GroupVerifierTest, TestRequiresAnyWithAllowMissingButOk) { + TestUtility::loadFromYaml(RequiresAnyConfig, proto_config_); + proto_config_.mutable_rules(0) + ->mutable_requires() + ->mutable_requires_any() + ->add_requirements() + ->mutable_allow_missing(); + + createAsyncMockAuthsAndVerifier(std::vector{"example_provider", "other_provider"}); + EXPECT_CALL(mock_cb_, onComplete(Status::Ok)).Times(1); + + auto headers = Http::TestRequestHeaderMapImpl{}; + context_ = Verifier::createContext(headers, parent_span_, &mock_cb_); + verifier_->verify(context_); + callbacks_["example_provider"](Status::JwtMissed); + callbacks_["other_provider"](Status::JwtUnknownIssuer); } } // namespace From 5d7da332aa890de2dd297929dc59d354c65137be Mon Sep 17 00:00:00 2001 From: Dmitry Rozhkov Date: Fri, 13 Nov 2020 01:17:41 +0200 Subject: [PATCH 088/117] memory: enable tcmalloc for aarch64 (#13830) Signed-off-by: Dmitry Rozhkov --- bazel/BUILD | 26 +++++++++++++++++++++++++- bazel/README.md | 2 +- bazel/envoy_internal.bzl | 8 ++++++++ bazel/envoy_library.bzl | 4 ++++ bazel/repository_locations.bzl | 12 ++++++------ docs/root/version_history/current.rst | 1 + 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/bazel/BUILD b/bazel/BUILD index e4def8093f52..268cdb752ec6 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -188,7 +188,7 @@ config_setting( ) # As select() can't be nested we need these specialized settings to avoid ambiguity when choosing -# tcmalloc's flavor for x86_64 builds. +# tcmalloc's flavor for x86_64 and aarch64 builds. config_setting( name = "disable_tcmalloc_on_linux_x86_64", values = { @@ -213,6 +213,30 @@ config_setting( }, ) +config_setting( + name = "disable_tcmalloc_on_linux_aarch64", + values = { + "define": "tcmalloc=disabled", + "cpu": "aarch64", + }, +) + +config_setting( + name = "gperftools_tcmalloc_on_linux_aarch64", + values = { + "define": "tcmalloc=gperftools", + "cpu": "aarch64", + }, +) + +config_setting( + name = "debug_tcmalloc_on_linux_aarch64", + values = { + "define": "tcmalloc=debug", + "cpu": "aarch64", + }, +) + config_setting( name = "disable_signal_trace", values = {"define": "signal_trace=disabled"}, diff --git a/bazel/README.md b/bazel/README.md index 6980607d8c88..f2b79eab5c00 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -620,7 +620,7 @@ The following optional features can be disabled on the Bazel build command-line: * Backtracing on signals with `--define signal_trace=disabled` * Active stream state dump on signals with `--define signal_trace=disabled` or `--define disable_object_dump_on_signal_trace=disabled` * tcmalloc with `--define tcmalloc=disabled`. Also you can choose Gperftools' implementation of - tcmalloc with `--define tcmalloc=gperftools` which is the default for non-x86 builds. + tcmalloc with `--define tcmalloc=gperftools` which is the default for builds other than x86_64 and aarch64. * deprecated features with `--define deprecated_features=disabled` diff --git a/bazel/envoy_internal.bzl b/bazel/envoy_internal.bzl index 4e4694f03cdd..91d7ac2abee8 100644 --- a/bazel/envoy_internal.bzl +++ b/bazel/envoy_internal.bzl @@ -70,11 +70,15 @@ def envoy_copts(repository, test = False): }) + select({ repository + "//bazel:disable_tcmalloc": ["-DABSL_MALLOC_HOOK_MMAP_DISABLE"], repository + "//bazel:disable_tcmalloc_on_linux_x86_64": ["-DABSL_MALLOC_HOOK_MMAP_DISABLE"], + repository + "//bazel:disable_tcmalloc_on_linux_aarch64": ["-DABSL_MALLOC_HOOK_MMAP_DISABLE"], repository + "//bazel:gperftools_tcmalloc": ["-DGPERFTOOLS_TCMALLOC"], repository + "//bazel:gperftools_tcmalloc_on_linux_x86_64": ["-DGPERFTOOLS_TCMALLOC"], + repository + "//bazel:gperftools_tcmalloc_on_linux_aarch64": ["-DGPERFTOOLS_TCMALLOC"], repository + "//bazel:debug_tcmalloc": ["-DENVOY_MEMORY_DEBUG_ENABLED=1", "-DGPERFTOOLS_TCMALLOC"], repository + "//bazel:debug_tcmalloc_on_linux_x86_64": ["-DENVOY_MEMORY_DEBUG_ENABLED=1", "-DGPERFTOOLS_TCMALLOC"], + repository + "//bazel:debug_tcmalloc_on_linux_aarch64": ["-DENVOY_MEMORY_DEBUG_ENABLED=1", "-DGPERFTOOLS_TCMALLOC"], repository + "//bazel:linux_x86_64": ["-DTCMALLOC"], + repository + "//bazel:linux_aarch64": ["-DTCMALLOC"], "//conditions:default": ["-DGPERFTOOLS_TCMALLOC"], }) + select({ repository + "//bazel:disable_signal_trace": [], @@ -131,11 +135,15 @@ def tcmalloc_external_dep(repository): return select({ repository + "//bazel:disable_tcmalloc": None, repository + "//bazel:disable_tcmalloc_on_linux_x86_64": None, + repository + "//bazel:disable_tcmalloc_on_linux_aarch64": None, repository + "//bazel:debug_tcmalloc": envoy_external_dep_path("gperftools"), repository + "//bazel:debug_tcmalloc_on_linux_x86_64": envoy_external_dep_path("gperftools"), + repository + "//bazel:debug_tcmalloc_on_linux_aarch64": envoy_external_dep_path("gperftools"), repository + "//bazel:gperftools_tcmalloc": envoy_external_dep_path("gperftools"), repository + "//bazel:gperftools_tcmalloc_on_linux_x86_64": envoy_external_dep_path("gperftools"), + repository + "//bazel:gperftools_tcmalloc_on_linux_aarch64": envoy_external_dep_path("gperftools"), repository + "//bazel:linux_x86_64": envoy_external_dep_path("tcmalloc"), + repository + "//bazel:linux_aarch64": envoy_external_dep_path("tcmalloc"), "//conditions:default": envoy_external_dep_path("gperftools"), }) diff --git a/bazel/envoy_library.bzl b/bazel/envoy_library.bzl index 5eb90df500c0..adcee0750790 100644 --- a/bazel/envoy_library.bzl +++ b/bazel/envoy_library.bzl @@ -21,11 +21,15 @@ def tcmalloc_external_deps(repository): return select({ repository + "//bazel:disable_tcmalloc": [], repository + "//bazel:disable_tcmalloc_on_linux_x86_64": [], + repository + "//bazel:disable_tcmalloc_on_linux_aarch64": [], repository + "//bazel:debug_tcmalloc": [envoy_external_dep_path("gperftools")], repository + "//bazel:debug_tcmalloc_on_linux_x86_64": [envoy_external_dep_path("gperftools")], + repository + "//bazel:debug_tcmalloc_on_linux_aarch64": [envoy_external_dep_path("gperftools")], repository + "//bazel:gperftools_tcmalloc": [envoy_external_dep_path("gperftools")], repository + "//bazel:gperftools_tcmalloc_on_linux_x86_64": [envoy_external_dep_path("gperftools")], + repository + "//bazel:gperftools_tcmalloc_on_linux_aarch64": [envoy_external_dep_path("gperftools")], repository + "//bazel:linux_x86_64": [envoy_external_dep_path("tcmalloc")], + repository + "//bazel:linux_aarch64": [envoy_external_dep_path("tcmalloc")], "//conditions:default": [envoy_external_dep_path("gperftools")], }) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 7607f13a46e3..734a5c8f175d 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -89,12 +89,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Abseil", project_desc = "Open source collection of C++ libraries drawn from the most fundamental pieces of Google’s internal codebase", project_url = "https://abseil.io/", - version = "093cc27604df1c4a179b73bc3f00d4d1ce2ce113", - sha256 = "55d33c75aff05a8c4a55bdf0eddad66c71a963107bc2add96cf8eb88ddb47a80", + version = "8f1c34a77a2ba04512b7f9cbc6013d405e6a0b31", + sha256 = "635367c5cac4bbab95d0485ba9e68fa422546b06ce050190c99be7e23aba3ce3", strip_prefix = "abseil-cpp-{version}", urls = ["https://github.com/abseil/abseil-cpp/archive/{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2020-10-01", + release_date = "2020-10-17", cpe = "N/A", ), com_github_c_ares_c_ares = dict( @@ -199,12 +199,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "tcmalloc", project_desc = "Fast, multi-threaded malloc implementation", project_url = "https://github.com/google/tcmalloc", - version = "d1311bf409db47c3441d3de6ea07d768c6551dec", - sha256 = "e22444b6544edd81f11c987dd5e482a2e00bbff717badb388779ca57525dad50", + version = "9f385356c34d4fc11f76a000b609e2b446c20667", + sha256 = "652e48e0b9ef645db04bff8a3d4841c60ce07275f5d98e18e698dc92bd111291", strip_prefix = "tcmalloc-{version}", urls = ["https://github.com/google/tcmalloc/archive/{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2020-09-16", + release_date = "2020-11-04", cpe = "N/A", ), com_github_gperftools_gperftools = dict( diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 7ad37b2efed6..07412294aeb1 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -17,6 +17,7 @@ Minor Behavior Changes * ext_authz filter: disable `envoy.reloadable_features.ext_authz_measure_timeout_on_check_created` by default. * ext_authz filter: the deprecated field :ref:`use_alpha ` is no longer supported and cannot be set anymore. * grpc_web filter: if a `grpc-accept-encoding` header is present it's passed as-is to the upstream and if it isn't `grpc-accept-encoding:identity` is sent instead. The header was always overwriten with `grpc-accept-encoding:identity,deflate,gzip` before. +* memory: enable new tcmalloc with restartable sequences for aarch64 builds. * tls: removed RSA key transport and SHA-1 cipher suites from the client-side defaults. * watchdog: the watchdog action :ref:`abort_action ` is now the default action to terminate the process if watchdog kill / multikill is enabled. * xds: to support TTLs, heartbeating has been added to xDS. As a result, responses that contain empty resources without updating the version will no longer be propagated to the From bfd366c15b44ec3d0c92bc9d74602343dfb5ee70 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Thu, 12 Nov 2020 15:27:03 -0800 Subject: [PATCH 089/117] api: Add boolean do_not_cache field to Resource message. (#13969) Signed-off-by: Mark D. Roth --- api/envoy/service/discovery/v3/discovery.proto | 15 ++++++++++++++- .../service/discovery/v4alpha/discovery.proto | 18 +++++++++++++++++- .../envoy/service/discovery/v3/discovery.proto | 15 ++++++++++++++- .../service/discovery/v4alpha/discovery.proto | 18 +++++++++++++++++- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/api/envoy/service/discovery/v3/discovery.proto b/api/envoy/service/discovery/v3/discovery.proto index c63d9e74355f..49bb7a931ef5 100644 --- a/api/envoy/service/discovery/v3/discovery.proto +++ b/api/envoy/service/discovery/v3/discovery.proto @@ -253,10 +253,19 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 7] +// [#next-free-field: 8] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Resource"; + // Cache control properties for the resource. + // [#not-implemented-hide:] + message CacheControl { + // If true, xDS proxies may not cache this resource. + // Note that this does not apply to clients other than xDS proxies, which must cache resources + // for their own use, regardless of the value of this field. + bool do_not_cache = 1; + } + // The resource's name, to distinguish it from others of the same type of resource. string name = 3 [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; @@ -288,4 +297,8 @@ message Resource { // testing where the fault injection should be terminated in the event that Envoy loses contact // with the management server. google.protobuf.Duration ttl = 6; + + // Cache control properties for the resource. + // [#not-implemented-hide:] + CacheControl cache_control = 7; } diff --git a/api/envoy/service/discovery/v4alpha/discovery.proto b/api/envoy/service/discovery/v4alpha/discovery.proto index 45f9a524db04..e6d15a5d8f93 100644 --- a/api/envoy/service/discovery/v4alpha/discovery.proto +++ b/api/envoy/service/discovery/v4alpha/discovery.proto @@ -255,11 +255,23 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 7] +// [#next-free-field: 8] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.service.discovery.v3.Resource"; + // Cache control properties for the resource. + // [#not-implemented-hide:] + message CacheControl { + option (udpa.annotations.versioning).previous_message_type = + "envoy.service.discovery.v3.Resource.CacheControl"; + + // If true, xDS proxies may not cache this resource. + // Note that this does not apply to clients other than xDS proxies, which must cache resources + // for their own use, regardless of the value of this field. + bool do_not_cache = 1; + } + oneof name_specifier { // The resource's name, to distinguish it from others of the same type of resource. string name = 3; @@ -292,4 +304,8 @@ message Resource { // testing where the fault injection should be terminated in the event that Envoy loses contact // with the management server. google.protobuf.Duration ttl = 6; + + // Cache control properties for the resource. + // [#not-implemented-hide:] + CacheControl cache_control = 7; } diff --git a/generated_api_shadow/envoy/service/discovery/v3/discovery.proto b/generated_api_shadow/envoy/service/discovery/v3/discovery.proto index c63d9e74355f..49bb7a931ef5 100644 --- a/generated_api_shadow/envoy/service/discovery/v3/discovery.proto +++ b/generated_api_shadow/envoy/service/discovery/v3/discovery.proto @@ -253,10 +253,19 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 7] +// [#next-free-field: 8] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Resource"; + // Cache control properties for the resource. + // [#not-implemented-hide:] + message CacheControl { + // If true, xDS proxies may not cache this resource. + // Note that this does not apply to clients other than xDS proxies, which must cache resources + // for their own use, regardless of the value of this field. + bool do_not_cache = 1; + } + // The resource's name, to distinguish it from others of the same type of resource. string name = 3 [(udpa.annotations.field_migrate).oneof_promotion = "name_specifier"]; @@ -288,4 +297,8 @@ message Resource { // testing where the fault injection should be terminated in the event that Envoy loses contact // with the management server. google.protobuf.Duration ttl = 6; + + // Cache control properties for the resource. + // [#not-implemented-hide:] + CacheControl cache_control = 7; } diff --git a/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto b/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto index 45f9a524db04..e6d15a5d8f93 100644 --- a/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto +++ b/generated_api_shadow/envoy/service/discovery/v4alpha/discovery.proto @@ -255,11 +255,23 @@ message DeltaDiscoveryResponse { string nonce = 5; } -// [#next-free-field: 7] +// [#next-free-field: 8] message Resource { option (udpa.annotations.versioning).previous_message_type = "envoy.service.discovery.v3.Resource"; + // Cache control properties for the resource. + // [#not-implemented-hide:] + message CacheControl { + option (udpa.annotations.versioning).previous_message_type = + "envoy.service.discovery.v3.Resource.CacheControl"; + + // If true, xDS proxies may not cache this resource. + // Note that this does not apply to clients other than xDS proxies, which must cache resources + // for their own use, regardless of the value of this field. + bool do_not_cache = 1; + } + oneof name_specifier { // The resource's name, to distinguish it from others of the same type of resource. string name = 3; @@ -292,4 +304,8 @@ message Resource { // testing where the fault injection should be terminated in the event that Envoy loses contact // with the management server. google.protobuf.Duration ttl = 6; + + // Cache control properties for the resource. + // [#not-implemented-hide:] + CacheControl cache_control = 7; } From 2a252964ecd5e790aaac3a4d08d43bd3cad4b6c8 Mon Sep 17 00:00:00 2001 From: Kuat Date: Thu, 12 Nov 2020 19:00:13 -0800 Subject: [PATCH 090/117] deps: update cel-cpp (#13984) Commit Message: Update cel-cpp. The protobuf fix for CEL will be released with 3.14, and cel-cpp will be updated then. Additional Description: Risk Level: low Testing: Docs Changes: Release Notes: Platform Specific Features: Signed-off-by: Kuat Yessenov --- bazel/cel-cpp.patch | 6 ++--- bazel/repository_locations.bzl | 6 ++--- source/extensions/common/wasm/BUILD | 8 +++--- source/extensions/common/wasm/context.cc | 25 +++++++++++-------- source/extensions/common/wasm/wasm_state.cc | 3 ++- source/extensions/filters/common/expr/BUILD | 1 + .../extensions/filters/common/expr/context.h | 4 ++- 7 files changed, 31 insertions(+), 22 deletions(-) diff --git a/bazel/cel-cpp.patch b/bazel/cel-cpp.patch index aa8d795b1410..aee357153fdf 100644 --- a/bazel/cel-cpp.patch +++ b/bazel/cel-cpp.patch @@ -1,7 +1,7 @@ -diff --git a/eval/eval/field_backed_map_impl.cc b/eval/eval/field_backed_map_impl.cc +diff --git a/eval/public/containers/field_backed_map_impl.cc b/eval/public/containers/field_backed_map_impl.cc index cd56f51..4d2a546 100644 ---- a/eval/eval/field_backed_map_impl.cc -+++ b/eval/eval/field_backed_map_impl.cc +--- a/eval/public/containers/field_backed_map_impl.cc ++++ b/eval/public/containers/field_backed_map_impl.cc @@ -117,7 +117,9 @@ int FieldBackedMapImpl::size() const { const CelList* FieldBackedMapImpl::ListKeys() const { return key_list_.get(); } diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 734a5c8f175d..015e604af0da 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -737,8 +737,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Common Expression Language (CEL) C++ library", project_desc = "Common Expression Language (CEL) C++ library", project_url = "https://opensource.google/projects/cel", - version = "b9453a09b28a1531c4917e8792b3ea61f6b1a447", - sha256 = "cad7d01139947d78e413d112cb8f7431fbb33cf66b0adf9c280824803fc2a72e", + version = "47244a458e7739ad38e178a3f3892d197de4a574", + sha256 = "51b1af23cb703a94d18fe7a5e2696f96cde5bc35a279c6c844e6363aea3982fb", strip_prefix = "cel-cpp-{version}", urls = ["https://github.com/google/cel-cpp/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -751,7 +751,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", ], - release_date = "2020-07-14", + release_date = "2020-10-25", cpe = "N/A", ), com_github_google_flatbuffers = dict( diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index f620531ced2c..e2a03e72fc0f 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -62,6 +62,7 @@ envoy_cc_library( "//source/common/singleton:const_singleton", "@com_github_google_flatbuffers//:flatbuffers", "@com_google_cel_cpp//eval/public:cel_value", + "@com_google_cel_cpp//eval/public/structs:cel_proto_wrapper", "@com_google_cel_cpp//tools:flatbuffers_backed_impl", ], ) @@ -97,9 +98,10 @@ envoy_cc_library( "//source/extensions/common/wasm/ext:declare_property_cc_proto", "//source/extensions/common/wasm/ext:envoy_null_vm_wasm_api", "//source/extensions/filters/common/expr:context_lib", - "@com_google_cel_cpp//eval/eval:field_access", - "@com_google_cel_cpp//eval/eval:field_backed_list_impl", - "@com_google_cel_cpp//eval/eval:field_backed_map_impl", + "@com_google_cel_cpp//eval/public/containers:field_access", + "@com_google_cel_cpp//eval/public/containers:field_backed_list_impl", + "@com_google_cel_cpp//eval/public/containers:field_backed_map_impl", + "@com_google_cel_cpp//eval/public/structs:cel_proto_wrapper", "@com_google_cel_cpp//eval/public:builtin_func_registrar", "@com_google_cel_cpp//eval/public:cel_expr_builder_factory", "@com_google_cel_cpp//eval/public:cel_value", diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index f5d0f3183a18..f38f9a2c3282 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -34,10 +34,11 @@ #include "absl/container/node_hash_map.h" #include "absl/strings/str_cat.h" #include "absl/synchronization/mutex.h" -#include "eval/eval/field_access.h" -#include "eval/eval/field_backed_list_impl.h" -#include "eval/eval/field_backed_map_impl.h" #include "eval/public/cel_value.h" +#include "eval/public/containers/field_access.h" +#include "eval/public/containers/field_backed_list_impl.h" +#include "eval/public/containers/field_backed_map_impl.h" +#include "eval/public/structs/cel_proto_wrapper.h" #include "openssl/bytestring.h" #include "openssl/hmac.h" #include "openssl/sha.h" @@ -421,6 +422,7 @@ static absl::flat_hash_map property_tokens = {PROPER absl::optional Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) const { + using google::api::expr::runtime::CelProtoWrapper; using google::api::expr::runtime::CelValue; const StreamInfo::StreamInfo* info = getConstRequestStreamInfo(); @@ -448,7 +450,7 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co switch (part_token->second) { case PropertyToken::METADATA: if (info) { - return CelValue::CreateMessage(&info->dynamicMetadata(), arena); + return CelProtoWrapper::CreateMessage(&info->dynamicMetadata(), arena); } break; case PropertyToken::REQUEST: @@ -485,9 +487,9 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co break; case PropertyToken::NODE: if (root_local_info_) { - return CelValue::CreateMessage(&root_local_info_->node(), arena); + return CelProtoWrapper::CreateMessage(&root_local_info_->node(), arena); } else if (plugin_) { - return CelValue::CreateMessage(&plugin()->local_info_.node(), arena); + return CelProtoWrapper::CreateMessage(&plugin()->local_info_.node(), arena); } break; case PropertyToken::SOURCE: @@ -509,7 +511,7 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co break; case PropertyToken::LISTENER_METADATA: if (plugin_) { - return CelValue::CreateMessage(plugin()->listener_metadata_, arena); + return CelProtoWrapper::CreateMessage(plugin()->listener_metadata_, arena); } break; case PropertyToken::CLUSTER_NAME: @@ -524,15 +526,16 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co break; case PropertyToken::CLUSTER_METADATA: if (info && info->upstreamHost()) { - return CelValue::CreateMessage(&info->upstreamHost()->cluster().metadata(), arena); + return CelProtoWrapper::CreateMessage(&info->upstreamHost()->cluster().metadata(), arena); } else if (info && info->upstreamClusterInfo().has_value() && info->upstreamClusterInfo().value()) { - return CelValue::CreateMessage(&info->upstreamClusterInfo().value()->metadata(), arena); + return CelProtoWrapper::CreateMessage(&info->upstreamClusterInfo().value()->metadata(), + arena); } break; case PropertyToken::UPSTREAM_HOST_METADATA: if (info && info->upstreamHost() && info->upstreamHost()->metadata()) { - return CelValue::CreateMessage(info->upstreamHost()->metadata().get(), arena); + return CelProtoWrapper::CreateMessage(info->upstreamHost()->metadata().get(), arena); } break; case PropertyToken::ROUTE_NAME: @@ -542,7 +545,7 @@ Context::findValue(absl::string_view name, Protobuf::Arena* arena, bool last) co break; case PropertyToken::ROUTE_METADATA: if (info && info->routeEntry()) { - return CelValue::CreateMessage(&info->routeEntry()->metadata(), arena); + return CelProtoWrapper::CreateMessage(&info->routeEntry()->metadata(), arena); } break; case PropertyToken::PLUGIN_NAME: diff --git a/source/extensions/common/wasm/wasm_state.cc b/source/extensions/common/wasm/wasm_state.cc index 573523f1d83e..7062afa0ab9d 100644 --- a/source/extensions/common/wasm/wasm_state.cc +++ b/source/extensions/common/wasm/wasm_state.cc @@ -1,5 +1,6 @@ #include "extensions/common/wasm/wasm_state.h" +#include "eval/public/structs/cel_proto_wrapper.h" #include "flatbuffers/reflection.h" #include "tools/flatbuffers_backed_impl.h" @@ -23,7 +24,7 @@ CelValue WasmState::exprValue(Protobuf::Arena* arena, bool last) const { } // Note that this is very expensive since it incurs a de-serialization const auto any = serializeAsProto(); - return CelValue::CreateMessage(any.get(), arena); + return google::api::expr::runtime::CelProtoWrapper::CreateMessage(any.get(), arena); } case WasmType::FlatBuffers: if (last) { diff --git a/source/extensions/filters/common/expr/BUILD b/source/extensions/filters/common/expr/BUILD index fbbcd725ba43..2e51de38daaa 100644 --- a/source/extensions/filters/common/expr/BUILD +++ b/source/extensions/filters/common/expr/BUILD @@ -34,6 +34,7 @@ envoy_cc_library( "//source/common/stream_info:utility_lib", "@com_google_cel_cpp//eval/public:cel_value", "@com_google_cel_cpp//eval/public:cel_value_producer", + "@com_google_cel_cpp//eval/public/structs:cel_proto_wrapper", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/common/expr/context.h b/source/extensions/filters/common/expr/context.h index 2faf80b0fd8f..ad6fcb972160 100644 --- a/source/extensions/filters/common/expr/context.h +++ b/source/extensions/filters/common/expr/context.h @@ -9,6 +9,7 @@ #include "eval/public/cel_value.h" #include "eval/public/cel_value_producer.h" +#include "eval/public/structs/cel_proto_wrapper.h" namespace Envoy { namespace Extensions { @@ -17,6 +18,7 @@ namespace Common { namespace Expr { using CelValue = google::api::expr::runtime::CelValue; +using CelProtoWrapper = google::api::expr::runtime::CelProtoWrapper; // Symbols for traversing the request properties constexpr absl::string_view Request = "request"; @@ -188,7 +190,7 @@ class MetadataProducer : public google::api::expr::runtime::CelValueProducer { public: MetadataProducer(const envoy::config::core::v3::Metadata& metadata) : metadata_(metadata) {} CelValue Produce(ProtobufWkt::Arena* arena) override { - return CelValue::CreateMessage(&metadata_, arena); + return CelProtoWrapper::CreateMessage(&metadata_, arena); } private: From 488973532ac72f56473bd2b6f770f0a011bf14a1 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Fri, 13 Nov 2020 09:02:41 -0800 Subject: [PATCH 091/117] wasm: fix order of callbacks for paused requests. (#13840) Fixes proxy-wasm/proxy-wasm-rust-sdk#43. Signed-off-by: Piotr Sikora --- bazel/external/cargo/Cargo.toml | 5 ++ bazel/repository_locations.bzl | 6 +- source/extensions/common/wasm/context.cc | 6 +- test/extensions/filters/http/wasm/BUILD | 1 + .../filters/http/wasm/test_data/BUILD | 11 +++ .../http/wasm/test_data/resume_call_rust.rs | 39 ++++++++++ .../wasm/test_data/test_resume_call_cpp.cc | 53 +++++++++++++ .../filters/http/wasm/wasm_filter_test.cc | 78 +++++++++++++++---- 8 files changed, 180 insertions(+), 19 deletions(-) create mode 100644 test/extensions/filters/http/wasm/test_data/resume_call_rust.rs create mode 100644 test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc diff --git a/bazel/external/cargo/Cargo.toml b/bazel/external/cargo/Cargo.toml index f56a3f47ad31..610d35df4e60 100644 --- a/bazel/external/cargo/Cargo.toml +++ b/bazel/external/cargo/Cargo.toml @@ -46,6 +46,11 @@ name = "http_metadata_rust" path = "../../../test/extensions/filters/http/wasm/test_data/metadata_rust.rs" crate-type = ["cdylib"] +[[example]] +name = "http_resume_call_rust" +path = "../../../test/extensions/filters/http/wasm/test_data/resume_call_rust.rs" +crate-type = ["cdylib"] + [[example]] name = "http_shared_data_rust" path = "../../../test/extensions/filters/http/wasm/test_data/shared_data_rust.rs" diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 015e604af0da..402f03c42bdf 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -870,8 +870,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "WebAssembly for Proxies (C++ host implementation)", project_desc = "WebAssembly for Proxies (C++ host implementation)", project_url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host", - version = "eceb02d5b7772ec1cd78a4d35356e57d2e6d59bb", - sha256 = "ae9d9b87d21d95647ebda197d130b37bddc5c6ee3e6630909a231fd55fcc9069", + version = "15827110ac35fdac9abdb6b05d04ee7ee2044dae", + sha256 = "77a2671205eb0973bee375a1bee4099edef991350433981f6e3508780318117d", strip_prefix = "proxy-wasm-cpp-host-{version}", urls = ["https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], @@ -882,7 +882,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", ], - release_date = "2020-11-10", + release_date = "2020-11-12", cpe = "N/A", ), emscripten_toolchain = dict( diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index f38f9a2c3282..006e7648c0e6 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -1493,12 +1493,14 @@ WasmResult Context::continueStream(WasmStreamType stream_type) { switch (stream_type) { case WasmStreamType::Request: if (decoder_callbacks_) { - decoder_callbacks_->continueDecoding(); + // We are in a reentrant call, so defer. + wasm()->addAfterVmCallAction([this] { decoder_callbacks_->continueDecoding(); }); } break; case WasmStreamType::Response: if (encoder_callbacks_) { - encoder_callbacks_->continueEncoding(); + // We are in a reentrant call, so defer. + wasm()->addAfterVmCallAction([this] { encoder_callbacks_->continueEncoding(); }); } break; default: diff --git a/test/extensions/filters/http/wasm/BUILD b/test/extensions/filters/http/wasm/BUILD index 579903b311fd..f8392be38a7a 100644 --- a/test/extensions/filters/http/wasm/BUILD +++ b/test/extensions/filters/http/wasm/BUILD @@ -24,6 +24,7 @@ envoy_extension_cc_test( "//test/extensions/filters/http/wasm/test_data:body_rust.wasm", "//test/extensions/filters/http/wasm/test_data:headers_rust.wasm", "//test/extensions/filters/http/wasm/test_data:metadata_rust.wasm", + "//test/extensions/filters/http/wasm/test_data:resume_call_rust.wasm", "//test/extensions/filters/http/wasm/test_data:shared_data_rust.wasm", "//test/extensions/filters/http/wasm/test_data:shared_queue_rust.wasm", "//test/extensions/filters/http/wasm/test_data:test_cpp.wasm", diff --git a/test/extensions/filters/http/wasm/test_data/BUILD b/test/extensions/filters/http/wasm/test_data/BUILD index 34fbbba8e3ab..f35b19e114b7 100644 --- a/test/extensions/filters/http/wasm/test_data/BUILD +++ b/test/extensions/filters/http/wasm/test_data/BUILD @@ -45,6 +45,15 @@ wasm_rust_binary( ], ) +wasm_rust_binary( + name = "resume_call_rust.wasm", + srcs = ["resume_call_rust.rs"], + deps = [ + "//bazel/external/cargo:log", + "//bazel/external/cargo:proxy_wasm", + ], +) + wasm_rust_binary( name = "shared_data_rust.wasm", srcs = ["shared_data_rust.rs"], @@ -72,6 +81,7 @@ envoy_cc_library( "test_cpp_null_plugin.cc", "test_grpc_call_cpp.cc", "test_grpc_stream_cpp.cc", + "test_resume_call_cpp.cc", "test_shared_data_cpp.cc", "test_shared_queue_cpp.cc", ], @@ -97,6 +107,7 @@ envoy_wasm_cc_binary( "test_cpp.cc", "test_grpc_call_cpp.cc", "test_grpc_stream_cpp.cc", + "test_resume_call_cpp.cc", "test_shared_data_cpp.cc", "test_shared_queue_cpp.cc", ], diff --git a/test/extensions/filters/http/wasm/test_data/resume_call_rust.rs b/test/extensions/filters/http/wasm/test_data/resume_call_rust.rs new file mode 100644 index 000000000000..d9eb08b1fa33 --- /dev/null +++ b/test/extensions/filters/http/wasm/test_data/resume_call_rust.rs @@ -0,0 +1,39 @@ +use log::info; +use proxy_wasm::traits::{Context, HttpContext}; +use proxy_wasm::types::*; +use std::time::Duration; + +#[no_mangle] +pub fn _start() { + proxy_wasm::set_log_level(LogLevel::Trace); + proxy_wasm::set_http_context(|_, _| -> Box { Box::new(TestStream) }); +} + +struct TestStream; + +impl HttpContext for TestStream { + fn on_http_request_headers(&mut self, _: usize) -> Action { + self.dispatch_http_call( + "cluster", + vec![(":method", "POST"), (":path", "/"), (":authority", "foo")], + Some(b"resume"), + vec![], + Duration::from_secs(1), + ) + .unwrap(); + info!("onRequestHeaders"); + Action::Pause + } + + fn on_http_request_body(&mut self, _: usize, _: bool) -> Action { + info!("onRequestBody"); + Action::Continue + } +} + +impl Context for TestStream { + fn on_http_call_response(&mut self, _: u32, _: usize, _: usize, _: usize) { + info!("continueRequest"); + self.resume_http_request(); + } +} diff --git a/test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc b/test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc new file mode 100644 index 000000000000..f557eb143859 --- /dev/null +++ b/test/extensions/filters/http/wasm/test_data/test_resume_call_cpp.cc @@ -0,0 +1,53 @@ +// NOLINT(namespace-envoy) +#include +#include +#include + +#ifndef NULL_PLUGIN +#include "proxy_wasm_intrinsics_lite.h" +#else +#include "extensions/common/wasm/ext/envoy_null_plugin.h" +#endif + +START_WASM_PLUGIN(HttpWasmTestCpp) + +class ResumeCallContext : public Context { +public: + explicit ResumeCallContext(uint32_t id, RootContext* root) : Context(id, root) {} + + FilterHeadersStatus onRequestHeaders(uint32_t, bool) override; + FilterDataStatus onRequestBody(size_t, bool) override; +}; + +class ResumeCallRootContext : public RootContext { +public: + explicit ResumeCallRootContext(uint32_t id, std::string_view root_id) + : RootContext(id, root_id) {} +}; + +static RegisterContextFactory register_ResumeCallContext(CONTEXT_FACTORY(ResumeCallContext), + ROOT_FACTORY(ResumeCallRootContext), + "resume_call"); + +FilterHeadersStatus ResumeCallContext::onRequestHeaders(uint32_t, bool) { + auto context_id = id(); + auto resume_callback = [context_id](uint32_t, size_t, uint32_t) { + getContext(context_id)->setEffectiveContext(); + logInfo("continueRequest"); + continueRequest(); + }; + if (root()->httpCall("cluster", {{":method", "POST"}, {":path", "/"}, {":authority", "foo"}}, + "resume", {}, 1000, resume_callback) != WasmResult::Ok) { + logError("unexpected failure"); + return FilterHeadersStatus::StopIteration; + } + logInfo("onRequestHeaders"); + return FilterHeadersStatus::StopIteration; +} + +FilterDataStatus ResumeCallContext::onRequestBody(size_t, bool) { + logInfo("onRequestBody"); + return FilterDataStatus::Continue; +} + +END_WASM_PLUGIN diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index a80dcd64a8e2..9999d453c75f 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -7,6 +7,7 @@ #include "test/test_common/wasm_base.h" using testing::Eq; +using testing::InSequence; using testing::Invoke; using testing::Return; using testing::ReturnRef; @@ -214,7 +215,7 @@ TEST_P(WasmHttpFilterTest, HeadersStopAndContinue) { EXPECT_CALL(filter(), log_(spdlog::level::info, Eq(absl::string_view("header path /")))); EXPECT_CALL(filter(), log_(spdlog::level::warn, Eq(absl::string_view("onDone 2")))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}, {"server", "envoy-wasm-pause"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, true)); root_context_->onTick(0); filter().clearRouteCache(); @@ -615,7 +616,56 @@ TEST_P(WasmHttpFilterTest, AsyncCall) { callbacks->onSuccess(request, std::move(response_message)); return proxy_wasm::WasmResult::Ok; })); - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, + filter().decodeHeaders(request_headers, false)); + + EXPECT_NE(callbacks, nullptr); +} + +TEST_P(WasmHttpFilterTest, StopAndResumeViaAsyncCall) { + setupTest("resume_call"); + setupFilter(); + + InSequence s; + + Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; + Http::MockAsyncClientRequest request(&cluster_manager_.async_client_); + Http::AsyncClient::Callbacks* callbacks = nullptr; + EXPECT_CALL(cluster_manager_, get(Eq("cluster"))); + EXPECT_CALL(cluster_manager_, httpAsyncClientForCluster("cluster")); + EXPECT_CALL(cluster_manager_.async_client_, send_(_, _, _)) + .WillOnce( + Invoke([&](Http::RequestMessagePtr& message, Http::AsyncClient::Callbacks& cb, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + EXPECT_EQ((Http::TestRequestHeaderMapImpl{{":method", "POST"}, + {":path", "/"}, + {":authority", "foo"}, + {"content-length", "6"}}), + message->headers()); + callbacks = &cb; + return &request; + })); + + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))) + .WillOnce(Invoke([&](uint32_t, absl::string_view) -> proxy_wasm::WasmResult { + Http::ResponseMessagePtr response_message(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + NiceMock span; + Http::TestResponseHeaderMapImpl response_header{{":status", "200"}}; + callbacks->onBeforeFinalizeUpstreamSpan(span, &response_header); + callbacks->onSuccess(request, std::move(response_message)); + return proxy_wasm::WasmResult::Ok; + })); + EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("continueRequest"))); + + Http::MockStreamDecoderFilterCallbacks decoder_callbacks; + filter().setDecoderFilterCallbacks(decoder_callbacks); + EXPECT_CALL(decoder_callbacks, continueDecoding()).WillOnce(Invoke([&]() { + // Verify that we're not resuming processing from within Wasm callback. + EXPECT_EQ(proxy_wasm::current_context_, nullptr); + })); + + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); EXPECT_NE(callbacks, nullptr); @@ -680,7 +730,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallFailure) { } else { EXPECT_CALL(rootContext(), log_(spdlog::level::info, Eq("async_call failed"))); } - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); EXPECT_NE(callbacks, nullptr); @@ -711,7 +761,7 @@ TEST_P(WasmHttpFilterTest, AsyncCallAfterDestroyed) { })); EXPECT_CALL(filter(), log_(spdlog::level::info, Eq("onRequestHeaders"))); - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); EXPECT_CALL(request, cancel()).WillOnce([&]() { callbacks = nullptr; }); @@ -772,7 +822,7 @@ TEST_P(WasmHttpFilterTest, GrpcCall) { })); EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -856,7 +906,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallFailure) { })); EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("failure bad"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); // Test some additional error paths. @@ -917,7 +967,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallCancel) { return std::move(client_factory); })); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); rootContext().onQueueReady(0); @@ -961,7 +1011,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallClose) { return std::move(client_factory); })); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); rootContext().onQueueReady(1); @@ -1006,7 +1056,7 @@ TEST_P(WasmHttpFilterTest, GrpcCallAfterDestroyed) { })); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); EXPECT_CALL(request, cancel()).WillOnce([&]() { callbacks = nullptr; }); @@ -1071,7 +1121,7 @@ TEST_P(WasmHttpFilterTest, GrpcStream) { EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close done"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -1102,7 +1152,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamCloseLocal) { EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response close"))); EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close ok"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -1132,7 +1182,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamCloseRemote) { EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("close close"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -1159,7 +1209,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamCancel) { setupGrpcStreamTest(callbacks); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; @@ -1187,7 +1237,7 @@ TEST_P(WasmHttpFilterTest, GrpcStreamOpenAtShutdown) { EXPECT_CALL(rootContext(), log_(spdlog::level::debug, Eq("response response"))); Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}}; - EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + EXPECT_EQ(Http::FilterHeadersStatus::StopAllIterationAndWatermark, filter().decodeHeaders(request_headers, false)); ProtobufWkt::Value value; From deded5308058e1484f36779b0a4f7bab0fc009a8 Mon Sep 17 00:00:00 2001 From: Fear <1@linux.com> Date: Sat, 14 Nov 2020 01:05:14 +0800 Subject: [PATCH 092/117] docs: fix udp proxy example yaml file (#14011) Signed-off-by: fear <1@linux.com> --- .../configuration/listeners/udp_filters/_include/udp-proxy.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/root/configuration/listeners/udp_filters/_include/udp-proxy.yaml b/docs/root/configuration/listeners/udp_filters/_include/udp-proxy.yaml index 5fde76139391..02a631626071 100644 --- a/docs/root/configuration/listeners/udp_filters/_include/udp-proxy.yaml +++ b/docs/root/configuration/listeners/udp_filters/_include/udp-proxy.yaml @@ -8,6 +8,7 @@ admin: static_resources: listeners: - name: listener_0 + reuse_port: true address: socket_address: protocol: UDP From 112c83838b67fc6a8b45684d14e7e2448aac9955 Mon Sep 17 00:00:00 2001 From: Ruslan Nigmatullin Date: Fri, 13 Nov 2020 09:31:27 -0800 Subject: [PATCH 093/117] logging: Remove --log-format-prefix-with-location option (#14010) Signed-off-by: Ruslan Nigmatullin --- docs/root/operations/cli.rst | 11 ----------- docs/root/version_history/current.rst | 1 + docs/root/version_history/v1.15.0.rst | 2 +- docs/root/version_history/v1.16.0.rst | 2 +- source/server/options_impl.cc | 8 -------- test/server/options_impl_test.cc | 14 +++----------- 6 files changed, 6 insertions(+), 32 deletions(-) diff --git a/docs/root/operations/cli.rst b/docs/root/operations/cli.rst index 6ff2795ef031..d4f6f2808213 100644 --- a/docs/root/operations/cli.rst +++ b/docs/root/operations/cli.rst @@ -116,9 +116,6 @@ following are the command line options that Envoy supports. *(optional)* The format string to use for laying out the log message metadata. If this is not set, a default format string ``"[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"`` is used. - When used in conjunction with :option:`--log-format-prefix-with-location` set to 1, the logger can be - configured to prefix ``%v`` by a file path and a line number. - When used in conjunction with :option:`--log-format-escaped`, the logger can be configured to log in a format that is parsable by log viewers. Known integrations are documented in the :ref:`application logging configuration ` section. @@ -161,14 +158,6 @@ following are the command line options that Envoy supports. :%#: Source line ("123") :%!: Source function ("myFunc") -.. option:: --log-format-prefix-with-location <1|0> - - *(optional)* This temporary flag allows replacing all entries of ``"%v"`` in the log format by - ``"[%g:%#] %v"``. This flag is provided for migration purposes only. If this is not set, a - default value 0 is used. - - **NOTE**: The flag will be removed at 1.17.0 release. - .. option:: --log-format-escaped *(optional)* This flag enables application log sanitization to escape C-style escape sequences. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 07412294aeb1..19f263230518 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -72,4 +72,5 @@ New Features Deprecated ---------- * gzip: :ref:`HTTP Gzip filter ` is rejected now unless explicitly allowed with :ref:`runtime override ` `envoy.deprecated_features.allow_deprecated_gzip_http_filter` set to `true`. +* logging: the `--log-format-prefix-with-location` option is removed. * ratelimit: the :ref:`dynamic metadata ` action is deprecated in favor of the more generic :ref:`metadata ` action. diff --git a/docs/root/version_history/v1.15.0.rst b/docs/root/version_history/v1.15.0.rst index f8943b9a9d77..d97953aac32c 100644 --- a/docs/root/version_history/v1.15.0.rst +++ b/docs/root/version_history/v1.15.0.rst @@ -108,7 +108,7 @@ New Features * listener: added in place filter chain update flow for tcp listener update which doesn't close connections if the corresponding network filter chain is equivalent during the listener update. Can be disabled by setting runtime feature `envoy.reloadable_features.listener_in_place_filterchain_update` to false. Also added additional draining filter chain stat for :ref:`listener manager ` to track the number of draining filter chains and the number of in place update attempts. -* logger: added :option:`--log-format-prefix-with-location` command line option to prefix '%v' with file path and line number. +* logger: added `--log-format-prefix-with-location` command line option to prefix '%v' with file path and line number. * lrs: added new *envoy_api_field_service.load_stats.v2.LoadStatsResponse.send_all_clusters* field in LRS response, which allows management servers to avoid explicitly listing all clusters it is interested in; behavior is allowed based on new "envoy.lrs.supports_send_all_clusters" capability diff --git a/docs/root/version_history/v1.16.0.rst b/docs/root/version_history/v1.16.0.rst index 003dd17ba1f0..d9c2d97d7f25 100644 --- a/docs/root/version_history/v1.16.0.rst +++ b/docs/root/version_history/v1.16.0.rst @@ -42,7 +42,7 @@ Minor Behavior Changes * http: the per-stream FilterState maintained by the HTTP connection manager will now provide read/write access to the downstream connection FilterState. As such, code that relies on interacting with this might see a change in behavior. * logging: added fine-grain logging for file level log control with logger management at administration interface. It can be enabled by option :option:`--enable-fine-grain-logging`. -* logging: changed default log format to `"[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"` and default value of :option:`--log-format-prefix-with-location` to `0`. +* logging: changed default log format to `"[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"` and default value of `--log-format-prefix-with-location` to `0`. * logging: nghttp2 log messages no longer appear at trace level unless `ENVOY_NGHTTP2_TRACE` is set in the environment. * lua: changed the response body returned by `httpCall()` API to raw data. Previously, the returned data was string. diff --git a/source/server/options_impl.cc b/source/server/options_impl.cc index b7c031e914a2..9a488b1fe2e7 100644 --- a/source/server/options_impl.cc +++ b/source/server/options_impl.cc @@ -111,11 +111,6 @@ OptionsImpl::OptionsImpl(std::vector args, TCLAP::SwitchArg enable_fine_grain_logging( "", "enable-fine-grain-logging", "Logger mode: enable file level log control(Fancy Logger)or not", cmd, false); - TCLAP::ValueArg log_format_prefix_with_location( - "", "log-format-prefix-with-location", - "Prefix all occurrences of '%v' in log format with with '[%g:%#] ' ('[path/to/file.cc:99] " - "').", - false, false, "bool", cmd); TCLAP::ValueArg log_path("", "log-path", "Path to logfile", false, "", "string", cmd); TCLAP::ValueArg restart_epoch("", "restart-epoch", "hot restart epoch #", false, 0, @@ -201,9 +196,6 @@ OptionsImpl::OptionsImpl(std::vector args, } log_format_ = log_format.getValue(); - if (log_format_prefix_with_location.getValue()) { - log_format_ = absl::StrReplaceAll(log_format_, {{"%%", "%%"}, {"%v", "[%g:%#] %v"}}); - } log_format_escaped_ = log_format_escaped.getValue(); enable_fine_grain_logging_ = enable_fine_grain_logging.getValue(); diff --git a/test/server/options_impl_test.cc b/test/server/options_impl_test.cc index 3ab6be0e647c..bf22008af5a3 100644 --- a/test/server/options_impl_test.cc +++ b/test/server/options_impl_test.cc @@ -300,8 +300,7 @@ TEST_F(OptionsImplTest, OptionsAreInSyncWithProto) { // 4. allow-unknown-fields - deprecated alias of allow-unknown-static-fields. // 5. use-fake-symbol-table - short-term override for rollout of real symbol-table implementation. // 6. hot restart version - print the hot restart version and exit. - // 7. log-format-prefix-with-location - short-term override for rollout of dynamic log format. - const uint32_t options_not_in_proto = 7; + const uint32_t options_not_in_proto = 6; // There are two deprecated options: "max_stats" and "max_obj_name_len". const uint32_t deprecated_options = 2; @@ -457,17 +456,10 @@ TEST_F(OptionsImplTest, LogFormatDefault) { EXPECT_EQ(options->logFormat(), "[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"); } -TEST_F(OptionsImplTest, LogFormatDefaultNoPrefix) { - std::unique_ptr options = - createOptionsImpl({"envoy", "-c", "hello", "--log-format-prefix-with-location", "0"}); - EXPECT_EQ(options->logFormat(), "[%Y-%m-%d %T.%e][%t][%l][%n] [%g:%#] %v"); -} - TEST_F(OptionsImplTest, LogFormatOverride) { std::unique_ptr options = - createOptionsImpl({"envoy", "-c", "hello", "--log-format", "%%v %v %t %v", - "--log-format-prefix-with-location 1"}); - EXPECT_EQ(options->logFormat(), "%%v [%g:%#] %v %t [%g:%#] %v"); + createOptionsImpl({"envoy", "-c", "hello", "--log-format", "%%v %v %t %v"}); + EXPECT_EQ(options->logFormat(), "%%v %v %t %v"); } TEST_F(OptionsImplTest, LogFormatOverrideNoPrefix) { From 4068ae8b36845876fe394baa90501aca2b5daeb0 Mon Sep 17 00:00:00 2001 From: Kuat Date: Fri, 13 Nov 2020 10:52:16 -0800 Subject: [PATCH 094/117] docs: document attributes for RBAC and Wasm (#13971) Organize the list of attributes better. Signed-off-by: Kuat Yessenov --- .../intro/arch_overview/advanced/advanced.rst | 1 + .../arch_overview/advanced/attributes.rst | 191 ++++++++++++++++++ .../arch_overview/security/rbac_filter.rst | 64 +----- 3 files changed, 196 insertions(+), 60 deletions(-) create mode 100644 docs/root/intro/arch_overview/advanced/attributes.rst diff --git a/docs/root/intro/arch_overview/advanced/advanced.rst b/docs/root/intro/arch_overview/advanced/advanced.rst index c26e0fee562c..b8fa3a05d102 100644 --- a/docs/root/intro/arch_overview/advanced/advanced.rst +++ b/docs/root/intro/arch_overview/advanced/advanced.rst @@ -5,3 +5,4 @@ Advanced :maxdepth: 2 data_sharing_between_filters + attributes diff --git a/docs/root/intro/arch_overview/advanced/attributes.rst b/docs/root/intro/arch_overview/advanced/attributes.rst new file mode 100644 index 000000000000..767a75b2d4c4 --- /dev/null +++ b/docs/root/intro/arch_overview/advanced/attributes.rst @@ -0,0 +1,191 @@ +.. _arch_overview_attributes: + +Attributes +========== + +Attributes refer to contextual properties provided by Envoy during request and +connection processing. They are named by a dot-separated path (e.g. +`request.path`), have a fixed type (e.g. `string` or `int`), and may be +absent or present depending on the context. Attributes are exposed to CEL +runtime in :ref:`RBAC filter `, as well as Wasm extensions +via `get_property` ABI method. + +Attribute value types are limited to: + +* `string` for UTF-8 strings +* `bytes` for byte buffers +* `int` for 64-bit signed integers +* `uint` for 64-bit unsigned integers +* `bool` for booleans +* `list` for lists of values +* `map` for associative arrays with string keys +* `timestamp` for timestamps as specified by `Timestamp `_ +* `duration` for durations as specified by `Duration `_ +* Protocol buffer message types + +CEL provides standard helper functions for operating on abstract types such as +`getMonth` for `timestamp` values. Note that integer literals (e.g. `7`) are of +type `int`, which is distinct from `uint` (e.g. `7u`), and the arithmetic +conversion is not automatic (use `uint(7)` for explicit conversion). + +Wasm extensions receive the attribute values as a serialized buffer according +to the type of the attribute. Strings and bytes are passed as-is, integers are +passed as 64 bits directly, timestamps and durations are approximated to +nano-seconds, and structured values are converted to a sequence of pairs +recursively. + +.. _arch_overview_request_attributes: + +Request attributes +------------------ + +The following request attributes are generally available upon initial request +processing, which makes them suitable for RBAC policies: + +.. csv-table:: + :header: Attribute, Type, Description + :escape: ' + :widths: 1, 1, 4 + + request.path, string, The path portion of the URL + request.url_path, string, The path portion of the URL without the query string + request.host, string, The host portion of the URL + request.scheme, string, The scheme portion of the URL e.g. "http" + request.method, string, Request method e.g. "GET" + request.headers, "map", All request headers indexed by the lower-cased header name + request.referer, string, Referer request header + request.useragent, string, User agent request header + request.time, timestamp, Time of the first byte received + request.id, string, Request ID corresponding to `x-request-id` header value + request.protocol, string, "Request protocol ('"HTTP/1.0'", '"HTTP/1.1'", '"HTTP/2'", or '"HTTP/3'")" + +Header values in `request.headers` associative array are comma-concatenated in case of multiple values. + +Additional attributes are available once the request completes: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + request.duration, duration, Total duration of the request + request.size, int, Size of the request body. Content length header is used if available. + request.total_size, int, Total size of the request including the approximate uncompressed size of the headers + +Response attributes +------------------- + +Response attributes are only available after the request completes. + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + response.code, int, Response HTTP status code + response.code_details, string, Internal response code details (subject to change) + response.flags, int, Additional details about the response beyond the standard response code encoded as a bit-vector + response.grpc_status, int, Response gRPC status code + response.headers, "map", All response headers indexed by the lower-cased header name + response.trailers, "map", All response trailers indexed by the lower-cased trailer name + response.size, int, Size of the response body + response.total_size, int, Total size of the response including the approximate uncompressed size of the headers and the trailers + +Connection attributes +--------------------- + +The following attributes are available once the downstream connection is +established (which also applies to HTTP requests making them suitable for +RBAC): + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + source.address, string, Downstream connection remote address + source.port, int, Downstream connection remote port + destination.address, string, Downstream connection local address + destination.port, int, Downstream connection local port + connection.id, uint, Downstream connection ID + connection.mtls, bool, Indicates whether TLS is applied to the downstream connection and the peer ceritificate is presented + connection.requested_server_name, string, Requested server name in the downstream TLS connection + connection.tls_version, string, TLS version of the downstream TLS connection + connection.subject_local_certificate, string, The subject field of the local certificate in the downstream TLS connection + connection.subject_peer_certificate, string, The subject field of the peer certificate in the downstream TLS connection + connection.dns_san_local_certificate, string, The first DNS entry in the SAN field of the local certificate in the downstream TLS connection + connection.dns_san_peer_certificate, string, The first DNS entry in the SAN field of the peer certificate in the downstream TLS connection + connection.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the downstream TLS connection + connection.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the downstream TLS connection + +The following additional attributes are available upon the downstream connection termination: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + connection.termination_details, string, Internal termination details of the connection (subject to change) + +Upstream attributes +------------------- + +The following attributes are available once the upstream connection is established: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + upstream.address, string, Upstream connection remote address + upstream.port, int, Upstream connection remote port + upstream.tls_version, string, TLS version of the upstream TLS connection + upstream.subject_local_certificate, string, The subject field of the local certificate in the upstream TLS connection + upstream.subject_peer_certificate, string, The subject field of the peer certificate in the upstream TLS connection + upstream.dns_san_local_certificate, string, The first DNS entry in the SAN field of the local certificate in the upstream TLS connection + upstream.dns_san_peer_certificate, string, The first DNS entry in the SAN field of the peer certificate in the upstream TLS connection + upstream.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the upstream TLS connection + upstream.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the upstream TLS connection + upstream.local_address, string, The local address of the upstream connection + upstream.transport_failure_reason, string, The upstream transport failure reason e.g. certificate validation failed + +Metadata and filter state +------------------------- + +Data exchanged between filters is available as the following attributes: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + metadata, :ref:`Metadata`, Dynamic request metadata + filter_state, "map", Mapping from a filter state name to its serialized string value + +Note that these attributes may change during the life of a request as the data can be +updated by filters at any point. + +Wasm attributes +--------------- + +In addition to all above, the following extra attributes are available to Wasm extensions: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 4 + + plugin_name, string, Plugin name + plugin_root_id, string, Plugin root ID + plugin_vm_id, string, Plugin VM ID + node, :ref:`Node`, Local node description + cluster_name, string, Upstream cluster name + cluster_metadata, :ref:`Metadata`, Upstream cluster metadata + listener_direction, int, Enumeration value of the :ref:`listener traffic direction` + listener_metadata, :ref:`Metadata`, Listener metadata + route_name, string, Route name + route_metadata, :ref:`Metadata`, Route metadata + upstream_host_metadata, :ref:`Metadata`, Upstream host metadata + +Path expressions +---------------- + +Path expressions allow access to inner fields in structured attributes via a +sequence of field names, map, and list indexes following an attribute name. For +example, `get_property({"node", "id"})` in Wasm ABI extracts the value of `id` +field in `node` message attribute, while `get_property({"request", "headers", +"my-header"})` refers to the comma-concatenated value of a particular request +header. diff --git a/docs/root/intro/arch_overview/security/rbac_filter.rst b/docs/root/intro/arch_overview/security/rbac_filter.rst index b8c1190234b0..2826a7c3ae2d 100644 --- a/docs/root/intro/arch_overview/security/rbac_filter.rst +++ b/docs/root/intro/arch_overview/security/rbac_filter.rst @@ -58,64 +58,8 @@ whether the request path starts with `/v1/`: - const_expr: string_value: /v1/ -The following attributes are exposed to the language runtime: +Envoy provides a number of :ref:`request attributes ` +for expressive policies. Most attributes are optional and provide the default +value based on the type of the attribute. CEL supports presence checks for +attributes and maps using `has()` syntax, e.g. `has(request.referer)`. -.. csv-table:: - :header: Attribute, Type, Description - :widths: 1, 1, 2 - - request.path, string, The path portion of the URL - request.url_path, string, The path portion of the URL without the query string - request.host, string, The host portion of the URL - request.scheme, string, The scheme portion of the URL - request.method, string, Request method - request.headers, string map, All request headers - request.referer, string, Referer request header - request.useragent, string, User agent request header - request.time, timestamp, Time of the first byte received - request.duration, duration, Total duration of the request - request.id, string, Request ID - request.size, int, Size of the request body - request.total_size, int, Total size of the request including the headers - request.protocol, string, Request protocol e.g. "HTTP/2" - response.code, int, Response HTTP status code - response.code_details, string, Internal response code details (subject to change) - response.grpc_status, int, Response gRPC status code - response.headers, string map, All response headers - response.trailers, string map, All response trailers - response.size, int, Size of the response body - response.total_size, int, Total size of the response including the approximate uncompressed size of the headers and the trailers - response.flags, int, Additional details about the response beyond the standard response code - source.address, string, Downstream connection remote address - source.port, int, Downstream connection remote port - destination.address, string, Downstream connection local address - destination.port, int, Downstream connection local port - metadata, :ref:`Metadata`, Dynamic metadata - filter_state, map string to bytes, Filter state mapping data names to their serialized string value - connection.mtls, bool, Indicates whether TLS is applied to the downstream connection and the peer ceritificate is presented - connection.requested_server_name, string, Requested server name in the downstream TLS connection - connection.tls_version, string, TLS version of the downstream TLS connection - connection.subject_local_certificate, string, The subject field of the local certificate in the downstream TLS connection - connection.subject_peer_certificate, string, The subject field of the peer certificate in the downstream TLS connection - connection.dns_san_local_certificate, string, The first DNS entry in the SAN field of the local certificate in the downstream TLS connection - connection.dns_san_peer_certificate, string, The first DNS entry in the SAN field of the peer certificate in the downstream TLS connection - connection.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the downstream TLS connection - connection.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the downstream TLS connection - connection.id, uint, Downstream connection ID - connection.termination_details, string, The termination details of the connection - upstream.address, string, Upstream connection remote address - upstream.port, int, Upstream connection remote port - upstream.tls_version, string, TLS version of the upstream TLS connection - upstream.subject_local_certificate, string, The subject field of the local certificate in the upstream TLS connection - upstream.subject_peer_certificate, string, The subject field of the peer certificate in the upstream TLS connection - upstream.dns_san_local_certificate, string, The first DNS entry in the SAN field of the local certificate in the upstream TLS connection - upstream.dns_san_peer_certificate, string, The first DNS entry in the SAN field of the peer certificate in the upstream TLS connection - upstream.uri_san_local_certificate, string, The first URI entry in the SAN field of the local certificate in the upstream TLS connection - upstream.uri_san_peer_certificate, string, The first URI entry in the SAN field of the peer certificate in the upstream TLS connection - upstream.local_address, string, The local address of the upstream connection - upstream.transport_failure_reason, string, The upstream transport failure reason e.g. certificate validation failed - - -Most attributes are optional and provide the default value based on the type of the attribute. -CEL supports presence checks for attributes and maps using `has()` syntax, e.g. -`has(request.referer)`. From d0dda3f901c26547d397489d32b2c62184963ace Mon Sep 17 00:00:00 2001 From: Matt Klein Date: Fri, 13 Nov 2020 11:00:09 -0800 Subject: [PATCH 095/117] cluster manager: make add/update and initial host set update atomic (#13906) Previously, we would do a separate TLS set to add/update the cluster, and then another TLS operation to populate the initial host set. For cluster updates for a server already in operation this can lead to a gap of no hosts. Signed-off-by: Matt Klein --- .../common/upstream/cluster_manager_impl.cc | 241 +++++++++++------- source/common/upstream/cluster_manager_impl.h | 92 +++++-- .../upstream/cluster_manager_impl_test.cc | 137 ++++++---- test/common/upstream/test_cluster_manager.h | 10 +- 4 files changed, 306 insertions(+), 174 deletions(-) diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 6eaef38a901b..3e816fc6e160 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -56,28 +56,29 @@ void addOptionsIfNotNull(Network::Socket::OptionsSharedPtr& options, } // namespace -void ClusterManagerInitHelper::addCluster(Cluster& cluster) { +void ClusterManagerInitHelper::addCluster(ClusterManagerCluster& cm_cluster) { // See comments in ClusterManagerImpl::addOrUpdateCluster() for why this is only called during // server initialization. ASSERT(state_ != State::AllClustersInitialized); - const auto initialize_cb = [&cluster, this] { onClusterInit(cluster); }; + const auto initialize_cb = [&cm_cluster, this] { onClusterInit(cm_cluster); }; + Cluster& cluster = cm_cluster.cluster(); if (cluster.initializePhase() == Cluster::InitializePhase::Primary) { // Remove the previous cluster before the cluster object is destroyed. primary_init_clusters_.remove_if( - [name_to_remove = cluster.info()->name()](Cluster* cluster_iter) { - return cluster_iter->info()->name() == name_to_remove; + [name_to_remove = cluster.info()->name()](ClusterManagerCluster* cluster_iter) { + return cluster_iter->cluster().info()->name() == name_to_remove; }); - primary_init_clusters_.push_back(&cluster); + primary_init_clusters_.push_back(&cm_cluster); cluster.initialize(initialize_cb); } else { ASSERT(cluster.initializePhase() == Cluster::InitializePhase::Secondary); // Remove the previous cluster before the cluster object is destroyed. secondary_init_clusters_.remove_if( - [name_to_remove = cluster.info()->name()](Cluster* cluster_iter) { - return cluster_iter->info()->name() == name_to_remove; + [name_to_remove = cluster.info()->name()](ClusterManagerCluster* cluster_iter) { + return cluster_iter->cluster().info()->name() == name_to_remove; }); - secondary_init_clusters_.push_back(&cluster); + secondary_init_clusters_.push_back(&cm_cluster); if (started_secondary_initialize_) { // This can happen if we get a second CDS update that adds new clusters after we have // already started secondary init. In this case, just immediately initialize. @@ -89,24 +90,24 @@ void ClusterManagerInitHelper::addCluster(Cluster& cluster) { primary_init_clusters_.size(), secondary_init_clusters_.size()); } -void ClusterManagerInitHelper::onClusterInit(Cluster& cluster) { +void ClusterManagerInitHelper::onClusterInit(ClusterManagerCluster& cluster) { ASSERT(state_ != State::AllClustersInitialized); per_cluster_init_callback_(cluster); removeCluster(cluster); } -void ClusterManagerInitHelper::removeCluster(Cluster& cluster) { +void ClusterManagerInitHelper::removeCluster(ClusterManagerCluster& cluster) { if (state_ == State::AllClustersInitialized) { return; } // There is a remote edge case where we can remove a cluster via CDS that has not yet been // initialized. When called via the remove cluster API this code catches that case. - std::list* cluster_list; - if (cluster.initializePhase() == Cluster::InitializePhase::Primary) { + std::list* cluster_list; + if (cluster.cluster().initializePhase() == Cluster::InitializePhase::Primary) { cluster_list = &primary_init_clusters_; } else { - ASSERT(cluster.initializePhase() == Cluster::InitializePhase::Secondary); + ASSERT(cluster.cluster().initializePhase() == Cluster::InitializePhase::Secondary); cluster_list = &secondary_init_clusters_; } @@ -114,7 +115,8 @@ void ClusterManagerInitHelper::removeCluster(Cluster& cluster) { // present in the initializer list. If so, this is fine. cluster_list->remove(&cluster); ENVOY_LOG(debug, "cm init: init complete: cluster={} primary={} secondary={}", - cluster.info()->name(), primary_init_clusters_.size(), secondary_init_clusters_.size()); + cluster.cluster().info()->name(), primary_init_clusters_.size(), + secondary_init_clusters_.size()); maybeFinishInitialize(); } @@ -124,10 +126,10 @@ void ClusterManagerInitHelper::initializeSecondaryClusters() { // the item currently being initialized, so we eschew range-based-for and do this complicated // dance to increment the iterator before calling initialize. for (auto iter = secondary_init_clusters_.begin(); iter != secondary_init_clusters_.end();) { - Cluster* cluster = *iter; + ClusterManagerCluster* cluster = *iter; ++iter; - ENVOY_LOG(debug, "initializing secondary cluster {}", cluster->info()->name()); - cluster->initialize([cluster, this] { onClusterInit(*cluster); }); + ENVOY_LOG(debug, "initializing secondary cluster {}", cluster->cluster().info()->name()); + cluster->cluster().initialize([cluster, this] { onClusterInit(*cluster); }); } } @@ -252,7 +254,7 @@ ClusterManagerImpl::ClusterManagerImpl( random_(api.randomGenerator()), bind_config_(bootstrap.cluster_manager().upstream_bind_config()), local_info_(local_info), cm_stats_(generateStats(stats)), - init_helper_(*this, [this](Cluster& cluster) { onClusterInit(cluster); }), + init_helper_(*this, [this](ClusterManagerCluster& cluster) { onClusterInit(cluster); }), config_tracker_entry_( admin.getConfigTracker().add("clusters", [this] { return dumpClusterConfigs(); })), time_source_(main_thread_dispatcher.timeSource()), dispatcher_(main_thread_dispatcher), @@ -372,10 +374,29 @@ ClusterManagerImpl::ClusterManagerImpl( // Once the initial set of static bootstrap clusters are created (including the local cluster), // we can instantiate the thread local cluster manager. - tls_.set([this, local_cluster_name = local_cluster_name_](Event::Dispatcher& dispatcher) { - return std::make_shared(*this, dispatcher, local_cluster_name); + tls_.set([this](Event::Dispatcher& dispatcher) { + return std::make_shared(*this, dispatcher); }); + // For active clusters that exist in bootstrap, post an empty thread local cluster update to + // populate them. + // TODO(mattklein123): It would be nice if we did not do this and instead all thread local cluster + // creation happened as part of the cluster init flow, however there are certain cases that depend + // on this behavior including route checking. It may be possible to fix static route checking to + // not depend on this behavior, but for now this is consistent with the way we have always done + // this so in the interest of minimal change it is not being done now. + for (auto& cluster : active_clusters_) { + // Skip posting the thread local cluster which is created as part of the thread local cluster + // manager constructor. See the TODO in that code for eventually cleaning this up. + if (local_cluster_name_ && local_cluster_name_.value() == cluster.first) { + continue; + } + + // Avoid virtual call in the constructor. This only impacts tests. Remove this when fixing + // the above TODO. + postThreadLocalClusterUpdateNonVirtual(*cluster.second, ThreadLocalClusterUpdateParams()); + } + // We can now potentially create the CDS API once the backing cluster exists. if (dyn_resources.has_cds_config()) { cds_api_ = factory_.createCds(dyn_resources.cds_config(), *this); @@ -388,7 +409,7 @@ ClusterManagerImpl::ClusterManagerImpl( // initialize any primary clusters. Post-init processing further initializes any thread // aware load balancer and sets up the per-worker host set updates. for (auto& cluster : active_clusters_) { - init_helper_.addCluster(*cluster.second->cluster_); + init_helper_.addCluster(*cluster.second); } // Potentially move to secondary initialization on the static bootstrap clusters if all primary @@ -421,12 +442,13 @@ ClusterManagerStats ClusterManagerImpl::generateStats(Stats::Scope& scope) { POOL_GAUGE_PREFIX(scope, final_prefix))}; } -void ClusterManagerImpl::onClusterInit(Cluster& cluster) { +void ClusterManagerImpl::onClusterInit(ClusterManagerCluster& cm_cluster) { // This routine is called when a cluster has finished initializing. The cluster has not yet // been setup for cross-thread updates to avoid needless updates during initialization. The order // of operations here is important. We start by initializing the thread aware load balancer if // needed. This must happen first so cluster updates are heard first by the load balancer. // Also, it assures that all of clusters which this function is called should be always active. + auto& cluster = cm_cluster.cluster(); auto cluster_data = warming_clusters_.find(cluster.info()->name()); // We have a situation that clusters will be immediately active, such as static and primary // cluster. So we must have this prevention logic here. @@ -461,9 +483,9 @@ void ClusterManagerImpl::onClusterInit(Cluster& cluster) { } }); - cluster.prioritySet().addPriorityUpdateCb([&cluster, this](uint32_t priority, - const HostVector& hosts_added, - const HostVector& hosts_removed) { + cluster.prioritySet().addPriorityUpdateCb([&cm_cluster, this](uint32_t priority, + const HostVector& hosts_added, + const HostVector& hosts_removed) { // This fires when a cluster is about to have an updated member set. We need to send this // out to all of the thread local configurations. @@ -480,38 +502,49 @@ void ClusterManagerImpl::onClusterInit(Cluster& cluster) { // // See https://github.com/envoyproxy/envoy/pull/3941 for more context. bool scheduled = false; - const auto merge_timeout = - PROTOBUF_GET_MS_OR_DEFAULT(cluster.info()->lbConfig(), update_merge_window, 1000); + const auto merge_timeout = PROTOBUF_GET_MS_OR_DEFAULT(cm_cluster.cluster().info()->lbConfig(), + update_merge_window, 1000); // Remember: we only merge updates with no adds/removes — just hc/weight/metadata changes. const bool is_mergeable = hosts_added.empty() && hosts_removed.empty(); if (merge_timeout > 0) { // If this is not mergeable, we should cancel any scheduled updates since // we'll deliver it immediately. - scheduled = scheduleUpdate(cluster, priority, is_mergeable, merge_timeout); + scheduled = scheduleUpdate(cm_cluster, priority, is_mergeable, merge_timeout); } // If an update was not scheduled for later, deliver it immediately. if (!scheduled) { cm_stats_.cluster_updated_.inc(); - postThreadLocalClusterUpdate(cluster, priority, hosts_added, hosts_removed); + postThreadLocalClusterUpdate( + cm_cluster, ThreadLocalClusterUpdateParams(priority, hosts_added, hosts_removed)); } }); - // Finally, if the cluster has any hosts, post updates cross-thread so the per-thread load - // balancers are ready. + // Finally, post updates cross-thread so the per-thread load balancers are ready. First we + // populate any update information that may be available after cluster init. + ThreadLocalClusterUpdateParams params; for (auto& host_set : cluster.prioritySet().hostSetsPerPriority()) { if (host_set->hosts().empty()) { continue; } - postThreadLocalClusterUpdate(cluster, host_set->priority(), host_set->hosts(), HostVector{}); + params.per_priority_update_params_.emplace_back(host_set->priority(), host_set->hosts(), + HostVector{}); + } + // At this point the update is posted if either there are actual updates or the cluster has + // not been added yet. The latter can only happen with dynamic cluster as static clusters are + // added immediately. + // TODO(mattklein123): Per related TODOs we will see if we can centralize all logic so that + // clusters only get added in this path and all of the special casing can be removed. + if (!params.per_priority_update_params_.empty() || !cm_cluster.addedOrUpdated()) { + postThreadLocalClusterUpdate(cm_cluster, std::move(params)); } } -bool ClusterManagerImpl::scheduleUpdate(const Cluster& cluster, uint32_t priority, bool mergeable, - const uint64_t timeout) { +bool ClusterManagerImpl::scheduleUpdate(ClusterManagerCluster& cluster, uint32_t priority, + bool mergeable, const uint64_t timeout) { // Find pending updates for this cluster. - auto& updates_by_prio = updates_map_[cluster.info()->name()]; + auto& updates_by_prio = updates_map_[cluster.cluster().info()->name()]; if (!updates_by_prio) { updates_by_prio = std::make_unique(); } @@ -566,7 +599,7 @@ bool ClusterManagerImpl::scheduleUpdate(const Cluster& cluster, uint32_t priorit return true; } -void ClusterManagerImpl::applyUpdates(const Cluster& cluster, uint32_t priority, +void ClusterManagerImpl::applyUpdates(ClusterManagerCluster& cluster, uint32_t priority, PendingUpdates& updates) { // Deliver pending updates. @@ -576,7 +609,8 @@ void ClusterManagerImpl::applyUpdates(const Cluster& cluster, uint32_t priority, static const HostVector hosts_added; static const HostVector hosts_removed; - postThreadLocalClusterUpdate(cluster, priority, hosts_added, hosts_removed); + postThreadLocalClusterUpdate( + cluster, ThreadLocalClusterUpdateParams(priority, hosts_added, hosts_removed)); cm_stats_.cluster_updated_via_merge_.inc(); updates.last_updated_ = time_source_.monotonicTime(); @@ -604,7 +638,7 @@ bool ClusterManagerImpl::addOrUpdateCluster(const envoy::config::cluster::v3::Cl if (existing_active_cluster != active_clusters_.end()) { // The following init manager remove call is a NOP in the case we are already initialized. // It's just kept here to avoid additional logic. - init_helper_.removeCluster(*existing_active_cluster->second->cluster_); + init_helper_.removeCluster(*existing_active_cluster->second); } cm_stats_.cluster_modified_.inc(); } else { @@ -629,15 +663,13 @@ bool ClusterManagerImpl::addOrUpdateCluster(const envoy::config::cluster::v3::Cl auto& cluster_entry = warming_clusters_.at(cluster_name); if (!all_clusters_initialized) { ENVOY_LOG(debug, "add/update cluster {} during init", cluster_name); - createOrUpdateThreadLocalCluster(*cluster_entry); - init_helper_.addCluster(*cluster_entry->cluster_); + init_helper_.addCluster(*cluster_entry); } else { ENVOY_LOG(debug, "add/update cluster {} starting warming", cluster_name); cluster_entry->cluster_->initialize([this, cluster_name] { ENVOY_LOG(debug, "warming cluster {} complete", cluster_name); auto state_changed_cluster_entry = warming_clusters_.find(cluster_name); - createOrUpdateThreadLocalCluster(*state_changed_cluster_entry->second); - onClusterInit(*state_changed_cluster_entry->second->cluster_); + onClusterInit(*state_changed_cluster_entry->second); }); } @@ -656,32 +688,13 @@ void ClusterManagerImpl::clusterWarmingToActive(const std::string& cluster_name) warming_clusters_.erase(warming_it); } -void ClusterManagerImpl::createOrUpdateThreadLocalCluster(ClusterData& cluster) { - tls_.runOnAllThreads([new_cluster = cluster.cluster_->info(), - thread_aware_lb_factory = cluster.loadBalancerFactory()]( - OptRef cluster_manager) { - if (cluster_manager->thread_local_clusters_.count(new_cluster->name()) > 0) { - ENVOY_LOG(debug, "updating TLS cluster {}", new_cluster->name()); - } else { - ENVOY_LOG(debug, "adding TLS cluster {}", new_cluster->name()); - } - - auto thread_local_cluster = new ThreadLocalClusterManagerImpl::ClusterEntry( - *cluster_manager, new_cluster, thread_aware_lb_factory); - cluster_manager->thread_local_clusters_[new_cluster->name()].reset(thread_local_cluster); - for (auto& cb : cluster_manager->update_callbacks_) { - cb->onClusterAddOrUpdate(*thread_local_cluster); - } - }); -} - bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { bool removed = false; auto existing_active_cluster = active_clusters_.find(cluster_name); if (existing_active_cluster != active_clusters_.end() && existing_active_cluster->second->added_via_api_) { removed = true; - init_helper_.removeCluster(*existing_active_cluster->second->cluster_); + init_helper_.removeCluster(*existing_active_cluster->second); active_clusters_.erase(existing_active_cluster); ENVOY_LOG(info, "removing cluster {}", cluster_name); @@ -699,7 +712,7 @@ bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { if (existing_warming_cluster != warming_clusters_.end() && existing_warming_cluster->second->added_via_api_) { removed = true; - init_helper_.removeCluster(*existing_warming_cluster->second->cluster_); + init_helper_.removeCluster(*existing_warming_cluster->second); warming_clusters_.erase(existing_warming_cluster); ENVOY_LOG(info, "removing warming cluster {}", cluster_name); } @@ -926,19 +939,63 @@ void ClusterManagerImpl::postThreadLocalDrainConnections(const Cluster& cluster, }); } -void ClusterManagerImpl::postThreadLocalClusterUpdate(const Cluster& cluster, uint32_t priority, - const HostVector& hosts_added, - const HostVector& hosts_removed) { - const auto& host_set = cluster.prioritySet().hostSetsPerPriority()[priority]; - - tls_.runOnAllThreads([name = cluster.info()->name(), priority, - update_params = HostSetImpl::updateHostsParams(*host_set), - locality_weights = host_set->localityWeights(), hosts_added, hosts_removed, - overprovisioning_factor = host_set->overprovisioningFactor()]( - OptRef cluster_manager) { - cluster_manager->updateClusterMembership(name, priority, update_params, locality_weights, - hosts_added, hosts_removed, overprovisioning_factor); - }); +void ClusterManagerImpl::postThreadLocalClusterUpdateNonVirtual( + ClusterManagerCluster& cm_cluster, ThreadLocalClusterUpdateParams&& params) { + const bool is_local_cluster = local_cluster_name_.has_value() && + local_cluster_name_.value() == cm_cluster.cluster().info()->name(); + bool add_or_update_cluster = false; + if (!cm_cluster.addedOrUpdated()) { + add_or_update_cluster = true; + cm_cluster.setAddedOrUpdated(); + } + if (is_local_cluster) { + // TODO(mattklein123): This is needed because of the special case of how local cluster is + // initialized in the thread local cluster manager constructor. This will all be cleaned up + // in a follow up. + add_or_update_cluster = false; + } + + LoadBalancerFactorySharedPtr load_balancer_factory; + if (add_or_update_cluster) { + load_balancer_factory = cm_cluster.loadBalancerFactory(); + } + + for (auto& per_priority : params.per_priority_update_params_) { + const auto& host_set = + cm_cluster.cluster().prioritySet().hostSetsPerPriority()[per_priority.priority_]; + per_priority.update_hosts_params_ = HostSetImpl::updateHostsParams(*host_set); + per_priority.locality_weights_ = host_set->localityWeights(); + per_priority.overprovisioning_factor_ = host_set->overprovisioningFactor(); + } + + tls_.runOnAllThreads( + [info = cm_cluster.cluster().info(), params = std::move(params), add_or_update_cluster, + load_balancer_factory](OptRef cluster_manager) { + if (add_or_update_cluster) { + if (cluster_manager->thread_local_clusters_.count(info->name()) > 0) { + ENVOY_LOG(debug, "updating TLS cluster {}", info->name()); + } else { + ENVOY_LOG(debug, "adding TLS cluster {}", info->name()); + } + + auto thread_local_cluster = new ThreadLocalClusterManagerImpl::ClusterEntry( + *cluster_manager, info, load_balancer_factory); + cluster_manager->thread_local_clusters_[info->name()].reset(thread_local_cluster); + // TODO(mattklein123): It would be better if update callbacks were done after the initial + // cluster member is seeded, assuming it is. In the interest of minimal change this is + // deferred for a future change. + for (auto& cb : cluster_manager->update_callbacks_) { + cb->onClusterAddOrUpdate(*thread_local_cluster); + } + } + + for (const auto& per_priority : params.per_priority_update_params_) { + cluster_manager->updateClusterMembership( + info->name(), per_priority.priority_, per_priority.update_hosts_params_, + per_priority.locality_weights_, per_priority.hosts_added_, + per_priority.hosts_removed_, per_priority.overprovisioning_factor_); + } + }); } void ClusterManagerImpl::postThreadLocalHealthFailure(const HostSharedPtr& host) { @@ -1024,32 +1081,22 @@ ProtobufTypes::MessagePtr ClusterManagerImpl::dumpClusterConfigs() { } ClusterManagerImpl::ThreadLocalClusterManagerImpl::ThreadLocalClusterManagerImpl( - ClusterManagerImpl& parent, Event::Dispatcher& dispatcher, - const absl::optional& local_cluster_name) + ClusterManagerImpl& parent, Event::Dispatcher& dispatcher) : parent_(parent), thread_local_dispatcher_(dispatcher) { // If local cluster is defined then we need to initialize it first. - if (local_cluster_name) { - ENVOY_LOG(debug, "adding TLS local cluster {}", local_cluster_name.value()); - auto& local_cluster = parent.active_clusters_.at(local_cluster_name.value()); - thread_local_clusters_[local_cluster_name.value()] = std::make_unique( + // TODO(mattklein123): Technically accessing active_clusters_ here is a race condition. This has + // been this way "forever" but should be fixed in a follow up. + if (parent.localClusterName()) { + ENVOY_LOG(debug, "adding TLS local cluster {}", parent.localClusterName().value()); + auto& local_cluster = parent.active_clusters_.at(parent.localClusterName().value()); + thread_local_clusters_[parent.localClusterName().value()] = std::make_unique( *this, local_cluster->cluster_->info(), local_cluster->loadBalancerFactory()); } - local_priority_set_ = local_cluster_name - ? &thread_local_clusters_[local_cluster_name.value()]->priority_set_ - : nullptr; - - for (auto& cluster : parent.active_clusters_) { - // If local cluster name is set then we already initialized this cluster. - if (local_cluster_name && local_cluster_name.value() == cluster.first) { - continue; - } - - ENVOY_LOG(debug, "adding TLS initial cluster {}", cluster.first); - ASSERT(thread_local_clusters_.count(cluster.first) == 0); - thread_local_clusters_[cluster.first] = std::make_unique( - *this, cluster.second->cluster_->info(), cluster.second->loadBalancerFactory()); - } + local_priority_set_ = + parent.localClusterName() + ? &thread_local_clusters_[parent.localClusterName().value()]->priority_set_ + : nullptr; } ClusterManagerImpl::ThreadLocalClusterManagerImpl::~ThreadLocalClusterManagerImpl() { diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index ba3c55ba2af1..63a5e701e9d6 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -97,6 +97,27 @@ class ProdClusterManagerFactory : public ClusterManagerFactory { // For friend declaration in ClusterManagerInitHelper. class ClusterManagerImpl; +/** + * Wrapper for a cluster owned by the cluster manager. Used by both the cluster manager and the + * cluster manager init helper which needs to pass clusters back to the cluster manager. + */ +class ClusterManagerCluster { +public: + virtual ~ClusterManagerCluster() = default; + + // Return the underlying cluster. + virtual Cluster& cluster() PURE; + + // Return a new load balancer factory if the cluster has one. + virtual LoadBalancerFactorySharedPtr loadBalancerFactory() PURE; + + // Return true if a cluster has already been added or updated. + virtual bool addedOrUpdated() PURE; + + // Set when a cluster has been added or updated. This is only called a single time for a cluster. + virtual void setAddedOrUpdated() PURE; +}; + /** * This is a helper class used during cluster management initialization. Dealing with primary * clusters, secondary clusters, and CDS, is quite complicated, so this makes it easier to test. @@ -107,8 +128,9 @@ class ClusterManagerInitHelper : Logger::Loggable { * @param per_cluster_init_callback supplies the callback to call when a cluster has itself * initialized. The cluster manager can use this for post-init processing. */ - ClusterManagerInitHelper(ClusterManager& cm, - const std::function& per_cluster_init_callback) + ClusterManagerInitHelper( + ClusterManager& cm, + const std::function& per_cluster_init_callback) : cm_(cm), per_cluster_init_callback_(per_cluster_init_callback) {} enum class State { @@ -135,9 +157,9 @@ class ClusterManagerInitHelper : Logger::Loggable { AllClustersInitialized }; - void addCluster(Cluster& cluster); + void addCluster(ClusterManagerCluster& cluster); void onStaticLoadComplete(); - void removeCluster(Cluster& cluster); + void removeCluster(ClusterManagerCluster& cluster); void setCds(CdsApi* cds); void setPrimaryClustersInitializedCb(ClusterManager::PrimaryClustersReadyCallback callback); void setInitializedCb(ClusterManager::InitializationCompleteCallback callback); @@ -151,15 +173,15 @@ class ClusterManagerInitHelper : Logger::Loggable { void initializeSecondaryClusters(); void maybeFinishInitialize(); - void onClusterInit(Cluster& cluster); + void onClusterInit(ClusterManagerCluster& cluster); ClusterManager& cm_; - std::function per_cluster_init_callback_; + std::function per_cluster_init_callback_; CdsApi* cds_{}; ClusterManager::PrimaryClustersReadyCallback primary_clusters_initialized_callback_; ClusterManager::InitializationCompleteCallback initialized_callback_; - std::list primary_init_clusters_; - std::list secondary_init_clusters_; + std::list primary_init_clusters_; + std::list secondary_init_clusters_; State state_{State::Loading}; bool started_secondary_initialize_{}; }; @@ -273,9 +295,33 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable per_priority_update_params_; + }; + + virtual void postThreadLocalClusterUpdate(ClusterManagerCluster& cm_cluster, + ThreadLocalClusterUpdateParams&& params) { + return postThreadLocalClusterUpdateNonVirtual(cm_cluster, std::move(params)); + } private: /** @@ -361,8 +407,7 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable; - ThreadLocalClusterManagerImpl(ClusterManagerImpl& parent, Event::Dispatcher& dispatcher, - const absl::optional& local_cluster_name); + ThreadLocalClusterManagerImpl(ClusterManagerImpl& parent, Event::Dispatcher& dispatcher); ~ThreadLocalClusterManagerImpl() override; void drainConnPools(const HostVector& hosts); void drainConnPools(HostSharedPtr old_host, ConnPoolsContainer& container); @@ -395,7 +440,7 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggablefactory(); } else { return nullptr; } } + bool addedOrUpdated() override { return added_or_updated_; } + void setAddedOrUpdated() override { + ASSERT(!added_or_updated_); + added_or_updated_ = true; + } const envoy::config::cluster::v3::Cluster cluster_config_; const uint64_t config_hash_; @@ -421,6 +473,7 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable; using ClusterUpdatesMap = absl::node_hash_map; - void applyUpdates(const Cluster& cluster, uint32_t priority, PendingUpdates& updates); - bool scheduleUpdate(const Cluster& cluster, uint32_t priority, bool mergeable, + void applyUpdates(ClusterManagerCluster& cluster, uint32_t priority, PendingUpdates& updates); + bool scheduleUpdate(ClusterManagerCluster& cluster, uint32_t priority, bool mergeable, const uint64_t timeout); - void createOrUpdateThreadLocalCluster(ClusterData& cluster); ProtobufTypes::MessagePtr dumpClusterConfigs(); static ClusterManagerStats generateStats(Stats::Scope& scope); @@ -485,8 +537,10 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable cm_; - ClusterManagerInitHelper init_helper_{cm_, [this](Cluster& cluster) { onClusterInit(cluster); }}; + ClusterManagerInitHelper init_helper_{ + cm_, [this](ClusterManagerCluster& cluster) { onClusterInit(cluster); }}; +}; + +class MockClusterManagerCluster : public ClusterManagerCluster { +public: + MockClusterManagerCluster() { ON_CALL(*this, cluster()).WillByDefault(ReturnRef(cluster_)); } + + MOCK_METHOD(Cluster&, cluster, ()); + MOCK_METHOD(LoadBalancerFactorySharedPtr, loadBalancerFactory, ()); + bool addedOrUpdated() override { return added_or_updated_; } + void setAddedOrUpdated() override { + ASSERT(!added_or_updated_); + added_or_updated_ = true; + } + + NiceMock cluster_; + bool added_or_updated_{}; }; TEST_F(ClusterManagerInitHelperTest, ImmediateInitialize) { InSequence s; - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(cluster1, initialize(_)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.addCluster(cluster1); EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); - cluster1.initialize_callback_(); + cluster1.cluster_.initialize_callback_(); init_helper_.onStaticLoadComplete(); init_helper_.startInitializingSecondaryClusters(); @@ -3062,20 +3080,21 @@ TEST_F(ClusterManagerInitHelperTest, ImmediateInitialize) { TEST_F(ClusterManagerInitHelperTest, StaticSdsInitialize) { InSequence s; - NiceMock sds; - ON_CALL(sds, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(sds, initialize(_)); + NiceMock sds; + ON_CALL(sds.cluster_, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(sds.cluster_, initialize(_)); init_helper_.addCluster(sds); EXPECT_CALL(*this, onClusterInit(Ref(sds))); - sds.initialize_callback_(); + sds.cluster_.initialize_callback_(); - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster1); init_helper_.onStaticLoadComplete(); - EXPECT_CALL(cluster1, initialize(_)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.startInitializingSecondaryClusters(); ReadyWatcher cm_initialized; @@ -3083,22 +3102,24 @@ TEST_F(ClusterManagerInitHelperTest, StaticSdsInitialize) { EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); EXPECT_CALL(cm_initialized, ready()); - cluster1.initialize_callback_(); + cluster1.cluster_.initialize_callback_(); } // Verify that primary cluster can be updated in warming state. TEST_F(ClusterManagerInitHelperTest, TestUpdateWarming) { InSequence s; - auto sds = std::make_unique>(); - ON_CALL(*sds, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(*sds, initialize(_)); + auto sds = std::make_unique>(); + ON_CALL(sds->cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(sds->cluster_, initialize(_)); init_helper_.addCluster(*sds); init_helper_.onStaticLoadComplete(); - NiceMock updated_sds; - ON_CALL(updated_sds, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(updated_sds, initialize(_)); + NiceMock updated_sds; + ON_CALL(updated_sds.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(updated_sds.cluster_, initialize(_)); init_helper_.addCluster(updated_sds); // The override cluster is added. Manually drop the previous cluster. In production flow this is @@ -3110,7 +3131,7 @@ TEST_F(ClusterManagerInitHelperTest, TestUpdateWarming) { EXPECT_CALL(*this, onClusterInit(Ref(updated_sds))); EXPECT_CALL(primary_initialized, ready()); - updated_sds.initialize_callback_(); + updated_sds.cluster_.initialize_callback_(); } TEST_F(ClusterManagerInitHelperTest, UpdateAlreadyInitialized) { @@ -3122,25 +3143,27 @@ TEST_F(ClusterManagerInitHelperTest, UpdateAlreadyInitialized) { ReadyWatcher cm_initialized; init_helper_.setInitializedCb([&]() -> void { cm_initialized.ready(); }); - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(cluster1, initialize(_)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.addCluster(cluster1); - NiceMock cluster2; - ON_CALL(cluster2, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(cluster2, initialize(_)); + NiceMock cluster2; + ON_CALL(cluster2.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(cluster2.cluster_, initialize(_)); init_helper_.addCluster(cluster2); init_helper_.onStaticLoadComplete(); EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); - cluster1.initialize_callback_(); + cluster1.cluster_.initialize_callback_(); init_helper_.removeCluster(cluster1); EXPECT_CALL(*this, onClusterInit(Ref(cluster2))); EXPECT_CALL(primary_clusters_initialized, ready()); - cluster2.initialize_callback_(); + cluster2.cluster_.initialize_callback_(); EXPECT_CALL(cm_initialized, ready()); init_helper_.startInitializingSecondaryClusters(); @@ -3159,18 +3182,19 @@ TEST_F(ClusterManagerInitHelperTest, InitSecondaryWithoutEdsPaused) { ReadyWatcher cm_initialized; init_helper_.setInitializedCb([&]() -> void { cm_initialized.ready(); }); - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster1); EXPECT_CALL(primary_clusters_initialized, ready()); init_helper_.onStaticLoadComplete(); - EXPECT_CALL(cluster1, initialize(_)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.startInitializingSecondaryClusters(); EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); EXPECT_CALL(cm_initialized, ready()); - cluster1.initialize_callback_(); + cluster1.cluster_.initialize_callback_(); } // If secondary clusters initialization triggered inside of CdsApiImpl::onConfigUpdate()'s @@ -3186,19 +3210,20 @@ TEST_F(ClusterManagerInitHelperTest, InitSecondaryWithEdsPaused) { ReadyWatcher cm_initialized; init_helper_.setInitializedCb([&]() -> void { cm_initialized.ready(); }); - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster1); EXPECT_CALL(primary_clusters_initialized, ready()); init_helper_.onStaticLoadComplete(); - EXPECT_CALL(cluster1, initialize(_)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.startInitializingSecondaryClusters(); EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); EXPECT_CALL(cm_initialized, ready()); - cluster1.initialize_callback_(); + cluster1.cluster_.initialize_callback_(); } TEST_F(ClusterManagerInitHelperTest, AddSecondaryAfterSecondaryInit) { @@ -3210,44 +3235,48 @@ TEST_F(ClusterManagerInitHelperTest, AddSecondaryAfterSecondaryInit) { ReadyWatcher cm_initialized; init_helper_.setInitializedCb([&]() -> void { cm_initialized.ready(); }); - NiceMock cluster1; - ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Primary)); - EXPECT_CALL(cluster1, initialize(_)); + NiceMock cluster1; + ON_CALL(cluster1.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Primary)); + EXPECT_CALL(cluster1.cluster_, initialize(_)); init_helper_.addCluster(cluster1); - NiceMock cluster2; - cluster2.info_->name_ = "cluster2"; - ON_CALL(cluster2, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + NiceMock cluster2; + cluster2.cluster_.info_->name_ = "cluster2"; + ON_CALL(cluster2.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster2); init_helper_.onStaticLoadComplete(); EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); EXPECT_CALL(primary_clusters_initialized, ready()); - EXPECT_CALL(cluster2, initialize(_)); - cluster1.initialize_callback_(); + EXPECT_CALL(cluster2.cluster_, initialize(_)); + cluster1.cluster_.initialize_callback_(); init_helper_.startInitializingSecondaryClusters(); - NiceMock cluster3; - cluster3.info_->name_ = "cluster3"; + NiceMock cluster3; + cluster3.cluster_.info_->name_ = "cluster3"; - ON_CALL(cluster3, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); - EXPECT_CALL(cluster3, initialize(_)); + ON_CALL(cluster3.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); + EXPECT_CALL(cluster3.cluster_, initialize(_)); init_helper_.addCluster(cluster3); EXPECT_CALL(*this, onClusterInit(Ref(cluster3))); - cluster3.initialize_callback_(); + cluster3.cluster_.initialize_callback_(); EXPECT_CALL(*this, onClusterInit(Ref(cluster2))); EXPECT_CALL(cm_initialized, ready()); - cluster2.initialize_callback_(); + cluster2.cluster_.initialize_callback_(); } // Tests the scenario encountered in Issue 903: The cluster was removed from // the secondary init list while traversing the list. TEST_F(ClusterManagerInitHelperTest, RemoveClusterWithinInitLoop) { InSequence s; - NiceMock cluster; - ON_CALL(cluster, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + NiceMock cluster; + ON_CALL(cluster.cluster_, initializePhase()) + .WillByDefault(Return(Cluster::InitializePhase::Secondary)); init_helper_.addCluster(cluster); // onStaticLoadComplete() must not initialize secondary clusters @@ -3256,7 +3285,7 @@ TEST_F(ClusterManagerInitHelperTest, RemoveClusterWithinInitLoop) { // Set up the scenario seen in Issue 903 where initialize() ultimately results // in the removeCluster() call. In the real bug this was a long and complex call // chain. - EXPECT_CALL(cluster, initialize(_)).WillOnce(Invoke([&](std::function) -> void { + EXPECT_CALL(cluster.cluster_, initialize(_)).WillOnce(Invoke([&](std::function) -> void { init_helper_.removeCluster(cluster); })); diff --git a/test/common/upstream/test_cluster_manager.h b/test/common/upstream/test_cluster_manager.h index d76c5b06b0f1..3cce3f09182d 100644 --- a/test/common/upstream/test_cluster_manager.h +++ b/test/common/upstream/test_cluster_manager.h @@ -199,10 +199,12 @@ class MockedUpdatedClusterManagerImpl : public TestClusterManagerImpl { local_cluster_update_(local_cluster_update), local_hosts_removed_(local_hosts_removed) {} protected: - void postThreadLocalClusterUpdate(const Cluster&, uint32_t priority, - const HostVector& hosts_added, - const HostVector& hosts_removed) override { - local_cluster_update_.post(priority, hosts_added, hosts_removed); + void postThreadLocalClusterUpdate(ClusterManagerCluster&, + ThreadLocalClusterUpdateParams&& params) override { + for (const auto& per_priority : params.per_priority_update_params_) { + local_cluster_update_.post(per_priority.priority_, per_priority.hosts_added_, + per_priority.hosts_removed_); + } } void postThreadLocalDrainConnections(const Cluster&, const HostVector& hosts_removed) override { From 93aa95749894c8adecf3c213b0378b1187eec860 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 13 Nov 2020 19:12:42 +0000 Subject: [PATCH 096/117] docs: Sandbox cleanups/updates (#13989) Signed-off-by: Ryan Northey --- docs/root/_static/css/envoy.css | 5 + docs/root/operations/admin.rst | 2 + .../_include/docker-env-setup-link.rst | 3 + .../sandboxes/_include/docker-env-setup.rst | 23 --- docs/root/start/sandboxes/cache.rst | 40 +++-- docs/root/start/sandboxes/cors.rst | 21 ++- docs/root/start/sandboxes/csrf.rst | 21 ++- docs/root/start/sandboxes/double-proxy.rst | 32 +++- .../dynamic-configuration-control-plane.rst | 60 +++++--- .../dynamic-configuration-filesystem.rst | 47 ++++-- docs/root/start/sandboxes/ext_authz.rst | 53 +++++-- docs/root/start/sandboxes/fault_injection.rst | 46 ++++-- docs/root/start/sandboxes/front_proxy.rst | 49 ++++--- docs/root/start/sandboxes/grpc_bridge.rst | 26 +++- docs/root/start/sandboxes/index.rst | 44 +++++- .../start/sandboxes/jaeger_native_tracing.rst | 46 ++++-- docs/root/start/sandboxes/jaeger_tracing.rst | 36 +++-- .../sandboxes/load_reporting_service.rst | 35 +++-- docs/root/start/sandboxes/lua.rst | 38 +++-- docs/root/start/sandboxes/mysql.rst | 38 ++++- docs/root/start/sandboxes/postgres.rst | 41 ++++-- docs/root/start/sandboxes/redis.rst | 34 ++++- docs/root/start/sandboxes/setup.rst | 137 ++++++++++++++++++ .../start/sandboxes/skywalking_tracing.rst | 43 ++++-- docs/root/start/sandboxes/tls.rst | 29 ++-- docs/root/start/sandboxes/wasm-cc.rst | 47 ++++-- docs/root/start/sandboxes/websocket.rst | 23 +-- docs/root/start/sandboxes/zipkin_tracing.rst | 34 +++-- 28 files changed, 784 insertions(+), 269 deletions(-) create mode 100644 docs/root/start/sandboxes/_include/docker-env-setup-link.rst delete mode 100644 docs/root/start/sandboxes/_include/docker-env-setup.rst create mode 100644 docs/root/start/sandboxes/setup.rst diff --git a/docs/root/_static/css/envoy.css b/docs/root/_static/css/envoy.css index 8021e5df6f21..ade3cb1b16a1 100644 --- a/docs/root/_static/css/envoy.css +++ b/docs/root/_static/css/envoy.css @@ -23,3 +23,8 @@ table.docutils div.line-block { margin-left: 4px; padding: 4px; } + +/* make inline sidebars flow down the right of page */ +.rst-content .sidebar { + clear: right; +} diff --git a/docs/root/operations/admin.rst b/docs/root/operations/admin.rst index 1816322afcfe..ae2dd425a28e 100644 --- a/docs/root/operations/admin.rst +++ b/docs/root/operations/admin.rst @@ -324,6 +324,8 @@ modify different aspects of the server: traffic direction are stopped, listener additions and modifications in that direction are not allowed. +.. _operations_admin_interface_server_info: + .. http:get:: /server_info Outputs a JSON message containing information about the running server. diff --git a/docs/root/start/sandboxes/_include/docker-env-setup-link.rst b/docs/root/start/sandboxes/_include/docker-env-setup-link.rst new file mode 100644 index 000000000000..c30a4edae344 --- /dev/null +++ b/docs/root/start/sandboxes/_include/docker-env-setup-link.rst @@ -0,0 +1,3 @@ +:ref:`Sandbox environment ` + Setup your sandbox environment with Docker and Docker Compose, + and clone the Envoy repository with Git. diff --git a/docs/root/start/sandboxes/_include/docker-env-setup.rst b/docs/root/start/sandboxes/_include/docker-env-setup.rst deleted file mode 100644 index a1ee8dfede6f..000000000000 --- a/docs/root/start/sandboxes/_include/docker-env-setup.rst +++ /dev/null @@ -1,23 +0,0 @@ -The following documentation runs through the setup of Envoy described above. - -Step 1: Install Docker -********************** - -Ensure that you have a recent versions of ``docker`` and ``docker-compose`` installed. - -A simple way to achieve this is via the `Docker Desktop `_. - -Step 2: Clone the Envoy repo -**************************** - -If you have not cloned the Envoy repo, clone it with: - -.. tabs:: - - .. code-tab:: console SSH - - git clone git@github.com:envoyproxy/envoy - - .. code-tab:: console HTTPS - - git clone https://github.com/envoyproxy/envoy.git diff --git a/docs/root/start/sandboxes/cache.rst b/docs/root/start/sandboxes/cache.rst index 8d34e7305490..d58eb7d41dfe 100644 --- a/docs/root/start/sandboxes/cache.rst +++ b/docs/root/start/sandboxes/cache.rst @@ -1,20 +1,32 @@ .. _install_sandboxes_cache_filter: -Cache Filter +Cache filter ============ .. TODO(yosrym93): When a documentation is written for a production-ready Cache Filter, link to it through this doc. +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + In this example, we demonstrate how HTTP caching can be utilized in Envoy by using the Cache Filter. The setup of this sandbox is based on the setup of the :ref:`Front Proxy sandbox `. All incoming requests are routed via the front Envoy, which acts as a reverse proxy sitting on -the edge of the ``envoymesh`` network. Port ``8000`` is exposed by docker -compose (see :repo:`/examples/cache/docker-compose.yaml`) to handle ``HTTP`` calls +the edge of the ``envoymesh`` network. + +Port ``8000`` is exposed by :download:`docker-compose.yaml <_include/cache/docker-compose.yaml>` to handle ``HTTP`` calls to the services. Two backend services are deployed behind the front Envoy, each with a sidecar Envoy. The front Envoy is configured to run the Cache Filter, which stores cacheable responses in an in-memory cache, -and serves it to subsequent requests. In this demo, the responses that are served by the deployed services are stored in :repo:`/examples/cache/responses.yaml`. -This file is mounted to both services' containers, so any changes made to the stored responses while the services are running should be instantly effective (no need to rebuild or rerun). +and serves it to subsequent requests. + +In this demo, the responses that are served by the deployed services are stored in :download:`responses.yaml <_include/cache/responses.yaml>`. + +This file is mounted to both services' containers, so any changes made to the stored responses while the services are +running should be instantly effective (no need to rebuild or rerun). For the purposes of the demo, a response's date of creation is appended to its body before being served. An Etag is computed for every response for validation purposes, which only depends on the response body in the yaml file (i.e. the appended date is not taken into account). @@ -22,11 +34,11 @@ Cached responses can be identified by having an ``age`` header. Validated respon as when a response is validated the ``date`` header is updated, while the body stays the same. Validated responses do not have an ``age`` header. Responses served from the backend service have no ``age`` header, and their ``date`` header is the same as their generation date. -.. include:: _include/docker-env-setup.rst - -Step 3: Start all of our containers +Step 1: Start all of our containers *********************************** +Change to the ``examples/cache`` directory. + .. code-block:: console $ pwd @@ -41,7 +53,7 @@ Step 3: Start all of our containers cache_service1_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 8000/tcp cache_service2_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 8000/tcp -Step 4: Test Envoy's HTTP caching capabilities +Step 2: Test Envoy's HTTP caching capabilities ********************************************** You can now send a request to both services via the ``front-envoy``. Note that since the two services have different routes, @@ -54,7 +66,8 @@ To send a request: ``service_no``: The service to send the request to, 1 or 2. -``response``: The response that is being requested. The responses are found in :repo:`/examples/cache/responses.yaml`. +``response``: The response that is being requested. The responses are found in +:download:`responses.yaml <_include/cache/responses.yaml>`. The provided example responses are: @@ -222,5 +235,8 @@ You will receive a new response that's served from the backend service. The new response will be cached for subsequent requests. You can also add new responses to the yaml file with different ``cache-control`` headers and start experimenting! -To learn more about caching and ``cache-control`` headers visit -the `MDN Web Docs `_. + +.. seealso:: + + `MDN Web Docs `_. + Learn more about caching and ``cache-control`` on the web. diff --git a/docs/root/start/sandboxes/cors.rst b/docs/root/start/sandboxes/cors.rst index cf016484a417..df639cfc17a9 100644 --- a/docs/root/start/sandboxes/cors.rst +++ b/docs/root/start/sandboxes/cors.rst @@ -1,8 +1,12 @@ .. _install_sandboxes_cors: -CORS Filter +CORS filter =========== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + Cross-Origin Resource Sharing (CORS) is a method of enforcing client-side access controls on resources by specifying external domains that are able to access certain or all routes of your domain. Browsers use the presence of HTTP @@ -26,12 +30,10 @@ The CORS enforcement choices are: * Restricted: CORS is enabled on the route requested and the only allowed origin is ``envoyproxy.io``. This will result in a client-side CORS error. -.. include:: _include/docker-env-setup.rst - -Step 3: Start all of our containers +Step 1: Start all of our containers *********************************** -Switch to the ``frontend`` directory in the ``cors`` example, and start the containers: +Change to the ``examples/cors/frontend`` directory, and start the containers: .. code-block:: console @@ -61,7 +63,7 @@ Now, switch to the ``backend`` directory in the ``cors`` example, and start the backend_backend-service_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp backend_front-envoy_1 /docker-entrypoint.sh /bin ... Up 10000/tcp, 0.0.0.0:8002->8000/tcp, 0.0.0.0:8003->8001/tcp -Step 4: Test Envoy's CORS capabilities +Step 2: Test Envoy's CORS capabilities ************************************** You can now open a browser to view your frontend service at http://localhost:8000. @@ -77,7 +79,7 @@ For example: Access to XMLHttpRequest at 'http://192.168.99.100:8002/cors/disabled' from origin 'http://192.168.99.101:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. -Step 5: Check stats of backend via admin +Step 3: Check stats of backend via admin **************************************** When Envoy runs, it can listen to ``admin`` requests if a port is configured. @@ -92,3 +94,8 @@ invalid and valid origins increment as you make requests from the frontend clust http.ingress_http.cors.origin_invalid: 2 http.ingress_http.cors.origin_valid: 7 + +.. seealso:: + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. diff --git a/docs/root/start/sandboxes/csrf.rst b/docs/root/start/sandboxes/csrf.rst index e8bf316d8bb4..22beb26b064c 100644 --- a/docs/root/start/sandboxes/csrf.rst +++ b/docs/root/start/sandboxes/csrf.rst @@ -1,8 +1,12 @@ .. _install_sandboxes_csrf: -CSRF Filter +CSRF filter =========== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + Cross-Site Request Forgery (CSRF) is an attack that occurs when a malicious third-party website exploits a vulnerability that allows them to submit an undesired request on a user's behalf. To mitigate this attack this filter @@ -27,12 +31,10 @@ enforcement. The CSRF enforcement choices are: * Ignored: CSRF is enabled but the request type is a GET. This should bypass the CSRF filter and return successfully. -.. include:: _include/docker-env-setup.rst - -Step 3: Start all of our containers +Step 1: Start all of our containers *********************************** -Switch to the ``samesite`` directory in the ``csrf`` example, and start the containers: +Change to the ``examples/csrf/samesite`` directory, and start the containers: .. code-block:: console @@ -61,7 +63,7 @@ Now, switch to the ``crosssite`` directory in the ``csrf`` example, and start th crosssite_front-envoy_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 0.0.0.0:8002->8000/tcp crosssite_service_1 /docker-entrypoint.sh /bin ... Up 10000/tcp -Step 4: Test Envoy's CSRF capabilities +Step 2: Test Envoy's CSRF capabilities ************************************** You can now open a browser at http://localhost:8002 to view your ``crosssite`` frontend service. @@ -86,7 +88,7 @@ For example: If you change the destination to be the same as one displaying the website and set the ``CSRF`` enforcement to enabled the request will go through successfully. -Step 5: Check stats of backend via admin +Step 3: Check stats of backend via admin **************************************** When Envoy runs, it can listen to ``admin`` requests if a port is configured. In @@ -101,3 +103,8 @@ invalid and valid origins increment as you make requests from the frontend clust http.ingress_http.csrf.missing_source_origin: 0 http.ingress_http.csrf.request_invalid: 1 http.ingress_http.csrf.request_valid: 0 + +.. seealso:: + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. diff --git a/docs/root/start/sandboxes/double-proxy.rst b/docs/root/start/sandboxes/double-proxy.rst index 0aa68e46fc2e..75563a600a66 100644 --- a/docs/root/start/sandboxes/double-proxy.rst +++ b/docs/root/start/sandboxes/double-proxy.rst @@ -3,6 +3,16 @@ Double proxy (with ``mTLS`` encryption) ======================================= +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + + :ref:`openssl ` + Generate ``SSL`` keys and certificates. + This sandbox demonstrates a basic "double proxy" configuration, in which a simple Flask app connects to a PostgreSQL database, with two Envoy proxies in between. @@ -26,11 +36,9 @@ In order to use the sandbox you will first need to generate the necessary ``SSL This example walks through creating a certificate authority, and using it to create a domain key and sign certificates for the proxies. -.. include:: _include/docker-env-setup.rst - Change to the ``examples/double-proxy`` directory. -Step 3: Create a certificate authority +Step 1: Create a certificate authority ************************************** First create a key for the certificate authority: @@ -71,7 +79,7 @@ For the purpose of this example, the defaults should be sufficient. Common Name (e.g. server FQDN or YOUR name) []: Email Address []: -Step 4: Create a domain key +Step 2: Create a domain key *************************** Create a key for the example domain: @@ -84,7 +92,7 @@ Create a key for the example domain: .................................................+++++ e is 65537 (0x010001) -Step 5: Generate certificate signing requests for the proxies +Step 3: Generate certificate signing requests for the proxies ************************************************************* Use the domain key to generate certificate signing requests for each of the proxies: @@ -100,7 +108,7 @@ Use the domain key to generate certificate signing requests for each of the prox -subj "/C=US/ST=CA/O=MyExample, Inc./CN=proxy-postgres-backend.example.com" \ -out certs/proxy-postgres-backend.example.com.csr -Step 6: Sign the proxy certificates +Step 4: Sign the proxy certificates *********************************** You can now use the certificate authority that you created to sign the certificate requests. @@ -140,7 +148,7 @@ the proxies. They keys and certificates are stored in the ``certs/`` directory. -Step 7: Start all of our containers +Step 5: Start all of our containers *********************************** Build and start the containers. @@ -163,7 +171,7 @@ This will load the required keys and certificates into the frontend and backend double-proxy_proxy-postgres-backend_1 /docker-entrypoint.sh /usr ... Up 10000/tcp double-proxy_proxy-postgres-frontend_1 /docker-entrypoint.sh /usr ... Up 10000/tcp -Step 8: Check the flask app can connect to the database +Step 6: Check the flask app can connect to the database ******************************************************* Checking the response at http://localhost:10000, you should see the output from the Flask app: @@ -172,3 +180,11 @@ Checking the response at http://localhost:10000, you should see the output from $ curl -s http://localhost:10000 Connected to Postgres, version: PostgreSQL 13.0 (Debian 13.0-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit + +.. seealso:: + + :ref:`Securing Envoy quick start guide ` + Outline of key concepts for securing Envoy. + + :ref:`TLS sandbox ` + Examples of various ``TLS`` termination patterns with Envoy. diff --git a/docs/root/start/sandboxes/dynamic-configuration-control-plane.rst b/docs/root/start/sandboxes/dynamic-configuration-control-plane.rst index 6874a9c3158c..c6b9319b099e 100644 --- a/docs/root/start/sandboxes/dynamic-configuration-control-plane.rst +++ b/docs/root/start/sandboxes/dynamic-configuration-control-plane.rst @@ -3,19 +3,27 @@ Dynamic configuration (control plane) ===================================== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + + :ref:`jq ` + Parse ``json`` output from the upstream echo servers. + This example walks through configuring Envoy using the `Go Control Plane `_ reference implementation. It demonstrates how configuration provided to Envoy persists, even when the control plane is not available, and provides a trivial example of how to update Envoy's configuration dynamically. -.. include:: _include/docker-env-setup.rst +Step 1: Start the proxy container +********************************* Change directory to ``examples/dynamic-config-cp`` in the Envoy repository. -Step 3: Start the proxy container -********************************* - First build the containers and start the ``proxy`` container. This should also start two upstream ``HTTP`` echo servers, ``service1`` and ``service2``. @@ -36,7 +44,7 @@ The control plane has not yet been started. dynamic-config-cp_service1_1 /bin/echo-server Up 8080/tcp dynamic-config-cp_service2_1 /bin/echo-server Up 8080/tcp -Step 4: Check initial config and web response +Step 2: Check initial config and web response ********************************************* As we have not yet started the control plane, nothing should be responding on port ``10000``. @@ -46,8 +54,8 @@ As we have not yet started the control plane, nothing should be responding on po $ curl http://localhost:10000 curl: (56) Recv failure: Connection reset by peer -Dump the proxy's ``static_clusters`` configuration and you should see the cluster named ``xds_cluster`` -configured for the control plane: +Dump the proxy's :ref:`static_clusters ` +configuration and you should see the cluster named ``xds_cluster`` configured for the control plane: .. code-block:: console @@ -57,14 +65,15 @@ configured for the control plane: :language: json :emphasize-lines: 10, 18-19 -No ``dynamic_active_clusters`` have been configured yet: +No :ref:`dynamic_active_clusters ` +have been configured yet: .. code-block:: console $ curl -s http://localhost:19000/config_dump | jq '.configs[1].dynamic_active_clusters' null -Step 5: Start the control plane +Step 3: Start the control plane ******************************* Start up the ``go-control-plane`` service. @@ -83,7 +92,7 @@ You may need to wait a moment or two for it to become ``healthy``. dynamic-config-cp_service1_1 /bin/echo-server Up 8080/tcp dynamic-config-cp_service2_1 /bin/echo-server Up 8080/tcp -Step 6: Query the proxy +Step 4: Query the proxy *********************** Once the control plane has started and is ``healthy``, you should be able to make a request to port ``10000``, @@ -107,8 +116,8 @@ which will be served by ``service1``. Step 5: Dump Envoy's ``dynamic_active_clusters`` config ******************************************************* -If you now dump the proxy's ``dynamic_active_clusters`` configuration, you should see it is configured -with the ``example_proxy_cluster`` pointing to ``service1``, and a version of ``1``. +If you now dump the proxy's :ref:`dynamic_active_clusters ` +configuration, you should see it is configured with the ``example_proxy_cluster`` pointing to ``service1``, and a version of ``1``. .. code-block:: console @@ -134,11 +143,11 @@ The Envoy proxy should continue proxying responses from ``service1``. $ curl http://localhost:10000 | grep "served by" Request served by service1 -Step 7: Edit resource.go and restart the go-control-plane -********************************************************* +Step 7: Edit ``go`` file and restart the control plane +****************************************************** -The example setup starts `go-control-plane `_ -with a custom :download:`resource.go <_include/dynamic-config-cp/resource.go>` file which +The example setup starts the ``go-control-plane`` +service with a custom :download:`resource.go <_include/dynamic-config-cp/resource.go>` file which specifies the configuration provided to Envoy. Update this to have Envoy proxy instead to ``service2``. @@ -181,8 +190,9 @@ Now when you make a request to the proxy it should be served by the ``service2`` $ curl http://localhost:10000 | grep "served by" Request served by service2 -Dumping the ``dynamic_active_clusters`` you should see the cluster configuration now has a version -of ``2``, and ``example_proxy_cluster`` is configured to proxy to ``service2``: +Dumping the :ref:`dynamic_active_clusters ` +you should see the cluster configuration now has a version of ``2``, and ``example_proxy_cluster`` +is configured to proxy to ``service2``: .. code-block:: console @@ -191,3 +201,17 @@ of ``2``, and ``example_proxy_cluster`` is configured to proxy to ``service2``: .. literalinclude:: _include/dynamic-config-cp/response-config-active-clusters-updated.json :language: json :emphasize-lines: 3, 11, 19-20 + +.. seealso:: + + :ref:`Dynamic configuration (control plane) quick start guide ` + Quick start guide to dynamic configuration of Envoy with a control plane. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + :ref:`Dynamic configuration (filesystem) sandbox ` + Configure Envoy using filesystem-based dynamic configuration. + + `Go control plane `_ + Reference implementation of Envoy control plane written in ``go``. diff --git a/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst b/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst index b746257f9692..9364e98f40aa 100644 --- a/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst +++ b/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst @@ -3,18 +3,26 @@ Dynamic configuration (filesystem) ================================== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + + :ref:`jq ` + Parse ``json`` output from the upstream echo servers. + This example walks through configuring Envoy using filesystem-based dynamic configuration. It demonstrates how configuration provided to Envoy dynamically can be updated without restarting the server. -.. include:: _include/docker-env-setup.rst +Step 1: Start the proxy container +********************************* Change directory to ``examples/dynamic-config-fs`` in the Envoy repository. -Step 3: Start the proxy container -********************************* - Build and start the containers. This should also start two upstream ``HTTP`` echo servers, ``service1`` and ``service2``. @@ -33,7 +41,7 @@ This should also start two upstream ``HTTP`` echo servers, ``service1`` and ``se dynamic-config-fs_service1_1 /bin/echo-server Up 8080/tcp dynamic-config-fs_service2_1 /bin/echo-server Up 8080/tcp -Step 4: Check web response +Step 2: Check web response ************************** You should be able to make a request to port ``10000``, which will be served by ``service1``. @@ -52,11 +60,11 @@ You should be able to make a request to port ``10000``, which will be served by X-Request-Id: 6672902d-56ca-456c-be6a-992a603cab9a X-Envoy-Expected-Rq-Timeout-Ms: 15000 -Step 5: Dump Envoy's ``dynamic_active_clusters`` config +Step 3: Dump Envoy's ``dynamic_active_clusters`` config ******************************************************* -If you now dump the proxy’s ``dynamic_active_clusters`` configuration, you should see it is configured with -the ``example_proxy_cluster`` pointing to ``service1``. +If you now dump the proxy’s :ref:`dynamic_active_clusters ` +configuration, you should see it is configured with the ``example_proxy_cluster`` pointing to ``service1``. .. code-block:: console @@ -66,14 +74,14 @@ the ``example_proxy_cluster`` pointing to ``service1``. :language: json :emphasize-lines: 10, 18-19 -Step 5: Edit ``cds.yaml`` inside the container to update upstream cluster +Step 4: Edit ``cds.yaml`` inside the container to update upstream cluster ************************************************************************* The example setup provides Envoy with two dynamic configuration files: -- :download:`cds.yaml <_include/dynamic-config-fs/configs/cds.yaml>` to provide a :ref:`Cluster +- :download:`configs/cds.yaml <_include/dynamic-config-fs/configs/cds.yaml>` to provide a :ref:`Cluster discovery service (CDS) `. -- :download:`lds.yaml <_include/dynamic-config-fs/configs/lds.yaml>` to provide a :ref:`Listener +- :download:`configs/lds.yaml <_include/dynamic-config-fs/configs/lds.yaml>` to provide a :ref:`Listener discovery service (CDS) `. Edit ``cds.yaml`` inside the container and change the cluster address @@ -92,7 +100,7 @@ You can do this using ``sed`` inside the container: docker-compose exec -T proxy sed -i s/service1/service2/ /var/lib/envoy/cds.yaml -Step 6: Check Envoy uses updated configuration +Step 5: Check Envoy uses updated configuration ********************************************** Checking the web response again, the request should now be handled by ``service2``: @@ -102,8 +110,8 @@ Checking the web response again, the request should now be handled by ``service2 $ curl http://localhost:10000 | grep "served by" Request served by service2 -Dumping the ``dynamic_active_clusters``, the ``example_proxy_cluster`` should now be -configured to proxy to ``service2``: +Dumping the :ref:`dynamic_active_clusters `, +the ``example_proxy_cluster`` should now be configured to proxy to ``service2``: .. code-block:: console @@ -112,3 +120,14 @@ configured to proxy to ``service2``: .. literalinclude:: _include/dynamic-config-fs/response-config-active-clusters-updated.json :language: json :emphasize-lines: 10, 18-19 + +.. seealso:: + + :ref:`Dynamic configuration (filesystem) quick start guide ` + Quick start guide to filesystem-based dynamic configuration of Envoy. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + :ref:`Dynamic configuration (control plane) sandbox ` + Configure Envoy dynamically with the Go Control Plane. diff --git a/docs/root/start/sandboxes/ext_authz.rst b/docs/root/start/sandboxes/ext_authz.rst index f9e687cc03a0..35114beca8ea 100644 --- a/docs/root/start/sandboxes/ext_authz.rst +++ b/docs/root/start/sandboxes/ext_authz.rst @@ -1,7 +1,14 @@ .. _install_sandboxes_ext_authz: -External Authorization Filter -============================= +External authorization (``ext_authz``) filter +============================================= + +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. The External Authorization sandbox demonstrates Envoy's :ref:`ext_authz filter ` capability to delegate authorization of incoming requests through Envoy to an external services. @@ -14,11 +21,11 @@ service behind the proxy will be checked by an external HTTP or gRPC service. In for every authorized call, the external authorization service adds additional ``x-current-user`` header entry to the original request headers to be forwarded to the upstream service. -.. include:: _include/docker-env-setup.rst - -Step 3: Start all of our containers +Step 1: Start all of our containers *********************************** +Change to the ``examples/ext_authz`` directory. + To build this sandbox example and start the example services, run the following commands: .. code-block:: console @@ -41,11 +48,16 @@ To build this sandbox example and start the example services, run the following This sandbox has multiple setup controlled by ``FRONT_ENVOY_YAML`` environment variable which points to the effective Envoy configuration to be used. The default value of ``FRONT_ENVOY_YAML`` can be defined in the ``.env`` file or provided inline when running the ``docker-compose up`` - command. For more information, pease take a look at `environment variables in Compose documentation `_. + command. + + For more information, please take a look at + `environment variables in Compose documentation `_. -By default, ``FRONT_ENVOY_YAML`` points to ``config/grpc-service/v3.yaml`` file which bootstraps -front-envoy with ext_authz HTTP filter with gRPC service ``V3`` (this is specified by :ref:`transport_api_version field`). -The possible values of ``FRONT_ENVOY_YAML`` can be found inside the ``envoy/examples/ext_authz/config`` +By default, ``FRONT_ENVOY_YAML`` points to :download:`config/grpc-service/v3.yaml <_include/ext_authz/config/grpc-service/v3.yaml>` +file which bootstraps front-envoy with ext_authz HTTP filter with gRPC service ``V3`` (this is specified by +:ref:`transport_api_version field`). + +The possible values of ``FRONT_ENVOY_YAML`` can be found inside the ``config`` directory. For example, to run Envoy with ext_authz HTTP filter with HTTP service will be: @@ -87,8 +99,8 @@ to provide a ``Bearer`` token via the ``Authorization`` header. .. note:: - A complete list of users is defined in ``envoy/examples/ext_authz/auth/users.json`` file. For - example, the ``token1`` used in the below example is corresponding to ``user1``. + A complete list of users is defined in :download:`auth/users.json <_include/ext_authz/auth/users.json>` + file. For example, the ``token1`` used in the below example is corresponding to ``user1``. An example of successful requests can be observed as follows: @@ -115,7 +127,7 @@ An example of successful requests can be observed as follows: Hello user1 from behind Envoy! We can also employ `Open Policy Agent `_ server -(with `envoy_ext_authz_grpc `_ plugin enabled) +(with `envoy_ext_authz_grpc `_ plugin enabled) as the authorization server. To run this example: .. code-block:: console @@ -151,7 +163,8 @@ And sending a request to the upstream service (via the Front Envoy) gives: Hello OPA from behind Envoy! From the logs, we can observe the policy decision message from the Open Policy Agent server (for -the above request against the defined policy in ``config/opa-service/policy.rego``): +the above request against the defined policy in +:download:`config/opa-service/policy.rego <_include/ext_authz/config/opa-service/policy.rego>`): .. code-block:: console @@ -205,3 +218,17 @@ Trying to send a request with method other than ``GET`` gives a rejection: < date: Thu, 02 Jul 2020 06:46:13 GMT < server: envoy < content-length: 0 + +.. seealso:: + + :ref:`ext_authz filter ` + Learn more about using Envoy's ``ext_authz`` filter. + + `Open Policy Agent `_ + Policy-based control for cloud native environments. + + `envoy_ext_authz_grpc `_ + Open Policy Agent Envoy plugin. + + `environment variables in Compose documentation `_. + Further information about using env variables with Docker Compose. diff --git a/docs/root/start/sandboxes/fault_injection.rst b/docs/root/start/sandboxes/fault_injection.rst index 2faae8c3ac57..c699110c3d68 100644 --- a/docs/root/start/sandboxes/fault_injection.rst +++ b/docs/root/start/sandboxes/fault_injection.rst @@ -1,15 +1,23 @@ .. _install_sandboxes_fault_injection: -Fault Injection Filter +Fault injection filter ====================== -This simple example demonstrates Envoy's :ref:`fault injection ` capability using Envoy's :ref:`runtime support ` to control the feature. +.. sidebar:: Requirements -.. include:: _include/docker-env-setup.rst + .. include:: _include/docker-env-setup-link.rst -Step 3: Start all of our containers +This simple example demonstrates Envoy's :ref:`fault injection ` capability +using Envoy's :ref:`runtime support ` to control the feature. + +It demonstrates fault injection that cause the request to abort and fail, and also faults that simply delay the +response. + +Step 1: Start all of our containers *********************************** +Change to the ``examples/fault_injection`` directory. + Terminal 1 .. code-block:: console @@ -25,7 +33,7 @@ Terminal 1 fault-injection_backend_1 gunicorn -b 0.0.0.0:80 htt Up 0.0.0.0:8080->80/tcp fault-injection_envoy_1 /docker-entrypoint.sh /usr Up 10000/tcp, 0.0.0.0:9211->9211/tcp -Step 4: Start sending continuous stream of HTTP requests +Step 2: Start sending continuous stream of HTTP requests ******************************************************** Terminal 2 @@ -37,9 +45,14 @@ Terminal 2 $ docker-compose exec envoy bash $ bash send_request.sh -The script above (``send_request.sh``) sends a continuous stream of HTTP requests to Envoy, which in turn forwards the requests to the backend container. Fauilt injection is configured in Envoy but turned off (i.e. affects 0% of requests). Consequently, you should see a continuous sequence of HTTP 200 response codes. +The script above (:download:`send_request.sh <_include/fault-injection/send_request.sh>`) sends a continuous stream +of HTTP requests to Envoy, which in turn forwards the requests to the backend container. + +Fault injection is configured in Envoy but turned off (i.e. affects 0% of requests). + +Consequently, you should see a continuous sequence of ``HTTP 200`` response codes. -Step 5: Test Envoy's abort fault injection +Step 3: Test Envoy's abort fault injection ****************************************** Turn on *abort* fault injection via the runtime using the commands below. @@ -51,8 +64,9 @@ Terminal 3 $ docker-compose exec envoy bash $ bash enable_abort_fault_injection.sh -The script above enables HTTP aborts for 100% of requests. So, you should now see a continuous sequence of HTTP 503 -responses for all requests. +The script above enables ``HTTP`` aborts for 100% of requests. + +You should now see a continuous sequence of ``HTTP 503`` responses for all requests. To disable the abort injection: @@ -62,7 +76,7 @@ Terminal 3 $ bash disable_abort_fault_injection.sh -Step 6: Test Envoy's delay fault injection +Step 4: Test Envoy's delay fault injection ****************************************** Turn on *delay* fault injection via the runtime using the commands below. @@ -74,7 +88,10 @@ Terminal 3 $ docker-compose exec envoy bash $ bash enable_delay_fault_injection.sh -The script above will add a 3-second delay to 50% of HTTP requests. You should now see a continuous sequence of HTTP 200 responses for all requests, but half of the requests will take 3 seconds to complete. +The script above will add a 3-second delay to 50% of ``HTTP`` requests. + +You should now see a continuous sequence of ``HTTP 200`` responses for all requests, but half of the requests +will take 3 seconds to complete. To disable the delay injection: @@ -84,7 +101,7 @@ Terminal 3 $ bash disable_delay_fault_injection.sh -Step 7: Check the current runtime filesystem +Step 5: Check the current runtime filesystem ******************************************** To see the current runtime filesystem overview: @@ -94,3 +111,8 @@ Terminal 3 .. code-block:: console $ tree /srv/runtime + +.. seealso:: + + :ref:`Fault injection ` + Learn more about Envoy's ``HTTP`` fault injection filter. diff --git a/docs/root/start/sandboxes/front_proxy.rst b/docs/root/start/sandboxes/front_proxy.rst index afd0d213953b..5b3652b47fe5 100644 --- a/docs/root/start/sandboxes/front_proxy.rst +++ b/docs/root/start/sandboxes/front_proxy.rst @@ -1,12 +1,20 @@ .. _install_sandboxes_front_proxy: -Front Proxy +Front proxy =========== -To get a flavor of what Envoy has to offer as a front proxy, we are releasing a `docker compose `_ -sandbox that deploys a front Envoy and a couple of services (simple Flask apps) colocated with a -running service Envoy. The three containers will be deployed inside a virtual network called -``envoymesh``. +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + +To get a flavor of what Envoy has to offer as a front proxy, we are releasing a +`docker compose `_ sandbox that deploys a front Envoy and a +couple of services (simple Flask apps) colocated with a running service Envoy. + +The three containers will be deployed inside a virtual network called ``envoymesh``. Below you can see a graphic showing the docker compose deployment: @@ -15,22 +23,22 @@ Below you can see a graphic showing the docker compose deployment: All incoming requests are routed via the front Envoy, which is acting as a reverse proxy sitting on the edge of the ``envoymesh`` network. Port ``8080``, ``8443``, and ``8001`` are exposed by docker -compose (see :repo:`/examples/front-proxy/docker-compose.yaml`) to handle ``HTTP``, ``HTTPS`` calls -to the services and requests to ``/admin`` respectively. +compose (see :download:`docker-compose.yaml <_include/front-proxy/docker-compose.yaml>`) to handle +``HTTP``, ``HTTPS`` calls to the services and requests to ``/admin`` respectively. Moreover, notice that all traffic routed by the front Envoy to the service containers is actually -routed to the service Envoys (routes setup in :repo:`/examples/front-proxy/front-envoy.yaml`). +routed to the service Envoys (routes setup in :download:`front-envoy.yaml <_include/front-proxy/front-envoy.yaml>`). In turn the service Envoys route the request to the Flask app via the loopback -address (routes setup in :repo:`/examples/front-proxy/service-envoy.yaml`). This +address (routes setup in :download:`service-envoy.yaml <_include/front-proxy/service-envoy.yaml>`). This setup illustrates the advantage of running service Envoys collocated with your services: all requests are handled by the service Envoy, and efficiently routed to your services. -.. include:: _include/docker-env-setup.rst - -Step 3: Start all of our containers +Step 1: Start all of our containers *********************************** +Change to the ``examples/front-proxy`` directory. + .. code-block:: console $ pwd @@ -45,7 +53,7 @@ Step 3: Start all of our containers front-proxy_service1_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp front-proxy_service2_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp -Step 4: Test Envoy's routing capabilities +Step 2: Test Envoy's routing capabilities ***************************************** You can now send a request to both services via the ``front-envoy``. @@ -142,7 +150,7 @@ We can also use ``HTTPS`` to call services behind the front Envoy. For example, < Hello from behind Envoy (service 1)! hostname: 36418bc3c824 resolvedhostname: 192.168.160.4 -Step 5: Test Envoy's load balancing capabilities +Step 3: Test Envoy's load balancing capabilities ************************************************ Now let's scale up our ``service1`` nodes to demonstrate the load balancing abilities of Envoy: @@ -211,7 +219,7 @@ requests by doing a round robin of the three ``service1`` machines: < Hello from behind Envoy (service 1)! hostname: 36418bc3c824 resolvedhostname: 192.168.160.4 -Step 6: Enter containers and curl services +Step 4: Enter containers and curl services ****************************************** In addition of using ``curl`` from your host machine, you can also enter the @@ -231,7 +239,7 @@ enter the ``front-envoy`` container, and ``curl`` for services locally: root@81288499f9d7:/# curl localhost:8080/service/2 Hello from behind Envoy (service 2)! hostname: 92f4a3737bbc resolvedhostname: 172.19.0.2 -Step 7: Enter container and curl admin +Step 5: Enter container and curl admin ************************************** When Envoy runs it also attaches an ``admin`` to your desired port. @@ -240,8 +248,8 @@ In the example configs the admin is bound to port ``8001``. We can ``curl`` it to gain useful information: -- ``/server_info`` provides information about the Envoy version you are running. -- ``/stats`` provides statistics about the Envoy server. +- :ref:`/server_info ` provides information about the Envoy version you are running. +- :ref:`/stats ` provides statistics about the Envoy server. In the example we can we can enter the ``front-envoy`` container to query admin: @@ -318,3 +326,8 @@ In the example we can we can enter the ``front-envoy`` container to query admin: Notice that we can get the number of members of upstream clusters, number of requests fulfilled by them, information about http ingress, and a plethora of other useful stats. + +.. seealso:: + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. diff --git a/docs/root/start/sandboxes/grpc_bridge.rst b/docs/root/start/sandboxes/grpc_bridge.rst index 7e5dd8cb45ce..7d729bcad428 100644 --- a/docs/root/start/sandboxes/grpc_bridge.rst +++ b/docs/root/start/sandboxes/grpc_bridge.rst @@ -1,8 +1,12 @@ .. _install_sandboxes_grpc_bridge: -gRPC Bridge +gRPC bridge =========== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + The gRPC bridge sandbox is an example usage of Envoy's :ref:`gRPC bridge filter `. @@ -16,16 +20,17 @@ The client send messages through a proxy that upgrades the HTTP requests from `` Another Envoy feature demonstrated in this example is Envoy's ability to do authority base routing via its route configuration. -.. include:: _include/docker-env-setup.rst - -Step 3: Generate the protocol stubs +Step 1: Generate the protocol stubs *********************************** +Change to the ``examples/grpc-bridge`` directory. + A docker-compose file is provided that generates the stubs for both ``client`` and ``server`` from the specification in the ``protos`` directory. -Inspecting the ``docker-compose-protos.yaml`` file, you will see that it contains both the ``python`` -and ``go`` gRPC protoc commands necessary for generating the protocol stubs. +Inspecting the :download:`docker-compose-protos.yaml <_include/grpc-bridge/docker-compose-protos.yaml>` file, +you will see that it contains both the ``python`` and ``go`` gRPC protoc commands necessary for generating the +protocol stubs. Generate the stubs as follows: @@ -59,7 +64,7 @@ respective directories: These generated ``python`` and ``go`` stubs can be included as external modules. -Step 4: Start all of our containers +Step 2: Start all of our containers *********************************** To build this sandbox example and start the example services, run the following commands: @@ -79,7 +84,7 @@ To build this sandbox example and start the example services, run the following grpc-bridge_grpc-server-proxy_1 /docker-entrypoint.sh /bin ... Up 10000/tcp, 0.0.0.0:8811->8811/tcp grpc-bridge_grpc-server_1 /bin/sh -c /bin/server Up 0.0.0.0:8081->8081/tcp -Step 5: Send requests to the Key/Value store +Step 3: Send requests to the Key/Value store ******************************************** To use the Python service and send gRPC requests: @@ -126,3 +131,8 @@ In the running docker-compose container, you should see the gRPC service printin grpc_1 | 2017/05/30 12:05:09 set: foo = bar grpc_1 | 2017/05/30 12:05:12 get: foo grpc_1 | 2017/05/30 12:05:18 set: foo = baz + +.. seealso:: + + :ref:`gRPC bridge filter `. + Learn more about configuring Envoy's gRPC bridge filter. diff --git a/docs/root/start/sandboxes/index.rst b/docs/root/start/sandboxes/index.rst index 28d5ecfb3a55..d1b686d01820 100644 --- a/docs/root/start/sandboxes/index.rst +++ b/docs/root/start/sandboxes/index.rst @@ -3,14 +3,48 @@ Sandboxes --------- -We've created a number of sandboxes using Docker Compose that set up different -environments to test out Envoy's features and show sample configurations. As we -gauge peoples' interests we will add more sandboxes demonstrating different -features. The following sandboxes are available: +.. sidebar:: Contributing + + If there are other sandboxes you would like to see demonstrated here + please + `open a ticket on github `_. + + :repo:`The Envoy project welcomes contributions ` and would be happy to review a + `Pull Request `_ with the necessary changes + should you be able to create one. + + :repo:`See the sandbox developer documentation ` for more information about + creating your own sandbox. + +.. sidebar:: Compatibility + + As the examples use the pre-built :ref:`Envoy Docker images ` they should work + on the following architectures: + + - x86_64 + - ARM 64 + + Some of the examples may use pre-built (x86) binaries and will therefore have more limited + compatibility. + +We have created a number of sandboxes using `Docker Compose `_ +that set up environments to test out Envoy's features and show sample configurations. + +These can be used to learn Envoy and model your own configurations. + + +Before you begin you will need to install the sandbox environment. .. toctree:: :maxdepth: 2 + setup + +The following sandboxes are available: + +.. toctree:: + :maxdepth: 1 + cache cors csrf @@ -28,8 +62,8 @@ features. The following sandboxes are available: mysql postgres redis + skywalking_tracing tls wasm-cc websocket zipkin_tracing - skywalking_tracing diff --git a/docs/root/start/sandboxes/jaeger_native_tracing.rst b/docs/root/start/sandboxes/jaeger_native_tracing.rst index e99531c21d7b..6853380704dc 100644 --- a/docs/root/start/sandboxes/jaeger_native_tracing.rst +++ b/docs/root/start/sandboxes/jaeger_native_tracing.rst @@ -1,8 +1,20 @@ .. _install_sandboxes_jaeger_native_tracing: -Jaeger Native Tracing +Jaeger native tracing ===================== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + +.. sidebar:: Compatibility + + The jaeger native tracing sandbox uses a binary built for ``x86_64``, and will therefore + only work on that architecture. + The Jaeger tracing sandbox demonstrates Envoy's :ref:`request tracing ` capabilities using `Jaeger `_ as the tracing provider and Jaeger's native `C++ client `_ as a plugin. Using Jaeger with its @@ -14,22 +26,17 @@ native client instead of with Envoy's builtin Zipkin client has the following ad can be used, including probabilistic or remote where sampling can be centrally controlled from Jaeger's backend. - Spans are sent to the collector in a more efficient binary encoding. - This sandbox is very similar to the front proxy architecture described above, with one difference: service1 makes an API call to service2 before returning a response. The three containers will be deployed inside a virtual network called ``envoymesh``. -.. note:: - - The jaeger native tracing sandbox only works on x86-64. - All incoming requests are routed via the front Envoy, which is acting as a reverse proxy sitting on the edge of the ``envoymesh`` network. Port ``8000`` is exposed -by docker compose (see :repo:`/examples/jaeger-native-tracing/docker-compose.yaml`). Notice that +by docker compose (see :download:`docker-compose.yaml <_include/jaeger-native-tracing/docker-compose.yaml>`). Notice that all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in -:repo:`/examples/jaeger-native-tracing/front-envoy-jaeger.yaml`) and setup to propagate the spans generated +:download:`front-envoy-jaeger.yaml <_include/jaeger-native-tracing/front-envoy-jaeger.yaml>`) and setup to propagate the spans generated by the Jaeger tracer to a Jaeger cluster (trace driver setup -in :repo:`/examples/jaeger-native-tracing/front-envoy-jaeger.yaml`). +in :download:`front-envoy-jaeger.yaml <_include/jaeger-native-tracing/front-envoy-jaeger.yaml>`). Before routing a request to the appropriate service Envoy or the application, Envoy will take care of generating the appropriate spans for tracing (parent/child context spans). @@ -40,11 +47,9 @@ One of the most important benefits of tracing from Envoy is that it will take ca propagating the traces to the Jaeger service cluster. However, in order to fully take advantage of tracing, the application has to propagate trace headers that Envoy generates, while making calls to other services. In the sandbox we have provided, the simple flask app -(see trace function in :repo:`/examples/front-proxy/service.py`) acting as service1 propagates +(see trace function in :download:`front-proxy/service.py <_include/front-proxy/service.py>`) acting as service1 propagates the trace headers while making an outbound call to service2. -.. include:: _include/docker-env-setup.rst - Step 3: Build the sandbox ************************* @@ -97,3 +102,20 @@ Point your browser to http://localhost:16686 . You should see the Jaeger dashboa Set the service to "front-proxy" and hit 'Find Traces'. You should see traces from the front-proxy. Click on a trace to explore the path taken by the request from front-proxy to service1 to service2, as well as the latency incurred at each hop. + +.. seealso:: + + :ref:`Request tracing ` + Learn more about using Envoy's request tracing. + + `Jaeger `_ + Jaeger tracing website. + + `Jaeger C++ client `_ + The Jaeger C++ cient. + + `Jaeger Go client `_ + The Jaeger Go client. + + `Jaeger sampling strategies `_ + More information about Jaeger sampling strategies. diff --git a/docs/root/start/sandboxes/jaeger_tracing.rst b/docs/root/start/sandboxes/jaeger_tracing.rst index 6029185d5ab5..63fd1e77f6ea 100644 --- a/docs/root/start/sandboxes/jaeger_tracing.rst +++ b/docs/root/start/sandboxes/jaeger_tracing.rst @@ -1,8 +1,15 @@ .. _install_sandboxes_jaeger_tracing: -Jaeger Tracing +Jaeger tracing ============== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + The Jaeger tracing sandbox demonstrates Envoy's :ref:`request tracing ` capabilities using `Jaeger `_ as the tracing provider. This sandbox is very similar to the front proxy architecture described above, with one difference: @@ -11,11 +18,11 @@ The three containers will be deployed inside a virtual network called ``envoymes All incoming requests are routed via the front Envoy, which is acting as a reverse proxy sitting on the edge of the ``envoymesh`` network. Port ``8000`` is exposed -by docker compose (see :repo:`/examples/jaeger-tracing/docker-compose.yaml`). Notice that +by docker compose (see :download:`docker-compose.yaml <_include/jaeger-tracing/docker-compose.yaml>`). Notice that all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in -:repo:`/examples/jaeger-tracing/front-envoy-jaeger.yaml`) and setup to propagate the spans generated +:download:`front-envoy-jaeger.yaml <_include/jaeger-tracing/front-envoy-jaeger.yaml>`) and setup to propagate the spans generated by the Jaeger tracer to a Jaeger cluster (trace driver setup -in :repo:`/examples/jaeger-tracing/front-envoy-jaeger.yaml`). +in :download:`front-envoy-jaeger.yaml <_include/jaeger-tracing/front-envoy-jaeger.yaml>`). Before routing a request to the appropriate service Envoy or the application, Envoy will take care of generating the appropriate spans for tracing (parent/child context spans). @@ -26,12 +33,10 @@ One of the most important benefits of tracing from Envoy is that it will take ca propagating the traces to the Jaeger service cluster. However, in order to fully take advantage of tracing, the application has to propagate trace headers that Envoy generates, while making calls to other services. In the sandbox we have provided, the simple flask app -(see trace function in :repo:`/examples/front-proxy/service.py`) acting as service1 propagates +(see trace function in :download:`service.py <_include/front-proxy/service.py>`) acting as service1 propagates the trace headers while making an outbound call to service2. -.. include:: _include/docker-env-setup.rst - -Step 3: Build the sandbox +Step 1: Build the sandbox ************************* To build this sandbox example, and start the example apps run the following commands: @@ -51,7 +56,7 @@ To build this sandbox example, and start the example apps run the following comm jaeger-tracing_service1_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp jaeger-tracing_service2_1 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp -Step 4: Generate some load +Step 2: Generate some load ************************** You can now send a request to service1 via the front-envoy as follows: @@ -76,10 +81,21 @@ You can now send a request to service1 via the front-envoy as follows: Hello from behind Envoy (service 1)! hostname: f26027f1ce28 resolvedhostname: 172.19.0.6 * Connection #0 to host 192.168.99.100 left intact -Step 5: View the traces in Jaeger UI +Step 3: View the traces in Jaeger UI ************************************ Point your browser to http://localhost:16686 . You should see the Jaeger dashboard. Set the service to "front-proxy" and hit 'Find Traces'. You should see traces from the front-proxy. Click on a trace to explore the path taken by the request from front-proxy to service1 to service2, as well as the latency incurred at each hop. + +.. seealso:: + + :ref:`Request tracing ` + Learn more about using Envoy's request tracing. + + :ref:`Jaeger native tracing sandbox ` + An example of using Jaeger natively with Envoy. + + `Jaeger `_ + Jaeger tracing website. diff --git a/docs/root/start/sandboxes/load_reporting_service.rst b/docs/root/start/sandboxes/load_reporting_service.rst index 4258cf57fcc0..59d1bdfd19f8 100644 --- a/docs/root/start/sandboxes/load_reporting_service.rst +++ b/docs/root/start/sandboxes/load_reporting_service.rst @@ -1,9 +1,14 @@ .. _install_sandboxes_load_reporting_service: -Load Reporting Service (LRS) -============================ +Load reporting service (``LRS``) +================================ -This simple example demonstrates Envoy's Load Reporting Service (LRS) capability and how to use it. +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + +This simple example demonstrates Envoy's :ref:`Load reporting service (LRS) ` +capability and how to use it. Lets say Cluster A (downstream) talks to Cluster B (Upstream) and Cluster C (Upstream). When enabling Load Report for Cluster A, LRS server should be sending LoadStatsResponse to Cluster A with LoadStatsResponse.Clusters to be B and C. @@ -12,13 +17,14 @@ from Cluster A to Cluster C. In this example, all incoming requests are routed via Envoy to a simple goLang web server aka http_server. We scale up two containers and randomly send requests to each. Envoy is configured to initiate the connection with LRS Server. -LRS Server enables the stats by sending LoadStatsResponse. Sending requests to http_server will be counted towards successful requests and will be visible in LRS Server logs. +LRS Server enables the stats by sending LoadStatsResponse. Sending requests to http_server will be counted towards successful +requests and will be visible in LRS Server logs. -.. include:: _include/docker-env-setup.rst - -Step 3: Build the sandbox +Step 1: Build the sandbox ************************* +Change to the ``examples/load-reporting-service`` directory. + Terminal 1 :: $ pwd @@ -39,7 +45,7 @@ Terminal 2 :: load-reporting-service_http_service_2 /bin/sh -c /usr/local/bin/ ... Up 10000/tcp, 0.0.0.0:80->80/tcp load-reporting-service_lrs_server_1 go run main.go Up 0.0.0.0:18000->18000/tcp -Step 4: Start sending stream of HTTP requests +Step 2: Start sending stream of HTTP requests ********************************************* Terminal 2 :: @@ -48,9 +54,10 @@ Terminal 2 :: envoy/examples/load_reporting_service $ bash send_requests.sh -The script above (``send_requests.sh``) sends requests randomly to each Envoy, which in turn forwards the requests to the backend service. +The script above (:download:`send_requests.sh <_include/load-reporting-service/send_requests.sh>`) sends requests +randomly to each Envoy, which in turn forwards the requests to the backend service. -Step 5: See Envoy Stats +Step 3: See Envoy Stats *********************** You should see @@ -73,3 +80,11 @@ Terminal 1 :: ............................ lrs_server_1 | 2020/02/12 17:09:09 Got stats from cluster `http_service` node `0022a319e1e2` - cluster_name:"local_service" upstream_locality_stats: total_successful_requests:3 total_issued_requests:3 > load_report_interval: lrs_server_1 | 2020/02/12 17:09:09 Got stats from cluster `http_service` node `2417806c9d9a` - cluster_name:"local_service" upstream_locality_stats: total_successful_requests:9 total_issued_requests:9 > load_report_interval: + +.. seealso:: + + :ref:`Load reporting service ` + Overview of Envoy's Load reporting service. + + :ref:`Load reporting service API(V3) ` + The Load reporting service API. diff --git a/docs/root/start/sandboxes/lua.rst b/docs/root/start/sandboxes/lua.rst index a3d5ab30a479..b8538f2f5872 100644 --- a/docs/root/start/sandboxes/lua.rst +++ b/docs/root/start/sandboxes/lua.rst @@ -1,19 +1,31 @@ .. _install_sandboxes_lua: -Lua Filter +Lua filter ========== -In this example, we show how a Lua filter can be used with the Envoy -proxy. The Envoy proxy configuration includes a Lua -filter that contains two functions namely -``envoy_on_request(request_handle)`` and -``envoy_on_response(response_handle)`` as documented :ref:`here `. +.. sidebar:: Requirements -.. include:: _include/docker-env-setup.rst + .. include:: _include/docker-env-setup-link.rst -Step 3: Build the sandbox + :ref:`curl ` + Used to make ``HTTP`` requests. + +In this example, we show how a `Lua `_ filter can be used with the Envoy +proxy. + +The Envoy proxy configuration includes a Lua filter that contains two functions: + +- ``envoy_on_request(request_handle)`` +- ``envoy_on_response(response_handle)`` + +:ref:`See here ` for an overview of Envoy's Lua filter and documentation +regarding these functions. + +Step 1: Build the sandbox ************************* +Change to the ``examples/lua`` directory. + .. code-block:: console $ pwd @@ -27,7 +39,7 @@ Step 3: Build the sandbox lua_proxy_1 /docker-entrypoint.sh /bin ... Up 10000/tcp, 0.0.0.0:8000->8000/tcp lua_web_service_1 node ./index.js Up 0.0.0.0:8080->80/tcp -Step 4: Send a request to the service +Step 2: Send a request to the service ************************************* The output from the ``curl`` command below should include the headers ``foo``. @@ -83,3 +95,11 @@ Terminal 1 } * Connection #0 to host localhost left intact }* Closing connection 0 + +.. seealso:: + + :ref:`Envoy Lua filter ` + Learn more about the Envoy Lua filter. + + `Lua `_ + The Lua programming language. diff --git a/docs/root/start/sandboxes/mysql.rst b/docs/root/start/sandboxes/mysql.rst index ab5d384c5424..ca588f94faa0 100644 --- a/docs/root/start/sandboxes/mysql.rst +++ b/docs/root/start/sandboxes/mysql.rst @@ -1,16 +1,27 @@ .. _install_sandboxes_mysql: -MySQL Filter +MySQL filter ============ -In this example, we show how the :ref:`MySQL filter ` can be used with the Envoy proxy. The Envoy proxy configuration includes a MySQL filter that parses queries and collects MySQL-specific -metrics. +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. -.. include:: _include/docker-env-setup.rst +In this example, we show how the :ref:`MySQL filter ` can be used with the Envoy proxy. -Step 3: Build the sandbox +The Envoy proxy configuration includes a MySQL filter that parses queries and collects MySQL-specific +metrics. + +Step 1: Build the sandbox ************************* +Change to the ``examples/mysql`` directory. + +Build and start the containers. + Terminal 1 .. code-block:: console @@ -26,7 +37,7 @@ Terminal 1 mysql_mysql_1 docker-entrypoint.sh mysqld Up 3306/tcp mysql_proxy_1 /docker-entrypoint.sh /bin Up 10000/tcp, 0.0.0.0:1999->1999/tcp, 0.0.0.0:8001->8001/tcp -Step 4: Issue commands using mysql +Step 2: Issue commands using mysql ********************************** Use ``mysql`` to issue some commands and verify they are routed via Envoy. Note @@ -71,7 +82,7 @@ Terminal 1 mysql> exit Bye -Step 5: Check egress stats +Step 3: Check egress stats ************************** Check egress stats were updated. @@ -91,7 +102,7 @@ Terminal 1 mysql.egress_mysql.sessions: 1 mysql.egress_mysql.upgraded_to_ssl: 0 -Step 6: Check TCP stats +Step 4: Check TCP stats *********************** Check TCP stats were updated. @@ -112,3 +123,14 @@ Terminal 1 tcp.mysql_tcp.idle_timeout: 0 tcp.mysql_tcp.upstream_flush_active: 0 tcp.mysql_tcp.upstream_flush_total: 0 + +.. seealso:: + + :ref:`Envoy MySQL filter ` + Learn more about using the Envoy MySQL filter. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + `MySQL `_ + The MySQL database. diff --git a/docs/root/start/sandboxes/postgres.rst b/docs/root/start/sandboxes/postgres.rst index 0419533cff08..0e11ecb1e502 100644 --- a/docs/root/start/sandboxes/postgres.rst +++ b/docs/root/start/sandboxes/postgres.rst @@ -1,18 +1,28 @@ .. _install_sandboxes_postgres: -Postgres Filter -=============== +PostgreSQL filter +================= -In this example, we show how the :ref:`Postgres filter ` -can be used with the Envoy proxy. The Envoy proxy configuration includes a Postgres filter that -parses queries and collects Postgres-specific metrics. +.. sidebar:: Requirements -.. include:: _include/docker-env-setup.rst + .. include:: _include/docker-env-setup-link.rst + :ref:`curl ` + Used to make ``HTTP`` requests. -Step 3: Build the sandbox +In this example, we show how the :ref:`PostgreSQL filter ` +can be used with the Envoy proxy. + +The Envoy proxy configuration includes a PostgreSQL filter that parses queries and collects Postgres-specific metrics. + + +Step 1: Build the sandbox ************************* +Change to the ``examples/postgres`` directory. + +Build and start the containers. + .. code-block:: console $ pwd @@ -26,7 +36,7 @@ Step 3: Build the sandbox postgres_postgres_1 docker-entrypoint.sh postgres Up 5432/tcp postgres_proxy_1 /docker-entrypoint.sh /usr ... Up 10000/tcp, 0.0.0.0:1999->1999/tcp, 0.0.0.0:8001->8001/tcp -Step 4: Issue commands using psql +Step 2: Issue commands using psql ********************************* This example uses ``psql`` client inside a container to issue some commands and @@ -65,7 +75,7 @@ filter can't decode encrypted sessions. testdb=# \q -Step 5: Check egress stats +Step 3: Check egress stats ************************** Check egress stats were updated. @@ -105,7 +115,7 @@ Check egress stats were updated. postgres.egress_postgres.transactions_rollback: 0 -Step 6: Check TCP stats +Step 4: Check TCP stats *********************** Check TCP stats were updated. @@ -125,3 +135,14 @@ Check TCP stats were updated. tcp.postgres_tcp.max_downstream_connection_duration: 0 tcp.postgres_tcp.upstream_flush_active: 0 tcp.postgres_tcp.upstream_flush_total: 0 + +.. seealso:: + + :ref:`Envoy PostgreSQL filter ` + Learn more about using the Envoy PostgreSQL filter. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + `PostgreSQL `_ + The PostgreSQL database. diff --git a/docs/root/start/sandboxes/redis.rst b/docs/root/start/sandboxes/redis.rst index 4faf9ef93120..5d304337ec63 100644 --- a/docs/root/start/sandboxes/redis.rst +++ b/docs/root/start/sandboxes/redis.rst @@ -1,15 +1,26 @@ .. _install_sandboxes_redis_filter: -Redis Filter +Redis filter ============ -In this example, we show how a :ref:`Redis filter ` can be used with the Envoy proxy. The Envoy proxy configuration includes a Redis filter that routes egress requests to redis server. +.. sidebar:: Requirements -.. include:: _include/docker-env-setup.rst + .. include:: _include/docker-env-setup-link.rst -Step 3: Build the sandbox + :ref:`redis ` + Redis client to query the server. + +In this example, we show how a :ref:`Redis filter ` can be used with the Envoy proxy. + +The Envoy proxy configuration includes a Redis filter that routes egress requests to redis server. + +Step 1: Build the sandbox ************************* +Change to the ``examples/redis`` directory. + +Build and start the containers. + Terminal 1 .. code-block:: console @@ -25,7 +36,7 @@ Terminal 1 redis_proxy_1 /docker-entrypoint.sh /bin Up 10000/tcp, 0.0.0.0:1999->1999/tcp, 0.0.0.0:8001->8001/tcp redis_redis_1 docker-entrypoint.sh redis Up 6379/tcp -Step 4: Issue Redis commands +Step 2: Issue Redis commands **************************** Issue Redis commands using your favourite Redis client, such as ``redis-cli``, and verify they are routed via Envoy. @@ -43,7 +54,7 @@ Terminal 1 $ redis-cli -h localhost -p 1999 get bar "bar" -Step 5: Verify egress stats +Step 3: Verify egress stats *************************** Go to ``http://localhost:8001/stats?usedonly&filter=redis.egress_redis.command`` and verify the following stats: @@ -52,3 +63,14 @@ Go to ``http://localhost:8001/stats?usedonly&filter=redis.egress_redis.command`` redis.egress_redis.command.get.total: 2 redis.egress_redis.command.set.total: 2 + +.. seealso:: + + :ref:`Envoy Redis filter ` + Learn more about using the Envoy Redis filter. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + `Redis `_ + The Redis in-memory data structure store. diff --git a/docs/root/start/sandboxes/setup.rst b/docs/root/start/sandboxes/setup.rst new file mode 100644 index 000000000000..a679426b0525 --- /dev/null +++ b/docs/root/start/sandboxes/setup.rst @@ -0,0 +1,137 @@ +.. _start_sandboxes_setup: + +Setup the sandbox environment +============================= + +Before you can run the Envoy sandboxes you will need to set up your environment +with :ref:`Docker ` and +:ref:`Docker Compose `. + +You should also clone the :ref:`Envoy repository ` with +:ref:`Git ` + +Some of the examples require the installation of +:ref:`additional dependencies `. + +It is indicated in the sandbox documentation where this is the case. + +.. tip:: + + If you are working on a Mac OS or Windows system, a simple way to install both + :ref:`Docker ` and + :ref:`Docker Compose ` is with + `Docker Desktop `_. + +.. _start_sandboxes_setup_docker: + +Install Docker +-------------- + +Ensure that you have a recent versions of ``docker`` installed. + +You will need a minimum version of ``18.06.0+``. + +Version ``19.03`` is well tested. + +The user account running the examples will need to have permission to use Docker on your system. + +Full instructions for installing Docker can be found on the `Docker website `_ + +.. _start_sandboxes_setup_docker_compose: + +Install Docker Compose +---------------------- + +The examples use +`Docker compose configuration version 3.7 `_. + +You will need to install a fairly recent version of `Docker Compose `_. + +Version ``1.27.4`` is well tested. + +Docker Compose is a `python application `_ and can be +installed through a variety of methods including `pip `_ and +`native operating system installation `_. + +.. _start_sandboxes_setup_git: + +Install Git +----------- + +The Envoy project is managed using `Git `_. + +You can `find instructions for installing Git on various operating systems here `_. + +.. _start_sandboxes_setup_envoy: + +Clone the Envoy repository +-------------------------- + +If you have not cloned the `Envoy repository `_ already, +clone it with: + +.. tabs:: + + .. code-tab:: console SSH + + git clone git@github.com:envoyproxy/envoy + + .. code-tab:: console HTTPS + + git clone https://github.com/envoyproxy/envoy.git + +.. _start_sandboxes_setup_additional: + +Additional dependencies +----------------------- + +The following utilities are used in only some of the sandbox examples, and installation is +therefore optional. + +.. _start_sandboxes_setup_curl: + +curl +~~~~ + +Many of the examples use the `curl `_ utility to make ``HTTP`` requests. + +Instructions for installing `curl `_ on many platforms and operating systems +can be `found on the curl website `_. + +.. _start_sandboxes_setup_jq: + +jq +~~~ + +The `jq `_ tool is very useful for parsing ``json`` data, +whether it be ``HTTP`` response data, logs or statistics. + +Instructions for installing `jq `_ on many platforms and operating systems +can be `found on the jq website `_. + +.. _start_sandboxes_setup_openssl: + +openssl +~~~~~~~ + +`OpenSSL `_ is a robust, commercial-grade, and full-featured toolkit for +the Transport Layer Security (``TLS``) and Secure Sockets Layer (``SSL``) protocols. + +Binary distributions of `OpenSSL `_ are available for Mac OS with `brew `_ +and in most if not all flavours of Linux. + +Windows users can either use an `unofficial binary `_ or compile from source. + +Check for installation instructions specific to your operating system. + +.. _start_sandboxes_setup_redis: + +redis +~~~~~ + +Binary distributions of `Redis `_ are available for Mac OS with `brew `_ +and in most flavours of Linux. + +Windows users should check out the `Windows port of Redis `_. + +Check for installation instructions specific to your operating system. diff --git a/docs/root/start/sandboxes/skywalking_tracing.rst b/docs/root/start/sandboxes/skywalking_tracing.rst index 66f07dd15718..fa8c31ff46a9 100644 --- a/docs/root/start/sandboxes/skywalking_tracing.rst +++ b/docs/root/start/sandboxes/skywalking_tracing.rst @@ -1,8 +1,15 @@ .. _install_sandboxes_skywalking_tracing: -SkyWalking Tracing +SkyWalking tracing ================== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + The SkyWalking tracing sandbox demonstrates Envoy's :ref:`request tracing ` capabilities using `SkyWalking `_ as the tracing provider. This sandbox is very similar to the Zipkin sandbox. All containers will be deployed inside a virtual network @@ -10,20 +17,21 @@ called ``envoymesh``. All incoming requests are routed via the front Envoy, which is acting as a reverse proxy sitting on the edge of the ``envoymesh`` network. Port ``8000`` is exposed -by docker compose (see :repo:`/examples/skywalking-tracing/docker-compose.yaml`). Notice that -all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in -:repo:`/examples/skywalking-tracing/front-envoy-skywalking.yaml`) and setup to propagate the spans generated -by the SkyWalking tracer to a SkyWalking cluster (trace driver setup -in :repo:`/examples/skywalking-tracing/front-envoy-skywalking.yaml`). +by docker compose (see :download:`docker-compose.yaml <_include/skywalking-tracing/docker-compose.yaml>`). + +Notice that all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in +:download:`front-envoy-skywalking.yaml <_include/skywalking-tracing/front-envoy-skywalking.yaml>`) +and setup to propagate the spans generated by the SkyWalking tracer to a SkyWalking cluster (trace driver setup +in :download:`front-envoy-skywalking.yaml <_include/skywalking-tracing/front-envoy-skywalking.yaml>`). When service1 accepts the request forwarded from front envoy, it will make an API call to service2 before returning a response. -.. include:: _include/docker-env-setup.rst - -Step 3: Build the sandbox +Step 1: Build the sandbox ************************* +Change to the ``examples/skywalking-tracing`` directory. + To build this sandbox example, and start the example apps run the following commands: .. code-block:: console @@ -43,7 +51,7 @@ To build this sandbox example, and start the example apps run the following comm skywalking-tracing_skywalking-oap_1 bash docker-entrypoint.sh Up (healthy) 0.0.0.0:11800->11800/tcp, 1234/tcp, 0.0.0.0:12800->12800/tcp skywalking-tracing_skywalking-ui_1 bash docker-entrypoint.sh Up 0.0.0.0:8080->8080/tcp -Step 4: Generate some load +Step 2: Generate some load ************************** You can now send a request to service1 via the front-envoy as follows: @@ -79,11 +87,22 @@ You can get SkyWalking stats of front-envoy after some requests as follows: tracing.skywalking.segments_flushed: 0 tracing.skywalking.segments_sent: 13 -Step 5: View the traces in SkyWalking UI +Step 3: View the traces in SkyWalking UI **************************************** Point your browser to http://localhost:8080 . You should see the SkyWalking dashboard. Set the service to "front-envoy" and set the start time to a few minutes before -the start of the test (step 2) and hit enter. You should see traces from the front-proxy. +the start of the test and hit enter. You should see traces from the front-proxy. Click on a trace to explore the path taken by the request from front-proxy to service1 to service2, as well as the latency incurred at each hop. + +.. seealso:: + + :ref:`Request tracing ` + Learn more about using Envoy's request tracing. + + :ref:`Envoy admin quick start guide ` + Quick start guide to the Envoy admin interface. + + `Apache SkyWalking `_ + SkyWalking observability analysis platform and application performance management system. diff --git a/docs/root/start/sandboxes/tls.rst b/docs/root/start/sandboxes/tls.rst index aed1ed2cccab..84942094bb81 100644 --- a/docs/root/start/sandboxes/tls.rst +++ b/docs/root/start/sandboxes/tls.rst @@ -1,12 +1,17 @@ .. _install_sandboxes_tls: -TLS -=== +Transport layer security (``TLS``) +================================== .. sidebar:: Requirements - `jq `_ - Used to parse ``json`` output from the upstream echo servers. + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + + :ref:`jq ` + Parse ``json`` output from the upstream echo servers. This example walks through some of the ways that Envoy can be configured to make use of encrypted connections using ``HTTP`` over ``TLS``. @@ -32,13 +37,11 @@ configured with Envoy, please see the :ref:`securing Envoy quick start guide ` where you control both sides of the connection, or relevant protocols are available. -.. include:: _include/docker-env-setup.rst +Step 1: Build the sandbox +************************* Change directory to ``examples/tls`` in the Envoy repository. -Step 3: Build the sandbox -************************* - This starts four proxies listening on ``localhost`` ports ``10000-10003``. It also starts two upstream services, one ``HTTP`` and one ``HTTPS``, which echo back received headers @@ -63,7 +66,7 @@ The upstream services listen on the internal Docker network on ports ``80`` and tls_service-http_1 node ./index.js Up tls_service-https_1 node ./index.js Up -Step 4: Test proxying ``https`` -> ``http`` +Step 2: Test proxying ``https`` -> ``http`` ******************************************* The Envoy proxy listening on https://localhost:10000 terminates ``HTTPS`` and proxies to the upstream ``HTTP`` service. @@ -87,7 +90,7 @@ The upstream ``service-http`` handles the request. $ curl -sk https://localhost:10000 | jq -r '.os.hostname' service-http -Step 5: Test proxying ``https`` -> ``https`` +Step 3: Test proxying ``https`` -> ``https`` ******************************************** The Envoy proxy listening on https://localhost:10001 terminates ``HTTPS`` and proxies to the upstream ``HTTPS`` service. @@ -112,7 +115,7 @@ The upstream ``service-https`` handles the request. $ curl -sk https://localhost:10001 | jq -r '.os.hostname' service-https -Step 6: Test proxying ``http`` -> ``https`` +Step 4: Test proxying ``http`` -> ``https`` ******************************************* The Envoy proxy listening on http://localhost:10002 terminates ``HTTP`` and proxies to the upstream ``HTTPS`` service. @@ -121,7 +124,7 @@ The :download:`http -> https configuration <_include/tls/envoy-http-https.yaml>` :ref:`transport_socket ` to the :ref:`cluster `. -Querying the service at port ``10001`` you should see an ``x-forwarded-proto`` header of ``http`` has +Querying the service at port ``10002`` you should see an ``x-forwarded-proto`` header of ``http`` has been added: .. code-block:: console @@ -137,7 +140,7 @@ The upstream ``service-https`` handles the request. service-https -Step 7: Test proxying ``https`` passthrough +Step 5: Test proxying ``https`` passthrough ******************************************* The Envoy proxy listening on https://localhost:10003 proxies directly to the upstream ``HTTPS`` service which diff --git a/docs/root/start/sandboxes/wasm-cc.rst b/docs/root/start/sandboxes/wasm-cc.rst index b28541a15a4e..8a0c9517bb14 100644 --- a/docs/root/start/sandboxes/wasm-cc.rst +++ b/docs/root/start/sandboxes/wasm-cc.rst @@ -1,22 +1,28 @@ .. _install_sandboxes_wasm_filter: -WASM C++ filter +Wasm C++ filter =============== -This sandbox demonstrates a basic C++ Wasm filter which injects content into the body of an ``HTTP`` response, and adds -and updates some headers. +.. sidebar:: Requirements -It also takes you through the steps required to build your own C++ Wasm filter, and run it with Envoy. + .. include:: _include/docker-env-setup-link.rst -.. include:: _include/docker-env-setup.rst + :ref:`curl ` + Used to make ``HTTP`` requests. -Step 3: Start all of our containers -*********************************** - -.. note:: +.. sidebar:: Compatibility The provided Wasm binary was compiled for the ``x86_64`` architecture. If you would like to use this sandbox - with the ``arm64`` architecture, skip to Step 5. + with the ``arm64`` architecture, change directory to ``examples/wasm-cc`` and skip to Step 3. + +This sandbox demonstrates a basic :ref:`Envoy Wasm filter ` written in C++ which injects +content into the body of an ``HTTP`` response, and adds and updates some headers. + +It also takes you through the steps required to build your own C++ :ref:`Wasm filter `, +and run it with Envoy. + +Step 1: Start all of our containers +*********************************** First lets start the containers - an Envoy proxy which uses a Wasm Filter, and a backend which echos back our request. @@ -35,7 +41,7 @@ Change to the ``examples/wasm-cc`` folder in the Envoy repo, and start the compo wasm_proxy_1 /docker-entrypoint.sh /usr ... Up 10000/tcp, 0.0.0.0:8000->8000/tcp wasm_web_service_1 node ./index.js Up -Step 4: Check web response +Step 2: Check web response ************************** The Wasm filter should inject "Hello, world" at the end of the response body when you make a request to the proxy. @@ -55,7 +61,7 @@ The filter also sets the ``content-type`` header to ``text/plain``, and adds a c $ curl -v http://localhost:8000 | grep "x-wasm-custom: " x-wasm-custom: FOO -Step 5: Compile the updated filter +Step 3: Compile the updated filter ********************************** There are two source code files provided for the Wasm filter. @@ -93,10 +99,10 @@ The compiled binary should now be in the ``lib`` folder. -r-xr-xr-x 1 root root 59641 Oct 20 00:00 envoy_filter_http_wasm_example.wasm -r-xr-xr-x 1 root root 59653 Oct 20 10:16 envoy_filter_http_wasm_updated_example.wasm -Step 6: Edit the Dockerfile and restart the proxy +Step 4: Edit the Dockerfile and restart the proxy ************************************************* -Edit the ``Dockerfile-proxy`` recipe provided in the example to use the updated binary you created in step 5. +Edit the ``Dockerfile-proxy`` recipe provided in the example to use the updated binary you created in step 3. Find the ``COPY`` line that adds the Wasm binary to the image: @@ -117,7 +123,7 @@ Now, rebuild and start the proxy container. $ docker-compose up --build -d proxy -Step 7: Check the proxy has been updated +Step 5: Check the proxy has been updated **************************************** The Wasm filter should instead inject "Hello, Wasm world" at the end of the response body. @@ -136,3 +142,14 @@ The ``content-type`` and ``x-wasm-custom`` headers should also have changed $ curl -v http://localhost:8000 | grep "x-wasm-custom: " x-wasm-custom: BAR + +.. seealso:: + + :ref:`Envoy Wasm filter ` + Further information about the Envoy Wasm filter. + + :ref:`Envoy Wasm API(V3) ` + The Envoy Wasm API - version 3. + + `Proxy Wasm C++ SDK `_ + WebAssembly for proxies (C++ SDK) diff --git a/docs/root/start/sandboxes/websocket.rst b/docs/root/start/sandboxes/websocket.rst index 4128936af15a..7f28770c7eae 100644 --- a/docs/root/start/sandboxes/websocket.rst +++ b/docs/root/start/sandboxes/websocket.rst @@ -5,8 +5,10 @@ WebSockets .. sidebar:: Requirements - `openssl `_ - Used to create a TLS certificate for the websocket. + .. include:: _include/docker-env-setup-link.rst + + :ref:`openssl ` + Generate ``SSL`` keys and certificates. This example walks through some of the ways that Envoy can be configured to proxy WebSockets. @@ -24,13 +26,11 @@ of proxying to encrypted and non-encrypted upstream sockets. You should also :ref:`authenticate clients ` where you control both sides of the connection, or relevant protocols are available. -.. include:: _include/docker-env-setup.rst +Step 1: Create a certificate file for wss +***************************************** Change directory to ``examples/websocket`` in the Envoy repository. -Step 3: Create a certificate file for wss -***************************************** - .. code-block:: console $ pwd @@ -44,7 +44,7 @@ Step 3: Create a certificate file for wss ----- $ openssl pkcs12 -export -passout pass: -out certs/output.pkcs12 -inkey certs/key.pem -in certs/cert.pem -Step 4: Build and start the sandbox +Step 2: Build and start the sandbox *********************************** This starts three proxies listening on ``localhost`` ports ``10000-30000``. @@ -70,7 +70,7 @@ The socket servers are very trivial implementations, that simply output ``[ws] H websocket_service-ws_1 websocat -E ws-listen:0.0. ... Up websocket_service-wss_1 websocat wss-listen:0.0.0. ... Up -Step 5: Test proxying ``ws`` -> ``ws`` +Step 3: Test proxying ``ws`` -> ``ws`` ************************************** The proxy listening on port ``10000`` terminates the WebSocket connection without ``TLS`` and then proxies @@ -99,7 +99,7 @@ You can start an interactive session with the socket as follows: Type ``Ctrl-c`` to exit the socket session. -Step 6: Test proxying ``wss`` -> ``wss`` +Step 4: Test proxying ``wss`` -> ``wss`` **************************************** The proxy listening on port ``20000`` terminates the WebSocket connection with ``TLS`` and then proxies @@ -125,7 +125,7 @@ You can start an interactive session with the socket as follows: Type ``Ctrl-c`` to exit the socket session. -Step 7: Test proxying ``wss`` passthrough +Step 5: Test proxying ``wss`` passthrough ***************************************** The proxy listening on port ``30000`` passes through all ``TCP`` traffic to an upstream ``TLS`` WebSocket. @@ -154,3 +154,6 @@ Type ``Ctrl-c`` to exit the socket session. :ref:`Double proxy sandbox ` An example of securing traffic between proxies with validation and mutual authentication using ``mTLS`` with non-``HTTP`` traffic. + + :ref:`TLS sandbox ` + Examples of various ``TLS`` termination patterns with Envoy. diff --git a/docs/root/start/sandboxes/zipkin_tracing.rst b/docs/root/start/sandboxes/zipkin_tracing.rst index 24571b51278a..e2f9a10412a2 100644 --- a/docs/root/start/sandboxes/zipkin_tracing.rst +++ b/docs/root/start/sandboxes/zipkin_tracing.rst @@ -1,8 +1,15 @@ .. _install_sandboxes_zipkin_tracing: -Zipkin Tracing +Zipkin tracing ============== +.. sidebar:: Requirements + + .. include:: _include/docker-env-setup-link.rst + + :ref:`curl ` + Used to make ``HTTP`` requests. + The Zipkin tracing sandbox demonstrates Envoy's :ref:`request tracing ` capabilities using `Zipkin `_ as the tracing provider. This sandbox is very similar to the front proxy architecture described above, with one difference: @@ -11,11 +18,11 @@ The three containers will be deployed inside a virtual network called ``envoymes All incoming requests are routed via the front Envoy, which is acting as a reverse proxy sitting on the edge of the ``envoymesh`` network. Port ``8000`` is exposed -by docker compose (see :repo:`/examples/zipkin-tracing/docker-compose.yaml`). Notice that -all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in -:repo:`/examples/zipkin-tracing/front-envoy-zipkin.yaml`) and setup to propagate the spans generated -by the Zipkin tracer to a Zipkin cluster (trace driver setup -in :repo:`/examples/zipkin-tracing/front-envoy-zipkin.yaml`). +by docker compose (see :download:`docker-compose.yaml <_include/zipkin-tracing/docker-compose.yaml>`). +Notice that all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing +setup in :download:`front-envoy-zipkin.yaml <_include/zipkin-tracing/front-envoy-zipkin.yaml>`) and setup +to propagate the spans generated by the Zipkin tracer to a Zipkin cluster (trace driver setup +in :download:`front-envoy-zipkin.yaml <_include/zipkin-tracing/front-envoy-zipkin.yaml>`). Before routing a request to the appropriate service Envoy or the application, Envoy will take care of generating the appropriate spans for tracing (parent/child/shared context spans). @@ -26,14 +33,14 @@ One of the most important benefits of tracing from Envoy is that it will take ca propagating the traces to the Zipkin service cluster. However, in order to fully take advantage of tracing, the application has to propagate trace headers that Envoy generates, while making calls to other services. In the sandbox we have provided, the simple flask app -(see trace function in :repo:`/examples/front-proxy/service.py`) acting as service1 propagates +(see trace function in :download:`service.py <_include/front-proxy/service.py>`) acting as service1 propagates the trace headers while making an outbound call to service2. -.. include:: _include/docker-env-setup.rst - Step 3: Build the sandbox ************************* +Change directory to ``examples/zipkin-tracing`` in the Envoy repository. + To build this sandbox example, and start the example apps run the following commands: .. code-block:: console @@ -84,3 +91,12 @@ Set the service to "front-proxy" and set the start time to a few minutes before the start of the test (step 2) and hit enter. You should see traces from the front-proxy. Click on a trace to explore the path taken by the request from front-proxy to service1 to service2, as well as the latency incurred at each hop. + + +.. seealso:: + + :ref:`Request tracing ` + Learn more about using Envoy's request tracing. + + `Zipkin `_ + Zipkin tracing website. From 122257ef6ade0009feafc3c9142d480260fe069f Mon Sep 17 00:00:00 2001 From: htuch Date: Fri, 13 Nov 2020 16:23:26 -0500 Subject: [PATCH 097/117] sds: additional support for symlink-based key rotation. (#13721) There are a few limitations in our existing support for symlink-based key rotation: We don't atomically resolve symlinks, so a single snapshot might have inconsistent symlink resolutions for different watched files. Watches are on parent directories, e.g. for /foo/bar/baz on /foo/bar, which doesn't support common key rotation schemes were /foo/new/baz is rotated via a mv -Tf /foo/new /foo/bar. The solution is to provide a structured WatchedDirectory for Secrets to opt into when monitoring DataSources. SDS will used WatchedDirectory to setup the inotify watch instead of the DataSource path. On update, it will read key/cert twice, verifying file content hash consistency. Risk level: Low (opt-in feature) Testing: Unit and integration tests added. Fixes #13663 Fixes #10979 Fixes #13370 Signed-off-by: Harvey Tuch --- api/envoy/config/core/v3/base.proto | 7 + api/envoy/config/core/v4alpha/base.proto | 10 + .../transport_sockets/tls/v3/common.proto | 37 +- .../tls/v4alpha/common.proto | 37 +- docs/root/configuration/security/secret.rst | 64 +++ docs/root/version_history/current.rst | 3 + .../envoy/config/core/v3/base.proto | 7 + .../envoy/config/core/v4alpha/base.proto | 10 + .../transport_sockets/tls/v3/common.proto | 37 +- .../tls/v4alpha/common.proto | 37 +- source/common/config/BUILD | 11 + source/common/config/watched_directory.cc | 14 + source/common/config/watched_directory.h | 28 ++ source/common/secret/BUILD | 1 + source/common/secret/sds_api.cc | 138 +++++-- source/common/secret/sds_api.h | 104 ++++- test/common/config/BUILD | 10 + test/common/config/watched_directory_test.cc | 33 ++ test/common/secret/BUILD | 5 +- test/common/secret/sds_api_test.cc | 385 ++++++++++++++++-- test/integration/BUILD | 5 + .../sds_dynamic_integration_test.cc | 138 +++++++ .../sds_dynamic_key_rotation_setup.sh | 23 ++ test/test_common/utility.cc | 4 + test/test_common/utility.h | 1 + 25 files changed, 1046 insertions(+), 103 deletions(-) create mode 100644 source/common/config/watched_directory.cc create mode 100644 source/common/config/watched_directory.h create mode 100644 test/common/config/watched_directory_test.cc create mode 100755 test/integration/sds_dynamic_key_rotation_setup.sh diff --git a/api/envoy/config/core/v3/base.proto b/api/envoy/config/core/v3/base.proto index 4d7d69fae70b..5b5339ea5bc5 100644 --- a/api/envoy/config/core/v3/base.proto +++ b/api/envoy/config/core/v3/base.proto @@ -313,6 +313,13 @@ message HeaderMap { repeated HeaderValue headers = 1; } +// A directory that is watched for changes, e.g. by inotify on Linux. Move/rename +// events inside this directory trigger the watch. +message WatchedDirectory { + // Directory path to watch. + string path = 1 [(validate.rules).string = {min_len: 1}]; +} + // Data source consisting of either a file or an inline value. message DataSource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.DataSource"; diff --git a/api/envoy/config/core/v4alpha/base.proto b/api/envoy/config/core/v4alpha/base.proto index dc1104a219b7..27b0b356b1a7 100644 --- a/api/envoy/config/core/v4alpha/base.proto +++ b/api/envoy/config/core/v4alpha/base.proto @@ -308,6 +308,16 @@ message HeaderMap { repeated HeaderValue headers = 1; } +// A directory that is watched for changes, e.g. by inotify on Linux. Move/rename +// events inside this directory trigger the watch. +message WatchedDirectory { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.core.v3.WatchedDirectory"; + + // Directory path to watch. + string path = 1 [(validate.rules).string = {min_len: 1}]; +} + // Data source consisting of either a file or an inline value. message DataSource { option (udpa.annotations.versioning).previous_message_type = "envoy.config.core.v3.DataSource"; diff --git a/api/envoy/extensions/transport_sockets/tls/v3/common.proto b/api/envoy/extensions/transport_sockets/tls/v3/common.proto index 2f45a754416b..587e3271836b 100644 --- a/api/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -128,16 +128,36 @@ message PrivateKeyProvider { } } -// [#next-free-field: 7] +// [#next-free-field: 8] message TlsCertificate { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.TlsCertificate"; // The TLS certificate chain. + // + // If *certificate_chain* is a filesystem path, a watch will be added to the + // parent directory for any file moves to support rotation. This currently + // only applies to dynamic secrets, when the *TlsCertificate* is delivered via + // SDS. config.core.v3.DataSource certificate_chain = 1; // The TLS private key. + // + // If *private_key* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. config.core.v3.DataSource private_key = 2 [(udpa.annotations.sensitive) = true]; + // If specified, updates of file-based *certificate_chain* and *private_key* + // sources will be triggered by this watch. The certificate/key pair will be + // read together and validated for atomic read consistency (i.e. no + // intervening modification occurred between cert/key read, verified by file + // hash comparisons). This allows explicit control over the path watched, by + // default the parent directories of the filesystem paths in + // *certificate_chain* and *private_key* are watched if this field is not + // specified. This only applies when a *TlsCertificate* is delivered by SDS + // with references to filesystem paths. + config.core.v3.WatchedDirectory watched_directory = 7; + // BoringSSL private key method provider. This is an alternative to :ref:`private_key // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key @@ -191,7 +211,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 12] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CertificateValidationContext"; @@ -233,8 +253,21 @@ message CertificateValidationContext { // // See :ref:`the TLS overview ` for a list of common // system CA locations. + // + // If *trusted_ca* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *CertificateValidationContext* is + // delivered via SDS. config.core.v3.DataSource trusted_ca = 1; + // If specified, updates of a file-based *trusted_ca* source will be triggered + // by this watch. This allows explicit control over the path watched, by + // default the parent directory of the filesystem path in *trusted_ca* is + // watched if this field is not specified. This only applies when a + // *CertificateValidationContext* is delivered by SDS with references to + // filesystem paths. + config.core.v3.WatchedDirectory watched_directory = 11; + // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the // SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate // matches one of the specified values. diff --git a/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto b/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto index 1382863cb804..b2fa6f672628 100644 --- a/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto @@ -129,17 +129,37 @@ message PrivateKeyProvider { } } -// [#next-free-field: 7] +// [#next-free-field: 8] message TlsCertificate { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.tls.v3.TlsCertificate"; // The TLS certificate chain. + // + // If *certificate_chain* is a filesystem path, a watch will be added to the + // parent directory for any file moves to support rotation. This currently + // only applies to dynamic secrets, when the *TlsCertificate* is delivered via + // SDS. config.core.v4alpha.DataSource certificate_chain = 1; // The TLS private key. + // + // If *private_key* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. config.core.v4alpha.DataSource private_key = 2 [(udpa.annotations.sensitive) = true]; + // If specified, updates of file-based *certificate_chain* and *private_key* + // sources will be triggered by this watch. The certificate/key pair will be + // read together and validated for atomic read consistency (i.e. no + // intervening modification occurred between cert/key read, verified by file + // hash comparisons). This allows explicit control over the path watched, by + // default the parent directories of the filesystem paths in + // *certificate_chain* and *private_key* are watched if this field is not + // specified. This only applies when a *TlsCertificate* is delivered by SDS + // with references to filesystem paths. + config.core.v4alpha.WatchedDirectory watched_directory = 7; + // BoringSSL private key method provider. This is an alternative to :ref:`private_key // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key @@ -193,7 +213,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 12] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext"; @@ -235,8 +255,21 @@ message CertificateValidationContext { // // See :ref:`the TLS overview ` for a list of common // system CA locations. + // + // If *trusted_ca* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *CertificateValidationContext* is + // delivered via SDS. config.core.v4alpha.DataSource trusted_ca = 1; + // If specified, updates of a file-based *trusted_ca* source will be triggered + // by this watch. This allows explicit control over the path watched, by + // default the parent directory of the filesystem path in *trusted_ca* is + // watched if this field is not specified. This only applies when a + // *CertificateValidationContext* is delivered by SDS with references to + // filesystem paths. + config.core.v4alpha.WatchedDirectory watched_directory = 11; + // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the // SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate // matches one of the specified values. diff --git a/docs/root/configuration/security/secret.rst b/docs/root/configuration/security/secret.rst index 087cf388b759..fdbc88242d02 100644 --- a/docs/root/configuration/security/secret.rst +++ b/docs/root/configuration/security/secret.rst @@ -33,6 +33,26 @@ SDS Configuration *SdsSecretConfig* is used in two fields in :ref:`CommonTlsContext `. The first field is *tls_certificate_sds_secret_configs* to use SDS to get :ref:`TlsCertificate `. The second field is *validation_context_sds_secret_config* to use SDS to get :ref:`CertificateValidationContext `. +.. _sds_key_rotation: + +Key rotation +------------ + +It's usually preferrable to perform key rotation via gRPC SDS, but when this is not possible or +desired (e.g. during bootstrap of SDS credentials), SDS allows for filesystem rotation when secrets +refer to filesystem paths. This currently is supported for the following secret types: + +* :ref:`TlsCertificate ` +* :ref:`CertificateValidationContext ` + +By default, directories containing secrets are watched for filesystem move events. Explicit control over +the watched directory is possible by specifying a *watched_directory* path in :ref:`TlsCertificate +` and +:ref:`CertificateValidationContext +`. + +An example of key rotation is provided :ref:`below `. + Example one: static_resource ----------------------------- @@ -211,9 +231,11 @@ In contrast, :ref:`sds_server_example` requires a restart to reload xDS certific "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext" common_tls_context: tls_certificate_sds_secret_configs: + name: tls_sds sds_config: path: /etc/envoy/tls_certificate_sds_secret.yaml validation_context_sds_secret_config: + name: validation_context_sds sds_config: path: /etc/envoy/validation_context_sds_secret.yaml @@ -239,6 +261,39 @@ Path to CA certificate bundle for validating the xDS server certificate is given trusted_ca: filename: /certs/cacert.pem +In the above example, a watch will be established on ``/certs``. File movement in this directory +will trigger an update. An alternative common key rotation scheme that provides improved atomicity +is to establish an active symlink ``/certs/current`` and use an atomic move operation to replace the +symlink. The watch in this case needs to be on the certificate's grandparent directory. Envoy +supports this scheme via the use of *watched_directory*. Continuing the above examples: + +.. code-block:: yaml + + resources: + - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" + tls_certificate: + certificate_chain: + filename: /certs/current/sds_cert.pem + private_key: + filename: /certs/current/sds_key.pem + watched_directory: + path: /certs + +.. code-block:: yaml + + resources: + - "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret" + validation_context: + trusted_ca: + filename: /certs/current/cacert.pem + watched_directory: + path: /certs + +Secret rotation can be performed with: + +.. code-block:: bash + + ln -s /certs/new && mv -Tf /certs/new /certs/current Statistics ---------- @@ -261,3 +316,12 @@ For upstream clusters, they are in the *cluster..client_ssl_socket ssl_context_update_by_sds, Total number of ssl context has been updated. upstream_context_secrets_not_ready, Total number of upstream connections reset due to empty ssl certificate. + +SDS has a :ref:`statistics ` tree rooted in the *sds..* +namespace. In addition, the following statistics are tracked in this namespace: + +.. csv-table:: + :header: Name, Description + :widths: 1, 2 + + key_rotation_failed, Total number of filesystem key rotations that failed outside of an SDS update. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 19f263230518..f72b7f9842d0 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -63,6 +63,9 @@ New Features * overload: add :ref:`envoy.overload_actions.reduce_timeouts ` overload action to enable scaling timeouts down with load. * ratelimit: added support for use of various :ref:`metadata ` as a ratelimit action. * ratelimit: added :ref:`disable_x_envoy_ratelimited_header ` option to disable `X-Envoy-RateLimited` header. +* sds: improved support for atomic :ref:`key rotations ` and added configurable rotation triggers for + :ref:`TlsCertificate ` and + :ref:`CertificateValidationContext `. * tcp: added a new :ref:`envoy.overload_actions.reject_incoming_connections ` action to reject incoming TCP connections. * tls: added support for RSA certificates with 4096-bit keys in FIPS mode. * tracing: added SkyWalking tracer. diff --git a/generated_api_shadow/envoy/config/core/v3/base.proto b/generated_api_shadow/envoy/config/core/v3/base.proto index 35b015710e5c..1184c89de6e2 100644 --- a/generated_api_shadow/envoy/config/core/v3/base.proto +++ b/generated_api_shadow/envoy/config/core/v3/base.proto @@ -311,6 +311,13 @@ message HeaderMap { repeated HeaderValue headers = 1; } +// A directory that is watched for changes, e.g. by inotify on Linux. Move/rename +// events inside this directory trigger the watch. +message WatchedDirectory { + // Directory path to watch. + string path = 1 [(validate.rules).string = {min_len: 1}]; +} + // Data source consisting of either a file or an inline value. message DataSource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.DataSource"; diff --git a/generated_api_shadow/envoy/config/core/v4alpha/base.proto b/generated_api_shadow/envoy/config/core/v4alpha/base.proto index 03fcc5a461e0..95ca4f77a2bc 100644 --- a/generated_api_shadow/envoy/config/core/v4alpha/base.proto +++ b/generated_api_shadow/envoy/config/core/v4alpha/base.proto @@ -315,6 +315,16 @@ message HeaderMap { repeated HeaderValue headers = 1; } +// A directory that is watched for changes, e.g. by inotify on Linux. Move/rename +// events inside this directory trigger the watch. +message WatchedDirectory { + option (udpa.annotations.versioning).previous_message_type = + "envoy.config.core.v3.WatchedDirectory"; + + // Directory path to watch. + string path = 1 [(validate.rules).string = {min_len: 1}]; +} + // Data source consisting of either a file or an inline value. message DataSource { option (udpa.annotations.versioning).previous_message_type = "envoy.config.core.v3.DataSource"; diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto index da834e8afc54..c5452fced643 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -127,16 +127,36 @@ message PrivateKeyProvider { } } -// [#next-free-field: 7] +// [#next-free-field: 8] message TlsCertificate { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.TlsCertificate"; // The TLS certificate chain. + // + // If *certificate_chain* is a filesystem path, a watch will be added to the + // parent directory for any file moves to support rotation. This currently + // only applies to dynamic secrets, when the *TlsCertificate* is delivered via + // SDS. config.core.v3.DataSource certificate_chain = 1; // The TLS private key. + // + // If *private_key* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. config.core.v3.DataSource private_key = 2 [(udpa.annotations.sensitive) = true]; + // If specified, updates of file-based *certificate_chain* and *private_key* + // sources will be triggered by this watch. The certificate/key pair will be + // read together and validated for atomic read consistency (i.e. no + // intervening modification occurred between cert/key read, verified by file + // hash comparisons). This allows explicit control over the path watched, by + // default the parent directories of the filesystem paths in + // *certificate_chain* and *private_key* are watched if this field is not + // specified. This only applies when a *TlsCertificate* is delivered by SDS + // with references to filesystem paths. + config.core.v3.WatchedDirectory watched_directory = 7; + // BoringSSL private key method provider. This is an alternative to :ref:`private_key // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key @@ -190,7 +210,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 12] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.auth.CertificateValidationContext"; @@ -230,8 +250,21 @@ message CertificateValidationContext { // // See :ref:`the TLS overview ` for a list of common // system CA locations. + // + // If *trusted_ca* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *CertificateValidationContext* is + // delivered via SDS. config.core.v3.DataSource trusted_ca = 1; + // If specified, updates of a file-based *trusted_ca* source will be triggered + // by this watch. This allows explicit control over the path watched, by + // default the parent directory of the filesystem path in *trusted_ca* is + // watched if this field is not specified. This only applies when a + // *CertificateValidationContext* is delivered by SDS with references to + // filesystem paths. + config.core.v3.WatchedDirectory watched_directory = 11; + // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the // SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate // matches one of the specified values. diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto index 1382863cb804..b2fa6f672628 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto @@ -129,17 +129,37 @@ message PrivateKeyProvider { } } -// [#next-free-field: 7] +// [#next-free-field: 8] message TlsCertificate { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.tls.v3.TlsCertificate"; // The TLS certificate chain. + // + // If *certificate_chain* is a filesystem path, a watch will be added to the + // parent directory for any file moves to support rotation. This currently + // only applies to dynamic secrets, when the *TlsCertificate* is delivered via + // SDS. config.core.v4alpha.DataSource certificate_chain = 1; // The TLS private key. + // + // If *private_key* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *TlsCertificate* is delivered via SDS. config.core.v4alpha.DataSource private_key = 2 [(udpa.annotations.sensitive) = true]; + // If specified, updates of file-based *certificate_chain* and *private_key* + // sources will be triggered by this watch. The certificate/key pair will be + // read together and validated for atomic read consistency (i.e. no + // intervening modification occurred between cert/key read, verified by file + // hash comparisons). This allows explicit control over the path watched, by + // default the parent directories of the filesystem paths in + // *certificate_chain* and *private_key* are watched if this field is not + // specified. This only applies when a *TlsCertificate* is delivered by SDS + // with references to filesystem paths. + config.core.v4alpha.WatchedDirectory watched_directory = 7; + // BoringSSL private key method provider. This is an alternative to :ref:`private_key // ` field. This can't be // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key @@ -193,7 +213,7 @@ message TlsSessionTicketKeys { [(validate.rules).repeated = {min_items: 1}, (udpa.annotations.sensitive) = true]; } -// [#next-free-field: 11] +// [#next-free-field: 12] message CertificateValidationContext { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext"; @@ -235,8 +255,21 @@ message CertificateValidationContext { // // See :ref:`the TLS overview ` for a list of common // system CA locations. + // + // If *trusted_ca* is a filesystem path, a watch will be added to the parent + // directory for any file moves to support rotation. This currently only + // applies to dynamic secrets, when the *CertificateValidationContext* is + // delivered via SDS. config.core.v4alpha.DataSource trusted_ca = 1; + // If specified, updates of a file-based *trusted_ca* source will be triggered + // by this watch. This allows explicit control over the path watched, by + // default the parent directory of the filesystem path in *trusted_ca* is + // watched if this field is not specified. This only applies when a + // *CertificateValidationContext* is delivered by SDS with references to + // filesystem paths. + config.core.v4alpha.WatchedDirectory watched_directory = 11; + // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the // SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate // matches one of the specified values. diff --git a/source/common/config/BUILD b/source/common/config/BUILD index cf1ac31142ff..7c5460df96bb 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -444,6 +444,17 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "watched_directory_lib", + srcs = ["watched_directory.cc"], + hdrs = ["watched_directory.h"], + deps = [ + "//include/envoy/event:dispatcher_interface", + "//include/envoy/filesystem:watcher_interface", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "well_known_names", srcs = ["well_known_names.cc"], diff --git a/source/common/config/watched_directory.cc b/source/common/config/watched_directory.cc new file mode 100644 index 000000000000..c3843ee3f71c --- /dev/null +++ b/source/common/config/watched_directory.cc @@ -0,0 +1,14 @@ +#include "common/config/watched_directory.h" + +namespace Envoy { +namespace Config { + +WatchedDirectory::WatchedDirectory(const envoy::config::core::v3::WatchedDirectory& config, + Event::Dispatcher& dispatcher) { + watcher_ = dispatcher.createFilesystemWatcher(); + watcher_->addWatch(absl::StrCat(config.path(), "/"), Filesystem::Watcher::Events::MovedTo, + [this](uint32_t) { cb_(); }); +} + +} // namespace Config +} // namespace Envoy diff --git a/source/common/config/watched_directory.h b/source/common/config/watched_directory.h new file mode 100644 index 000000000000..34e965a0b4df --- /dev/null +++ b/source/common/config/watched_directory.h @@ -0,0 +1,28 @@ +#pragma once + +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/event/dispatcher.h" +#include "envoy/filesystem/watcher.h" + +namespace Envoy { +namespace Config { + +// Implement the common functionality of envoy::config::core::v3::WatchedDirectory. +class WatchedDirectory { +public: + using Callback = std::function; + + WatchedDirectory(const envoy::config::core::v3::WatchedDirectory& config, + Event::Dispatcher& dispatcher); + + void setCallback(Callback cb) { cb_ = cb; } + +private: + std::unique_ptr watcher_; + Callback cb_; +}; + +using WatchedDirectoryPtr = std::unique_ptr; + +} // namespace Config +} // namespace Envoy diff --git a/source/common/secret/BUILD b/source/common/secret/BUILD index f486c2f7ce8e..84defb7ef88b 100644 --- a/source/common/secret/BUILD +++ b/source/common/secret/BUILD @@ -58,6 +58,7 @@ envoy_cc_library( "//source/common/config:api_version_lib", "//source/common/config:subscription_base_interface", "//source/common/config:utility_lib", + "//source/common/config:watched_directory_lib", "//source/common/init:target_lib", "//source/common/protobuf:utility_lib", "//source/common/ssl:certificate_validation_context_config_impl_lib", diff --git a/source/common/secret/sds_api.cc b/source/common/secret/sds_api.cc index 163b91a13a31..3f319f03e638 100644 --- a/source/common/secret/sds_api.cc +++ b/source/common/secret/sds_api.cc @@ -12,6 +12,10 @@ namespace Envoy { namespace Secret { +SdsApiStats SdsApi::generateStats(Stats::Scope& scope) { + return {ALL_SDS_API_STATS(POOL_COUNTER(scope))}; +} + SdsApi::SdsApi(envoy::config::core::v3::ConfigSource sds_config, absl::string_view sds_config_name, Config::SubscriptionFactory& subscription_factory, TimeSource& time_source, ProtobufMessage::ValidationVisitor& validation_visitor, Stats::Store& stats, @@ -19,16 +23,17 @@ SdsApi::SdsApi(envoy::config::core::v3::ConfigSource sds_config, absl::string_vi : Envoy::Config::SubscriptionBase( sds_config.resource_api_version(), validation_visitor, "name"), init_target_(fmt::format("SdsApi {}", sds_config_name), [this] { initialize(); }), - stats_(stats), sds_config_(std::move(sds_config)), sds_config_name_(sds_config_name), - secret_hash_(0), clean_up_(std::move(destructor_cb)), + dispatcher_(dispatcher), api_(api), + scope_(stats.createScope(absl::StrCat("sds.", sds_config_name, "."))), + sds_api_stats_(generateStats(*scope_)), sds_config_(std::move(sds_config)), + sds_config_name_(sds_config_name), secret_hash_(0), clean_up_(std::move(destructor_cb)), subscription_factory_(subscription_factory), time_source_(time_source), secret_data_{sds_config_name_, "uninitialized", - time_source_.systemTime()}, - dispatcher_(dispatcher), api_(api) { + time_source_.systemTime()} { const auto resource_name = getResourceName(); // This has to happen here (rather than in initialize()) as it can throw exceptions. subscription_ = subscription_factory_.subscriptionFromConfigSource( - sds_config_, Grpc::Common::typeUrl(resource_name), stats_, *this, resource_decoder_); + sds_config_, Grpc::Common::typeUrl(resource_name), *scope_, *this, resource_decoder_); // TODO(JimmyCYJ): Implement chained_init_manager, so that multiple init_manager // can be chained together to behave as one init_manager. In that way, we let @@ -36,6 +41,48 @@ SdsApi::SdsApi(envoy::config::core::v3::ConfigSource sds_config, absl::string_vi // each init manager has a chance to initialize its targets. } +void SdsApi::resolveDataSource(const FileContentMap& files, + envoy::config::core::v3::DataSource& data_source) { + if (data_source.specifier_case() == + envoy::config::core::v3::DataSource::SpecifierCase::kFilename) { + const std::string& content = files.at(data_source.filename()); + data_source.set_inline_bytes(content); + } +} + +void SdsApi::onWatchUpdate() { + // Filesystem reads and update callbacks can fail if the key material is missing or bad. We're not + // under an onConfigUpdate() context, so we need to catch these cases explicitly here. + try { + // Obtain a stable set of files. If a rotation happens while we're reading, + // then we need to try again. + uint64_t prev_hash = 0; + FileContentMap files = loadFiles(); + uint64_t next_hash = getHashForFiles(files); + const uint64_t MaxBoundedRetries = 5; + for (uint64_t bounded_retries = MaxBoundedRetries; + next_hash != prev_hash && bounded_retries > 0; --bounded_retries) { + files = loadFiles(); + prev_hash = next_hash; + next_hash = getHashForFiles(files); + } + if (next_hash != prev_hash) { + ENVOY_LOG_MISC( + warn, "Unable to atomically refresh secrets due to > {} non-atomic rotations observed", + MaxBoundedRetries); + } + const uint64_t new_hash = next_hash; + if (new_hash != files_hash_) { + resolveSecret(files); + update_callback_manager_.runCallbacks(); + files_hash_ = new_hash; + } + } catch (const EnvoyException& e) { + ENVOY_LOG_MISC(warn, fmt::format("Failed to reload certificates: {}", e.what())); + sds_api_stats_.key_rotation_failed_.inc(); + } +} + void SdsApi::onConfigUpdate(const std::vector& resources, const std::string& version_info) { validateUpdateSize(resources.size()); @@ -47,35 +94,40 @@ void SdsApi::onConfigUpdate(const std::vector& resou fmt::format("Unexpected SDS secret (expecting {}): {}", sds_config_name_, secret.name())); } - uint64_t new_hash = MessageUtil::hash(secret); + const uint64_t new_hash = MessageUtil::hash(secret); if (new_hash != secret_hash_) { validateConfig(secret); secret_hash_ = new_hash; setSecret(secret); + const auto files = loadFiles(); + files_hash_ = getHashForFiles(files); + resolveSecret(files); update_callback_manager_.runCallbacks(); - // List DataSources that refer to files - auto files = getDataSourceFilenames(); - if (!files.empty()) { - // Create new watch, also destroys the old watch if any. - watcher_ = dispatcher_.createFilesystemWatcher(); - files_hash_ = getHashForFiles(); - for (auto const& filename : files) { - // Watch for directory instead of file. This allows users to do atomic renames - // on directory level (e.g. Kubernetes secret update). - const auto result = api_.fileSystem().splitPathFromFilename(filename); - watcher_->addWatch(absl::StrCat(result.directory_, "/"), - Filesystem::Watcher::Events::MovedTo, [this](uint32_t) { - uint64_t new_hash = getHashForFiles(); - if (new_hash != files_hash_) { - update_callback_manager_.runCallbacks(); - files_hash_ = new_hash; - } - }); - } + auto* watched_directory = getWatchedDirectory(); + // Either we have a watched path and can defer the watch monitoring to a + // WatchedDirectory object, or we need to implement per-file watches in the else + // clause. + if (watched_directory != nullptr) { + watched_directory->setCallback([this]() { onWatchUpdate(); }); } else { - watcher_.reset(); // Destroy the old watch if any + // List DataSources that refer to files + auto files = getDataSourceFilenames(); + if (!files.empty()) { + // Create new watch, also destroys the old watch if any. + watcher_ = dispatcher_.createFilesystemWatcher(); + for (auto const& filename : files) { + // Watch for directory instead of file. This allows users to do atomic renames + // on directory level (e.g. Kubernetes secret update). + const auto result = api_.fileSystem().splitPathFromFilename(filename); + watcher_->addWatch(absl::StrCat(result.directory_, "/"), + Filesystem::Watcher::Events::MovedTo, + [this](uint32_t) { onWatchUpdate(); }); + } + } else { + watcher_.reset(); // Destroy the old watch if any + } } } secret_data_.last_updated_ = time_source_.systemTime(); @@ -114,36 +166,44 @@ void SdsApi::initialize() { SdsApi::SecretData SdsApi::secretData() { return secret_data_; } -uint64_t SdsApi::getHashForFiles() { - uint64_t hash = 0; +SdsApi::FileContentMap SdsApi::loadFiles() { + FileContentMap files; for (auto const& filename : getDataSourceFilenames()) { - hash = HashUtil::xxHash64(api_.fileSystem().fileReadToEnd(filename), hash); + files[filename] = api_.fileSystem().fileReadToEnd(filename); + } + return files; +} + +uint64_t SdsApi::getHashForFiles(const FileContentMap& files) { + uint64_t hash = 0; + for (const auto& it : files) { + hash = HashUtil::xxHash64(it.second, hash); } return hash; } std::vector TlsCertificateSdsApi::getDataSourceFilenames() { std::vector files; - if (tls_certificate_secrets_ && tls_certificate_secrets_->has_certificate_chain() && - tls_certificate_secrets_->certificate_chain().specifier_case() == + if (sds_tls_certificate_secrets_ && sds_tls_certificate_secrets_->has_certificate_chain() && + sds_tls_certificate_secrets_->certificate_chain().specifier_case() == envoy::config::core::v3::DataSource::SpecifierCase::kFilename) { - files.push_back(tls_certificate_secrets_->certificate_chain().filename()); + files.push_back(sds_tls_certificate_secrets_->certificate_chain().filename()); } - if (tls_certificate_secrets_ && tls_certificate_secrets_->has_private_key() && - tls_certificate_secrets_->private_key().specifier_case() == + if (sds_tls_certificate_secrets_ && sds_tls_certificate_secrets_->has_private_key() && + sds_tls_certificate_secrets_->private_key().specifier_case() == envoy::config::core::v3::DataSource::SpecifierCase::kFilename) { - files.push_back(tls_certificate_secrets_->private_key().filename()); + files.push_back(sds_tls_certificate_secrets_->private_key().filename()); } return files; } std::vector CertificateValidationContextSdsApi::getDataSourceFilenames() { std::vector files; - if (certificate_validation_context_secrets_ && - certificate_validation_context_secrets_->has_trusted_ca() && - certificate_validation_context_secrets_->trusted_ca().specifier_case() == + if (sds_certificate_validation_context_secrets_ && + sds_certificate_validation_context_secrets_->has_trusted_ca() && + sds_certificate_validation_context_secrets_->trusted_ca().specifier_case() == envoy::config::core::v3::DataSource::SpecifierCase::kFilename) { - files.push_back(certificate_validation_context_secrets_->trusted_ca().filename()); + files.push_back(sds_certificate_validation_context_secrets_->trusted_ca().filename()); } return files; } diff --git a/source/common/secret/sds_api.h b/source/common/secret/sds_api.h index 52b78a37a49b..1c58feb6c5bc 100644 --- a/source/common/secret/sds_api.h +++ b/source/common/secret/sds_api.h @@ -23,6 +23,7 @@ #include "common/common/cleanup.h" #include "common/config/subscription_base.h" #include "common/config/utility.h" +#include "common/config/watched_directory.h" #include "common/init/target_impl.h" #include "common/ssl/certificate_validation_context_config_impl.h" #include "common/ssl/tls_certificate_config_impl.h" @@ -30,6 +31,18 @@ namespace Envoy { namespace Secret { +/** + * All SDS API. @see stats_macros.h + */ +#define ALL_SDS_API_STATS(COUNTER) COUNTER(key_rotation_failed) + +/** + * Struct definition for all SDS API stats. @see stats_macros.h + */ +struct SdsApiStats { + ALL_SDS_API_STATS(GENERATE_COUNTER_STRUCT) +}; + /** * SDS API implementation that fetches secrets from SDS server via Subscription. */ @@ -60,8 +73,13 @@ class SdsApi : public Envoy::Config::SubscriptionBase< } protected: + // Ordered for hash stability. + using FileContentMap = std::map; + // Creates new secrets. virtual void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret&) PURE; + // Refresh secrets, e.g. re-resolve symlinks in secret paths. + virtual void resolveSecret(const FileContentMap& /*files*/){}; virtual void validateConfig(const envoy::extensions::transport_sockets::tls::v3::Secret&) PURE; Common::CallbackManager<> update_callback_manager_; @@ -74,15 +92,26 @@ class SdsApi : public Envoy::Config::SubscriptionBase< void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; virtual std::vector getDataSourceFilenames() PURE; + virtual Config::WatchedDirectory* getWatchedDirectory() PURE; + + void resolveDataSource(const FileContentMap& files, + envoy::config::core::v3::DataSource& data_source); Init::TargetImpl init_target_; + Event::Dispatcher& dispatcher_; + Api::Api& api_; private: void validateUpdateSize(int num_resources); void initialize(); - uint64_t getHashForFiles(); + FileContentMap loadFiles(); + uint64_t getHashForFiles(const FileContentMap& files); + // Invoked for filesystem watches on update. + void onWatchUpdate(); + SdsApiStats generateStats(Stats::Scope& scope); - Stats::Store& stats_; + Stats::ScopePtr scope_; + SdsApiStats sds_api_stats_; const envoy::config::core::v3::ConfigSource sds_config_; Config::SubscriptionPtr subscription_; @@ -94,8 +123,6 @@ class SdsApi : public Envoy::Config::SubscriptionBase< Config::SubscriptionFactory& subscription_factory_; TimeSource& time_source_; SecretData secret_data_; - Event::Dispatcher& dispatcher_; - Api::Api& api_; std::unique_ptr watcher_; bool registered_init_target_{false}; }; @@ -142,7 +169,7 @@ class TlsCertificateSdsApi : public SdsApi, public TlsCertificateConfigProvider // SecretProvider const envoy::extensions::transport_sockets::tls::v3::TlsCertificate* secret() const override { - return tls_certificate_secrets_.get(); + return resolved_tls_certificate_secrets_.get(); } Common::CallbackHandle* addValidationCallback( std::function) @@ -158,15 +185,37 @@ class TlsCertificateSdsApi : public SdsApi, public TlsCertificateConfigProvider protected: void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) override { - tls_certificate_secrets_ = + sds_tls_certificate_secrets_ = std::make_unique( secret.tls_certificate()); + resolved_tls_certificate_secrets_ = nullptr; + if (secret.tls_certificate().has_watched_directory()) { + watched_directory_ = std::make_unique( + secret.tls_certificate().watched_directory(), dispatcher_); + } else { + watched_directory_.reset(); + } + } + void resolveSecret(const FileContentMap& files) override { + resolved_tls_certificate_secrets_ = + std::make_unique( + *sds_tls_certificate_secrets_); + // We replace path based secrets with inlined secrets on update. + resolveDataSource(files, *resolved_tls_certificate_secrets_->mutable_certificate_chain()); + resolveDataSource(files, *resolved_tls_certificate_secrets_->mutable_private_key()); } void validateConfig(const envoy::extensions::transport_sockets::tls::v3::Secret&) override {} std::vector getDataSourceFilenames() override; + Config::WatchedDirectory* getWatchedDirectory() override { return watched_directory_.get(); } private: - TlsCertificatePtr tls_certificate_secrets_; + // Path to watch for rotation. + Config::WatchedDirectoryPtr watched_directory_; + // TlsCertificate according to SDS source. + TlsCertificatePtr sds_tls_certificate_secrets_; + // TlsCertificate after reloading. Path based certificates are inlined for + // future read consistency. + TlsCertificatePtr resolved_tls_certificate_secrets_; }; /** @@ -205,7 +254,7 @@ class CertificateValidationContextSdsApi : public SdsApi, // SecretProvider const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext* secret() const override { - return certificate_validation_context_secrets_.get(); + return resolved_certificate_validation_context_secrets_.get(); } Common::CallbackHandle* addUpdateCallback(std::function callback) override { if (secret()) { @@ -223,9 +272,26 @@ class CertificateValidationContextSdsApi : public SdsApi, protected: void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) override { - certificate_validation_context_secrets_ = std::make_unique< + sds_certificate_validation_context_secrets_ = std::make_unique< envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext>( secret.validation_context()); + resolved_certificate_validation_context_secrets_ = nullptr; + if (secret.validation_context().has_watched_directory()) { + watched_directory_ = std::make_unique( + secret.validation_context().watched_directory(), dispatcher_); + } else { + watched_directory_.reset(); + } + } + + void resolveSecret(const FileContentMap& files) override { + // Copy existing CertificateValidationContext. + resolved_certificate_validation_context_secrets_ = std::make_unique< + envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext>( + *sds_certificate_validation_context_secrets_); + // We replace path based secrets with inlined secrets on update. + resolveDataSource(files, + *resolved_certificate_validation_context_secrets_->mutable_trusted_ca()); } void @@ -233,9 +299,16 @@ class CertificateValidationContextSdsApi : public SdsApi, validation_callback_manager_.runCallbacks(secret.validation_context()); } std::vector getDataSourceFilenames() override; + Config::WatchedDirectory* getWatchedDirectory() override { return watched_directory_.get(); } private: - CertificateValidationContextPtr certificate_validation_context_secrets_; + // Directory to watch for rotation. + Config::WatchedDirectoryPtr watched_directory_; + // CertificateValidationContext according to SDS source; + CertificateValidationContextPtr sds_certificate_validation_context_secrets_; + // CertificateValidationContext after resolving paths via watched_directory_. + CertificateValidationContextPtr resolved_certificate_validation_context_secrets_; + // Path based certificates are inlined for future read consistency. Common::CallbackManager< const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext&> validation_callback_manager_; @@ -306,6 +379,7 @@ class TlsSessionTicketKeysSdsApi : public SdsApi, public TlsSessionTicketKeysCon validation_callback_manager_.runCallbacks(secret.session_ticket_keys()); } std::vector getDataSourceFilenames() override; + Config::WatchedDirectory* getWatchedDirectory() override { return nullptr; } private: Secret::TlsSessionTicketKeysPtr tls_session_ticket_keys_; @@ -346,7 +420,7 @@ class GenericSecretSdsApi : public SdsApi, public GenericSecretConfigProvider { // SecretProvider const envoy::extensions::transport_sockets::tls::v3::GenericSecret* secret() const override { - return generic_secret.get(); + return generic_secret_.get(); } Common::CallbackHandle* addUpdateCallback(std::function callback) override { return update_callback_manager_.add(callback); @@ -359,17 +433,19 @@ class GenericSecretSdsApi : public SdsApi, public GenericSecretConfigProvider { protected: void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) override { - generic_secret = std::make_unique( - secret.generic_secret()); + generic_secret_ = + std::make_unique( + secret.generic_secret()); } void validateConfig(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) override { validation_callback_manager_.runCallbacks(secret.generic_secret()); } std::vector getDataSourceFilenames() override; + Config::WatchedDirectory* getWatchedDirectory() override { return nullptr; } private: - GenericSecretPtr generic_secret; + GenericSecretPtr generic_secret_; Common::CallbackManager validation_callback_manager_; }; diff --git a/test/common/config/BUILD b/test/common/config/BUILD index 52c508569ec6..c95c4b8bb72e 100644 --- a/test/common/config/BUILD +++ b/test/common/config/BUILD @@ -502,3 +502,13 @@ envoy_cc_test( "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], ) + +envoy_cc_test( + name = "watched_directory_test", + srcs = ["watched_directory_test.cc"], + deps = [ + "//source/common/config:watched_directory_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/filesystem:filesystem_mocks", + ], +) diff --git a/test/common/config/watched_directory_test.cc b/test/common/config/watched_directory_test.cc new file mode 100644 index 000000000000..8988306d4ad4 --- /dev/null +++ b/test/common/config/watched_directory_test.cc @@ -0,0 +1,33 @@ +#include "envoy/filesystem/watcher.h" + +#include "common/config/watched_directory.h" + +#include "test/mocks/event/mocks.h" +#include "test/mocks/filesystem/mocks.h" + +#include "gtest/gtest.h" + +using testing::Return; +using testing::SaveArg; + +namespace Envoy { +namespace Config { + +TEST(WatchedDirectory, All) { + Event::MockDispatcher dispatcher; + envoy::config::core::v3::WatchedDirectory config; + config.set_path("foo/bar"); + auto* watcher = new Filesystem::MockWatcher(); + EXPECT_CALL(dispatcher, createFilesystemWatcher_()).WillOnce(Return(watcher)); + Filesystem::Watcher::OnChangedCb cb; + EXPECT_CALL(*watcher, addWatch("foo/bar/", Filesystem::Watcher::Events::MovedTo, _)) + .WillOnce(SaveArg<2>(&cb)); + WatchedDirectory wd(config, dispatcher); + bool called = false; + wd.setCallback([&called] { called = true; }); + cb(Filesystem::Watcher::Events::MovedTo); + EXPECT_TRUE(called); +} + +} // namespace Config +} // namespace Envoy diff --git a/test/common/secret/BUILD b/test/common/secret/BUILD index 48572641a39b..6161773b7b8c 100644 --- a/test/common/secret/BUILD +++ b/test/common/secret/BUILD @@ -43,12 +43,15 @@ envoy_cc_test( "//source/common/secret:sds_api_lib", "//source/common/ssl:certificate_validation_context_config_impl_lib", "//source/common/ssl:tls_certificate_config_impl_lib", + "//test/common/stats:stat_test_utility_lib", + "//test/mocks/config:config_mocks", + "//test/mocks/filesystem:filesystem_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/init:init_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/secret:secret_mocks", - "//test/mocks/server:instance_mocks", "//test/test_common:environment_lib", + "//test/test_common:logging_lib", "//test/test_common:registry_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc index 8d5cdb00294a..12386bbf535e 100644 --- a/test/common/secret/sds_api_test.cc +++ b/test/common/secret/sds_api_test.cc @@ -12,29 +12,38 @@ #include "common/ssl/certificate_validation_context_config_impl.h" #include "common/ssl/tls_certificate_config_impl.h" +#include "test/common/stats/stat_test_utility.h" +#include "test/mocks/config/mocks.h" +#include "test/mocks/filesystem/mocks.h" #include "test/mocks/grpc/mocks.h" #include "test/mocks/init/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/secret/mocks.h" -#include "test/mocks/server/instance.h" #include "test/test_common/environment.h" +#include "test/test_common/logging.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using ::testing::_; +using ::testing::InSequence; using ::testing::Invoke; using ::testing::InvokeWithoutArgs; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::Throw; namespace Envoy { namespace Secret { namespace { -class SdsApiTest : public testing::Test { +class SdsApiTestBase { protected: - SdsApiTest() - : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher("test_thread")) {} + SdsApiTestBase() { + api_ = Api::createApiForTest(); + dispatcher_ = api_->allocateDispatcher("test_thread"); + } void initialize() { init_target_handle_->initialize(init_watcher_); } void setupMocks() { @@ -51,19 +60,21 @@ class SdsApiTest : public testing::Test { Event::GlobalTimeSystem time_system_; Init::TargetHandlePtr init_target_handle_; Event::DispatcherPtr dispatcher_; + Stats::TestUtil::TestStore stats_; }; +class SdsApiTest : public testing::Test, public SdsApiTestBase {}; + // Validate that SdsApi object is created and initialized successfully. TEST_F(SdsApiTest, BasicTest) { ::testing::InSequence s; const envoy::service::secret::v3::SdsDummy dummy; - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); initialize(); } @@ -72,7 +83,6 @@ TEST_F(SdsApiTest, BasicTest) { // has been already initialized. This is a regression test for // https://github.com/envoyproxy/envoy/issues/12013 TEST_F(SdsApiTest, InitManagerInitialised) { - NiceMock server; std::string sds_config = R"EOF( resources: @@ -90,7 +100,7 @@ TEST_F(SdsApiTest, InitManagerInitialised) { NiceMock callbacks; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("name"); - Config::SubscriptionStats stats(Config::Utility::generateStats(server.stats())); + Config::SubscriptionStats stats(Config::Utility::generateStats(stats_)); NiceMock validation_visitor; envoy::config::core::v3::ConfigSource config_source; @@ -113,8 +123,8 @@ TEST_F(SdsApiTest, InitManagerInitialised) { EXPECT_EQ(Init::Manager::State::Initializing, init_manager.state()); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); EXPECT_NO_THROW(sds_api.registerInitTarget(init_manager)); } @@ -122,7 +132,6 @@ TEST_F(SdsApiTest, InitManagerInitialised) { // https://github.com/envoyproxy/envoy/issues/10976. TEST_F(SdsApiTest, BadConfigSource) { ::testing::InSequence s; - NiceMock server; envoy::config::core::v3::ConfigSource config_source; EXPECT_CALL(subscription_factory_, subscriptionFromConfigSource(_, _, _, _, _)) .WillOnce(InvokeWithoutArgs([]() -> Config::SubscriptionPtr { @@ -131,19 +140,18 @@ TEST_F(SdsApiTest, BadConfigSource) { })); EXPECT_THROW_WITH_MESSAGE(TlsCertificateSdsApi( config_source, "abc.com", subscription_factory_, time_system_, - validation_visitor_, server.stats(), []() {}, *dispatcher_, *api_), + validation_visitor_, stats_, []() {}, *dispatcher_, *api_), EnvoyException, "bad config"); } // Validate that TlsCertificateSdsApi updates secrets successfully if a good secret // is passed to onConfigUpdate(). TEST_F(SdsApiTest, DynamicTlsCertificateUpdateSuccess) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); initialize(); NiceMock secret_callback; @@ -180,15 +188,317 @@ TEST_F(SdsApiTest, DynamicTlsCertificateUpdateSuccess) { handle->remove(); } +class SdsRotationApiTest : public SdsApiTestBase { +protected: + SdsRotationApiTest() { + api_ = Api::createApiForTest(filesystem_); + setupMocks(); + EXPECT_CALL(filesystem_, splitPathFromFilename(_)) + .WillRepeatedly(Invoke([](absl::string_view path) -> Filesystem::PathSplitResult { + return Filesystem::fileSystemForTest().splitPathFromFilename(path); + })); + } + + Secret::MockSecretCallbacks secret_callback_; + Common::CallbackHandle* handle_{}; + std::vector watch_cbs_; + Event::MockDispatcher mock_dispatcher_; + Filesystem::MockInstance filesystem_; +}; + +class TlsCertificateSdsRotationApiTest : public testing::TestWithParam, + public SdsRotationApiTest { +protected: + TlsCertificateSdsRotationApiTest() + : watched_directory_(GetParam()), cert_path_("/foo/bar/cert.pem"), + key_path_("/foo/bar/key.pem"), expected_watch_path_("/foo/bar/"), trigger_path_("/foo") { + envoy::config::core::v3::ConfigSource config_source; + sds_api_ = std::make_unique( + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, mock_dispatcher_, *api_); + sds_api_->registerInitTarget(init_manager_); + initialize(); + handle_ = sds_api_->addUpdateCallback([this]() { secret_callback_.onAddOrUpdateSecret(); }); + } + + ~TlsCertificateSdsRotationApiTest() override { handle_->remove(); } + + void onConfigUpdate(const std::string& cert_value, const std::string& key_value) { + const std::string yaml = fmt::format( + R"EOF( + name: "abc.com" + tls_certificate: + certificate_chain: + filename: "{}" + private_key: + filename: "{}" + )EOF", + cert_path_, key_path_); + envoy::extensions::transport_sockets::tls::v3::Secret typed_secret; + TestUtility::loadFromYaml(yaml, typed_secret); + if (watched_directory_) { + typed_secret.mutable_tls_certificate()->mutable_watched_directory()->set_path(trigger_path_); + } + const auto decoded_resources = TestUtility::decodeResources({typed_secret}); + + auto* watcher = new Filesystem::MockWatcher(); + if (watched_directory_) { + EXPECT_CALL(mock_dispatcher_, createFilesystemWatcher_()).WillOnce(Return(watcher)); + EXPECT_CALL(*watcher, addWatch(trigger_path_ + "/", Filesystem::Watcher::Events::MovedTo, _)) + .WillOnce( + Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { + watch_cbs_.push_back(cb); + })); + EXPECT_CALL(filesystem_, fileReadToEnd(cert_path_)).WillOnce(Return(cert_value)); + EXPECT_CALL(filesystem_, fileReadToEnd(key_path_)).WillOnce(Return(key_value)); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + } else { + EXPECT_CALL(filesystem_, fileReadToEnd(cert_path_)).WillOnce(Return(cert_value)); + EXPECT_CALL(filesystem_, fileReadToEnd(key_path_)).WillOnce(Return(key_value)); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + EXPECT_CALL(mock_dispatcher_, createFilesystemWatcher_()).WillOnce(Return(watcher)); + EXPECT_CALL(*watcher, addWatch(expected_watch_path_, Filesystem::Watcher::Events::MovedTo, _)) + .Times(2) + .WillRepeatedly( + Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { + watch_cbs_.push_back(cb); + })); + } + subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + } + + const bool watched_directory_; + std::string cert_path_; + std::string key_path_; + std::string expected_watch_path_; + std::string trigger_path_; + std::unique_ptr sds_api_; +}; + +INSTANTIATE_TEST_SUITE_P(TlsCertificateSdsRotationApiTestParams, TlsCertificateSdsRotationApiTest, + testing::Values(false, true)); + +class CertificateValidationContextSdsRotationApiTest : public testing::TestWithParam, + public SdsRotationApiTest { +protected: + CertificateValidationContextSdsRotationApiTest() { + envoy::config::core::v3::ConfigSource config_source; + sds_api_ = std::make_unique( + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, mock_dispatcher_, *api_); + sds_api_->registerInitTarget(init_manager_); + initialize(); + handle_ = sds_api_->addUpdateCallback([this]() { secret_callback_.onAddOrUpdateSecret(); }); + } + + ~CertificateValidationContextSdsRotationApiTest() override { handle_->remove(); } + + void onConfigUpdate(const std::string& trusted_ca_path, const std::string& trusted_ca_value, + const std::string& watch_path) { + const std::string yaml = fmt::format( + R"EOF( + name: "abc.com" + validation_context: + trusted_ca: + filename: "{}" + allow_expired_certificate: true + )EOF", + trusted_ca_path); + envoy::extensions::transport_sockets::tls::v3::Secret typed_secret; + TestUtility::loadFromYaml(yaml, typed_secret); + const auto decoded_resources = TestUtility::decodeResources({typed_secret}); + + auto* watcher = new Filesystem::MockWatcher(); + EXPECT_CALL(filesystem_, fileReadToEnd(trusted_ca_path)).WillOnce(Return(trusted_ca_value)); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + EXPECT_CALL(mock_dispatcher_, createFilesystemWatcher_()).WillOnce(Return(watcher)); + EXPECT_CALL(*watcher, addWatch(watch_path, Filesystem::Watcher::Events::MovedTo, _)) + .WillOnce(Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { + watch_cbs_.push_back(cb); + })); + subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + } + + std::unique_ptr sds_api_; +}; + +INSTANTIATE_TEST_SUITE_P(CertificateValidationContextSdsRotationApiTestParams, + CertificateValidationContextSdsRotationApiTest, + testing::Values(false, true)); + +// Initial onConfigUpdate() of TlsCertificate secret. +TEST_P(TlsCertificateSdsRotationApiTest, InitialUpdate) { + InSequence s; + onConfigUpdate("a", "b"); + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("a", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("b", secret.private_key().inline_bytes()); +} + +// Two distinct updates with onConfigUpdate() of TlsCertificate secret. +TEST_P(TlsCertificateSdsRotationApiTest, MultiUpdate) { + InSequence s; + onConfigUpdate("a", "b"); + { + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("a", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("b", secret.private_key().inline_bytes()); + } + + cert_path_ = "/new/foo/bar/cert.pem"; + key_path_ = "/new/foo/bar/key.pem"; + expected_watch_path_ = "/new/foo/bar/"; + onConfigUpdate("c", "d"); + { + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("c", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("d", secret.private_key().inline_bytes()); + } +} + +// Watch trigger without file change has no effect. +TEST_P(TlsCertificateSdsRotationApiTest, NopWatchTrigger) { + InSequence s; + onConfigUpdate("a", "b"); + + for (const auto& cb : watch_cbs_) { + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("a")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("b")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("a")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("b")); + cb(Filesystem::Watcher::Events::MovedTo); + } + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("a", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("b", secret.private_key().inline_bytes()); +} + +// Basic rotation of TlsCertificate. +TEST_P(TlsCertificateSdsRotationApiTest, RotationWatchTrigger) { + InSequence s; + onConfigUpdate("a", "b"); + + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + watch_cbs_[0](Filesystem::Watcher::Events::MovedTo); + if (!watched_directory_) { + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + watch_cbs_[1](Filesystem::Watcher::Events::MovedTo); + } + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("c", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("d", secret.private_key().inline_bytes()); +} + +// Failed rotation of TlsCertificate. +TEST_P(TlsCertificateSdsRotationApiTest, FailedRotation) { + InSequence s; + onConfigUpdate("a", "b"); + + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")) + .WillOnce(Throw(EnvoyException("fail"))); + EXPECT_LOG_CONTAINS("warn", "Failed to reload certificates: ", + watch_cbs_[0](Filesystem::Watcher::Events::MovedTo)); + EXPECT_EQ(1U, stats_.counter("sds.abc.com.key_rotation_failed").value()); + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("a", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("b", secret.private_key().inline_bytes()); +} + +// Basic rotation of CertificateValidationContext. +TEST_P(CertificateValidationContextSdsRotationApiTest, CertificateValidationContext) { + InSequence s; + onConfigUpdate("/foo/bar/ca.pem", "a", "/foo/bar/"); + + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/ca.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/ca.pem")).WillOnce(Return("c")); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + watch_cbs_[0](Filesystem::Watcher::Events::MovedTo); + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("c", secret.trusted_ca().inline_bytes()); +} + +// Hash consistency verification prevents races. +TEST_P(TlsCertificateSdsRotationApiTest, RotationConsistency) { + InSequence s; + onConfigUpdate("a", "b"); + + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("a")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + watch_cbs_[0](Filesystem::Watcher::Events::MovedTo); + if (!watched_directory_) { + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + watch_cbs_[1](Filesystem::Watcher::Events::MovedTo); + } + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("c", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("d", secret.private_key().inline_bytes()); +} + +// Hash consistency verification failure, no callback. +TEST_P(TlsCertificateSdsRotationApiTest, RotationConsistencyExhaustion) { + InSequence s; + onConfigUpdate("a", "b"); + + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("a")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("c")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("e")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("f")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("d")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("f")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("g")); + // We've exhausted the bounded retries, but continue with the non-atomic rotation. + EXPECT_CALL(secret_callback_, onAddOrUpdateSecret()); + EXPECT_LOG_CONTAINS( + "warn", "Unable to atomically refresh secrets due to > 5 non-atomic rotations observed", + watch_cbs_[0](Filesystem::Watcher::Events::MovedTo)); + if (!watched_directory_) { + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("f")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("g")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/cert.pem")).WillOnce(Return("f")); + EXPECT_CALL(filesystem_, fileReadToEnd("/foo/bar/key.pem")).WillOnce(Return("g")); + watch_cbs_[1](Filesystem::Watcher::Events::MovedTo); + } + + const auto& secret = *sds_api_->secret(); + EXPECT_EQ("f", secret.certificate_chain().inline_bytes()); + EXPECT_EQ("g", secret.private_key().inline_bytes()); +} + class PartialMockSds : public SdsApi { public: - PartialMockSds(NiceMock& server, NiceMock& init_manager, + PartialMockSds(Stats::Store& stats, NiceMock& init_manager, envoy::config::core::v3::ConfigSource& config_source, Config::SubscriptionFactory& subscription_factory, TimeSource& time_source, Event::Dispatcher& dispatcher, Api::Api& api) : SdsApi( - config_source, "abc.com", subscription_factory, time_source, validation_visitor_, - server.stats(), []() {}, dispatcher, api) { + config_source, "abc.com", subscription_factory, time_source, validation_visitor_, stats, + []() {}, dispatcher, api) { registerInitTarget(init_manager); } @@ -202,6 +512,7 @@ class PartialMockSds : public SdsApi { void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret&) override {} void validateConfig(const envoy::extensions::transport_sockets::tls::v3::Secret&) override {} std::vector getDataSourceFilenames() override { return {}; } + Config::WatchedDirectory* getWatchedDirectory() override { return nullptr; } NiceMock validation_visitor_; }; @@ -214,11 +525,10 @@ TEST_F(SdsApiTest, Delta) { Config::DecodedResourceImpl resource(std::move(secret), "name", {}, "version1"); std::vector resources{resource}; - NiceMock server; envoy::config::core::v3::ConfigSource config_source; Event::GlobalTimeSystem time_system; setupMocks(); - PartialMockSds sds(server, init_manager_, config_source, subscription_factory_, time_system, + PartialMockSds sds(stats_, init_manager_, config_source, subscription_factory_, time_system, *dispatcher_, *api_); initialize(); EXPECT_CALL(sds, onConfigUpdate(DecodedResourcesEq(resources), "version1")); @@ -238,12 +548,11 @@ TEST_F(SdsApiTest, Delta) { // Tests SDS's use of the delta variant of onConfigUpdate(). TEST_F(SdsApiTest, DeltaUpdateSuccess) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); NiceMock secret_callback; @@ -284,12 +593,11 @@ TEST_F(SdsApiTest, DeltaUpdateSuccess) { // Validate that CertificateValidationContextSdsApi updates secrets successfully if // a good secret is passed to onConfigUpdate(). TEST_F(SdsApiTest, DynamicCertificateValidationContextUpdateSuccess) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); CertificateValidationContextSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); NiceMock secret_callback; @@ -339,12 +647,11 @@ class MockCvcValidationCallback : public CvcValidationCallback { // a good secret is passed to onConfigUpdate(), and that merged CertificateValidationContext // provides correct information. TEST_F(SdsApiTest, DefaultCertificateValidationContextTest) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); CertificateValidationContextSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); NiceMock secret_callback; @@ -428,12 +735,11 @@ class MockGenericSecretValidationCallback : public GenericSecretValidationCallba // Validate that GenericSecretSdsApi updates secrets successfully if // a good secret is passed to onConfigUpdate(). TEST_F(SdsApiTest, GenericSecretSdsApiTest) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); GenericSecretSdsApi sds_api( config_source, "encryption_key", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + stats_, []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); NiceMock secret_callback; @@ -474,12 +780,11 @@ name: "encryption_key" // Validate that SdsApi throws exception if an empty secret is passed to onConfigUpdate(). TEST_F(SdsApiTest, EmptyResource) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); initialize(); @@ -490,12 +795,11 @@ TEST_F(SdsApiTest, EmptyResource) { // Validate that SdsApi throws exception if multiple secrets are passed to onConfigUpdate(). TEST_F(SdsApiTest, SecretUpdateWrongSize) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); std::string yaml = @@ -521,12 +825,11 @@ TEST_F(SdsApiTest, SecretUpdateWrongSize) { // Validate that SdsApi throws exception if secret name passed to onConfigUpdate() // does not match configured name. TEST_F(SdsApiTest, SecretUpdateWrongSecretName) { - NiceMock server; envoy::config::core::v3::ConfigSource config_source; setupMocks(); TlsCertificateSdsApi sds_api( - config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, - server.stats(), []() {}, *dispatcher_, *api_); + config_source, "abc.com", subscription_factory_, time_system_, validation_visitor_, stats_, + []() {}, *dispatcher_, *api_); sds_api.registerInitTarget(init_manager_); std::string yaml = diff --git a/test/integration/BUILD b/test/integration/BUILD index db3e796099c6..43dfa373074d 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1139,8 +1139,13 @@ envoy_cc_test( "sds_dynamic_integration_test.cc", ], data = [ + "sds_dynamic_key_rotation_setup.sh", "//test/config/integration/certs", ], + # TODO(envoyproxy/windows-dev): The key rotation in SdsDynamicKeyRotationIntegrationTest via + # TestEnvironment::renameFile() fails on Windows. The renameFile() implementation does not + # correctly handle symlinks. + tags = ["fails_on_windows"], deps = [ ":http_integration_lib", "//source/common/config:api_version_lib", diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index 2a44fc58dc86..f686a0946fb4 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -139,6 +139,7 @@ class SdsDynamicIntegrationBaseTest : public Grpc::GrpcClientIntegrationParamTes const std::string server_cert_; const std::string validation_secret_; const std::string client_cert_; + bool v3_resource_api_{false}; }; // Downstream SDS integration test: static Listener with ssl cert from SDS @@ -201,6 +202,103 @@ class SdsDynamicDownstreamIntegrationTest : public SdsDynamicIntegrationBaseTest INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, SdsDynamicDownstreamIntegrationTest, GRPC_CLIENT_INTEGRATION_PARAMS); +class SdsDynamicKeyRotationIntegrationTest : public SdsDynamicDownstreamIntegrationTest { +protected: + envoy::extensions::transport_sockets::tls::v3::Secret getCurrentServerSecret() { + envoy::extensions::transport_sockets::tls::v3::Secret secret; + secret.set_name(server_cert_); + auto* tls_certificate = secret.mutable_tls_certificate(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::temporaryPath("root/current/servercert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::temporaryPath("root/current/serverkey.pem")); + auto* watched_directory = tls_certificate->mutable_watched_directory(); + watched_directory->set_path(TestEnvironment::temporaryPath("root")); + return secret; + } +}; + +// We don't care about multiple gRPC types here, Envoy gRPC is fine, the +// interest is on the filesystem. +INSTANTIATE_TEST_SUITE_P( + IpVersionsClientType, SdsDynamicKeyRotationIntegrationTest, + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + testing::Values(Grpc::ClientType::EnvoyGrpc))); + +// Validate that a basic key-cert rotation works via symlink rename. +TEST_P(SdsDynamicKeyRotationIntegrationTest, BasicRotation) { + v3_resource_api_ = true; + TestEnvironment::exec( + {TestEnvironment::runfilesPath("test/integration/sds_dynamic_key_rotation_setup.sh")}); + + on_server_init_function_ = [this]() { + createSdsStream(*(fake_upstreams_[1])); + sendSdsResponse(getCurrentServerSecret()); + }; + initialize(); + + // Initial update from filesystem. + test_server_->waitForCounterGe( + listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 1); + + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(); + }; + // First request with server{cert,key}.pem. + testRouterHeaderOnlyRequestAndResponse(&creator); + cleanupUpstreamAndDownstream(); + // Rotate. + TestEnvironment::renameFile(TestEnvironment::temporaryPath("root/new"), + TestEnvironment::temporaryPath("root/current")); + test_server_->waitForCounterGe( + listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 2); + // The rotation is not a SDS attempt, so no change to these stats. + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.server_cert.update_rejected")->value()); + + // First request with server_ecdsa{cert,key}.pem. + testRouterHeaderOnlyRequestAndResponse(&creator); +} + +// Validate that rotating to a directory with missing certs is handled. +TEST_P(SdsDynamicKeyRotationIntegrationTest, EmptyRotation) { + v3_resource_api_ = true; + TestEnvironment::exec( + {TestEnvironment::runfilesPath("test/integration/sds_dynamic_key_rotation_setup.sh")}); + + on_server_init_function_ = [this]() { + createSdsStream(*(fake_upstreams_[1])); + sendSdsResponse(getCurrentServerSecret()); + }; + initialize(); + + // Initial update from filesystem. + test_server_->waitForCounterGe( + listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds"), 1); + + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(); + }; + // First request with server{cert,key}.pem. + testRouterHeaderOnlyRequestAndResponse(&creator); + cleanupUpstreamAndDownstream(); + + // Rotate to an empty directory, this should fail. + TestEnvironment::renameFile(TestEnvironment::temporaryPath("root/empty"), + TestEnvironment::temporaryPath("root/current")); + test_server_->waitForCounterEq("sds.server_cert.key_rotation_failed", 1); + EXPECT_EQ(1, + test_server_ + ->counter(listenerStatPrefix("server_ssl_socket_factory.ssl_context_update_by_sds")) + ->value()); + // The rotation is not a SDS attempt, so no change to these stats. + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.server_cert.update_rejected")->value()); + + // Requests continue to work with key/cert pair. + testRouterHeaderOnlyRequestAndResponse(&creator); +} + // A test that SDS server send a good server secret for a static listener. // The first ssl request should be OK. TEST_P(SdsDynamicDownstreamIntegrationTest, BasicSuccess) { @@ -214,6 +312,10 @@ TEST_P(SdsDynamicDownstreamIntegrationTest, BasicSuccess) { return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.server_cert.update_rejected")->value()); } // A test that SDS server send a bad secret for a static listener, @@ -231,6 +333,10 @@ TEST_P(SdsDynamicDownstreamIntegrationTest, WrongSecretFirst) { EXPECT_FALSE(codec_client_->connected()); codec_client_->connection()->close(Network::ConnectionCloseType::NoFlush); + // Failure + EXPECT_EQ(0, test_server_->counter("sds.server_cert.update_success")->value()); + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_rejected")->value()); + sendSdsResponse(getServerSecret()); // Wait for ssl_context_updated_by_sds counter. @@ -241,6 +347,10 @@ TEST_P(SdsDynamicDownstreamIntegrationTest, WrongSecretFirst) { return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_success")->value()); + EXPECT_EQ(1, test_server_->counter("sds.server_cert.update_rejected")->value()); } class SdsDynamicDownstreamCertValidationContextTest : public SdsDynamicDownstreamIntegrationTest { @@ -369,6 +479,10 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, BasicSuccess) { return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.validation_secret.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.validation_secret.update_rejected")->value()); } // A test that SDS server sends a certificate validation context for a static listener. @@ -386,6 +500,10 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, CombinedCertValidationCont return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.validation_secret.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.validation_secret.update_rejected")->value()); } // A test that verifies that both: static cluster and LDS listener are updated when using @@ -409,6 +527,10 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, BasicWithSharedSecret) { return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.validation_secret.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.validation_secret.update_rejected")->value()); } // A test that verifies that both: static cluster and LDS listener are updated when using @@ -433,6 +555,10 @@ TEST_P(SdsDynamicDownstreamCertValidationContextTest, CombinedValidationContextW return makeSslClientConnection(); }; testRouterHeaderOnlyRequestAndResponse(&creator); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.validation_secret.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.validation_secret.update_rejected")->value()); } // Upstream SDS integration test: a static cluster has ssl cert from SDS. @@ -504,6 +630,10 @@ TEST_P(SdsDynamicUpstreamIntegrationTest, BasicSuccess) { "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); testRouterHeaderOnlyRequestAndResponse(); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.client_cert.update_success")->value()); + EXPECT_EQ(0, test_server_->counter("sds.client_cert.update_rejected")->value()); } // To test a static cluster with sds. SDS send a bad client secret first. @@ -527,11 +657,19 @@ TEST_P(SdsDynamicUpstreamIntegrationTest, WrongSecretFirst) { ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + // Failure + EXPECT_EQ(0, test_server_->counter("sds.client_cert.update_success")->value()); + EXPECT_EQ(1, test_server_->counter("sds.client_cert.update_rejected")->value()); + sendSdsResponse(getClientSecret()); test_server_->waitForCounterGe( "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); testRouterHeaderOnlyRequestAndResponse(); + + // Success + EXPECT_EQ(1, test_server_->counter("sds.client_cert.update_success")->value()); + EXPECT_EQ(1, test_server_->counter("sds.client_cert.update_rejected")->value()); } } // namespace Ssl diff --git a/test/integration/sds_dynamic_key_rotation_setup.sh b/test/integration/sds_dynamic_key_rotation_setup.sh new file mode 100755 index 000000000000..2e5f30d88751 --- /dev/null +++ b/test/integration/sds_dynamic_key_rotation_setup.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +TEST_CERTS=test/config/integration/certs + +ROOT="${TEST_TMPDIR}"/root +SERVER_KEYCERT="${ROOT}"/server +SERVER_ECDSA_KEYCERT="${ROOT}"/server_ecdsa +EMPTY_KEYCERT="${ROOT}"/empty_keycert + +rm -rf "${ROOT}" +mkdir -p "${SERVER_KEYCERT}" +mkdir -p "${SERVER_ECDSA_KEYCERT}" +mkdir -p "${EMPTY_KEYCERT}" + +cp -f "${TEST_CERTS}"/server{cert,key}.pem "${SERVER_KEYCERT}" +cp -f "${TEST_CERTS}"/server_ecdsacert.pem "${SERVER_ECDSA_KEYCERT}"/servercert.pem +cp -f "${TEST_CERTS}"/server_ecdsakey.pem "${SERVER_ECDSA_KEYCERT}"/serverkey.pem + +ln -sf "${SERVER_KEYCERT}" "${ROOT}"/current +ln -sf "${SERVER_ECDSA_KEYCERT}" "${ROOT}"/new +ln -sf "${EMPTY_KEYCERT}" "${ROOT}"/empty diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index adb3e8b4de7c..08c04e889c74 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -379,6 +379,10 @@ ApiPtr createApiForTest() { Filesystem::fileSystemForTest()); } +ApiPtr createApiForTest(Filesystem::Instance& filesystem) { + return std::make_unique(Thread::threadFactoryForTest(), filesystem); +} + ApiPtr createApiForTest(Random::RandomGenerator& random) { return std::make_unique(Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), nullptr, nullptr, &random); diff --git a/test/test_common/utility.h b/test/test_common/utility.h index 1e0d25cd57f9..6b4abcd7c63c 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -1034,6 +1034,7 @@ makeHeaderMap(const std::initializer_list>& namespace Api { ApiPtr createApiForTest(); +ApiPtr createApiForTest(Filesystem::Instance& filesystem); ApiPtr createApiForTest(Random::RandomGenerator& random); ApiPtr createApiForTest(Stats::Store& stat_store); ApiPtr createApiForTest(Stats::Store& stat_store, Random::RandomGenerator& random); From e9d85da91fcc504265b0cded8b4df101bc35603c Mon Sep 17 00:00:00 2001 From: Yuchen Dai Date: Fri, 13 Nov 2020 15:05:14 -0800 Subject: [PATCH 098/117] CDS: remove warming cluster if CDS response desired (#13997) Signed-off-by: Yuchen Dai --- include/envoy/upstream/cluster_manager.h | 9 +- .../common/grpc/async_client_manager_impl.cc | 6 +- source/common/upstream/cds_api_impl.cc | 17 +++- source/common/upstream/cluster_manager_impl.h | 14 +-- source/common/upstream/load_stats_reporter.cc | 17 ++-- .../redis/cluster_refresh_manager_impl.cc | 6 +- .../extensions/stat_sinks/hystrix/hystrix.cc | 8 +- source/server/admin/clusters_handler.cc | 8 +- source/server/admin/config_dump_handler.cc | 13 ++- .../grpc/async_client_manager_impl_test.cc | 9 +- test/common/upstream/cds_api_impl_test.cc | 34 ++++--- .../upstream/cluster_manager_impl_test.cc | 6 +- .../upstream/load_stats_reporter_test.cc | 3 +- .../redis/cluster_refresh_manager_test.cc | 6 +- .../stats_sinks/hystrix/hystrix_test.cc | 18 ++-- test/integration/ads_integration_test.cc | 95 +++++++++++++++++-- .../custom_cluster_integration_test.cc | 8 +- test/integration/eds_integration_test.cc | 8 +- test/mocks/upstream/cluster_manager.h | 3 +- test/server/admin/clusters_handler_test.cc | 6 +- test/server/admin/config_dump_handler_test.cc | 20 ++-- test/server/configuration_impl_test.cc | 4 +- 22 files changed, 216 insertions(+), 102 deletions(-) diff --git a/include/envoy/upstream/cluster_manager.h b/include/envoy/upstream/cluster_manager.h index 5939092a371b..beb88299da28 100644 --- a/include/envoy/upstream/cluster_manager.h +++ b/include/envoy/upstream/cluster_manager.h @@ -126,12 +126,15 @@ class ClusterManager { initializeSecondaryClusters(const envoy::config::bootstrap::v3::Bootstrap& bootstrap) PURE; using ClusterInfoMap = absl::node_hash_map>; + struct ClusterInfoMaps { + ClusterInfoMap active_clusters_; + ClusterInfoMap warming_clusters_; + }; /** - * @return ClusterInfoMap all current clusters. These are the primary (not thread local) - * clusters which should only be used for stats/admin. + * @return ClusterInfoMap all current clusters including active and warming. */ - virtual ClusterInfoMap clusters() PURE; + virtual ClusterInfoMaps clusters() PURE; using ClusterSet = absl::flat_hash_set; diff --git a/source/common/grpc/async_client_manager_impl.cc b/source/common/grpc/async_client_manager_impl.cc index dc039473395c..5f809755f89d 100644 --- a/source/common/grpc/async_client_manager_impl.cc +++ b/source/common/grpc/async_client_manager_impl.cc @@ -21,9 +21,9 @@ AsyncClientFactoryImpl::AsyncClientFactoryImpl(Upstream::ClusterManager& cm, } const std::string& cluster_name = config.envoy_grpc().cluster_name(); - auto clusters = cm_.clusters(); - const auto& it = clusters.find(cluster_name); - if (it == clusters.end()) { + auto all_clusters = cm_.clusters(); + const auto& it = all_clusters.active_clusters_.find(cluster_name); + if (it == all_clusters.active_clusters_.end()) { throw EnvoyException(fmt::format("Unknown gRPC client cluster '{}'", cluster_name)); } if (it->second.get().info()->addedViaApi()) { diff --git a/source/common/upstream/cds_api_impl.cc b/source/common/upstream/cds_api_impl.cc index 0ca7d5763d5a..4568bf84b8f3 100644 --- a/source/common/upstream/cds_api_impl.cc +++ b/source/common/upstream/cds_api_impl.cc @@ -38,15 +38,22 @@ CdsApiImpl::CdsApiImpl(const envoy::config::core::v3::ConfigSource& cds_config, void CdsApiImpl::onConfigUpdate(const std::vector& resources, const std::string& version_info) { - ClusterManager::ClusterInfoMap clusters_to_remove = cm_.clusters(); - std::vector clusters; + auto all_existing_clusters = cm_.clusters(); + // Exclude the clusters which CDS wants to add. for (const auto& resource : resources) { - clusters_to_remove.erase(resource.get().name()); + all_existing_clusters.active_clusters_.erase(resource.get().name()); + all_existing_clusters.warming_clusters_.erase(resource.get().name()); } Protobuf::RepeatedPtrField to_remove_repeated; - for (const auto& [cluster_name, _] : clusters_to_remove) { + for (const auto& [cluster_name, _] : all_existing_clusters.active_clusters_) { *to_remove_repeated.Add() = cluster_name; } + for (const auto& [cluster_name, _] : all_existing_clusters.warming_clusters_) { + // Do not add the cluster twice when the cluster is both active and warming. + if (all_existing_clusters.active_clusters_.count(cluster_name) == 0) { + *to_remove_repeated.Add() = cluster_name; + } + } onConfigUpdate(resources, to_remove_repeated, version_info); } @@ -64,7 +71,7 @@ void CdsApiImpl::onConfigUpdate(const std::vector& a removed_resources.size()); std::vector exception_msgs; - absl::node_hash_set cluster_names; + absl::flat_hash_set cluster_names(added_resources.size()); bool any_applied = false; for (const auto& resource : added_resources) { envoy::config::cluster::v3::Cluster cluster; diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 63a5e701e9d6..837afe731a4e 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -236,15 +236,17 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggablecluster_); + clusters_maps.active_clusters_.emplace(cluster.first, *cluster.second->cluster_); } - - return clusters_map; + for (auto& cluster : warming_clusters_) { + clusters_maps.warming_clusters_.emplace(cluster.first, *cluster.second->cluster_); + } + return clusters_maps; } + const ClusterSet& primaryClusters() override { return primary_clusters_; } ThreadLocalCluster* get(absl::string_view cluster) override; diff --git a/source/common/upstream/load_stats_reporter.cc b/source/common/upstream/load_stats_reporter.cc index fa5697e86fbd..b7ff8e94b7c3 100644 --- a/source/common/upstream/load_stats_reporter.cc +++ b/source/common/upstream/load_stats_reporter.cc @@ -62,11 +62,11 @@ void LoadStatsReporter::sendLoadStatsRequest() { // added to the cluster manager. When we get the notification, we record the current time in // clusters_ as the start time for the load reporting window for that cluster. request_.mutable_cluster_stats()->Clear(); + auto all_clusters = cm_.clusters(); for (const auto& cluster_name_and_timestamp : clusters_) { const std::string& cluster_name = cluster_name_and_timestamp.first; - auto cluster_info_map = cm_.clusters(); - auto it = cluster_info_map.find(cluster_name); - if (it == cluster_info_map.end()) { + auto it = all_clusters.active_clusters_.find(cluster_name); + if (it == all_clusters.active_clusters_.end()) { ENVOY_LOG(debug, "Cluster {} does not exist", cluster_name); continue; } @@ -154,7 +154,8 @@ void LoadStatsReporter::startLoadReportPeriod() { // converge. absl::node_hash_map existing_clusters; if (message_->send_all_clusters()) { - for (const auto& p : cm_.clusters()) { + auto cluster_info_map = cm_.clusters(); + for (const auto& p : cluster_info_map.active_clusters_) { const std::string& cluster_name = p.first; if (clusters_.count(cluster_name) > 0) { existing_clusters.emplace(cluster_name, clusters_[cluster_name]); @@ -173,9 +174,10 @@ void LoadStatsReporter::startLoadReportPeriod() { clusters_.emplace(cluster_name, existing_clusters.count(cluster_name) > 0 ? existing_clusters[cluster_name] : time_source_.monotonicTime().time_since_epoch()); + // TODO(lambdai): Move the clusters() call out of this lambda. auto cluster_info_map = cm_.clusters(); - auto it = cluster_info_map.find(cluster_name); - if (it == cluster_info_map.end()) { + auto it = cluster_info_map.active_clusters_.find(cluster_name); + if (it == cluster_info_map.active_clusters_.end()) { return; } // Don't reset stats for existing tracked clusters. @@ -193,7 +195,8 @@ void LoadStatsReporter::startLoadReportPeriod() { cluster.info()->loadReportStats().upstream_rq_dropped_.latch(); }; if (message_->send_all_clusters()) { - for (const auto& p : cm_.clusters()) { + auto cluster_info_map = cm_.clusters(); + for (const auto& p : cluster_info_map.active_clusters_) { const std::string& cluster_name = p.first; handle_cluster_func(cluster_name); } diff --git a/source/extensions/common/redis/cluster_refresh_manager_impl.cc b/source/extensions/common/redis/cluster_refresh_manager_impl.cc index 8da52d96665c..c3caa96d9eec 100644 --- a/source/extensions/common/redis/cluster_refresh_manager_impl.cc +++ b/source/extensions/common/redis/cluster_refresh_manager_impl.cc @@ -133,9 +133,9 @@ bool ClusterRefreshManagerImpl::onEvent(const std::string& cluster_name, EventTy if (post_callback) { main_thread_dispatcher_.post([this, cluster_name, info]() { // Ensure that cluster is still active before calling callback. - auto map = cm_.clusters(); - auto it = map.find(cluster_name); - if (it != map.end()) { + auto maps = cm_.clusters(); + auto it = maps.active_clusters_.find(cluster_name); + if (it != maps.active_clusters_.end()) { info->cb_(); } }); diff --git a/source/extensions/stat_sinks/hystrix/hystrix.cc b/source/extensions/stat_sinks/hystrix/hystrix.cc index a35f67a8d3f7..6f4a1808accf 100644 --- a/source/extensions/stat_sinks/hystrix/hystrix.cc +++ b/source/extensions/stat_sinks/hystrix/hystrix.cc @@ -339,7 +339,7 @@ void HystrixSink::flush(Stats::MetricSnapshot& snapshot) { } incCounter(); std::stringstream ss; - Upstream::ClusterManager::ClusterInfoMap clusters = server_.clusterManager().clusters(); + Upstream::ClusterManager::ClusterInfoMaps all_clusters = server_.clusterManager().clusters(); // Save a map of the relevant histograms per cluster in a convenient format. absl::node_hash_map time_histograms; @@ -370,7 +370,7 @@ void HystrixSink::flush(Stats::MetricSnapshot& snapshot) { } } - for (auto& cluster : clusters) { + for (auto& cluster : all_clusters.active_clusters_) { Upstream::ClusterInfoConstSharedPtr cluster_info = cluster.second.get().info(); std::unique_ptr& cluster_stats_cache_ptr = @@ -407,9 +407,9 @@ void HystrixSink::flush(Stats::MetricSnapshot& snapshot) { } // check if any clusters were removed, and remove from cache - if (clusters.size() < cluster_stats_cache_map_.size()) { + if (all_clusters.active_clusters_.size() < cluster_stats_cache_map_.size()) { for (auto it = cluster_stats_cache_map_.begin(); it != cluster_stats_cache_map_.end();) { - if (clusters.find(it->first) == clusters.end()) { + if (all_clusters.active_clusters_.find(it->first) == all_clusters.active_clusters_.end()) { auto next_it = std::next(it); cluster_stats_cache_map_.erase(it); it = next_it; diff --git a/source/server/admin/clusters_handler.cc b/source/server/admin/clusters_handler.cc index e0e17350b7c9..801045e63ecd 100644 --- a/source/server/admin/clusters_handler.cc +++ b/source/server/admin/clusters_handler.cc @@ -100,7 +100,9 @@ void setHealthFlag(Upstream::Host::HealthFlag flag, const Upstream::Host& host, // TODO(efimki): Add support of text readouts stats. void ClustersHandler::writeClustersAsJson(Buffer::Instance& response) { envoy::admin::v3::Clusters clusters; - for (const auto& [name, cluster_ref] : server_.clusterManager().clusters()) { + // TODO(mattklein123): Add ability to see warming clusters in admin output. + auto all_clusters = server_.clusterManager().clusters(); + for (const auto& [name, cluster_ref] : all_clusters.active_clusters_) { const Upstream::Cluster& cluster = cluster_ref.get(); Upstream::ClusterInfoConstSharedPtr cluster_info = cluster.info(); @@ -184,7 +186,9 @@ void ClustersHandler::writeClustersAsJson(Buffer::Instance& response) { // TODO(efimki): Add support of text readouts stats. void ClustersHandler::writeClustersAsText(Buffer::Instance& response) { - for (const auto& [name, cluster_ref] : server_.clusterManager().clusters()) { + // TODO(mattklein123): Add ability to see warming clusters in admin output. + auto all_clusters = server_.clusterManager().clusters(); + for (const auto& [name, cluster_ref] : all_clusters.active_clusters_) { const Upstream::Cluster& cluster = cluster_ref.get(); const std::string& cluster_name = cluster.info()->name(); addOutlierInfo(cluster_name, cluster.outlierDetector(), response); diff --git a/source/server/admin/config_dump_handler.cc b/source/server/admin/config_dump_handler.cc index 9e1d54e9d3e9..e255f084757c 100644 --- a/source/server/admin/config_dump_handler.cc +++ b/source/server/admin/config_dump_handler.cc @@ -149,7 +149,9 @@ ConfigDumpHandler::addResourceToDump(envoy::admin::v3::ConfigDump& dump, const std::string& resource, bool include_eds) const { Envoy::Server::ConfigTracker::CbsMap callbacks_map = config_tracker_.getCallbacksMap(); if (include_eds) { - if (!server_.clusterManager().clusters().empty()) { + // TODO(mattklein123): Add ability to see warming clusters in admin output. + auto all_clusters = server_.clusterManager().clusters(); + if (!all_clusters.active_clusters_.empty()) { callbacks_map.emplace("endpoint", [this] { return dumpEndpointConfigs(); }); } } @@ -195,7 +197,9 @@ void ConfigDumpHandler::addAllConfigToDump(envoy::admin::v3::ConfigDump& dump, bool include_eds) const { Envoy::Server::ConfigTracker::CbsMap callbacks_map = config_tracker_.getCallbacksMap(); if (include_eds) { - if (!server_.clusterManager().clusters().empty()) { + // TODO(mattklein123): Add ability to see warming clusters in admin output. + auto all_clusters = server_.clusterManager().clusters(); + if (!all_clusters.active_clusters_.empty()) { callbacks_map.emplace("endpoint", [this] { return dumpEndpointConfigs(); }); } } @@ -220,8 +224,9 @@ void ConfigDumpHandler::addAllConfigToDump(envoy::admin::v3::ConfigDump& dump, ProtobufTypes::MessagePtr ConfigDumpHandler::dumpEndpointConfigs() const { auto endpoint_config_dump = std::make_unique(); - - for (const auto& [name, cluster_ref] : server_.clusterManager().clusters()) { + // TODO(mattklein123): Add ability to see warming clusters in admin output. + auto all_clusters = server_.clusterManager().clusters(); + for (const auto& [name, cluster_ref] : all_clusters.active_clusters_) { UNREFERENCED_PARAMETER(name); const Upstream::Cluster& cluster = cluster_ref.get(); Upstream::ClusterInfoConstSharedPtr cluster_info = cluster.info(); diff --git a/test/common/grpc/async_client_manager_impl_test.cc b/test/common/grpc/async_client_manager_impl_test.cc index 55d5c14e2fb1..448f52aa6140 100644 --- a/test/common/grpc/async_client_manager_impl_test.cc +++ b/test/common/grpc/async_client_manager_impl_test.cc @@ -38,10 +38,10 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcOk) { envoy::config::core::v3::GrpcService grpc_service; grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); - Upstream::ClusterManager::ClusterInfoMap cluster_map; + Upstream::ClusterManager::ClusterInfoMaps cluster_maps; Upstream::MockClusterMockPrioritySet cluster; - cluster_map.emplace("foo", cluster); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(cluster_map)); + cluster_maps.active_clusters_.emplace("foo", cluster); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(cluster_maps)); EXPECT_CALL(cluster, info()); EXPECT_CALL(*cluster.info_, addedViaApi()); @@ -65,7 +65,8 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcDynamicCluster) { Upstream::ClusterManager::ClusterInfoMap cluster_map; Upstream::MockClusterMockPrioritySet cluster; cluster_map.emplace("foo", cluster); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(cluster_map)); + EXPECT_CALL(cm_, clusters()) + .WillOnce(Return(Upstream::ClusterManager::ClusterInfoMaps{cluster_map, {}})); EXPECT_CALL(cluster, info()); EXPECT_CALL(*cluster.info_, addedViaApi()).WillOnce(Return(true)); EXPECT_THROW_WITH_MESSAGE( diff --git a/test/common/upstream/cds_api_impl_test.cc b/test/common/upstream/cds_api_impl_test.cc index f052749677fb..a914225dd9d0 100644 --- a/test/common/upstream/cds_api_impl_test.cc +++ b/test/common/upstream/cds_api_impl_test.cc @@ -54,16 +54,20 @@ class CdsApiImplTest : public testing::Test { .WillOnce(Throw(EnvoyException(exception_msg))); } - ClusterManager::ClusterInfoMap makeClusterMap(const std::vector& clusters) { - ClusterManager::ClusterInfoMap map; - for (const auto& cluster : clusters) { - map.emplace(cluster, cm_.thread_local_cluster_.cluster_); + ClusterManager::ClusterInfoMaps + makeClusterInfoMaps(const std::vector& active_clusters, + const std::vector& warming_clusters = {}) { + ClusterManager::ClusterInfoMaps maps; + for (const auto& cluster : active_clusters) { + maps.active_clusters_.emplace(cluster, cm_.thread_local_cluster_.cluster_); } - return map; + for (const auto& cluster : warming_clusters) { + maps.warming_clusters_.emplace(cluster, cm_.thread_local_cluster_.cluster_); + } + return maps; } NiceMock cm_; - Upstream::ClusterManager::ClusterInfoMap cluster_map_; Upstream::MockClusterMockPrioritySet mock_cluster_; Stats::IsolatedStoreImpl store_; CdsApiPtr cds_; @@ -92,7 +96,7 @@ version_info: '0' auto response1 = TestUtility::parseYaml(response1_yaml); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(ClusterManager::ClusterInfoMap{})); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); expectAdd("cluster1", "0"); EXPECT_CALL(initialized_, ready()); EXPECT_EQ("", cds_->versionInfo()); @@ -108,7 +112,7 @@ version_info: '1' )EOF"; auto response2 = TestUtility::parseYaml(response2_yaml); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterMap({"cluster1"}))); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({"cluster1"}))); EXPECT_CALL(cm_, removeCluster("cluster1")).WillOnce(Return(true)); const auto decoded_resources_2 = TestUtility::decodeResources(response2); @@ -126,7 +130,7 @@ TEST_F(CdsApiImplTest, ValidateDuplicateClusters) { cluster_1.set_name("duplicate_cluster"); const auto decoded_resources = TestUtility::decodeResources({cluster_1, cluster_1}); - EXPECT_CALL(cm_, clusters()).WillRepeatedly(Return(cluster_map_)); + EXPECT_CALL(cm_, clusters()).WillRepeatedly(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); EXPECT_THROW_WITH_MESSAGE(cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), EnvoyException, @@ -139,7 +143,7 @@ TEST_F(CdsApiImplTest, EmptyConfigUpdate) { setup(); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(ClusterManager::ClusterInfoMap{})); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); cds_callbacks_->onConfigUpdate({}, ""); @@ -151,7 +155,7 @@ TEST_F(CdsApiImplTest, ConfigUpdateWith2ValidClusters) { setup(); } - EXPECT_CALL(cm_, clusters()).WillOnce(Return(ClusterManager::ClusterInfoMap{})); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); envoy::config::cluster::v3::Cluster cluster_1; @@ -224,7 +228,7 @@ TEST_F(CdsApiImplTest, ConfigUpdateAddsSecondClusterEvenIfFirstThrows) { setup(); } - EXPECT_CALL(cm_, clusters()).WillOnce(Return(ClusterManager::ClusterInfoMap{})); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); envoy::config::cluster::v3::Cluster cluster_1; @@ -269,7 +273,7 @@ version_info: '0' auto response1 = TestUtility::parseYaml(response1_yaml); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(ClusterManager::ClusterInfoMap{})); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); expectAdd("cluster1", "0"); expectAdd("cluster2", "0"); EXPECT_CALL(initialized_, ready()); @@ -298,7 +302,7 @@ version_info: '1' auto response2 = TestUtility::parseYaml(response2_yaml); - EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterMap({"cluster1", "cluster2"}))); + EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({"cluster1", "cluster2"}))); expectAdd("cluster1", "1"); expectAdd("cluster3", "1"); EXPECT_CALL(cm_, removeCluster("cluster2")); @@ -334,7 +338,7 @@ version_info: '0' auto response1 = TestUtility::parseYaml(response1_yaml); - EXPECT_CALL(cm_, clusters()).WillRepeatedly(Return(cluster_map_)); + EXPECT_CALL(cm_, clusters()).WillRepeatedly(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); const auto decoded_resources = TestUtility::decodeResources(response1); diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index a4f2122c4945..1e3217285dea 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -361,7 +361,7 @@ TEST_F(ClusterManagerImplTest, ValidClusterName) { create(parseBootstrapFromV3Yaml(yaml)); cluster_manager_->clusters() - .find("cluster:name") + .active_clusters_.find("cluster:name") ->second.get() .info() ->statsScope() @@ -1490,7 +1490,7 @@ TEST_F(ClusterManagerImplTest, DynamicAddRemove) { EXPECT_TRUE(cluster_manager_->addOrUpdateCluster(update_cluster, "")); EXPECT_EQ(cluster2->info_, cluster_manager_->get("fake_cluster")->info()); - EXPECT_EQ(1UL, cluster_manager_->clusters().size()); + EXPECT_EQ(1UL, cluster_manager_->clusters().active_clusters_.size()); Http::ConnectionPool::MockInstance* cp = new Http::ConnectionPool::MockInstance(); EXPECT_CALL(factory_, allocateConnPool_(_, _, _)).WillOnce(Return(cp)); EXPECT_EQ(cp, cluster_manager_->httpConnPoolForCluster("fake_cluster", ResourcePriority::Default, @@ -1520,7 +1520,7 @@ TEST_F(ClusterManagerImplTest, DynamicAddRemove) { EXPECT_CALL(*cp2, addDrainedCallback(_)).WillOnce(SaveArg<0>(&drained_cb2)); EXPECT_TRUE(cluster_manager_->removeCluster("fake_cluster")); EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); - EXPECT_EQ(0UL, cluster_manager_->clusters().size()); + EXPECT_EQ(0UL, cluster_manager_->clusters().active_clusters_.size()); // Close the TCP connection. Success is no ASSERT or crash due to referencing // the removed cluster. diff --git a/test/common/upstream/load_stats_reporter_test.cc b/test/common/upstream/load_stats_reporter_test.cc index 111e7356a064..46105592c00a 100644 --- a/test/common/upstream/load_stats_reporter_test.cc +++ b/test/common/upstream/load_stats_reporter_test.cc @@ -125,7 +125,8 @@ TEST_F(LoadStatsReporterTest, ExistingClusters) { foo_cluster.info_->load_report_stats_.upstream_rq_dropped_.add(2); foo_cluster.info_->eds_service_name_ = "bar"; NiceMock bar_cluster; - MockClusterManager::ClusterInfoMap cluster_info{{"foo", foo_cluster}, {"bar", bar_cluster}}; + MockClusterManager::ClusterInfoMaps cluster_info{{{"foo", foo_cluster}, {"bar", bar_cluster}}, + {}}; ON_CALL(cm_, clusters()).WillByDefault(Return(cluster_info)); deliverLoadStatsResponse({"foo"}); // Initial stats report for foo on timer tick. diff --git a/test/extensions/common/redis/cluster_refresh_manager_test.cc b/test/extensions/common/redis/cluster_refresh_manager_test.cc index 916a1457da31..ea07e1624384 100644 --- a/test/extensions/common/redis/cluster_refresh_manager_test.cc +++ b/test/extensions/common/redis/cluster_refresh_manager_test.cc @@ -33,8 +33,8 @@ class ClusterRefreshManagerTest : public testing::Test { : cluster_name_("fake_cluster"), refresh_manager_(std::make_shared( dispatcher_, cm_, time_system_)) { time_system_.setMonotonicTime(std::chrono::seconds(1)); - map_.emplace("fake_cluster", mock_cluster_); - ON_CALL(cm_, clusters()).WillByDefault(Return(map_)); + cluster_maps_.active_clusters_.emplace("fake_cluster", mock_cluster_); + ON_CALL(cm_, clusters()).WillByDefault(Return(cluster_maps_)); } ~ClusterRefreshManagerTest() override = default; @@ -104,7 +104,7 @@ class ClusterRefreshManagerTest : public testing::Test { const std::string cluster_name_; NiceMock dispatcher_; NiceMock cm_; - Upstream::ClusterManager::ClusterInfoMap map_; + Upstream::ClusterManager::ClusterInfoMaps cluster_maps_; Upstream::MockClusterMockPrioritySet mock_cluster_; Event::SimulatedTimeSystem time_system_; std::shared_ptr refresh_manager_; diff --git a/test/extensions/stats_sinks/hystrix/hystrix_test.cc b/test/extensions/stats_sinks/hystrix/hystrix_test.cc index 29e7c79d02da..38f88019602c 100644 --- a/test/extensions/stats_sinks/hystrix/hystrix_test.cc +++ b/test/extensions/stats_sinks/hystrix/hystrix_test.cc @@ -128,9 +128,9 @@ class HystrixSinkTest : public testing::Test { void createClusterAndCallbacks() { // Set cluster. - cluster_map_.emplace(cluster1_name_, cluster1_.cluster_); + cluster_maps_.active_clusters_.emplace(cluster1_name_, cluster1_.cluster_); ON_CALL(server_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); - ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_map_)); + ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_maps_)); ON_CALL(callbacks_, encodeData(_, _)).WillByDefault(Invoke([&](Buffer::Instance& data, bool) { // Set callbacks to send data to buffer. This will append to the end of the buffer, so @@ -141,15 +141,15 @@ class HystrixSinkTest : public testing::Test { void addClusterToMap(const std::string& cluster_name, NiceMock& cluster) { - cluster_map_.emplace(cluster_name, cluster); - // Redefining since cluster_map_ is returned by value. - ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_map_)); + cluster_maps_.active_clusters_.emplace(cluster_name, cluster); + // Redefining since cluster_maps_ is returned by value. + ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_maps_)); } void removeClusterFromMap(const std::string& cluster_name) { - cluster_map_.erase(cluster_name); - // Redefining since cluster_map_ is returned by value. - ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_map_)); + cluster_maps_.active_clusters_.erase(cluster_name); + // Redefining since cluster_maps_ is returned by value. + ON_CALL(cluster_manager_, clusters()).WillByDefault(Return(cluster_maps_)); } void addSecondClusterHelper(Buffer::OwnedImpl& buffer) { @@ -245,7 +245,7 @@ class HystrixSinkTest : public testing::Test { NiceMock callbacks_; NiceMock server_; - Upstream::ClusterManager::ClusterInfoMap cluster_map_; + Upstream::ClusterManager::ClusterInfoMaps cluster_maps_; Buffer::OwnedImpl cluster_stats_buffer_; std::unique_ptr sink_; diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index 3fc55beb56e2..a9d96e44a8a2 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -553,8 +553,9 @@ TEST_P(AdsIntegrationTest, CdsPausedDuringWarming) { // Send the second warming cluster. sendDiscoveryResponse( - Config::TypeUrl::get().Cluster, {buildCluster("warming_cluster_2")}, - {buildCluster("warming_cluster_2")}, {}, "3"); + Config::TypeUrl::get().Cluster, + {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, + {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, {}, "3"); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); // We would've got a Cluster discovery request with version 2 here, had the CDS not been paused. @@ -586,6 +587,87 @@ TEST_P(AdsIntegrationTest, CdsPausedDuringWarming) { {"warming_cluster_2", "warming_cluster_1"}, {}, {})); } +TEST_P(AdsIntegrationTest, RemoveWarmingCluster) { + initialize(); + + // Send initial configuration, validate we can process a request. + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "", {}, {}, {}, true)); + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {buildCluster("cluster_0")}, + {buildCluster("cluster_0")}, {}, "1"); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "", + {"cluster_0"}, {"cluster_0"}, {})); + + sendDiscoveryResponse( + Config::TypeUrl::get().ClusterLoadAssignment, {buildClusterLoadAssignment("cluster_0")}, + {buildClusterLoadAssignment("cluster_0")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "1", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Listener, "", {}, {}, {})); + sendDiscoveryResponse( + Config::TypeUrl::get().Listener, {buildListener("listener_0", "route_config_0")}, + {buildListener("listener_0", "route_config_0")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"cluster_0"}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().RouteConfiguration, "", + {"route_config_0"}, {"route_config_0"}, {})); + sendDiscoveryResponse( + Config::TypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_0", "cluster_0")}, + {buildRouteConfig("route_config_0", "cluster_0")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Listener, "1", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().RouteConfiguration, "1", + {"route_config_0"}, {}, {})); + + test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + makeSingleRequest(); + + // Send the first warming cluster. + sendDiscoveryResponse( + Config::TypeUrl::get().Cluster, {buildCluster("warming_cluster_1")}, + {buildCluster("warming_cluster_1")}, {"cluster_0"}, "2"); + + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"warming_cluster_1"}, {"warming_cluster_1"}, {"cluster_0"})); + + // Send the second warming cluster and remove the first cluster. + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {buildCluster("warming_cluster_2")}, + {buildCluster("warming_cluster_2")}, + // Delta: remove warming_cluster_1. + {"warming_cluster_1"}, "3"); + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 1); + // We would've got a Cluster discovery request with version 2 here, had the CDS not been paused. + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"warming_cluster_2"}, {"warming_cluster_2"}, + {"warming_cluster_1"})); + + // Finish warming the clusters. Note that the first warming cluster is not included in the + // response. + sendDiscoveryResponse( + Config::TypeUrl::get().ClusterLoadAssignment, + {buildClusterLoadAssignment("warming_cluster_2")}, + {buildClusterLoadAssignment("warming_cluster_2")}, {"cluster_0"}, "2"); + + // Validate that all clusters are warmed. + test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 0); + test_server_->waitForGaugeEq("cluster_manager.active_clusters", 3); + + // CDS is resumed and EDS response was acknowledged. + if (sotw_or_delta_ == Grpc::SotwOrDelta::Delta) { + // Envoy will ACK both Cluster messages. Since they arrived while CDS was paused, they aren't + // sent until CDS is unpaused. Since version 3 has already arrived by the time the version 2 + // ACK goes out, they're both acknowledging version 3. + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "3", {}, {}, {})); + } + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "3", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "2", + {"warming_cluster_2"}, {}, {})); +} // Validate that warming listeners are removed when left out of SOTW update. TEST_P(AdsIntegrationTest, RemoveWarmingListener) { initialize(); @@ -696,8 +778,9 @@ TEST_P(AdsIntegrationTest, ClusterWarmingOnNamedResponse) { // Send the second warming cluster. sendDiscoveryResponse( - Config::TypeUrl::get().Cluster, {buildCluster("warming_cluster_2")}, - {buildCluster("warming_cluster_2")}, {}, "3"); + Config::TypeUrl::get().Cluster, + {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, + {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, {}, "3"); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", @@ -1359,8 +1442,8 @@ TEST_P(AdsClusterV2Test, CdsPausedDuringWarming) { // Send the second warming cluster. sendDiscoveryResponse( - cds_type_url, {buildCluster("warming_cluster_2")}, {buildCluster("warming_cluster_2")}, {}, - "3", true); + cds_type_url, {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, + {buildCluster("warming_cluster_1"), buildCluster("warming_cluster_2")}, {}, "3", true); test_server_->waitForGaugeEq("cluster_manager.warming_clusters", 2); // We would've got a Cluster discovery request with version 2 here, had the CDS not been paused. diff --git a/test/integration/custom_cluster_integration_test.cc b/test/integration/custom_cluster_integration_test.cc index 62d5cac5ab9f..84c936e006c5 100644 --- a/test/integration/custom_cluster_integration_test.cc +++ b/test/integration/custom_cluster_integration_test.cc @@ -69,10 +69,10 @@ TEST_P(CustomClusterIntegrationTest, TestCustomConfig) { initialize(); // Verify the cluster is correctly setup with the custom priority - const auto& cluster_map = test_server_->server().clusterManager().clusters(); - EXPECT_EQ(1, cluster_map.size()); - EXPECT_EQ(1, cluster_map.count("cluster_0")); - const auto& cluster_ref = cluster_map.find("cluster_0")->second; + const auto& cluster_maps = test_server_->server().clusterManager().clusters(); + EXPECT_EQ(1, cluster_maps.active_clusters_.size()); + EXPECT_EQ(1, cluster_maps.active_clusters_.count("cluster_0")); + const auto& cluster_ref = cluster_maps.active_clusters_.find("cluster_0")->second; const auto& hostset_per_priority = cluster_ref.get().prioritySet().hostSetsPerPriority(); EXPECT_EQ(11, hostset_per_priority.size()); const Envoy::Upstream::HostSetPtr& host_set = hostset_per_priority[10]; diff --git a/test/integration/eds_integration_test.cc b/test/integration/eds_integration_test.cc index 3e1c237e7c59..b8ca3c6f07c2 100644 --- a/test/integration/eds_integration_test.cc +++ b/test/integration/eds_integration_test.cc @@ -316,9 +316,9 @@ TEST_P(EdsIntegrationTest, OverprovisioningFactorUpdate) { setEndpoints(4, 4, 0); auto get_and_compare = [this](const uint32_t expected_factor) { const auto& cluster_map = test_server_->server().clusterManager().clusters(); - EXPECT_EQ(1, cluster_map.size()); - EXPECT_EQ(1, cluster_map.count("cluster_0")); - const auto& cluster_ref = cluster_map.find("cluster_0")->second; + EXPECT_EQ(1, cluster_map.active_clusters_.size()); + EXPECT_EQ(1, cluster_map.active_clusters_.count("cluster_0")); + const auto& cluster_ref = cluster_map.active_clusters_.find("cluster_0")->second; const auto& hostset_per_priority = cluster_ref.get().prioritySet().hostSetsPerPriority(); EXPECT_EQ(1, hostset_per_priority.size()); const Envoy::Upstream::HostSetPtr& host_set = hostset_per_priority[0]; @@ -340,7 +340,7 @@ TEST_P(EdsIntegrationTest, BatchMemberUpdateCb) { auto& priority_set = test_server_->server() .clusterManager() .clusters() - .find("cluster_0") + .active_clusters_.find("cluster_0") ->second.get() .prioritySet(); diff --git a/test/mocks/upstream/cluster_manager.h b/test/mocks/upstream/cluster_manager.h index c24b1b045acd..cc3071052f67 100644 --- a/test/mocks/upstream/cluster_manager.h +++ b/test/mocks/upstream/cluster_manager.h @@ -42,7 +42,8 @@ class MockClusterManager : public ClusterManager { MOCK_METHOD(void, setInitializedCb, (InitializationCompleteCallback)); MOCK_METHOD(void, initializeSecondaryClusters, (const envoy::config::bootstrap::v3::Bootstrap& bootstrap)); - MOCK_METHOD(ClusterInfoMap, clusters, ()); + MOCK_METHOD(ClusterInfoMaps, clusters, ()); + MOCK_METHOD(const ClusterSet&, primaryClusters, ()); MOCK_METHOD(ThreadLocalCluster*, get, (absl::string_view cluster)); MOCK_METHOD(Http::ConnectionPool::Instance*, httpConnPoolForCluster, diff --git a/test/server/admin/clusters_handler_test.cc b/test/server/admin/clusters_handler_test.cc index f0ba3f5ae7e5..4d41b6c24969 100644 --- a/test/server/admin/clusters_handler_test.cc +++ b/test/server/admin/clusters_handler_test.cc @@ -14,11 +14,11 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, AdminInstanceTest, TestUtility::ipTestParamsToString); TEST_P(AdminInstanceTest, ClustersJson) { - Upstream::ClusterManager::ClusterInfoMap cluster_map; - ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_map)); + Upstream::ClusterManager::ClusterInfoMaps cluster_maps; + ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_maps)); NiceMock cluster; - cluster_map.emplace(cluster.info_->name_, cluster); + cluster_maps.active_clusters_.emplace(cluster.info_->name_, cluster); NiceMock outlier_detector; ON_CALL(Const(cluster), outlierDetector()).WillByDefault(Return(&outlier_detector)); diff --git a/test/server/admin/config_dump_handler_test.cc b/test/server/admin/config_dump_handler_test.cc index 7c7f5f57781f..6075dffa4a67 100644 --- a/test/server/admin/config_dump_handler_test.cc +++ b/test/server/admin/config_dump_handler_test.cc @@ -113,11 +113,11 @@ TEST_P(AdminInstanceTest, ConfigDumpMaintainsOrder) { // Test that using ?include_eds parameter adds EDS to the config dump. TEST_P(AdminInstanceTest, ConfigDumpWithEndpoint) { - Upstream::ClusterManager::ClusterInfoMap cluster_map; - ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_map)); + Upstream::ClusterManager::ClusterInfoMaps cluster_maps; + ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_maps)); NiceMock cluster; - cluster_map.emplace(cluster.info_->name_, cluster); + cluster_maps.active_clusters_.emplace(cluster.info_->name_, cluster); ON_CALL(*cluster.info_, addedViaApi()).WillByDefault(Return(false)); @@ -186,11 +186,11 @@ TEST_P(AdminInstanceTest, ConfigDumpWithEndpoint) { // Test EDS config dump while multiple localities and priorities exist TEST_P(AdminInstanceTest, ConfigDumpWithLocalityEndpoint) { - Upstream::ClusterManager::ClusterInfoMap cluster_map; - ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_map)); + Upstream::ClusterManager::ClusterInfoMaps cluster_maps; + ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_maps)); NiceMock cluster; - cluster_map.emplace(cluster.info_->name_, cluster); + cluster_maps.active_clusters_.emplace(cluster.info_->name_, cluster); ON_CALL(*cluster.info_, addedViaApi()).WillByDefault(Return(false)); @@ -398,11 +398,11 @@ TEST_P(AdminInstanceTest, ConfigDumpFiltersByResource) { // We add both static and dynamic endpoint config to the dump, but expect only // dynamic in the JSON with ?resource=dynamic_endpoint_configs. TEST_P(AdminInstanceTest, ConfigDumpWithEndpointFiltersByResource) { - Upstream::ClusterManager::ClusterInfoMap cluster_map; - ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_map)); + Upstream::ClusterManager::ClusterInfoMaps cluster_maps; + ON_CALL(server_.cluster_manager_, clusters()).WillByDefault(ReturnPointee(&cluster_maps)); NiceMock cluster_1; - cluster_map.emplace(cluster_1.info_->name_, cluster_1); + cluster_maps.active_clusters_.emplace(cluster_1.info_->name_, cluster_1); ON_CALL(*cluster_1.info_, addedViaApi()).WillByDefault(Return(true)); @@ -419,7 +419,7 @@ TEST_P(AdminInstanceTest, ConfigDumpWithEndpointFiltersByResource) { NiceMock cluster_2; cluster_2.info_->name_ = "fake_cluster_2"; - cluster_map.emplace(cluster_2.info_->name_, cluster_2); + cluster_maps.active_clusters_.emplace(cluster_2.info_->name_, cluster_2); ON_CALL(*cluster_2.info_, addedViaApi()).WillByDefault(Return(false)); diff --git a/test/server/configuration_impl_test.cc b/test/server/configuration_impl_test.cc index eeeba3585b77..c8e63cb180f6 100644 --- a/test/server/configuration_impl_test.cc +++ b/test/server/configuration_impl_test.cc @@ -161,10 +161,10 @@ TEST_F(ConfigurationImplTest, SetUpstreamClusterPerConnectionBufferLimit) { MainImpl config; config.initialize(bootstrap, server_, cluster_manager_factory_); - ASSERT_EQ(1U, config.clusterManager()->clusters().count("test_cluster")); + ASSERT_EQ(1U, config.clusterManager()->clusters().active_clusters_.count("test_cluster")); EXPECT_EQ(8192U, config.clusterManager() ->clusters() - .find("test_cluster") + .active_clusters_.find("test_cluster") ->second.get() .info() ->perConnectionBufferLimitBytes()); From 680f914e4ee88360f46994bc871fb7b078a81c3b Mon Sep 17 00:00:00 2001 From: phlax Date: Sun, 15 Nov 2020 20:02:57 +0000 Subject: [PATCH 099/117] docs: Bump sphinxext-rediraffe version (#13996) Signed-off-by: Ryan Northey --- docs/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index c8e98061b50e..edff23bf3823 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -116,9 +116,9 @@ sphinxcontrib-jsmath==1.0.1 \ sphinxcontrib-qthelp==1.0.3 \ --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \ --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6 -sphinxext-rediraffe==0.2.4 \ - --hash=sha256:5428fb614d1fbc16964ba587aaa6b1c8ec92fd0b1d01bb6b369637446f43a27d \ - --hash=sha256:13e6474342df6643723976a3429edfc5e811e9f48b9f832c9fb6bdd9fe53fd83 +sphinxext-rediraffe==0.2.5 \ + --hash=sha256:7b706284d20602acecc1783cc58c8d0543937af1ee53f912bfdc4b258a7e649a \ + --hash=sha256:a17287cdee7763341b91762879e042b33a4916d6a2fc6d2f97a18107325bd2cc sphinxcontrib-serializinghtml==1.1.4 \ --hash=sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc \ --hash=sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a From 07776d77403fd3ae97eefcfca7645de45a3ab628 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Sun, 15 Nov 2020 12:34:25 -0800 Subject: [PATCH 100/117] wasm: fix CPE for Wasmtime. (#14024) Signed-off-by: Piotr Sikora --- bazel/repository_locations.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 402f03c42bdf..a91db6898b9c 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -632,7 +632,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", ], - cpe = "cpe:2.3:a:webassembly_virtual_machine_project:webassembly_virtual_machine:*", + cpe = "N/A", ), com_github_wasm_c_api = dict( project_name = "wasm-c-api", From 588d9344b31e6544869547c4bcd359b3b0f1d4cf Mon Sep 17 00:00:00 2001 From: Dongfang Qu Date: Mon, 16 Nov 2020 17:20:39 +0000 Subject: [PATCH 101/117] log the internal error message from *SSL when the cert and private key doesn't match (#14023) Fixes #14022 Signed-off-by: Dongfang Qu --- .../transport_sockets/tls/context_impl.cc | 6 +++-- test/server/listener_manager_impl_test.cc | 26 ++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index e163eddfc8b4..e79a1aeadb6d 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -417,9 +417,11 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c !tls_certificate.password().empty() ? const_cast(tls_certificate.password().c_str()) : nullptr)); + if (pkey == nullptr || !SSL_CTX_use_PrivateKey(ctx.ssl_ctx_.get(), pkey.get())) { - throw EnvoyException( - absl::StrCat("Failed to load private key from ", tls_certificate.privateKeyPath())); + throw EnvoyException(fmt::format("Failed to load private key from {}, Cause: {}", + tls_certificate.privateKeyPath(), + Utility::getLastCryptoError().value_or("unknown"))); } #ifdef BORINGSSL_FIPS diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 3d21de26252e..bf60d318a573 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -3566,7 +3566,9 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidPrivateKey) Network::Address::IpVersion::v4); EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), - EnvoyException, "Failed to load private key from "); + EnvoyException, + "Failed to load private key from , " + "Cause: error:0900006e:PEM routines:OPENSSL_internal:NO_START_LINE"); } TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidTrustedCA) { @@ -3591,6 +3593,28 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInvalidTrustedCA) { EnvoyException, "Failed to load trusted CA certificates from "); } +TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateCertPrivateKeyMismatch) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + address: + socket_address: { address: 127.0.0.1, port_value: 1234 } + filter_chains: + - transport_socket: + name: tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext + common_tls_context: + tls_certificates: + - certificate_chain: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns3_chain.pem" } + private_key: { filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns2_key.pem" } + )EOF", + Network::Address::IpVersion::v4); + + EXPECT_THROW_WITH_REGEX( + manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), EnvoyException, + "Failed to load private key from .*, " + "Cause: error:0b000074:X.509 certificate routines:OPENSSL_internal:KEY_VALUES_MISMATCH"); +} + TEST_F(ListenerManagerImplWithRealFiltersTest, Metadata) { const std::string yaml = TestEnvironment::substitute(R"EOF( address: From dac6e58738d64b15ea26d1641906b68c16d55616 Mon Sep 17 00:00:00 2001 From: htuch Date: Mon, 16 Nov 2020 14:02:03 -0500 Subject: [PATCH 102/117] sds: improve watched directory documentation. (#14029) Some followup docs tweaks to #13721. Signed-off-by: Harvey Tuch --- .../extensions/transport_sockets/tls/v3/common.proto | 6 ++++-- .../extensions/transport_sockets/tls/v4alpha/common.proto | 6 ++++-- docs/root/configuration/security/secret.rst | 8 ++++++-- .../extensions/transport_sockets/tls/v3/common.proto | 6 ++++-- .../extensions/transport_sockets/tls/v4alpha/common.proto | 6 ++++-- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/api/envoy/extensions/transport_sockets/tls/v3/common.proto b/api/envoy/extensions/transport_sockets/tls/v3/common.proto index 587e3271836b..2b545b35ee12 100644 --- a/api/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -155,7 +155,8 @@ message TlsCertificate { // default the parent directories of the filesystem paths in // *certificate_chain* and *private_key* are watched if this field is not // specified. This only applies when a *TlsCertificate* is delivered by SDS - // with references to filesystem paths. + // with references to filesystem paths. See the :ref:`SDS key rotation + // ` documentation for further details. config.core.v3.WatchedDirectory watched_directory = 7; // BoringSSL private key method provider. This is an alternative to :ref:`private_key @@ -265,7 +266,8 @@ message CertificateValidationContext { // default the parent directory of the filesystem path in *trusted_ca* is // watched if this field is not specified. This only applies when a // *CertificateValidationContext* is delivered by SDS with references to - // filesystem paths. + // filesystem paths. See the :ref:`SDS key rotation ` + // documentation for further details. config.core.v3.WatchedDirectory watched_directory = 11; // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the diff --git a/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto b/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto index b2fa6f672628..30859bc2a3eb 100644 --- a/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto +++ b/api/envoy/extensions/transport_sockets/tls/v4alpha/common.proto @@ -157,7 +157,8 @@ message TlsCertificate { // default the parent directories of the filesystem paths in // *certificate_chain* and *private_key* are watched if this field is not // specified. This only applies when a *TlsCertificate* is delivered by SDS - // with references to filesystem paths. + // with references to filesystem paths. See the :ref:`SDS key rotation + // ` documentation for further details. config.core.v4alpha.WatchedDirectory watched_directory = 7; // BoringSSL private key method provider. This is an alternative to :ref:`private_key @@ -267,7 +268,8 @@ message CertificateValidationContext { // default the parent directory of the filesystem path in *trusted_ca* is // watched if this field is not specified. This only applies when a // *CertificateValidationContext* is delivered by SDS with references to - // filesystem paths. + // filesystem paths. See the :ref:`SDS key rotation ` + // documentation for further details. config.core.v4alpha.WatchedDirectory watched_directory = 11; // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the diff --git a/docs/root/configuration/security/secret.rst b/docs/root/configuration/security/secret.rst index fdbc88242d02..5ad3650cc19e 100644 --- a/docs/root/configuration/security/secret.rst +++ b/docs/root/configuration/security/secret.rst @@ -45,11 +45,15 @@ refer to filesystem paths. This currently is supported for the following secret * :ref:`TlsCertificate ` * :ref:`CertificateValidationContext ` -By default, directories containing secrets are watched for filesystem move events. Explicit control over -the watched directory is possible by specifying a *watched_directory* path in :ref:`TlsCertificate +By default, directories containing secrets are watched for filesystem move events. For example, a +key or trusted CA certificates at ``/foo/bar/baz/cert.pem`` will be watched at `/foo/bar/baz`. +Explicit control over the watched directory is possible by specifying a *watched_directory* path in +:ref:`TlsCertificate ` and :ref:`CertificateValidationContext `. +This allows watches to be established at path predecessors, e.g. ``/foo/bar``; this capability is +useful when implementing common key rotation schemes. An example of key rotation is provided :ref:`below `. diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto index c5452fced643..2ddca5720fc8 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v3/common.proto @@ -154,7 +154,8 @@ message TlsCertificate { // default the parent directories of the filesystem paths in // *certificate_chain* and *private_key* are watched if this field is not // specified. This only applies when a *TlsCertificate* is delivered by SDS - // with references to filesystem paths. + // with references to filesystem paths. See the :ref:`SDS key rotation + // ` documentation for further details. config.core.v3.WatchedDirectory watched_directory = 7; // BoringSSL private key method provider. This is an alternative to :ref:`private_key @@ -262,7 +263,8 @@ message CertificateValidationContext { // default the parent directory of the filesystem path in *trusted_ca* is // watched if this field is not specified. This only applies when a // *CertificateValidationContext* is delivered by SDS with references to - // filesystem paths. + // filesystem paths. See the :ref:`SDS key rotation ` + // documentation for further details. config.core.v3.WatchedDirectory watched_directory = 11; // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the diff --git a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto index b2fa6f672628..30859bc2a3eb 100644 --- a/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto +++ b/generated_api_shadow/envoy/extensions/transport_sockets/tls/v4alpha/common.proto @@ -157,7 +157,8 @@ message TlsCertificate { // default the parent directories of the filesystem paths in // *certificate_chain* and *private_key* are watched if this field is not // specified. This only applies when a *TlsCertificate* is delivered by SDS - // with references to filesystem paths. + // with references to filesystem paths. See the :ref:`SDS key rotation + // ` documentation for further details. config.core.v4alpha.WatchedDirectory watched_directory = 7; // BoringSSL private key method provider. This is an alternative to :ref:`private_key @@ -267,7 +268,8 @@ message CertificateValidationContext { // default the parent directory of the filesystem path in *trusted_ca* is // watched if this field is not specified. This only applies when a // *CertificateValidationContext* is delivered by SDS with references to - // filesystem paths. + // filesystem paths. See the :ref:`SDS key rotation ` + // documentation for further details. config.core.v4alpha.WatchedDirectory watched_directory = 11; // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the From 935ded9b2d5c0279bdefb984d3e2e6086be98ea9 Mon Sep 17 00:00:00 2001 From: danzh Date: Mon, 16 Nov 2020 14:27:13 -0500 Subject: [PATCH 103/117] quiche: update QUICHE tar (#13949) Signed-off-by: Dan Zhang --- bazel/envoy_internal.bzl | 2 + bazel/external/quiche.BUILD | 85 +-- bazel/repository_locations.bzl | 8 +- source/extensions/quic_listeners/quiche/BUILD | 1 + .../quiche/active_quic_listener.cc | 2 +- .../quiche/envoy_quic_client_connection.cc | 2 +- .../quiche/envoy_quic_client_stream.cc | 5 + .../quiche/envoy_quic_connection.cc | 6 +- .../quiche/envoy_quic_connection.h | 1 + .../quiche/envoy_quic_dispatcher.cc | 6 +- .../quiche/envoy_quic_dispatcher.h | 2 +- .../quiche/envoy_quic_proof_source.cc | 2 +- .../quiche/envoy_quic_proof_source.h | 2 +- .../quiche/envoy_quic_proof_source_base.cc | 7 +- .../quiche/envoy_quic_proof_source_base.h | 6 +- .../quiche/envoy_quic_proof_verifier_base.cc | 4 +- .../quiche/envoy_quic_server_connection.cc | 10 +- .../quiche/envoy_quic_server_connection.h | 1 + .../quic_listeners/quiche/platform/BUILD | 42 +- .../quiche/platform/flags_impl.cc | 108 +++- .../quiche/platform/flags_impl.h | 46 +- .../quiche/platform/flags_list.h | 502 ------------------ .../quiche/platform/http2_flags_impl.h | 4 +- .../quiche/platform/quic_aligned_impl.h | 18 - .../quiche/platform/quic_cert_utils_impl.cc | 38 +- .../quiche/platform/quic_cert_utils_impl.h | 9 +- .../quiche/platform/quic_fallthrough_impl.h | 11 - .../quiche/platform/quic_file_utils_impl.cc | 4 +- .../quiche/platform/quic_file_utils_impl.h | 6 +- .../quiche/platform/quic_flags_impl.h | 6 +- .../platform/quic_hostname_utils_impl.cc | 6 +- .../platform/quic_hostname_utils_impl.h | 8 +- .../quiche/platform/quic_macros_impl.h | 13 - .../platform/quic_mem_slice_span_impl.cc | 3 +- .../platform/quic_mem_slice_span_impl.h | 9 +- ..._ptr_util_impl.h => quic_testvalue_impl.h} | 11 +- .../platform/quic_udp_socket_platform_impl.h | 3 + .../quiche/platform/quiche_arraysize_impl.h | 11 - .../quiche/platform/quiche_optional_impl.h | 17 - .../quiche/platform/quiche_text_utils_impl.h | 63 +-- .../quiche/platform/quiche_time_utils_impl.cc | 4 +- .../quiche/platform/quiche_time_utils_impl.h | 4 +- .../platform/spdy_endianness_util_impl.h | 29 - .../quiche/platform/spdy_flags_impl.h | 4 +- .../quiche/platform/spdy_string_utils_impl.h | 2 +- .../spdy_server_push_utils_for_envoy.cc | 10 +- .../quiche/envoy_quic_client_session_test.cc | 2 +- .../quiche/envoy_quic_client_stream_test.cc | 54 +- .../quiche/envoy_quic_proof_source_test.cc | 6 +- .../quiche/envoy_quic_proof_verifier_test.cc | 8 +- .../quiche/envoy_quic_server_session_test.cc | 3 +- .../quiche/envoy_quic_server_stream_test.cc | 53 +- .../quic_listeners/quiche/platform/BUILD | 22 - .../quiche/platform/http2_platform_test.cc | 22 +- .../quiche/platform/quic_platform_test.cc | 61 +-- .../quiche/platform/quic_test_output_impl.cc | 15 +- .../quiche/platform/quic_test_output_impl.h | 12 +- .../quiche/platform/quiche_platform_test.cc | 39 -- .../quiche/platform/spdy_platform_test.cc | 20 +- .../quic_listeners/quiche/test_proof_source.h | 2 +- .../quic_listeners/quiche/test_utils.h | 4 +- 61 files changed, 406 insertions(+), 1060 deletions(-) delete mode 100644 source/extensions/quic_listeners/quiche/platform/flags_list.h delete mode 100644 source/extensions/quic_listeners/quiche/platform/quic_aligned_impl.h delete mode 100644 source/extensions/quic_listeners/quiche/platform/quic_fallthrough_impl.h delete mode 100644 source/extensions/quic_listeners/quiche/platform/quic_macros_impl.h rename source/extensions/quic_listeners/quiche/platform/{quiche_ptr_util_impl.h => quic_testvalue_impl.h} (52%) delete mode 100644 source/extensions/quic_listeners/quiche/platform/quiche_arraysize_impl.h delete mode 100644 source/extensions/quic_listeners/quiche/platform/quiche_optional_impl.h delete mode 100644 source/extensions/quic_listeners/quiche/platform/spdy_endianness_util_impl.h delete mode 100644 test/extensions/quic_listeners/quiche/platform/quiche_platform_test.cc diff --git a/bazel/envoy_internal.bzl b/bazel/envoy_internal.bzl index 91d7ac2abee8..c122e36cd9c7 100644 --- a/bazel/envoy_internal.bzl +++ b/bazel/envoy_internal.bzl @@ -63,6 +63,8 @@ def envoy_copts(repository, test = False): }) + select({ repository + "//bazel:clang_build": ["-fno-limit-debug-info", "-Wgnu-conditional-omitted-operand", "-Wc++2a-extensions", "-Wrange-loop-analysis"], repository + "//bazel:gcc_build": ["-Wno-maybe-uninitialized"], + # TODO: Replace with /Zc:preprocessor for cl.exe versions >= 16.5 + repository + "//bazel:windows_x86_64": ["-experimental:preprocessor", "-Wv:19.4"], "//conditions:default": [], }) + select({ repository + "//bazel:no_debug_info": ["-g0"], diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 7541909aa191..b6b208fc5bbf 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -57,16 +57,12 @@ quiche_common_copts = [ "-Wno-unused-function", # quic_inlined_frame.h uses offsetof() to optimize memory usage in frames. "-Wno-invalid-offsetof", - "-Wno-range-loop-analysis", ] quiche_copts = select({ # Ignore unguarded #pragma GCC statements in QUICHE sources "@envoy//bazel:windows_x86_64": ["-wd4068"], # Remove these after upstream fix. - "@envoy//bazel:gcc_build": [ - "-Wno-sign-compare", - ] + quiche_common_copts, "//conditions:default": quiche_common_copts, }) @@ -737,7 +733,6 @@ envoy_cc_library( hdrs = [ "quiche/spdy/platform/api/spdy_bug_tracker.h", "quiche/spdy/platform/api/spdy_containers.h", - "quiche/spdy/platform/api/spdy_endianness_util.h", "quiche/spdy/platform/api/spdy_estimate_memory_usage.h", "quiche/spdy/platform/api/spdy_flags.h", "quiche/spdy/platform/api/spdy_logging.h", @@ -935,6 +930,7 @@ envoy_cc_library( copts = quiche_copts, repository = "@envoy", deps = [ + ":http2_hpack_huffman_hpack_huffman_encoder_lib", ":spdy_core_protocol_lib", ":spdy_platform", ], @@ -1049,19 +1045,16 @@ envoy_cc_library( envoy_cc_library( name = "quic_platform_base", hdrs = [ - "quiche/quic/platform/api/quic_aligned.h", "quiche/quic/platform/api/quic_bug_tracker.h", "quiche/quic/platform/api/quic_client_stats.h", "quiche/quic/platform/api/quic_containers.h", "quiche/quic/platform/api/quic_error_code_wrappers.h", "quiche/quic/platform/api/quic_estimate_memory_usage.h", "quiche/quic/platform/api/quic_exported_stats.h", - "quiche/quic/platform/api/quic_fallthrough.h", "quiche/quic/platform/api/quic_flag_utils.h", "quiche/quic/platform/api/quic_flags.h", "quiche/quic/platform/api/quic_iovec.h", "quiche/quic/platform/api/quic_logging.h", - "quiche/quic/platform/api/quic_macros.h", "quiche/quic/platform/api/quic_map_util.h", "quiche/quic/platform/api/quic_mem_slice.h", "quiche/quic/platform/api/quic_prefetch.h", @@ -1072,6 +1065,7 @@ envoy_cc_library( "quiche/quic/platform/api/quic_stream_buffer_allocator.h", "quiche/quic/platform/api/quic_string_utils.h", "quiche/quic/platform/api/quic_uint128.h", + "quiche/quic/platform/api/quic_testvalue.h", # TODO: uncomment the following files as implementations are added. # "quiche/quic/platform/api/quic_fuzzed_data_provider.h", # "quiche/quic/platform/api/quic_test_loopback.h", @@ -1147,7 +1141,6 @@ envoy_cc_test_library( hdrs = ["quiche/quic/platform/api/quic_port_utils.h"], repository = "@envoy", tags = ["nofips"], - deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_port_utils_impl_lib"], ) envoy_cc_library( @@ -1216,15 +1209,14 @@ envoy_cc_test_library( ) envoy_cc_library( - name = "quiche_common_platform_endian", - hdrs = ["quiche/common/platform/api/quiche_endian.h"], + name = "quiche_common_endian_lib", + hdrs = ["quiche/common/quiche_endian.h"], repository = "@envoy", tags = ["nofips"], visibility = ["//visibility:public"], deps = [ ":quiche_common_platform_export", - "@envoy//source/extensions/quic_listeners/quiche/platform:quiche_common_platform_endian_impl_lib", ], ) @@ -1932,6 +1924,7 @@ envoy_cc_library( visibility = ["//visibility:public"], deps = [ ":quic_core_clock_lib", + ":quic_core_crypto_certificate_view_lib", ":quic_core_crypto_encryption_lib", ":quic_core_crypto_hkdf_lib", ":quic_core_crypto_proof_source_interface_lib", @@ -2167,6 +2160,15 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "quic_core_flags_list_lib", + hdrs = ["quiche/quic/core/quic_flags_list.h"], + copts = quiche_copts, + repository = "@envoy", + tags = ["nofips"], + visibility = ["//visibility:public"], +) + envoy_cc_library( name = "quic_core_framer_lib", srcs = ["quiche/quic/core/quic_framer.cc"], @@ -2339,6 +2341,7 @@ envoy_cc_library( repository = "@envoy", tags = ["nofips"], deps = [ + ":http2_constants_lib", ":quic_core_data_lib", ":quic_core_error_codes_lib", ":quic_core_http_http_frames_lib", @@ -2723,6 +2726,27 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "quic_core_path_validator_lib", + srcs = ["quiche/quic/core/quic_path_validator.cc"], + hdrs = ["quiche/quic/core/quic_path_validator.h"], + copts = quiche_copts, + repository = "@envoy", + tags = ["nofips"], + deps = [ + ":quic_core_alarm_factory_interface_lib", + ":quic_core_alarm_interface_lib", + ":quic_core_arena_scoped_ptr_lib", + ":quic_core_clock_lib", + ":quic_core_constants_lib", + ":quic_core_crypto_random_lib", + ":quic_core_one_block_arena_lib", + ":quic_core_packet_writer_interface_lib", + ":quic_core_types_lib", + ":quic_platform", + ], +) + envoy_cc_library( name = "quic_core_process_packet_interface_lib", hdrs = ["quiche/quic/core/quic_process_packet_interface.h"], @@ -2735,6 +2759,15 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "quic_core_protocol_flags_list_lib", + hdrs = ["quiche/quic/core/quic_protocol_flags_list.h"], + copts = quiche_copts, + repository = "@envoy", + tags = ["nofips"], + visibility = ["//visibility:public"], +) + envoy_cc_library( name = "quic_core_qpack_blocking_manager_lib", srcs = ["quiche/quic/core/qpack/qpack_blocking_manager.cc"], @@ -2896,6 +2929,7 @@ envoy_cc_library( deps = [ ":http2_decoder_decode_buffer_lib", ":http2_decoder_decode_status_lib", + ":quic_core_error_codes_lib", ":quic_core_qpack_qpack_instruction_decoder_lib", ":quic_core_qpack_qpack_instructions_lib", ":quic_core_qpack_qpack_stream_receiver_lib", @@ -3368,7 +3402,7 @@ envoy_cc_library( ":quic_core_error_codes_lib", ":quic_core_time_lib", ":quic_platform_base", - ":quiche_common_platform_endian", + ":quiche_common_endian_lib", ], ) @@ -3420,6 +3454,7 @@ envoy_cc_library( repository = "@envoy", tags = ["nofips"], deps = [ + ":quic_core_circular_deque_lib", ":quic_core_connection_stats_lib", ":quic_core_packets_lib", ":quic_core_session_notifier_interface_lib", @@ -3459,6 +3494,7 @@ envoy_cc_library( deps = [ ":quic_core_versions_lib", ":quic_platform_base", + ":quiche_common_endian_lib", ], ) @@ -3475,7 +3511,6 @@ envoy_cc_library( ":quic_core_tag_lib", ":quic_core_types_lib", ":quic_platform_base", - ":quiche_common_platform_endian", ], ) @@ -3746,6 +3781,7 @@ envoy_cc_test_library( ":quic_core_packet_creator_lib", ":quic_core_packet_writer_interface_lib", ":quic_core_packets_lib", + ":quic_core_path_validator_lib", ":quic_core_received_packet_manager_lib", ":quic_core_sent_packet_manager_lib", ":quic_core_server_id_lib", @@ -3836,25 +3872,10 @@ envoy_cc_test_library( deps = [":epoll_server_platform"], ) -envoy_cc_library( - name = "quiche_common_platform_optional", - hdrs = ["quiche/common/platform/api/quiche_optional.h"], - repository = "@envoy", - tags = ["nofips"], - visibility = ["//visibility:public"], - deps = [ - ":quiche_common_platform_export", - "@envoy//source/extensions/quic_listeners/quiche/platform:quiche_common_platform_optional_impl_lib", - ], -) - envoy_cc_library( name = "quiche_common_platform", hdrs = [ - "quiche/common/platform/api/quiche_arraysize.h", "quiche/common/platform/api/quiche_logging.h", - "quiche/common/platform/api/quiche_optional.h", - "quiche/common/platform/api/quiche_ptr_util.h", "quiche/common/platform/api/quiche_str_cat.h", "quiche/common/platform/api/quiche_string_piece.h", "quiche/common/platform/api/quiche_text_utils.h", @@ -3866,7 +3887,6 @@ envoy_cc_library( visibility = ["//visibility:public"], deps = [ ":quiche_common_platform_export", - ":quiche_common_platform_optional", "@envoy//source/extensions/quic_listeners/quiche/platform:quiche_common_platform_impl_lib", ], ) @@ -3874,7 +3894,6 @@ envoy_cc_library( envoy_cc_test_library( name = "quiche_common_platform_test", srcs = [ - "quiche/common/platform/api/quiche_endian_test.cc", "quiche/common/platform/api/quiche_str_cat_test.cc", "quiche/common/platform/api/quiche_text_utils_test.cc", "quiche/common/platform/api/quiche_time_utils_test.cc", @@ -3884,7 +3903,6 @@ envoy_cc_test_library( tags = ["nofips"], deps = [ ":quiche_common_platform", - ":quiche_common_platform_endian", "@envoy//test/extensions/quic_listeners/quiche/platform:quiche_common_platform_test_impl_lib", ], ) @@ -3904,8 +3922,8 @@ envoy_cc_library( tags = ["nofips"], visibility = ["//visibility:public"], deps = [ + ":quiche_common_endian_lib", ":quiche_common_platform", - ":quiche_common_platform_endian", ], ) @@ -3944,6 +3962,7 @@ envoy_cc_test( deps = [ ":http2_platform", ":http2_test_tools_random", + ":quiche_common_test_tools_test_utils_lib", ], ) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index a91db6898b9c..211f52da0f5f 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -711,13 +711,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://quiche.googlesource.com/quiche", - # Static snapshot of https://quiche.googlesource.com/quiche/+archive/f555d99a084cdd086a349548c70fb558ac5847cf.tar.gz - version = "f555d99a084cdd086a349548c70fb558ac5847cf", - sha256 = "1833f08e7b0f18b49d7498b029b7f3e6559a82113ec82a98a9e945553756e351", + # Static snapshot of https://quiche.googlesource.com/quiche/+archive/ecc28c0d7428f3323ea26eb1ddb98a5e06b23dea.tar.gz + version = "ecc28c0d7428f3323ea26eb1ddb98a5e06b23dea", + sha256 = "52680dea984dbe899c27176155578b97276e1f1516b7c3a63fb16ba593061859", urls = ["https://storage.googleapis.com/quiche-envoy-integration/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.transport_sockets.quic"], - release_date = "2020-09-17", + release_date = "2020-11-10", cpe = "N/A", ), com_googlesource_googleurl = dict( diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index 29eb78d155e1..a90cfde6ddc6 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -212,6 +212,7 @@ envoy_cc_library( "//source/common/buffer:buffer_lib", "//source/common/common:assert_lib", "//source/common/http:header_map_lib", + "//source/common/http:header_utility_lib", "//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_storage_impl_lib", "@com_googlesource_quiche//:quic_core_http_client_lib", ], diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc index f4808adc52b0..86912292ae88 100644 --- a/source/extensions/quic_listeners/quiche/active_quic_listener.cc +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -55,7 +55,7 @@ ActiveQuicListener::ActiveQuicListener( quic::QuicRandom* const random = quic::QuicRandom::GetInstance(); random->RandBytes(random_seed_, sizeof(random_seed_)); crypto_config_ = std::make_unique( - quiche::QuicheStringPiece(reinterpret_cast(random_seed_), sizeof(random_seed_)), + absl::string_view(reinterpret_cast(random_seed_), sizeof(random_seed_)), quic::QuicRandom::GetInstance(), std::make_unique(listen_socket_, listener_config.filterChainManager(), stats_), diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc index b92a77147e91..40e980441915 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_connection.cc @@ -43,7 +43,7 @@ EnvoyQuicClientConnection::EnvoyQuicClientConnection( const quic::ParsedQuicVersionVector& supported_versions, Event::Dispatcher& dispatcher, Network::ConnectionSocketPtr&& connection_socket) : EnvoyQuicConnection( - server_connection_id, + server_connection_id, quic::QuicSocketAddress(), envoyIpAddressToQuicSocketAddress(connection_socket->remoteAddress()->ip()), helper, alarm_factory, writer, owns_writer, quic::Perspective::IS_CLIENT, supported_versions, std::move(connection_socket)), diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index b52b411df7e7..aeca6efdf198 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -20,6 +20,7 @@ #include "common/buffer/buffer_impl.h" #include "common/http/header_map_impl.h" +#include "common/http/header_utility.h" #include "common/common/assert.h" namespace Envoy { @@ -48,6 +49,10 @@ EnvoyQuicClientStream::EnvoyQuicClientStream(quic::PendingStream* pending, Http::Status EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& headers, bool end_stream) { + // Required headers must be present. This can only happen by some erroneous processing after the + // downstream codecs decode. + RETURN_IF_ERROR(Http::HeaderUtility::checkRequiredHeaders(headers)); + ENVOY_STREAM_LOG(debug, "encodeHeaders: (end_stream={}) {}.", *this, end_stream, headers); quic::QuicStream* writing_stream = quic::VersionUsesHttp3(transport_version()) diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc index dcc311a6eaac..d813dfe4badb 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc @@ -6,6 +6,7 @@ namespace Envoy { namespace Quic { EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicSocketAddress initial_self_address, quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, @@ -13,8 +14,9 @@ EnvoyQuicConnection::EnvoyQuicConnection(const quic::QuicConnectionId& server_co quic::Perspective perspective, const quic::ParsedQuicVersionVector& supported_versions, Network::ConnectionSocketPtr&& connection_socket) - : quic::QuicConnection(server_connection_id, initial_peer_address, &helper, &alarm_factory, - writer, owns_writer, perspective, supported_versions), + : quic::QuicConnection(server_connection_id, initial_self_address, initial_peer_address, + &helper, &alarm_factory, writer, owns_writer, perspective, + supported_versions), connection_socket_(std::move(connection_socket)) {} EnvoyQuicConnection::~EnvoyQuicConnection() { connection_socket_->close(); } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_connection.h index f4c8589d7118..f8543bc938d7 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_connection.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.h @@ -26,6 +26,7 @@ class EnvoyQuicConnection : public quic::QuicConnection, protected Logger::Loggable { public: EnvoyQuicConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicSocketAddress initial_self_address, quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc index ba8f7f3a8239..e6351f643653 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -48,11 +48,11 @@ void EnvoyQuicDispatcher::OnConnectionClosed(quic::QuicConnectionId connection_i } std::unique_ptr EnvoyQuicDispatcher::CreateQuicSession( - quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& /*self_address*/, - const quic::QuicSocketAddress& peer_address, quiche::QuicheStringPiece /*alpn*/, + quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& self_address, + const quic::QuicSocketAddress& peer_address, absl::string_view /*alpn*/, const quic::ParsedQuicVersion& version) { auto quic_connection = std::make_unique( - server_connection_id, peer_address, *helper(), *alarm_factory(), writer(), + server_connection_id, self_address, peer_address, *helper(), *alarm_factory(), writer(), /*owns_writer=*/false, quic::ParsedQuicVersionVector{version}, listen_socket_); auto quic_session = std::make_unique( config(), quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h index 589ff5327706..d59307f415ec 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h @@ -62,7 +62,7 @@ class EnvoyQuicDispatcher : public quic::QuicDispatcher { std::unique_ptr CreateQuicSession(quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& self_address, - const quic::QuicSocketAddress& peer_address, quiche::QuicheStringPiece alpn, + const quic::QuicSocketAddress& peer_address, absl::string_view alpn, const quic::ParsedQuicVersion& version) override; private: diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc index 1f65e4e7e6a0..967765829a28 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.cc @@ -36,7 +36,7 @@ EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address void EnvoyQuicProofSource::signPayload( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, - const std::string& hostname, uint16_t signature_algorithm, quiche::QuicheStringPiece in, + const std::string& hostname, uint16_t signature_algorithm, absl::string_view in, std::unique_ptr callback) { CertConfigWithFilterChain res = getTlsCertConfigAndFilterChain(server_address, client_address, hostname); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h index 6e1c74c9234c..e22bf3465f49 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source.h @@ -28,7 +28,7 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { // quic::ProofSource void signPayload(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, - uint16_t signature_algorithm, quiche::QuicheStringPiece in, + uint16_t signature_algorithm, absl::string_view in, std::unique_ptr callback) override; private: diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc index 2c82c04d901d..9ad3cb07f428 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.cc @@ -21,7 +21,7 @@ void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_ad const std::string& hostname, const std::string& server_config, quic::QuicTransportVersion /*transport_version*/, - quiche::QuicheStringPiece chlo_hash, + absl::string_view chlo_hash, std::unique_ptr callback) { quic::QuicReferenceCountedPointer chain = GetCertChain(server_address, client_address, hostname); @@ -68,13 +68,12 @@ void EnvoyQuicProofSourceBase::GetProof(const quic::QuicSocketAddress& server_ad auto signature_callback = std::make_unique(std::move(callback), chain); signPayload(server_address, client_address, hostname, sign_alg, - quiche::QuicheStringPiece(payload.get(), payload_size), - std::move(signature_callback)); + absl::string_view(payload.get(), payload_size), std::move(signature_callback)); } void EnvoyQuicProofSourceBase::ComputeTlsSignature( const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, - const std::string& hostname, uint16_t signature_algorithm, quiche::QuicheStringPiece in, + const std::string& hostname, uint16_t signature_algorithm, absl::string_view in, std::unique_ptr callback) { signPayload(server_address, client_address, hostname, signature_algorithm, in, std::move(callback)); diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h index b7d76981e519..a9e7e8c3f094 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_source_base.h @@ -57,7 +57,7 @@ class EnvoyQuicProofSourceBase : public quic::ProofSource, void GetProof(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, const std::string& server_config, quic::QuicTransportVersion /*transport_version*/, - quiche::QuicheStringPiece chlo_hash, + absl::string_view chlo_hash, std::unique_ptr callback) override; TicketCrypter* GetTicketCrypter() override { return nullptr; } @@ -65,14 +65,14 @@ class EnvoyQuicProofSourceBase : public quic::ProofSource, void ComputeTlsSignature(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, uint16_t signature_algorithm, - quiche::QuicheStringPiece in, + absl::string_view in, std::unique_ptr callback) override; protected: virtual void signPayload(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, uint16_t signature_algorithm, - quiche::QuicheStringPiece in, + absl::string_view in, std::unique_ptr callback) PURE; private: diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc index 229b3ab36628..e375905295a3 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_base.cc @@ -58,8 +58,8 @@ bool EnvoyQuicProofVerifierBase::verifySignature(const std::string& server_confi *error_details = "QuicPacketWriter error."; return false; } - bool valid = cert_view->VerifySignature(quiche::QuicheStringPiece(payload.get(), payload_size), - signature, sign_alg); + bool valid = cert_view->VerifySignature(absl::string_view(payload.get(), payload_size), signature, + sign_alg); if (!valid) { *error_details = "Signature is not valid."; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc index b8fa94221f05..974c6c8ebc8c 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.cc @@ -11,11 +11,13 @@ namespace Quic { EnvoyQuicServerConnection::EnvoyQuicServerConnection( const quic::QuicConnectionId& server_connection_id, - quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, - quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, bool owns_writer, + quic::QuicSocketAddress initial_self_address, quic::QuicSocketAddress initial_peer_address, + quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, + quic::QuicPacketWriter* writer, bool owns_writer, const quic::ParsedQuicVersionVector& supported_versions, Network::Socket& listen_socket) - : EnvoyQuicConnection(server_connection_id, initial_peer_address, helper, alarm_factory, writer, - owns_writer, quic::Perspective::IS_SERVER, supported_versions, + : EnvoyQuicConnection(server_connection_id, initial_self_address, initial_peer_address, helper, + alarm_factory, writer, owns_writer, quic::Perspective::IS_SERVER, + supported_versions, std::make_unique( // Wraps the real IoHandle instance so that if the connection socket // gets closed, the real IoHandle won't be affected. diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h index 7b7fac05e925..7625fad02d0b 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_connection.h @@ -10,6 +10,7 @@ namespace Quic { class EnvoyQuicServerConnection : public EnvoyQuicConnection { public: EnvoyQuicServerConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicSocketAddress initial_self_address, quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter* writer, diff --git a/source/extensions/quic_listeners/quiche/platform/BUILD b/source/extensions/quic_listeners/quiche/platform/BUILD index f53e07b58a33..839664d52f97 100644 --- a/source/extensions/quic_listeners/quiche/platform/BUILD +++ b/source/extensions/quic_listeners/quiche/platform/BUILD @@ -36,15 +36,16 @@ envoy_extension_package() envoy_cc_library( name = "flags_impl_lib", srcs = ["flags_impl.cc"], - hdrs = [ - "flags_impl.h", - "flags_list.h", - ], + hdrs = ["flags_impl.h"], external_deps = [ "abseil_base", "abseil_synchronization", ], visibility = ["//visibility:public"], + deps = [ + "@com_googlesource_quiche//:quic_core_flags_list_lib", + "@com_googlesource_quiche//:quic_core_protocol_flags_list_lib", + ], ) envoy_cc_library( @@ -62,7 +63,6 @@ envoy_cc_library( envoy_cc_library( name = "http2_platform_impl_lib", hdrs = [ - "http2_arraysize_impl.h", "http2_bug_tracker_impl.h", "http2_containers_impl.h", "http2_estimate_memory_usage_impl.h", @@ -74,7 +74,6 @@ envoy_cc_library( ], external_deps = [ "abseil_base", - "abseil_optional", "abseil_str_format", ], visibility = ["//visibility:public"], @@ -114,16 +113,13 @@ envoy_cc_library( "quic_mem_slice_impl.cc", ], hdrs = [ - "quic_aligned_impl.h", "quic_client_stats_impl.h", "quic_containers_impl.h", "quic_error_code_wrappers_impl.h", "quic_estimate_memory_usage_impl.h", - "quic_fallthrough_impl.h", "quic_flag_utils_impl.h", "quic_flags_impl.h", "quic_iovec_impl.h", - "quic_macros_impl.h", "quic_map_util_impl.h", "quic_mem_slice_impl.h", "quic_prefetch_impl.h", @@ -132,6 +128,7 @@ envoy_cc_library( "quic_server_stats_impl.h", "quic_stack_trace_impl.h", "quic_stream_buffer_allocator_impl.h", + "quic_testvalue_impl.h", "quic_uint128_impl.h", ], external_deps = [ @@ -141,7 +138,6 @@ envoy_cc_library( "abseil_memory", "abseil_node_hash_map", "abseil_node_hash_set", - "abseil_optional", ], tags = ["nofips"], visibility = ["//visibility:public"], @@ -236,6 +232,7 @@ envoy_cc_library( }), repository = "@envoy", tags = ["nofips"], + visibility = ["//visibility:public"], ) envoy_cc_library( @@ -250,23 +247,12 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "quiche_common_platform_optional_impl_lib", - hdrs = ["quiche_optional_impl.h"], - external_deps = [ - "abseil_node_hash_map", - ], - visibility = ["//visibility:public"], -) - envoy_cc_library( name = "quiche_common_platform_impl_lib", srcs = ["quiche_time_utils_impl.cc"], hdrs = [ - "quiche_arraysize_impl.h", "quiche_logging_impl.h", "quiche_map_util_impl.h", - "quiche_ptr_util_impl.h", "quiche_str_cat_impl.h", "quiche_string_piece_impl.h", "quiche_text_utils_impl.h", @@ -281,17 +267,14 @@ envoy_cc_library( deps = [ ":quic_platform_logging_impl_lib", ":string_utils_lib", - "@com_googlesource_quiche//:quiche_common_platform_optional", ], ) envoy_cc_library( name = "spdy_platform_impl_lib", hdrs = [ - "spdy_arraysize_impl.h", "spdy_bug_tracker_impl.h", "spdy_containers_impl.h", - "spdy_endianness_util_impl.h", "spdy_estimate_memory_usage_impl.h", "spdy_flags_impl.h", "spdy_logging_impl.h", @@ -331,14 +314,3 @@ envoy_cc_library( tags = ["nofips"], visibility = ["//visibility:public"], ) - -envoy_cc_library( - name = "quiche_common_platform_endian_impl_lib", - hdrs = ["quiche_endian_impl.h"], - tags = ["nofips"], - visibility = ["//visibility:public"], - deps = [ - "quiche_common_platform_export_impl_lib", - "//source/common/common:byte_order_lib", - ], -) diff --git a/source/extensions/quic_listeners/quiche/platform/flags_impl.cc b/source/extensions/quic_listeners/quiche/platform/flags_impl.cc index 70fb182d673d..9d4ea89ce3a6 100644 --- a/source/extensions/quic_listeners/quiche/platform/flags_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/flags_impl.cc @@ -15,12 +15,24 @@ namespace quiche { namespace { -absl::flat_hash_map MakeFlagMap() { +absl::flat_hash_map makeFlagMap() { absl::flat_hash_map flags; -#define QUICHE_FLAG(type, flag, value, help) flags.emplace(FLAGS_##flag->name(), FLAGS_##flag); -#include "extensions/quic_listeners/quiche/platform/flags_list.h" -#undef QUICHE_FLAG +#define QUIC_FLAG(flag, ...) flags.emplace(flag->name(), flag); +#include "quiche/quic/core/quic_flags_list.h" + QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_false, false) + QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_true, true) + QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_false, false) + QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_true, true) + QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_false, false) + QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_true, true) + QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_false, false) + QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_true, true) +#undef QUIC_FLAG + +#define QUIC_PROTOCOL_FLAG(type, flag, ...) flags.emplace(FLAGS_##flag->name(), FLAGS_##flag); +#include "quiche/quic/core/quic_protocol_flags_list.h" +#undef QUIC_PROTOCOL_FLAG return flags; } @@ -28,75 +40,123 @@ absl::flat_hash_map MakeFlagMap() { } // namespace // static -FlagRegistry& FlagRegistry::GetInstance() { +FlagRegistry& FlagRegistry::getInstance() { static auto* instance = new FlagRegistry(); return *instance; } -FlagRegistry::FlagRegistry() : flags_(MakeFlagMap()) {} +FlagRegistry::FlagRegistry() : flags_(makeFlagMap()) {} -void FlagRegistry::ResetFlags() const { +void FlagRegistry::resetFlags() const { for (auto& kv : flags_) { - kv.second->ResetValue(); + kv.second->resetValue(); } } -Flag* FlagRegistry::FindFlag(const std::string& name) const { +Flag* FlagRegistry::findFlag(const std::string& name) const { auto it = flags_.find(name); return (it != flags_.end()) ? it->second : nullptr; } -template <> bool TypedFlag::SetValueFromString(const std::string& value_str) { +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { static const auto* kTrueValues = new std::set({"1", "t", "true", "y", "yes"}); static const auto* kFalseValues = new std::set({"0", "f", "false", "n", "no"}); auto lower = absl::AsciiStrToLower(value_str); if (kTrueValues->find(lower) != kTrueValues->end()) { - SetValue(true); + setValue(true); return true; } if (kFalseValues->find(lower) != kFalseValues->end()) { - SetValue(false); + setValue(false); return true; } return false; } -template <> bool TypedFlag::SetValueFromString(const std::string& value_str) { +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { int32_t value; if (absl::SimpleAtoi(value_str, &value)) { - SetValue(value); + setValue(value); return true; } return false; } -template <> bool TypedFlag::SetValueFromString(const std::string& value_str) { +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { int64_t value; if (absl::SimpleAtoi(value_str, &value)) { - SetValue(value); + setValue(value); return true; } return false; } -template <> bool TypedFlag::SetValueFromString(const std::string& value_str) { +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { double value; if (absl::SimpleAtod(value_str, &value)) { - SetValue(value); + setValue(value); return true; } return false; } -template <> bool TypedFlag::SetValueFromString(const std::string& value_str) { - SetValue(value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { + setValue(value_str); return true; } +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { + unsigned long value; + if (absl::SimpleAtoi(value_str, &value)) { + setValue(value); + return true; + } + return false; +} + +template <> bool TypedFlag::setValueFromString(const std::string& value_str) { + unsigned long long value; + if (absl::SimpleAtoi(value_str, &value)) { + setValue(value); + return true; + } + return false; +} + // Flag definitions -#define QUICHE_FLAG(type, flag, value, help) \ - TypedFlag* FLAGS_##flag = new TypedFlag(#flag, value, help); -#include "extensions/quic_listeners/quiche/platform/flags_list.h" -#undef QUICHE_FLAG +#define QUIC_FLAG(flag, value) TypedFlag* flag = new TypedFlag(#flag, value, ""); +#include "quiche/quic/core/quic_flags_list.h" +QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_true, true) + +#undef QUIC_FLAG + +#define STRINGIFY(X) #X + +#define DEFINE_QUIC_PROTOCOL_FLAG_IMPL(type, flag, value, help) \ + TypedFlag* FLAGS_##flag = new TypedFlag(STRINGIFY(FLAGS_##flag), value, help); + +#define DEFINE_QUIC_PROTOCOL_FLAG_SINGLE_VALUE(type, flag, value, doc) \ + DEFINE_QUIC_PROTOCOL_FLAG_IMPL(type, flag, value, doc) + +#define DEFINE_QUIC_PROTOCOL_FLAG_TWO_VALUES(type, flag, internal_value, external_value, doc) \ + DEFINE_QUIC_PROTOCOL_FLAG_IMPL(type, flag, external_value, doc) + +// Select the right macro based on the number of arguments. +#define GET_6TH_ARG(arg1, arg2, arg3, arg4, arg5, arg6, ...) arg6 + +#define QUIC_PROTOCOL_FLAG_MACRO_CHOOSER(...) \ + GET_6TH_ARG(__VA_ARGS__, DEFINE_QUIC_PROTOCOL_FLAG_TWO_VALUES, \ + DEFINE_QUIC_PROTOCOL_FLAG_SINGLE_VALUE) + +#define QUIC_PROTOCOL_FLAG(...) QUIC_PROTOCOL_FLAG_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) +#include "quiche/quic/core/quic_protocol_flags_list.h" +#undef QUIC_PROTOCOL_FLAG } // namespace quiche diff --git a/source/extensions/quic_listeners/quiche/platform/flags_impl.h b/source/extensions/quic_listeners/quiche/platform/flags_impl.h index 5db939925510..83ed8430c07d 100644 --- a/source/extensions/quic_listeners/quiche/platform/flags_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/flags_impl.h @@ -26,13 +26,13 @@ class FlagRegistry { ~FlagRegistry() = default; // Return singleton instance. - static FlagRegistry& GetInstance(); + static FlagRegistry& getInstance(); // Reset all registered flags to their default values. - void ResetFlags() const; + void resetFlags() const; // Look up a flag by name. - Flag* FindFlag(const std::string& name) const; + Flag* findFlag(const std::string& name) const; private: FlagRegistry(); @@ -48,10 +48,10 @@ class Flag { virtual ~Flag() = default; // Set flag value from given string, returning true iff successful. - virtual bool SetValueFromString(const std::string& value_str) = 0; + virtual bool setValueFromString(const std::string& value_str) = 0; // Reset flag to default value. - virtual void ResetValue() = 0; + virtual void resetValue() = 0; // Return flag name. std::string name() const { return name_; } @@ -70,15 +70,15 @@ template class TypedFlag : public Flag { TypedFlag(const char* name, T default_value, const char* help) : Flag(name, help), value_(default_value), default_value_(default_value) {} - bool SetValueFromString(const std::string& value_str) override; + bool setValueFromString(const std::string& value_str) override; - void ResetValue() override { + void resetValue() override { absl::MutexLock lock(&mutex_); value_ = default_value_; } // Set flag value. - void SetValue(T value) { + void setValue(T value) { absl::MutexLock lock(&mutex_); value_ = value; } @@ -96,15 +96,29 @@ template class TypedFlag : public Flag { }; // SetValueFromString specializations -template <> bool TypedFlag::SetValueFromString(const std::string& value_str); -template <> bool TypedFlag::SetValueFromString(const std::string& value_str); -template <> bool TypedFlag::SetValueFromString(const std::string& value_str); -template <> bool TypedFlag::SetValueFromString(const std::string& value_str); -template <> bool TypedFlag::SetValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); +template <> bool TypedFlag::setValueFromString(const std::string& value_str); // Flag declarations -#define QUICHE_FLAG(type, flag, value, help) extern TypedFlag* FLAGS_##flag; -#include "extensions/quic_listeners/quiche/platform/flags_list.h" -#undef QUICHE_FLAG +#define QUIC_FLAG(flag, ...) extern TypedFlag* flag; +#include "quiche/quic/core/quic_flags_list.h" +QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_spdy_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_restart_flag_spdy_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_http2_testonly_default_true, true) +QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_false, false) +QUIC_FLAG(FLAGS_quic_restart_flag_http2_testonly_default_true, true) +#undef QUIC_FLAG + +#define QUIC_PROTOCOL_FLAG(type, flag, ...) extern TypedFlag* FLAGS_##flag; +#include "quiche/quic/core/quic_protocol_flags_list.h" +#undef QUIC_PROTOCOL_FLAG } // namespace quiche diff --git a/source/extensions/quic_listeners/quiche/platform/flags_list.h b/source/extensions/quic_listeners/quiche/platform/flags_list.h deleted file mode 100644 index 52454caec0a2..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/flags_list.h +++ /dev/null @@ -1,502 +0,0 @@ -// This file intentionally does not have header guards. It is intended to be -// included multiple times, each time with a different definition of -// QUICHE_FLAG. - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -// This file is generated by //third_party/quic/tools:quic_flags_list in -// Google3. - -#if defined(QUICHE_FLAG) - -QUICHE_FLAG( - bool, http2_reloadable_flag_http2_backend_alpn_failure_error_code, false, - "If true, the GFE will return a new ResponseCodeDetails error when ALPN to the backend fails.") - -QUICHE_FLAG(bool, http2_reloadable_flag_http2_ip_based_cwnd_exp, true, - "If true, enable IP address based CWND bootstrapping experiment with different " - "bandwidth models and priorities in HTTP2.") - -QUICHE_FLAG( - bool, http2_reloadable_flag_http2_load_based_goaway_warning, false, - "If true, load-based connection closures will send a warning GOAWAY before the actual GOAWAY.") - -QUICHE_FLAG(bool, http2_reloadable_flag_http2_security_requirement_for_client3, false, - "If true, check whether client meets security requirements during SSL handshake. If " - "flag is true and client does not meet security requirements, do not negotiate HTTP/2 " - "with client or terminate the session with SPDY_INADEQUATE_SECURITY if HTTP/2 is " - "already negotiated. The spec contains both cipher and TLS version requirements.") - -QUICHE_FLAG(bool, http2_reloadable_flag_http2_websocket_detection, false, - "If true, uses a HTTP/2-specific method of detecting websocket upgrade requests.") - -QUICHE_FLAG(bool, http2_reloadable_flag_permissive_http2_switch, false, - "If true, the GFE allows both HTTP/1.0 and HTTP/1.1 versions in HTTP/2 upgrade " - "requests/responses.") - -QUICHE_FLAG(bool, quic_reloadable_flag_advertise_quic_for_https_for_debugips, false, "") - -QUICHE_FLAG(bool, quic_reloadable_flag_advertise_quic_for_https_for_external_users, false, "") - -QUICHE_FLAG(bool, quic_reloadable_flag_gclb_quic_allow_alia, true, - "If gfe2_reloadable_flag_gclb_use_alia is also true, use Alia for GCLB QUIC " - "handshakes. To be used as a big red button if there's a problem with Alia/QUIC.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_abort_qpack_on_stream_close, false, - "If true, abort async QPACK header decompression in QuicSpdyStream::OnClose().") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_ack_delay_alarm_granularity, false, - "When true, ensure the ACK delay is never less than the alarm granularity when ACK " - "decimation is enabled.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_add_missing_connected_checks, false, - "If true, add missing connected checks.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_add_silent_idle_timeout, true, - "If true, when server is silently closing connections due to idle timeout, serialize " - "the connection close packets which will be added to time wait list.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_add_stream_info_to_idle_close_detail, false, - "If true, include stream information in idle timeout connection close detail.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_allow_backend_set_stream_ttl, false, - "If true, check backend response header for X-Response-Ttl. If it is provided, the " - "stream TTL is set. A QUIC stream will be immediately canceled when tries to write " - "data if this TTL expired.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, true, - "If true, allow client to enable BBRv2 on server via connection option 'B2ON'.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_alpn_dispatch, false, - "Support different QUIC sessions, as indicated by ALPN. Used for QBONE.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr2_avoid_too_low_probe_bw_cwnd, false, - "If true, QUIC BBRv2's PROBE_BW mode will not reduce cwnd below BDP+ack_height.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr2_fewer_startup_round_trips, false, - "When true, the 1RTT and 2RTT connection options decrease the number of round trips in " - "BBRv2 STARTUP without a 25% bandwidth increase to 1 or 2 round trips respectively.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_bbr2_limit_inflight_hi, false, - "When true, the B2HI connection option limits reduction of inflight_hi to (1-Beta)*CWND.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr2_use_post_inflight_to_detect_queuing, false, - "If true, QUIC BBRv2 will use inflight byte after congestion event to detect queuing " - "during PROBE_UP.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr_no_bytes_acked_in_startup_recovery, false, - "When in STARTUP and recovery, do not add bytes_acked to QUIC BBR's CWND in " - "CalculateCongestionWindow()") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bootstrap_cwnd_by_spdy_priority, true, - "If true, bootstrap initial QUIC cwnd by SPDY priorities.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_cap_large_client_initial_rtt, true, - "If true, cap client suggested initial RTT to 1s if it is longer than 1s.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_clean_up_spdy_session_destructor, false, - "If true, QuicSpdySession's destructor won't need to do cleanup.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_close_connection_in_on_can_write_with_blocked_writer, - false, - "If true, close connection if writer is still blocked while OnCanWrite is called.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_close_connection_on_serialization_failure, false, - "If true, close connection on packet serialization failures.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_conservative_bursts, false, - "If true, set burst token to 2 in cwnd bootstrapping experiment.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false, - "If true, uses conservative cwnd gain and pacing gain when cwnd gets bootstrapped.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_copy_bbr_cwnd_to_bbr2, false, - "If true, when switching from BBR to BBRv2, BBRv2 will use BBR's cwnd as its initial cwnd.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_default_enable_5rto_blackhole_detection2, true, - "If true, default-enable 5RTO blachole detection.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_default_on_pto, false, - "If true, default on PTO which unifies TLP + RTO loss recovery.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_default_to_bbr, true, - "When true, defaults to BBR congestion control instead of Cubic.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_default_to_bbr_v2, false, - "If true, use BBRv2 as the default congestion controller. Takes precedence over " - "--quic_default_to_bbr.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_server_blackhole_detection, false, - "If true, disable blackhole detection on server side.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_draft_27, false, - "If true, disable QUIC version h3-27.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_draft_29, false, - "If true, disable QUIC version h3-29.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_q043, false, - "If true, disable QUIC version Q043.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_q046, false, - "If true, disable QUIC version Q046.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_q050, false, - "If true, disable QUIC version Q050.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_t050, false, - "If true, disable QUIC version h3-T050.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_t051, false, - "If true, disable QUIC version h3-T051.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_discard_initial_packet_with_key_dropped, false, - "If true, discard INITIAL packet if the key has been dropped.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_do_not_accept_stop_waiting, false, - "In v44 and above, where STOP_WAITING is never sent, close the connection if it's received.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_donot_reset_ideal_next_packet_send_time, false, - "If true, stop resetting ideal_next_packet_send_time_ in pacing sender.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_loss_detection_experiment_at_gfe, false, - "If ture, enable GFE-picked loss detection experiment.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_loss_detection_tuner, false, - "If true, allow QUIC loss detection tuning to be enabled by connection option ELDT.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_enable_mtu_discovery_at_server, false, - "If true, QUIC will default enable MTU discovery at server, with a target of 1450 bytes.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enabled, false, "") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_arm_pto_for_application_data, false, - "If true, do not arm PTO for application data until handshake confirmed.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_bytes_left_for_batch_write, false, - "If true, convert bytes_left_for_batch_write_ to unsigned int.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_fix_http3_goaway_stream_id, false, - "If true, send the lowest stream ID that can be retried by the client in a GOAWAY frame. If " - "false, send the highest received stream ID, which actually should not be retried.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_out_of_order_sending, false, - "If true, fix a potential out of order sending caused by handshake gets confirmed " - "while the coalescer is not empty.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_fix_pto_pending_timer_count, false, - "If true, make sure there is pending timer credit when trying to PTO retransmit any packets.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_undecryptable_packets2, false, - "If true, remove processed undecryptable packets.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_get_stream_information_from_stream_map, true, - "If true, gQUIC will only consult stream_map in QuicSession::GetNumActiveStreams().") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_give_sent_packet_to_debug_visitor_after_sent, false, - "If true, QUIC connection will pass sent packet information to the debug visitor after " - "a packet is recorded as sent in sent packet manager.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_http3_new_default_urgency_value, false, - "If true, QuicStream::kDefaultUrgency is 3, otherwise 1.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_ip_based_cwnd_exp, true, - "If true, enable IP address based CWND bootstrapping experiment with different " - "bandwidth models and priorities. ") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_listener_never_fake_epollout, false, - "If true, QuicListener::OnSocketIsWritable will always return false, which means there " - "will never be a fake `EPOLLOUT` event in the next epoll iteration.") - -QUICHE_FLAG(bool, - quic_reloadable_flag_quic_neuter_initial_packet_in_coalescer_with_initial_key_discarded, - false, "If true, neuter initial packet in the coalescer when discarding initial keys.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_dup_experiment_id_2, false, - "If true, transport connection stats doesn't report duplicated experiments for same " - "connection.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_silent_close_for_idle_timeout, true, - "If true, always send connection close for idle timeout if NSLC is received.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_only_set_uaid_in_tcs_visitor, false, - "If true, QuicTransportConnectionStatsVisitor::PopulateTransportConnectionStats will " - "be the only place where TCS's uaid field is set.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_only_truncate_long_cids, true, - "In IETF QUIC, only truncate long CIDs from the client's Initial, don't modify them.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_preferred_altsvc_version, false, - "When true, we will send a preferred QUIC version at the start of our Alt-Svc list.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_proxy_write_packed_strings, false, - "If true, QuicProxyDispatcher will write packed_client_address and packed_server_vip " - "in TcpProxyHeaderProto.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_record_frontend_service_vip_mapping, true, - "If true, for L1 GFE, as requests come in, record frontend service to VIP mapping " - "which is used to announce VIP in SHLO for proxied sessions. ") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_record_received_min_ack_delay, false, - "If true, record the received min_ack_delay in transport parameters to QUIC config.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_reject_all_traffic, false, "") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_remove_zombie_streams, true, - "If true, QuicSession doesn't keep a separate zombie_streams. Instead, all streams are " - "stored in stream_map_.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_require_handshake_confirmation, false, - "If true, require handshake confirmation for QUIC connections, functionally disabling " - "0-rtt handshakes.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_send_key_update_not_yet_supported, false, - "When true, QUIC+TLS versions will send the key_update_not_yet_supported transport parameter.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_send_path_response, false, - "If true, send PATH_RESPONSE upon receiving PATH_CHALLENGE regardless of perspective. " - "--gfe2_reloadable_flag_quic_start_peer_migration_earlier has to be true before turn " - "on this flag.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_send_timestamps, false, - "When the STMP connection option is sent by the client, timestamps in the QUIC ACK " - "frame are sent and processed.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_server_push, false, - "If true, enable server push feature on QUIC.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_set_resumed_ssl_session_early, false, - "If true, set resumed_ssl_session if this is a 0-RTT connection.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_start_peer_migration_earlier, false, - "If true, while reading an IETF quic packet, start peer migration immediately when " - "detecting the existence of any non-probing frame instead of at the end of the packet.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_stop_sending_uses_ietf_error_code, false, - "If true, use IETF QUIC application error codes in STOP_SENDING frames. If false, use " - "QuicRstStreamErrorCodes.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_testonly_default_false, false, - "A testonly reloadable flag that will always default to false.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_testonly_default_true, true, - "A testonly reloadable flag that will always default to true.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_unified_iw_options, false, - "When true, set the initial congestion control window from connection options in " - "QuicSentPacketManager rather than TcpCubicSenderBytes.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_header_stage_idle_list2, false, - "If true, use header stage idle list for QUIC connections in GFE.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_leto_key_exchange, false, - "If true, QUIC will attempt to use the Leto key exchange service and only fall back to " - "local key exchange if that fails.") - -QUICHE_FLAG(bool, quic_reloadable_flag_send_quic_fallback_server_config_on_leto_error, false, - "If true and using Leto for QUIC shared-key calculations, GFE will react to a failure " - "to contact Leto by sending a REJ containing a fallback ServerConfig, allowing the " - "client to continue the handshake.") - -QUICHE_FLAG( - bool, quic_restart_flag_dont_fetch_quic_private_keys_from_leto, false, - "If true, GFE will not request private keys when fetching QUIC ServerConfigs from Leto.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_adjust_initial_cwnd_by_gws, true, - "If true, GFE informs backend that a client request is the first one on the connection " - "via frontline header \"first_request=1\". Also, adjust initial cwnd based on " - "X-Google-Gws-Initial-Cwnd-Mode sent by GWS.") - -QUICHE_FLAG( - bool, quic_restart_flag_quic_allow_loas_multipacket_chlo, false, - "If true, inspects QUIC CHLOs for kLOAS and early creates sessions to allow multi-packet CHLOs") - -QUICHE_FLAG( - bool, quic_restart_flag_quic_disable_gws_cwnd_experiment, false, - "If true, X-Google-Gws-Initial-Cwnd-Mode related header sent by GWS becomes no-op for QUIC.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_enable_tls_resumption_v4, true, - "If true, enables support for TLS resumption in QUIC.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_enable_zero_rtt_for_tls_v2, true, - "If true, support for IETF QUIC 0-rtt is enabled.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_offload_pacing_to_usps2, false, - "If true, QUIC offload pacing when using USPS as egress method.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_rx_ring_use_tpacket_v3, false, - "If true, use TPACKET_V3 for QuicRxRing instead of TPACKET_V2.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_should_accept_new_connection, false, - "If true, reject QUIC CHLO packets when dispatcher is asked to do so.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_support_release_time_for_gso, false, - "If true, QuicGsoBatchWriter will support release time if it is available and the " - "process has the permission to do so.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_testonly_default_false, false, - "A testonly restart flag that will always default to false.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_testonly_default_true, true, - "A testonly restart flag that will always default to true.") - -QUICHE_FLAG( - bool, quic_restart_flag_quic_use_leto_for_quic_configs, false, - "If true, use Leto to fetch QUIC server configs instead of using the seeds from Memento.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_use_pigeon_socket_to_backend, false, - "If true, create a shared pigeon socket for all quic to backend connections and switch " - "to use it after successful handshake.") - -QUICHE_FLAG(bool, spdy_reloadable_flag_quic_bootstrap_cwnd_by_spdy_priority, true, - "If true, bootstrap initial QUIC cwnd by SPDY priorities.") - -QUICHE_FLAG(bool, spdy_reloadable_flag_quic_clean_up_spdy_session_destructor, false, - "If true, QuicSpdySession's destructor won't need to do cleanup.") - -QUICHE_FLAG( - bool, spdy_reloadable_flag_spdy_discard_response_body_if_disallowed, false, - "If true, SPDY will discard all response body bytes when response code indicates no response " - "body should exist. Previously, we only discard partial bytes on the first response processing " - "and the rest of the response bytes would still be delivered even though the response code " - "said there should not be any body associated with the response code.") - -QUICHE_FLAG(bool, quic_allow_chlo_buffering, true, - "If true, allows packets to be buffered in anticipation of a " - "future CHLO, and allow CHLO packets to be buffered until next " - "iteration of the event loop.") - -QUICHE_FLAG(bool, quic_disable_pacing_for_perf_tests, false, "If true, disable pacing in QUIC") - -QUICHE_FLAG(bool, quic_enforce_single_packet_chlo, true, - "If true, enforce that QUIC CHLOs fit in one packet") - -QUICHE_FLAG(int64_t, quic_time_wait_list_max_connections, 600000, - "Maximum number of connections on the time-wait list. " - "A negative value implies no configured limit.") - -QUICHE_FLAG(int64_t, quic_time_wait_list_seconds, 200, - "Time period for which a given connection_id should live in " - "the time-wait state.") - -QUICHE_FLAG(double, quic_bbr_cwnd_gain, 2.0f, - "Congestion window gain for QUIC BBR during PROBE_BW phase.") - -QUICHE_FLAG(int32_t, quic_buffered_data_threshold, 8 * 1024, - "If buffered data in QUIC stream is less than this " - "threshold, buffers all provided data or asks upper layer for more data") - -QUICHE_FLAG(int32_t, quic_send_buffer_max_data_slice_size, 4 * 1024, - "Max size of data slice in bytes for QUIC stream send buffer.") - -QUICHE_FLAG(int32_t, quic_lumpy_pacing_size, 2, - "Number of packets that the pacing sender allows in bursts during " - "pacing. This flag is ignored if a flow's estimated bandwidth is " - "lower than 1200 kbps.") - -QUICHE_FLAG(double, quic_lumpy_pacing_cwnd_fraction, 0.25f, - "Congestion window fraction that the pacing sender allows in bursts " - "during pacing.") - -QUICHE_FLAG(int32_t, quic_max_pace_time_into_future_ms, 10, - "Max time that QUIC can pace packets into the future in ms.") - -QUICHE_FLAG(double, quic_pace_time_into_future_srtt_fraction, 0.125f, - "Smoothed RTT fraction that a connection can pace packets into the future.") - -QUICHE_FLAG(bool, quic_export_server_num_packets_per_write_histogram, false, - "If true, export number of packets written per write operation histogram.") - -QUICHE_FLAG(bool, quic_disable_version_negotiation_grease_randomness, false, - "If true, use predictable version negotiation versions.") - -QUICHE_FLAG(bool, quic_enable_http3_grease_randomness, true, - "If true, use random greased settings and frames.") - -QUICHE_FLAG(int64_t, quic_max_tracked_packet_count, 10000, "Maximum number of tracked packets.") - -QUICHE_FLAG(bool, quic_prober_uses_length_prefixed_connection_ids, false, - "If true, QuicFramer::WriteClientVersionNegotiationProbePacket uses " - "length-prefixed connection IDs.") - -QUICHE_FLAG(bool, quic_client_convert_http_header_name_to_lowercase, true, - "If true, HTTP request header names sent from QuicSpdyClientBase(and " - "descendents) will be automatically converted to lower case.") - -QUICHE_FLAG(bool, quic_enable_http3_server_push, false, - "If true, server push will be allowed in QUIC versions that use HTTP/3.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_probe_bw_base_duration_ms, 2000, - "The default minimum duration for BBRv2-native probes, in milliseconds.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_probe_bw_max_rand_duration_ms, 1000, - "The default upper bound of the random amount of BBRv2-native " - "probes, in milliseconds.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_probe_rtt_period_ms, 10000, - "The default period for entering PROBE_RTT, in milliseconds.") - -QUICHE_FLAG(double, quic_bbr2_default_loss_threshold, 0.02, - "The default loss threshold for QUIC BBRv2, should be a value " - "between 0 and 1.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_startup_full_loss_count, 8, - "The default minimum number of loss marking events to exit STARTUP.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_probe_bw_full_loss_count, 2, - "The default minimum number of loss marking events to exit PROBE_UP phase.") - -QUICHE_FLAG(double, quic_bbr2_default_inflight_hi_headroom, 0.01, - "The default fraction of unutilized headroom to try to leave in path " - "upon high loss.") - -QUICHE_FLAG(int32_t, quic_bbr2_default_initial_ack_height_filter_window, 10, - "The default initial value of the max ack height filter's window length.") - -QUICHE_FLAG(double, quic_ack_aggregation_bandwidth_threshold, 1.0, - "If the bandwidth during ack aggregation is smaller than (estimated " - "bandwidth * this flag), consider the current aggregation completed " - "and starts a new one.") - -QUICHE_FLAG(int32_t, quic_anti_amplification_factor, 5, - "Anti-amplification factor. Before address validation, server will " - "send no more than factor times bytes received.") - -QUICHE_FLAG(int32_t, quic_max_buffered_crypto_bytes, 16 * 1024, - "The maximum amount of CRYPTO frame data that can be buffered.") - -QUICHE_FLAG(int32_t, quic_max_aggressive_retransmittable_on_wire_ping_count, 0, - "If set to non-zero, the maximum number of consecutive pings that " - "can be sent with aggressive initial retransmittable on wire timeout " - "if there is no new data received. After which, the timeout will be " - "exponentially back off until exceeds the default ping timeout.") - -QUICHE_FLAG(int32_t, quic_max_congestion_window, 2000, "The maximum congestion window in packets.") - -QUICHE_FLAG(int32_t, quic_max_streams_window_divisor, 2, - "The divisor that controls how often MAX_STREAMS frame is sent.") - -QUICHE_FLAG(bool, http2_reloadable_flag_http2_testonly_default_false, false, - "A testonly reloadable flag that will always default to false.") - -QUICHE_FLAG(bool, http2_restart_flag_http2_testonly_default_false, false, - "A testonly restart flag that will always default to false.") - -QUICHE_FLAG(bool, spdy_reloadable_flag_spdy_testonly_default_false, false, - "A testonly reloadable flag that will always default to false.") - -QUICHE_FLAG(bool, spdy_restart_flag_spdy_testonly_default_false, false, - "A testonly restart flag that will always default to false.") - -#endif diff --git a/source/extensions/quic_listeners/quiche/platform/http2_flags_impl.h b/source/extensions/quic_listeners/quiche/platform/http2_flags_impl.h index 7d2561469780..dc6fe5429bde 100644 --- a/source/extensions/quic_listeners/quiche/platform/http2_flags_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/http2_flags_impl.h @@ -8,10 +8,10 @@ #include "extensions/quic_listeners/quiche/platform/flags_impl.h" -#define GetHttp2ReloadableFlagImpl(flag) quiche::FLAGS_http2_reloadable_flag_##flag->value() +#define GetHttp2ReloadableFlagImpl(flag) quiche::FLAGS_quic_reloadable_flag_##flag->value() #define SetHttp2ReloadableFlagImpl(flag, value) \ - quiche::FLAGS_http2_reloadable_flag_##flag->SetValue(value) + quiche::FLAGS_quic_reloadable_flag_##flag->setValue(value) #define HTTP2_CODE_COUNT_N_IMPL(flag, instance, total) \ do { \ diff --git a/source/extensions/quic_listeners/quiche/platform/quic_aligned_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_aligned_impl.h deleted file mode 100644 index 3f595380b720..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/quic_aligned_impl.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "absl/base/optimization.h" - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -#define QUIC_ALIGN_OF_IMPL alignof -#ifdef _MSC_VER -#define QUIC_ALIGNED_IMPL(X) __declspec(align(X)) -#else -#define QUIC_ALIGNED_IMPL(X) __attribute__((aligned(X))) -#endif -#define QUIC_CACHELINE_ALIGNED_IMPL ABSL_CACHELINE_ALIGNED -#define QUIC_CACHELINE_SIZE_IMPL ABSL_CACHELINE_SIZE diff --git a/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.cc b/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.cc index 2a886a12caa1..27b977908d95 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.cc @@ -10,25 +10,7 @@ namespace quic { -// static -bool QuicCertUtilsImpl::ExtractSubjectNameFromDERCert(quiche::QuicheStringPiece cert, - quiche::QuicheStringPiece* subject_out) { - CBS tbs_certificate; - if (!SeekToSubject(cert, &tbs_certificate)) { - return false; - } - - CBS subject; - if (!CBS_get_asn1_element(&tbs_certificate, &subject, CBS_ASN1_SEQUENCE)) { - return false; - } - *subject_out = - absl::string_view(reinterpret_cast(CBS_data(&subject)), CBS_len(&subject)); - return true; -} - -// static -bool QuicCertUtilsImpl::SeekToSubject(quiche::QuicheStringPiece cert, CBS* tbs_certificate) { +bool seekToSubject(absl::string_view cert, CBS* tbs_certificate) { CBS der; CBS_init(&der, reinterpret_cast(cert.data()), cert.size()); CBS certificate; @@ -65,4 +47,22 @@ bool QuicCertUtilsImpl::SeekToSubject(quiche::QuicheStringPiece cert, CBS* tbs_c return true; } +// static +// NOLINTNEXTLINE(readability-identifier-naming) +bool QuicCertUtilsImpl::ExtractSubjectNameFromDERCert(absl::string_view cert, + absl::string_view* subject_out) { + CBS tbs_certificate; + if (!seekToSubject(cert, &tbs_certificate)) { + return false; + } + + CBS subject; + if (!CBS_get_asn1_element(&tbs_certificate, &subject, CBS_ASN1_SEQUENCE)) { + return false; + } + *subject_out = + absl::string_view(reinterpret_cast(CBS_data(&subject)), CBS_len(&subject)); + return true; +} + } // namespace quic diff --git a/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.h index 0c41b9dbcf21..29b882b7d75d 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_cert_utils_impl.h @@ -6,18 +6,15 @@ // consumed or referenced directly by other Envoy code. It serves purely as a // porting layer for QUICHE. +#include "absl/strings/string_view.h" #include "openssl/base.h" -#include "quiche/common/platform/api/quiche_string_piece.h" namespace quic { class QuicCertUtilsImpl { public: - static bool ExtractSubjectNameFromDERCert(quiche::QuicheStringPiece cert, - quiche::QuicheStringPiece* subject_out); - -private: - static bool SeekToSubject(quiche::QuicheStringPiece cert, CBS* tbs_certificate); + // NOLINTNEXTLINE(readability-identifier-naming) + static bool ExtractSubjectNameFromDERCert(absl::string_view cert, absl::string_view* subject_out); }; } // namespace quic diff --git a/source/extensions/quic_listeners/quiche/platform/quic_fallthrough_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_fallthrough_impl.h deleted file mode 100644 index aa9d6bc36a18..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/quic_fallthrough_impl.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -#include "absl/base/macros.h" - -#define QUIC_FALLTHROUGH_INTENDED_IMPL ABSL_FALLTHROUGH_INTENDED diff --git a/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.cc b/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.cc index 91d52c44abbc..b2e396fab4be 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.cc @@ -36,6 +36,7 @@ void depthFirstTraverseDirectory(const std::string& dirname, std::vector ReadFileContentsImpl(const std::string& dirname) { std::vector files; depthFirstTraverseDirectory(dirname, files); @@ -43,7 +44,8 @@ std::vector ReadFileContentsImpl(const std::string& dirname) { } // Reads the contents of |filename| as a string into |contents|. -void ReadFileContentsImpl(quiche::QuicheStringPiece filename, std::string* contents) { +// NOLINTNEXTLINE(readability-identifier-naming) +void ReadFileContentsImpl(absl::string_view filename, std::string* contents) { #ifdef WIN32 Envoy::Filesystem::InstanceImplWin32 fs; #else diff --git a/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.h index 654c1ad1826b..25c31e9deca2 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_file_utils_impl.h @@ -8,7 +8,7 @@ #include -#include "quiche/common/platform/api/quiche_string_piece.h" +#include "absl/strings/string_view.h" namespace quic { @@ -16,6 +16,7 @@ namespace quic { * Traverses the directory |dirname| and returns all of the files it contains. * @param dirname full path without trailing '/'. */ +// NOLINTNEXTLINE(readability-identifier-naming)` std::vector ReadFileContentsImpl(const std::string& dirname); /** @@ -23,6 +24,7 @@ std::vector ReadFileContentsImpl(const std::string& dirname); * @param filename the full path to the file. * @param contents output location of the file content. */ -void ReadFileContentsImpl(quiche::QuicheStringPiece filename, std::string* contents); +// NOLINTNEXTLINE(readability-identifier-naming) +void ReadFileContentsImpl(absl::string_view filename, std::string* contents); } // namespace quic diff --git a/source/extensions/quic_listeners/quiche/platform/quic_flags_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_flags_impl.h index 872495f2db8e..d562bb1a4813 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_flags_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_flags_impl.h @@ -15,16 +15,16 @@ #define GetQuicFlagImpl(flag) (quiche::flag)->value() // |flag| is the global flag variable, which is a pointer to TypedFlag. -#define SetQuicFlagImpl(flag, value) (quiche::flag)->SetValue(value) +#define SetQuicFlagImpl(flag, value) (quiche::flag)->setValue(value) #define GetQuicReloadableFlagImpl(flag) quiche::FLAGS_quic_reloadable_flag_##flag->value() #define SetQuicReloadableFlagImpl(flag, value) \ - quiche::FLAGS_quic_reloadable_flag_##flag->SetValue(value) + quiche::FLAGS_quic_reloadable_flag_##flag->setValue(value) #define GetQuicRestartFlagImpl(flag) quiche::FLAGS_quic_restart_flag_##flag->value() -#define SetQuicRestartFlagImpl(flag, value) quiche::FLAGS_quic_restart_flag_##flag->SetValue(value) +#define SetQuicRestartFlagImpl(flag, value) quiche::FLAGS_quic_restart_flag_##flag->setValue(value) // Not wired into command-line parsing. #define DEFINE_QUIC_COMMAND_LINE_FLAG_IMPL(type, flag, value, help) \ diff --git a/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.cc b/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.cc index bcbafb56639e..75849611d6a2 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.cc @@ -19,7 +19,8 @@ namespace quic { // static -bool QuicHostnameUtilsImpl::IsValidSNI(quiche::QuicheStringPiece sni) { +// NOLINTNEXTLINE(readability-identifier-naming) +bool QuicHostnameUtilsImpl::IsValidSNI(absl::string_view sni) { // TODO(wub): Implement it on top of GoogleUrl, once it is available. return sni.find_last_of('.') != std::string::npos && @@ -27,7 +28,8 @@ bool QuicHostnameUtilsImpl::IsValidSNI(quiche::QuicheStringPiece sni) { } // static -std::string QuicHostnameUtilsImpl::NormalizeHostname(quiche::QuicheStringPiece hostname) { +// NOLINTNEXTLINE(readability-identifier-naming) +std::string QuicHostnameUtilsImpl::NormalizeHostname(absl::string_view hostname) { // TODO(wub): Implement it on top of GoogleUrl, once it is available. std::string host = absl::AsciiStrToLower(hostname); diff --git a/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.h index 2b7ed43571b4..67cd787d03c2 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_hostname_utils_impl.h @@ -6,7 +6,7 @@ // consumed or referenced directly by other Envoy code. It serves purely as a // porting layer for QUICHE. -#include "quiche/common/platform/api/quiche_string_piece.h" +#include "absl/strings/string_view.h" #include "quiche/quic/platform/api/quic_export.h" namespace quic { @@ -18,7 +18,8 @@ class QUIC_EXPORT_PRIVATE QuicHostnameUtilsImpl { // (2) check that the hostname contains valid characters only; and // (3) contains at least one dot. // NOTE(wub): Only (3) is implemented for now. - static bool IsValidSNI(quiche::QuicheStringPiece sni); + // NOLINTNEXTLINE(readability-identifier-naming) + static bool IsValidSNI(absl::string_view sni); // Normalize a hostname: // (1) Canonicalize it, similar to what Chromium does in @@ -27,7 +28,8 @@ class QUIC_EXPORT_PRIVATE QuicHostnameUtilsImpl { // (3) Remove the trailing '.'. // WARNING: May mutate |hostname| in place. // NOTE(wub): Only (2) and (3) are implemented for now. - static std::string NormalizeHostname(quiche::QuicheStringPiece hostname); + // NOLINTNEXTLINE(readability-identifier-naming) + static std::string NormalizeHostname(absl::string_view hostname); private: QuicHostnameUtilsImpl() = delete; diff --git a/source/extensions/quic_listeners/quiche/platform/quic_macros_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_macros_impl.h deleted file mode 100644 index b8b70a0426b4..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/quic_macros_impl.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -#include "absl/base/attributes.h" - -#define QUIC_MUST_USE_RESULT_IMPL ABSL_MUST_USE_RESULT -#define QUIC_UNUSED_IMPL ABSL_ATTRIBUTE_UNUSED -#define QUIC_CONST_INIT_IMPL ABSL_CONST_INIT diff --git a/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.cc b/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.cc index c2eb527d6584..9e46c37df344 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.cc @@ -10,7 +10,8 @@ namespace quic { -quiche::QuicheStringPiece QuicMemSliceSpanImpl::GetData(size_t index) { +// NOLINTNEXTLINE(readability-identifier-naming) +absl::string_view QuicMemSliceSpanImpl::GetData(size_t index) { Envoy::Buffer::RawSliceVector slices = buffer_->getRawSlices(/*max_slices=*/index + 1); ASSERT(slices.size() > index); return {reinterpret_cast(slices[index].mem_), slices[index].len_}; diff --git a/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h index 1824fb8d1fa5..ef40e6387057 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_span_impl.h @@ -9,7 +9,7 @@ #include "envoy/buffer/buffer.h" #include "absl/container/fixed_array.h" -#include "quiche/common/platform/api/quiche_string_piece.h" +#include "absl/strings/string_view.h" #include "quiche/quic/core/quic_types.h" #include "quiche/quic/platform/api/quic_mem_slice.h" @@ -43,9 +43,13 @@ class QuicMemSliceSpanImpl { } // QuicMemSliceSpan - quiche::QuicheStringPiece GetData(size_t index); + // NOLINTNEXTLINE(readability-identifier-naming) + absl::string_view GetData(size_t index); + // NOLINTNEXTLINE(readability-identifier-naming) QuicByteCount total_length() { return buffer_->length(); }; + // NOLINTNEXTLINE(readability-identifier-naming) size_t NumSlices() { return buffer_->getRawSlices().size(); } + // NOLINTNEXTLINE(readability-identifier-naming) template QuicByteCount ConsumeAll(ConsumeFunction consume); bool empty() const { return buffer_->length() == 0; } @@ -54,6 +58,7 @@ class QuicMemSliceSpanImpl { }; template +// NOLINTNEXTLINE(readability-identifier-naming) QuicByteCount QuicMemSliceSpanImpl::ConsumeAll(ConsumeFunction consume) { size_t saved_length = 0; for (auto& slice : buffer_->getRawSlices()) { diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_ptr_util_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_testvalue_impl.h similarity index 52% rename from source/extensions/quic_listeners/quiche/platform/quiche_ptr_util_impl.h rename to source/extensions/quic_listeners/quiche/platform/quic_testvalue_impl.h index aaebe5d5c352..4b0201c35af6 100644 --- a/source/extensions/quic_listeners/quiche/platform/quiche_ptr_util_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_testvalue_impl.h @@ -6,12 +6,11 @@ // consumed or referenced directly by other Envoy code. It serves purely as a // porting layer for QUICHE. -#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" -namespace quiche { +namespace quic { -template std::unique_ptr QuicheWrapUniqueImpl(T* ptr) { - return absl::WrapUnique(ptr); -} +// NOLINTNEXTLINE(readability-identifier-naming) +template void AdjustTestValueImpl(absl::string_view /*label*/, T* /*var*/) {} -} // namespace quiche +} // namespace quic diff --git a/source/extensions/quic_listeners/quiche/platform/quic_udp_socket_platform_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_udp_socket_platform_impl.h index 248cfc193e02..1e88abe466cc 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_udp_socket_platform_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_udp_socket_platform_impl.h @@ -19,4 +19,7 @@ inline bool GetGooglePacketHeadersFromControlMessageImpl(struct ::cmsghdr* /*cms return false; } +// NOLINTNEXTLINE(readability-identifier-naming) +inline void SetGoogleSocketOptionsImpl(int /*fd*/) {} + } // namespace quic diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_arraysize_impl.h b/source/extensions/quic_listeners/quiche/platform/quiche_arraysize_impl.h deleted file mode 100644 index 7a23b53da85d..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/quiche_arraysize_impl.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "absl/base/macros.h" - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -#define QUICHE_ARRAYSIZE_IMPL(array) ABSL_ARRAYSIZE(array) diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_optional_impl.h b/source/extensions/quic_listeners/quiche/platform/quiche_optional_impl.h deleted file mode 100644 index f8b2b6c0800d..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/quiche_optional_impl.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "absl/types/optional.h" - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -namespace quiche { - -template using QuicheOptionalImpl = absl::optional; - -#define QUICHE_NULLOPT_IMPL absl::nullopt - -} // namespace quiche diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_text_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quiche_text_utils_impl.h index 3a6d1a393a8b..7b87c1cd61e8 100644 --- a/source/extensions/quic_listeners/quiche/platform/quiche_text_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quiche_text_utils_impl.h @@ -2,7 +2,6 @@ #include "common/common/base64.h" -#include "extensions/quic_listeners/quiche/platform/quiche_optional_impl.h" #include "extensions/quic_listeners/quiche/platform/quiche_string_piece_impl.h" #include "extensions/quic_listeners/quiche/platform/string_utils.h" @@ -13,6 +12,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_split.h" +#include "absl/types/optional.h" // NOLINT(namespace-envoy) @@ -25,58 +25,16 @@ namespace quiche { class QuicheTextUtilsImpl { public: // NOLINTNEXTLINE(readability-identifier-naming) - static bool StartsWith(QuicheStringPieceImpl data, QuicheStringPieceImpl prefix) { - return absl::StartsWith(data, prefix); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static bool EndsWith(QuicheStringPieceImpl data, QuicheStringPieceImpl suffix) { - return absl::EndsWith(data, suffix); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static bool EndsWithIgnoreCase(QuicheStringPieceImpl data, QuicheStringPieceImpl suffix) { - return absl::EndsWithIgnoreCase(data, suffix); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static std::string ToLower(QuicheStringPieceImpl data) { return absl::AsciiStrToLower(data); } + static std::string ToLower(absl::string_view data) { return absl::AsciiStrToLower(data); } // NOLINTNEXTLINE(readability-identifier-naming) - static void RemoveLeadingAndTrailingWhitespace(QuicheStringPieceImpl* data) { + static void RemoveLeadingAndTrailingWhitespace(absl::string_view* data) { *data = absl::StripAsciiWhitespace(*data); } - // NOLINTNEXTLINE(readability-identifier-naming) - static bool StringToUint64(QuicheStringPieceImpl in, uint64_t* out) { - return absl::SimpleAtoi(in, out); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static bool StringToInt(QuicheStringPieceImpl in, int* out) { return absl::SimpleAtoi(in, out); } - - // NOLINTNEXTLINE(readability-identifier-naming) - static bool StringToUint32(QuicheStringPieceImpl in, uint32_t* out) { - return absl::SimpleAtoi(in, out); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static bool StringToSizeT(QuicheStringPieceImpl in, size_t* out) { - return absl::SimpleAtoi(in, out); - } - - // NOLINTNEXTLINE(readability-identifier-naming) - static std::string Uint64ToString(uint64_t in) { return absl::StrCat(in); } - - // NOLINTNEXTLINE(readability-identifier-naming) - static std::string HexEncode(QuicheStringPieceImpl data) { return absl::BytesToHexString(data); } - // NOLINTNEXTLINE(readability-identifier-naming) static std::string Hex(uint32_t v) { return absl::StrCat(absl::Hex(v)); } - // NOLINTNEXTLINE(readability-identifier-naming) - static std::string HexDecode(QuicheStringPieceImpl data) { return absl::HexStringToBytes(data); } - // NOLINTNEXTLINE(readability-identifier-naming) static void Base64Encode(const uint8_t* data, size_t data_len, std::string* output) { *output = @@ -84,27 +42,28 @@ class QuicheTextUtilsImpl { } // NOLINTNEXTLINE(readability-identifier-naming) - static QuicheOptionalImpl Base64Decode(QuicheStringPieceImpl input) { + static absl::optional Base64Decode(absl::string_view input) { return Envoy::Base64::decodeWithoutPadding(input); } // NOLINTNEXTLINE(readability-identifier-naming) - static std::string HexDump(QuicheStringPieceImpl binary_data) { - return quiche::HexDump(binary_data); - } + static std::string Uint64ToString(uint64_t in) { return absl::StrCat(in); } + + // NOLINTNEXTLINE(readability-identifier-naming) + static std::string HexDump(absl::string_view binary_data) { return quiche::HexDump(binary_data); } // NOLINTNEXTLINE(readability-identifier-naming) - static bool ContainsUpperCase(QuicheStringPieceImpl data) { + static bool ContainsUpperCase(absl::string_view data) { return std::any_of(data.begin(), data.end(), absl::ascii_isupper); } // NOLINTNEXTLINE(readability-identifier-naming) - static bool IsAllDigits(QuicheStringPieceImpl data) { + static bool IsAllDigits(absl::string_view data) { return std::all_of(data.begin(), data.end(), absl::ascii_isdigit); } // NOLINTNEXTLINE(readability-identifier-naming) - static std::vector Split(QuicheStringPieceImpl data, char delim) { + static std::vector Split(absl::string_view data, char delim) { return absl::StrSplit(data, delim); } }; diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.cc b/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.cc index 3260eafee4da..5387e059876a 100644 --- a/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.cc +++ b/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.cc @@ -9,7 +9,7 @@ namespace quiche { namespace { -QuicheOptional quicheUtcDateTimeToUnixSecondsInner(int year, int month, int day, int hour, +absl::optional quicheUtcDateTimeToUnixSecondsInner(int year, int month, int day, int hour, int minute, int second) { const absl::CivilSecond civil_time(year, month, day, hour, minute, second); if (second != 60 && (civil_time.year() != year || civil_time.month() != month || @@ -24,7 +24,7 @@ QuicheOptional quicheUtcDateTimeToUnixSecondsInner(int year, int month, } // namespace // NOLINTNEXTLINE(readability-identifier-naming) -QuicheOptional QuicheUtcDateTimeToUnixSecondsImpl(int year, int month, int day, int hour, +absl::optional QuicheUtcDateTimeToUnixSecondsImpl(int year, int month, int day, int hour, int minute, int second) { // Handle leap seconds without letting any other irregularities happen. if (second == 60) { diff --git a/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.h index a1b70b70a51e..5e2ef79567f5 100644 --- a/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quiche_time_utils_impl.h @@ -10,12 +10,12 @@ #include "absl/time/civil_time.h" #include "absl/time/time.h" -#include "quiche/common/platform/api/quiche_optional.h" +#include "absl/types/optional.h" namespace quiche { // NOLINTNEXTLINE(readability-identifier-naming) -QuicheOptional QuicheUtcDateTimeToUnixSecondsImpl(int year, int month, int day, int hour, +absl::optional QuicheUtcDateTimeToUnixSecondsImpl(int year, int month, int day, int hour, int minute, int second); } // namespace quiche diff --git a/source/extensions/quic_listeners/quiche/platform/spdy_endianness_util_impl.h b/source/extensions/quic_listeners/quiche/platform/spdy_endianness_util_impl.h deleted file mode 100644 index 737b81ee2914..000000000000 --- a/source/extensions/quic_listeners/quiche/platform/spdy_endianness_util_impl.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -#include "envoy/common/platform.h" - -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -namespace spdy { - -inline uint16_t SpdyNetToHost16Impl(uint16_t x) { return ntohs(x); } - -inline uint32_t SpdyNetToHost32Impl(uint32_t x) { return ntohl(x); } - -// TODO: implement -inline uint64_t SpdyNetToHost64Impl(uint64_t /*x*/) { return 0; } - -inline uint16_t SpdyHostToNet16Impl(uint16_t x) { return htons(x); } - -inline uint32_t SpdyHostToNet32Impl(uint32_t x) { return htonl(x); } - -// TODO: implement -inline uint64_t SpdyHostToNet64Impl(uint64_t /*x*/) { return 0; } - -} // namespace spdy diff --git a/source/extensions/quic_listeners/quiche/platform/spdy_flags_impl.h b/source/extensions/quic_listeners/quiche/platform/spdy_flags_impl.h index a3cbd680ffc5..833562fab5b9 100644 --- a/source/extensions/quic_listeners/quiche/platform/spdy_flags_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/spdy_flags_impl.h @@ -8,9 +8,9 @@ #include "extensions/quic_listeners/quiche/platform/flags_impl.h" -#define GetSpdyReloadableFlagImpl(flag) quiche::FLAGS_spdy_reloadable_flag_##flag->value() +#define GetSpdyReloadableFlagImpl(flag) quiche::FLAGS_quic_reloadable_flag_##flag->value() -#define GetSpdyRestartFlagImpl(flag) quiche::FLAGS_spdy_restart_flag_##flag->value() +#define GetSpdyRestartFlagImpl(flag) quiche::FLAGS_quic_restart_flag_##flag->value() #define SPDY_CODE_COUNT_N_IMPL(flag, instance, total) \ do { \ diff --git a/source/extensions/quic_listeners/quiche/platform/spdy_string_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/spdy_string_utils_impl.h index 41fa3cad815f..4b01b2dbddb3 100644 --- a/source/extensions/quic_listeners/quiche/platform/spdy_string_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/spdy_string_utils_impl.h @@ -50,7 +50,7 @@ inline std::string SpdyHexEncodeUInt32AndTrimImpl(uint32_t data) { inline std::string SpdyHexDumpImpl(absl::string_view data) { return quiche::HexDump(data); } struct SpdyStringPieceCaseHashImpl { - size_t operator()(quiche::QuicheStringPiece data) const { + size_t operator()(absl::string_view data) const { std::string lower = absl::AsciiStrToLower(data); return absl::Hash()(lower); } diff --git a/source/extensions/quic_listeners/quiche/spdy_server_push_utils_for_envoy.cc b/source/extensions/quic_listeners/quiche/spdy_server_push_utils_for_envoy.cc index 3bd0bc2950b2..5ac5738c4bfe 100644 --- a/source/extensions/quic_listeners/quiche/spdy_server_push_utils_for_envoy.cc +++ b/source/extensions/quic_listeners/quiche/spdy_server_push_utils_for_envoy.cc @@ -12,25 +12,29 @@ using spdy::SpdyHeaderBlock; namespace quic { // static +// NOLINTNEXTLINE(readability-identifier-naming) std::string SpdyServerPushUtils::GetPromisedUrlFromHeaders(const SpdyHeaderBlock& /*headers*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } // static std::string +// NOLINTNEXTLINE(readability-identifier-naming) SpdyServerPushUtils::GetPromisedHostNameFromHeaders(const SpdyHeaderBlock& /*headers*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } // static +// NOLINTNEXTLINE(readability-identifier-naming) bool SpdyServerPushUtils::PromisedUrlIsValid(const SpdyHeaderBlock& /*headers*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } // static -std::string SpdyServerPushUtils::GetPushPromiseUrl(quiche::QuicheStringPiece /*scheme*/, - quiche::QuicheStringPiece /*authority*/, - quiche::QuicheStringPiece /*path*/) { +// NOLINTNEXTLINE(readability-identifier-naming) +std::string SpdyServerPushUtils::GetPushPromiseUrl(absl::string_view /*scheme*/, + absl::string_view /*authority*/, + absl::string_view /*path*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc index c95a61e5ace8..e03c6ab3ba64 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_session_test.cc @@ -49,9 +49,9 @@ class TestEnvoyQuicClientConnection : public EnvoyQuicClientConnection { Network::ConnectionSocketPtr&& connection_socket) : EnvoyQuicClientConnection(server_connection_id, helper, alarm_factory, &writer, false, supported_versions, dispatcher, std::move(connection_socket)) { - SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); SetEncrypter(quic::ENCRYPTION_FORWARD_SECURE, std::make_unique(quic::Perspective::IS_CLIENT)); + SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); } MOCK_METHOD(void, SendConnectionClosePacket, (quic::QuicErrorCode, const std::string&)); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index 7711b7c88bf0..f90d5c96b512 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -48,11 +48,11 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); EXPECT_CALL(quic_session_, ShouldYield(_)).WillRepeatedly(testing::Return(false)); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillRepeatedly(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillRepeatedly( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) .WillRepeatedly(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, const quic::QuicSocketAddress&, quic::PerPacketOptions*) { @@ -147,7 +147,7 @@ TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { std::unique_ptr data_buffer; quic::QuicByteCount data_frame_header_length = quic::HttpEncoder::SerializeDataFrameHeader(response_body_.length(), &data_buffer); - quiche::QuicheStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + absl::string_view data_frame_header(data_buffer.get(), data_frame_header_length); data = absl::StrCat(data_frame_header, response_body_); } quic::QuicStreamFrame frame(stream_id_, false, 0, data); @@ -186,7 +186,7 @@ TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { std::unique_ptr data_buffer; quic::QuicByteCount data_frame_header_length = quic::HttpEncoder::SerializeDataFrameHeader(response_body_.length(), &data_buffer); - quiche::QuicheStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + absl::string_view data_frame_header(data_buffer.get(), data_frame_header_length); data = absl::StrCat(data_frame_header, response_body_); } quic::QuicStreamFrame frame(stream_id_, false, 0, data); @@ -286,11 +286,11 @@ TEST_P(EnvoyQuicClientStreamTest, HeadersContributeToWatermarkIquic) { // Make the stream blocked by congestion control. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t /*write_length*/, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{0u, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t /*write_length*/, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{0u, state != quic::NO_FIN}; + })); const auto result = quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/false); EXPECT_TRUE(result.ok()); @@ -305,11 +305,11 @@ TEST_P(EnvoyQuicClientStreamTest, HeadersContributeToWatermarkIquic) { // Unblock writing now, and this will write out 16kB data and cause stream to // be blocked by the flow control limit. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); EXPECT_CALL(stream_callbacks_, onBelowWriteBufferLowWatermark()); quic_session_.OnCanWrite(); EXPECT_TRUE(quic_stream_->IsFlowControlBlocked()); @@ -319,20 +319,20 @@ TEST_P(EnvoyQuicClientStreamTest, HeadersContributeToWatermarkIquic) { 32 * 1024); quic_stream_->OnWindowUpdateFrame(window_update1); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); quic_session_.OnCanWrite(); // No data should be buffered at this point. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{0u, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{0u, state != quic::NO_FIN}; + })); // Send more data. If watermark bytes counting were not cleared in previous // OnCanWrite, this write would have caused the stream to exceed its high watermark. std::string request1(16 * 1024 - 3, 'a'); diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index cbf66f511f50..8a493a8e8954 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -25,7 +25,7 @@ namespace Quic { class TestGetProofCallback : public quic::ProofSource::Callback { public: TestGetProofCallback(bool& called, bool should_succeed, const std::string& server_config, - quic::QuicTransportVersion& version, quiche::QuicheStringPiece chlo_hash, + quic::QuicTransportVersion& version, absl::string_view chlo_hash, Network::FilterChain& filter_chain) : called_(called), should_succeed_(should_succeed), server_config_(server_config), version_(version), chlo_hash_(chlo_hash), expected_filter_chain_(filter_chain) { @@ -100,7 +100,7 @@ class TestGetProofCallback : public quic::ProofSource::Callback { bool should_succeed_; const std::string& server_config_; const quic::QuicTransportVersion& version_; - quiche::QuicheStringPiece chlo_hash_; + absl::string_view chlo_hash_; Network::FilterChain& expected_filter_chain_; NiceMock store_; Event::GlobalTimeSystem time_system_; @@ -178,7 +178,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { quic::QuicSocketAddress server_address_; quic::QuicSocketAddress client_address_; quic::QuicTransportVersion version_{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash_{"aaaaa"}; + absl::string_view chlo_hash_{"aaaaa"}; std::string server_config_{"Server Config"}; std::string expected_certs_{quic::test::kTestCertificateChainPem}; std::string pkey_{quic::test::kTestCertificatePrivateKeyPem}; diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc index 4a1dfe144dd3..9cdc169cd6f5 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_verifier_test.cc @@ -163,7 +163,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureEmptyCertChain) { std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf_cert_); quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + absl::string_view chlo_hash{"aaaaa"}; std::string server_config{"Server Config"}; const std::string ocsp_response; const std::string cert_sct; @@ -181,7 +181,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidLeafCert) { std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf_cert_); quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + absl::string_view chlo_hash{"aaaaa"}; std::string server_config{"Server Config"}; const std::string ocsp_response; const std::string cert_sct; @@ -197,7 +197,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidLeafCert) { TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureUnsupportedECKey) { configCertVerificationDetails(true); quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + absl::string_view chlo_hash{"aaaaa"}; std::string server_config{"Server Config"}; const std::string ocsp_response; const std::string cert_sct; @@ -236,7 +236,7 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifyProofFailureInvalidSignature) { std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf_cert_); quic::QuicTransportVersion version{quic::QUIC_VERSION_UNSUPPORTED}; - quiche::QuicheStringPiece chlo_hash{"aaaaa"}; + absl::string_view chlo_hash{"aaaaa"}; std::string server_config{"Server Config"}; const std::string ocsp_response; const std::string cert_sct; diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc index 05307c6b9b7c..4fc37685788a 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -61,6 +61,7 @@ class TestEnvoyQuicServerConnection : public EnvoyQuicServerConnection { const quic::ParsedQuicVersionVector& supported_versions, Network::Socket& listen_socket) : EnvoyQuicServerConnection(quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Any4(), 12345), quic::QuicSocketAddress(quic::QuicIpAddress::Loopback4(), 12345), helper, alarm_factory, &writer, /*owns_writer=*/false, supported_versions, listen_socket) {} @@ -201,10 +202,10 @@ class EnvoyQuicServerSessionTest : public testing::TestWithParam { crypto_stream_ = test_crypto_stream; } quic::test::QuicServerSessionBasePeer::SetCryptoStream(&envoy_quic_session_, crypto_stream); - quic_connection_->SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); quic_connection_->SetEncrypter( quic::ENCRYPTION_FORWARD_SECURE, std::make_unique(quic::Perspective::IS_SERVER)); + quic_connection_->SetDefaultEncryptionLevel(quic::ENCRYPTION_FORWARD_SECURE); } bool installReadFilter() { diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index 42ba39344f4b..cea6897c4e1e 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -51,6 +51,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { POOL_GAUGE(listener_config_.listenerScope()), POOL_HISTOGRAM(listener_config_.listenerScope()))}), quic_connection_(quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 123), quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), connection_helper_, alarm_factory_, &writer_, /*owns_writer=*/false, {quic_version_}, *listener_config_.socket_), @@ -66,11 +67,11 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic_session_.ActivateStream(std::unique_ptr(quic_stream_)); EXPECT_CALL(quic_session_, ShouldYield(_)).WillRepeatedly(testing::Return(false)); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillRepeatedly(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillRepeatedly( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) .WillRepeatedly(Invoke([](const char*, size_t buf_len, const quic::QuicIpAddress&, const quic::QuicSocketAddress&, quic::PerPacketOptions*) { @@ -110,7 +111,7 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { std::unique_ptr data_buffer; quic::QuicByteCount data_frame_header_length = quic::HttpEncoder::SerializeDataFrameHeader(body.length(), &data_buffer); - quiche::QuicheStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + absl::string_view data_frame_header(data_buffer.get(), data_frame_header_length); data = absl::StrCat(data_frame_header, body); } return data; @@ -397,11 +398,11 @@ TEST_P(EnvoyQuicServerStreamTest, HeadersContributeToWatermarkIquic) { // Make the stream blocked by congestion control. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t /*write_length*/, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{0u, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t /*write_length*/, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{0u, state != quic::NO_FIN}; + })); quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); // Encode 16kB -10 bytes request body. Because the high watermark is 16KB, with previously @@ -415,11 +416,11 @@ TEST_P(EnvoyQuicServerStreamTest, HeadersContributeToWatermarkIquic) { // Unblock writing now, and this will write out 16kB data and cause stream to // be blocked by the flow control limit. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); EXPECT_CALL(stream_callbacks_, onBelowWriteBufferLowWatermark()); quic_session_.OnCanWrite(); EXPECT_TRUE(quic_stream_->IsFlowControlBlocked()); @@ -429,20 +430,20 @@ TEST_P(EnvoyQuicServerStreamTest, HeadersContributeToWatermarkIquic) { 32 * 1024); quic_stream_->OnWindowUpdateFrame(window_update1); EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillOnce(Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; - })); + .WillOnce( + Invoke([](quic::QuicStreamId, size_t write_length, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{write_length, state != quic::NO_FIN}; + })); quic_session_.OnCanWrite(); // No data should be buffered at this point. EXPECT_CALL(quic_session_, WritevData(_, _, _, _, _, _)) - .WillRepeatedly(Invoke([](quic::QuicStreamId, size_t, quic::QuicStreamOffset, - quic::StreamSendingState state, bool, - quiche::QuicheOptional) { - return quic::QuicConsumedData{0u, state != quic::NO_FIN}; - })); + .WillRepeatedly( + Invoke([](quic::QuicStreamId, size_t, quic::QuicStreamOffset, + quic::StreamSendingState state, bool, absl::optional) { + return quic::QuicConsumedData{0u, state != quic::NO_FIN}; + })); // Send more data. If watermark bytes counting were not cleared in previous // OnCanWrite, this write would have caused the stream to exceed its high watermark. std::string response1(16 * 1024 - 3, 'a'); diff --git a/test/extensions/quic_listeners/quiche/platform/BUILD b/test/extensions/quic_listeners/quiche/platform/BUILD index 420e812b85a7..7dbb08d821cc 100644 --- a/test/extensions/quic_listeners/quiche/platform/BUILD +++ b/test/extensions/quic_listeners/quiche/platform/BUILD @@ -9,16 +9,6 @@ licenses(["notice"]) # Apache 2 envoy_package() -envoy_cc_test( - name = "quiche_platform_test", - srcs = ["quiche_platform_test.cc"], - external_deps = ["quiche_common_platform"], - deps = [ - "@com_googlesource_quiche//:quiche_common_platform", - "@com_googlesource_quiche//:quiche_common_platform_endian", - ], -) - envoy_cc_test( name = "http2_platform_test", srcs = ["http2_platform_test.cc"], @@ -63,7 +53,6 @@ envoy_cc_test( "@com_googlesource_quiche//:quic_platform_mem_slice_span", "@com_googlesource_quiche//:quic_platform_mem_slice_storage", "@com_googlesource_quiche//:quic_platform_mock_log", - "@com_googlesource_quiche//:quic_platform_port_utils", "@com_googlesource_quiche//:quic_platform_sleep", "@com_googlesource_quiche//:quic_platform_system_event_loop", "@com_googlesource_quiche//:quic_platform_test", @@ -150,17 +139,6 @@ envoy_cc_test_library( deps = ["@com_googlesource_quiche//:quic_platform_base"], ) -envoy_cc_test_library( - name = "quic_platform_port_utils_impl_lib", - srcs = ["quic_port_utils_impl.cc"], - hdrs = ["quic_port_utils_impl.h"], - tags = ["nofips"], - deps = [ - "//source/common/network:utility_lib", - "//test/test_common:environment_lib", - ], -) - envoy_cc_test_library( name = "quic_platform_test_mem_slice_vector_impl_lib", hdrs = ["quic_test_mem_slice_vector_impl.h"], diff --git a/test/extensions/quic_listeners/quiche/platform/http2_platform_test.cc b/test/extensions/quic_listeners/quiche/platform/http2_platform_test.cc index 069a79eab0ef..35aee5d27373 100644 --- a/test/extensions/quic_listeners/quiche/platform/http2_platform_test.cc +++ b/test/extensions/quic_listeners/quiche/platform/http2_platform_test.cc @@ -72,20 +72,14 @@ TEST(Http2PlatformTest, Http2Log) { HTTP2_DLOG_EVERY_N(ERROR, 2) << "DLOG_EVERY_N(ERROR, 2)"; } -TEST(Http2PlatformTest, Http2StringPiece) { - std::string s = "bar"; - quiche::QuicheStringPiece sp(s); - EXPECT_EQ('b', sp[0]); -} - TEST(Http2PlatformTest, Http2Macro) { EXPECT_DEBUG_DEATH(HTTP2_UNREACHABLE(), ""); EXPECT_DEATH(HTTP2_DIE_IF_NULL(nullptr), ""); } TEST(Http2PlatformTest, Http2Flags) { - auto& flag_registry = quiche::FlagRegistry::GetInstance(); - flag_registry.ResetFlags(); + auto& flag_registry = quiche::FlagRegistry::getInstance(); + flag_registry.resetFlags(); EXPECT_FALSE(GetHttp2ReloadableFlag(http2_testonly_default_false)); SetHttp2ReloadableFlag(http2_testonly_default_false, true); EXPECT_TRUE(GetHttp2ReloadableFlag(http2_testonly_default_false)); @@ -93,22 +87,22 @@ TEST(Http2PlatformTest, Http2Flags) { for (std::string s : {"1", "t", "true", "TRUE", "y", "yes", "Yes"}) { SetHttp2ReloadableFlag(http2_testonly_default_false, false); EXPECT_FALSE(GetHttp2ReloadableFlag(http2_testonly_default_false)); - EXPECT_TRUE(flag_registry.FindFlag("http2_reloadable_flag_http2_testonly_default_false") - ->SetValueFromString(s)); + EXPECT_TRUE(flag_registry.findFlag("FLAGS_quic_reloadable_flag_http2_testonly_default_false") + ->setValueFromString(s)); EXPECT_TRUE(GetHttp2ReloadableFlag(http2_testonly_default_false)); } for (std::string s : {"0", "f", "false", "FALSE", "n", "no", "No"}) { SetHttp2ReloadableFlag(http2_testonly_default_false, true); EXPECT_TRUE(GetHttp2ReloadableFlag(http2_testonly_default_false)); - EXPECT_TRUE(flag_registry.FindFlag("http2_reloadable_flag_http2_testonly_default_false") - ->SetValueFromString(s)); + EXPECT_TRUE(flag_registry.findFlag("FLAGS_quic_reloadable_flag_http2_testonly_default_false") + ->setValueFromString(s)); EXPECT_FALSE(GetHttp2ReloadableFlag(http2_testonly_default_false)); } for (std::string s : {"some", "invalid", "values", ""}) { SetHttp2ReloadableFlag(http2_testonly_default_false, false); EXPECT_FALSE(GetHttp2ReloadableFlag(http2_testonly_default_false)); - EXPECT_FALSE(flag_registry.FindFlag("http2_reloadable_flag_http2_testonly_default_false") - ->SetValueFromString(s)); + EXPECT_FALSE(flag_registry.findFlag("FLAGS_quic_reloadable_flag_http2_testonly_default_false") + ->setValueFromString(s)); EXPECT_FALSE(GetHttp2ReloadableFlag(http2_testonly_default_false)); } } diff --git a/test/extensions/quic_listeners/quiche/platform/quic_platform_test.cc b/test/extensions/quic_listeners/quiche/platform/quic_platform_test.cc index 68141aa94039..902ad1a9ea0f 100644 --- a/test/extensions/quic_listeners/quiche/platform/quic_platform_test.cc +++ b/test/extensions/quic_listeners/quiche/platform/quic_platform_test.cc @@ -30,7 +30,6 @@ #include "gtest/gtest.h" #include "quiche/common/platform/api/quiche_string_piece.h" #include "quiche/epoll_server/fake_simple_epoll_server.h" -#include "quiche/quic/platform/api/quic_aligned.h" #include "quiche/quic/platform/api/quic_bug_tracker.h" #include "quiche/quic/platform/api/quic_cert_utils.h" #include "quiche/quic/platform/api/quic_client_stats.h" @@ -42,7 +41,6 @@ #include "quiche/quic/platform/api/quic_flags.h" #include "quiche/quic/platform/api/quic_hostname_utils.h" #include "quiche/quic/platform/api/quic_logging.h" -#include "quiche/quic/platform/api/quic_macros.h" #include "quiche/quic/platform/api/quic_map_util.h" #include "quiche/quic/platform/api/quic_mem_slice.h" #include "quiche/quic/platform/api/quic_mem_slice_span.h" @@ -50,7 +48,6 @@ #include "quiche/quic/platform/api/quic_mock_log.h" #include "quiche/quic/platform/api/quic_mutex.h" #include "quiche/quic/platform/api/quic_pcc_sender.h" -#include "quiche/quic/platform/api/quic_port_utils.h" #include "quiche/quic/platform/api/quic_ptr_util.h" #include "quiche/quic/platform/api/quic_server_stats.h" #include "quiche/quic/platform/api/quic_sleep.h" @@ -92,8 +89,6 @@ class QuicPlatformTest : public testing::Test { const int verbosity_log_threshold_; }; -TEST_F(QuicPlatformTest, QuicAlignOf) { EXPECT_LT(0, QUIC_ALIGN_OF(int)); } - enum class TestEnum { ZERO = 0, ONE, TWO, COUNT }; TEST_F(QuicPlatformTest, QuicBugTracker) { @@ -468,9 +463,9 @@ TEST_F(QuicPlatformTest, QuicCertUtils) { unsigned char* der = nullptr; int len = i2d_X509(x509_cert.get(), &der); ASSERT_GT(len, 0); - quiche::QuicheStringPiece out; + absl::string_view out; QuicCertUtils::ExtractSubjectNameFromDERCert( - quiche::QuicheStringPiece(reinterpret_cast(der), len), &out); + absl::string_view(reinterpret_cast(der), len), &out); EXPECT_EQ("0z1\v0\t\x6\x3U\x4\x6\x13\x2US1\x13" "0\x11\x6\x3U\x4\b\f\nCalifornia1\x16" "0\x14\x6\x3U\x4\a\f\rSan Francisco1\r" @@ -566,8 +561,8 @@ TEST_F(QuicPlatformTest, MonotonicityWithFakeEpollClock) { } TEST_F(QuicPlatformTest, QuicFlags) { - auto& flag_registry = quiche::FlagRegistry::GetInstance(); - flag_registry.ResetFlags(); + auto& flag_registry = quiche::FlagRegistry::getInstance(); + flag_registry.resetFlags(); EXPECT_FALSE(GetQuicReloadableFlag(quic_testonly_default_false)); EXPECT_TRUE(GetQuicReloadableFlag(quic_testonly_default_true)); @@ -583,14 +578,15 @@ TEST_F(QuicPlatformTest, QuicFlags) { SetQuicFlag(FLAGS_quic_time_wait_list_seconds, 100); EXPECT_EQ(100, GetQuicFlag(FLAGS_quic_time_wait_list_seconds)); - flag_registry.ResetFlags(); + flag_registry.resetFlags(); EXPECT_FALSE(GetQuicReloadableFlag(quic_testonly_default_false)); EXPECT_TRUE(GetQuicRestartFlag(quic_testonly_default_true)); EXPECT_EQ(200, GetQuicFlag(FLAGS_quic_time_wait_list_seconds)); - flag_registry.FindFlag("quic_reloadable_flag_quic_testonly_default_false") - ->SetValueFromString("true"); - flag_registry.FindFlag("quic_restart_flag_quic_testonly_default_true")->SetValueFromString("0"); - flag_registry.FindFlag("quic_time_wait_list_seconds")->SetValueFromString("100"); + flag_registry.findFlag("FLAGS_quic_reloadable_flag_quic_testonly_default_false") + ->setValueFromString("true"); + flag_registry.findFlag("FLAGS_quic_restart_flag_quic_testonly_default_true") + ->setValueFromString("0"); + flag_registry.findFlag("FLAGS_quic_time_wait_list_seconds")->setValueFromString("100"); EXPECT_TRUE(GetQuicReloadableFlag(quic_testonly_default_false)); EXPECT_FALSE(GetQuicRestartFlag(quic_testonly_default_true)); EXPECT_EQ(100, GetQuicFlag(FLAGS_quic_time_wait_list_seconds)); @@ -661,35 +657,6 @@ TEST_F(FileUtilsTest, ReadFileContents) { EXPECT_EQ(data, output); } -TEST_F(QuicPlatformTest, PickUnsedPort) { - int port = QuicPickServerPortForTestsOrDie(); - std::vector supported_versions = - Envoy::TestEnvironment::getIpVersionsForTest(); - for (auto ip_version : supported_versions) { - Envoy::Network::Address::InstanceConstSharedPtr addr = - Envoy::Network::Test::getCanonicalLoopbackAddress(ip_version); - Envoy::Network::Address::InstanceConstSharedPtr addr_with_port = - Envoy::Network::Utility::getAddressWithPort(*addr, port); - Envoy::Network::SocketImpl sock(Envoy::Network::Socket::Type::Datagram, addr_with_port); - // binding of given port should success. - EXPECT_EQ(0, sock.bind(addr_with_port).rc_); - } -} - -TEST_F(QuicPlatformTest, FailToPickUnsedPort) { - Envoy::Api::MockOsSysCalls os_sys_calls; - Envoy::TestThreadsafeSingletonInjector os_calls(&os_sys_calls); - // Actually create sockets. - EXPECT_CALL(os_sys_calls, socket(_, _, _)).WillRepeatedly([](int domain, int type, int protocol) { - os_fd_t fd = ::socket(domain, type, protocol); - return Envoy::Api::SysCallSocketResult{fd, errno}; - }); - // Fail bind call's to mimic port exhaustion. - EXPECT_CALL(os_sys_calls, bind(_, _, _)) - .WillRepeatedly(Return(Envoy::Api::SysCallIntResult{-1, SOCKET_ERROR_ADDR_IN_USE})); - EXPECT_DEATH(QuicPickServerPortForTestsOrDie(), "Failed to pick a port for test."); -} - TEST_F(QuicPlatformTest, TestEnvoyQuicBufferAllocator) { QuicStreamBufferAllocator allocator; Envoy::Stats::TestUtil::MemoryTest memory_test; @@ -711,14 +678,6 @@ TEST_F(QuicPlatformTest, TestSystemEventLoop) { QuicSystemEventLoop("dummy"); } -QUIC_MUST_USE_RESULT bool dummyTestFunction() { return false; } - -TEST_F(QuicPlatformTest, TestQuicMacros) { - // Just make sure it compiles. - EXPECT_FALSE(dummyTestFunction()); - int a QUIC_UNUSED; -} - TEST(EnvoyQuicMemSliceTest, ConstructMemSliceFromBuffer) { std::string str(512, 'b'); // Fragment needs to out-live buffer. diff --git a/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.cc b/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.cc index 556f6cd3e18a..9eaf8532aa49 100644 --- a/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.cc +++ b/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.cc @@ -19,7 +19,7 @@ namespace quic { namespace { -void QuicRecordTestOutputToFile(const std::string& filename, quiche::QuicheStringPiece data) { +void quicRecordTestOutputToFile(const std::string& filename, absl::string_view data) { const char* output_dir_env = std::getenv("QUIC_TEST_OUTPUT_DIR"); if (output_dir_env == nullptr) { QUIC_LOG(WARNING) << "Could not save test output since QUIC_TEST_OUTPUT_DIR is not set"; @@ -64,11 +64,13 @@ void QuicRecordTestOutputToFile(const std::string& filename, quiche::QuicheStrin } } // namespace -void QuicSaveTestOutputImpl(quiche::QuicheStringPiece filename, quiche::QuicheStringPiece data) { - QuicRecordTestOutputToFile(filename.data(), data); +// NOLINTNEXTLINE(readability-identifier-naming) +void QuicSaveTestOutputImpl(absl::string_view filename, absl::string_view data) { + quicRecordTestOutputToFile(filename.data(), data); } -bool QuicLoadTestOutputImpl(quiche::QuicheStringPiece filename, std::string* data) { +// NOLINTNEXTLINE(readability-identifier-naming) +bool QuicLoadTestOutputImpl(absl::string_view filename, std::string* data) { const char* read_dir_env = std::getenv("QUIC_TEST_OUTPUT_DIR"); if (read_dir_env == nullptr) { QUIC_LOG(WARNING) << "Could not load test output since QUIC_TEST_OUTPUT_DIR is not set"; @@ -96,7 +98,8 @@ bool QuicLoadTestOutputImpl(quiche::QuicheStringPiece filename, std::string* dat return true; } -void QuicRecordTraceImpl(quiche::QuicheStringPiece identifier, quiche::QuicheStringPiece data) { +// NOLINTNEXTLINE(readability-identifier-naming) +void QuicRecordTraceImpl(absl::string_view identifier, absl::string_view data) { const testing::TestInfo* test_info = testing::UnitTest::GetInstance()->current_test_info(); std::string timestamp = absl::FormatTime("%Y%m%d%H%M%S", absl::Now(), absl::LocalTimeZone()); @@ -104,7 +107,7 @@ void QuicRecordTraceImpl(quiche::QuicheStringPiece identifier, quiche::QuicheStr std::string filename = fmt::sprintf("%s.%s.%s.%s.qtr", test_info->name(), test_info->test_case_name(), identifier.data(), timestamp); - QuicRecordTestOutputToFile(filename, data); + quicRecordTestOutputToFile(filename, data); } } // namespace quic diff --git a/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.h b/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.h index a1c6c7305d48..fcf0c47b3a75 100644 --- a/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.h +++ b/test/extensions/quic_listeners/quiche/platform/quic_test_output_impl.h @@ -6,14 +6,16 @@ // consumed or referenced directly by other Envoy code. It serves purely as a // porting layer for QUICHE. -#include "quiche/common/platform/api/quiche_string_piece.h" +#include "absl/strings/string_view.h" namespace quic { +// NOLINTNEXTLINE(readability-identifier-naming) +void QuicSaveTestOutputImpl(absl::string_view filename, absl::string_view data); -void QuicSaveTestOutputImpl(quiche::QuicheStringPiece filename, quiche::QuicheStringPiece data); +// NOLINTNEXTLINE(readability-identifier-naming) +bool QuicLoadTestOutputImpl(absl::string_view filename, std::string* data); -bool QuicLoadTestOutputImpl(quiche::QuicheStringPiece filename, std::string* data); - -void QuicRecordTraceImpl(quiche::QuicheStringPiece identifier, quiche::QuicheStringPiece data); +// NOLINTNEXTLINE(readability-identifier-naming) +void QuicRecordTraceImpl(absl::string_view identifier, absl::string_view data); } // namespace quic diff --git a/test/extensions/quic_listeners/quiche/platform/quiche_platform_test.cc b/test/extensions/quic_listeners/quiche/platform/quiche_platform_test.cc deleted file mode 100644 index a733894b5505..000000000000 --- a/test/extensions/quic_listeners/quiche/platform/quiche_platform_test.cc +++ /dev/null @@ -1,39 +0,0 @@ -// NOLINT(namespace-envoy) - -// This file is part of the QUICHE platform implementation, and is not to be -// consumed or referenced directly by other Envoy code. It serves purely as a -// porting layer for QUICHE. - -#include "gtest/gtest.h" -#include "quiche/common/platform/api/quiche_arraysize.h" -#include "quiche/common/platform/api/quiche_endian.h" -#include "quiche/common/platform/api/quiche_optional.h" -#include "quiche/common/platform/api/quiche_ptr_util.h" -#include "quiche/common/platform/api/quiche_string_piece.h" - -namespace quiche { - -TEST(QuichePlatformTest, Arraysize) { - int array[] = {0, 1, 2, 3, 4}; - EXPECT_EQ(5, QUICHE_ARRAYSIZE(array)); -} - -TEST(QuichePlatformTest, StringPiece) { - std::string s = "bar"; - QuicheStringPiece sp(s); - EXPECT_EQ('b', sp[0]); -} - -TEST(QuichePlatformTest, WrapUnique) { - auto p = QuicheWrapUnique(new int(6)); - EXPECT_EQ(6, *p); -} - -TEST(QuichePlatformTest, TestQuicheOptional) { - QuicheOptional maybe_a; - EXPECT_FALSE(maybe_a.has_value()); - maybe_a = 1; - EXPECT_EQ(1, *maybe_a); -} - -} // namespace quiche diff --git a/test/extensions/quic_listeners/quiche/platform/spdy_platform_test.cc b/test/extensions/quic_listeners/quiche/platform/spdy_platform_test.cc index 56453e232c1a..eeae58c0ab26 100644 --- a/test/extensions/quic_listeners/quiche/platform/spdy_platform_test.cc +++ b/test/extensions/quic_listeners/quiche/platform/spdy_platform_test.cc @@ -8,7 +8,6 @@ #include "gtest/gtest.h" #include "quiche/spdy/platform/api/spdy_bug_tracker.h" #include "quiche/spdy/platform/api/spdy_containers.h" -#include "quiche/spdy/platform/api/spdy_endianness_util.h" #include "quiche/spdy/platform/api/spdy_estimate_memory_usage.h" #include "quiche/spdy/platform/api/spdy_flags.h" #include "quiche/spdy/platform/api/spdy_logging.h" @@ -47,11 +46,6 @@ TEST(SpdyPlatformTest, SpdyHashSet) { EXPECT_EQ(0, hset.count("qux")); } -TEST(SpdyPlatformTest, SpdyEndianness) { - EXPECT_EQ(0x1234, spdy::SpdyNetToHost16(spdy::SpdyHostToNet16(0x1234))); - EXPECT_EQ(0x12345678, spdy::SpdyNetToHost32(spdy::SpdyHostToNet32(0x12345678))); -} - TEST(SpdyPlatformTest, SpdyEstimateMemoryUsage) { std::string s = "foo"; // Stubbed out to always return 0. @@ -92,19 +86,19 @@ TEST(SpdyPlatformTest, SpdyTestHelpers) { } TEST(SpdyPlatformTest, SpdyFlags) { - auto& flag_registry = quiche::FlagRegistry::GetInstance(); - flag_registry.ResetFlags(); + auto& flag_registry = quiche::FlagRegistry::getInstance(); + flag_registry.resetFlags(); EXPECT_FALSE(GetSpdyReloadableFlag(spdy_testonly_default_false)); EXPECT_FALSE(GetSpdyRestartFlag(spdy_testonly_default_false)); - flag_registry.FindFlag("spdy_reloadable_flag_spdy_testonly_default_false") - ->SetValueFromString("true"); + flag_registry.findFlag("FLAGS_quic_reloadable_flag_spdy_testonly_default_false") + ->setValueFromString("true"); EXPECT_TRUE(GetSpdyReloadableFlag(spdy_testonly_default_false)); EXPECT_FALSE(GetSpdyRestartFlag(spdy_testonly_default_false)); - flag_registry.ResetFlags(); - flag_registry.FindFlag("spdy_restart_flag_spdy_testonly_default_false") - ->SetValueFromString("yes"); + flag_registry.resetFlags(); + flag_registry.findFlag("FLAGS_quic_restart_flag_spdy_testonly_default_false") + ->setValueFromString("yes"); EXPECT_FALSE(GetSpdyReloadableFlag(spdy_testonly_default_false)); EXPECT_TRUE(GetSpdyRestartFlag(spdy_testonly_default_false)); } diff --git a/test/extensions/quic_listeners/quiche/test_proof_source.h b/test/extensions/quic_listeners/quiche/test_proof_source.h index a249b43144fd..bbedfd6c7b00 100644 --- a/test/extensions/quic_listeners/quiche/test_proof_source.h +++ b/test/extensions/quic_listeners/quiche/test_proof_source.h @@ -36,7 +36,7 @@ class TestProofSource : public EnvoyQuicProofSourceBase { void signPayload(const quic::QuicSocketAddress& /*server_address*/, const quic::QuicSocketAddress& /*client_address*/, const std::string& /*hostname*/, uint16_t /*signature_algorithm*/, - quiche::QuicheStringPiece in, + absl::string_view in, std::unique_ptr callback) override { callback->Run(true, absl::StrCat("Fake signature for { ", in, " }"), std::make_unique(filter_chain_)); diff --git a/test/extensions/quic_listeners/quiche/test_utils.h b/test/extensions/quic_listeners/quiche/test_utils.h index 102f7608e50b..7f0ea78e8766 100644 --- a/test/extensions/quic_listeners/quiche/test_utils.h +++ b/test/extensions/quic_listeners/quiche/test_utils.h @@ -46,7 +46,7 @@ class MockEnvoyQuicSession : public quic::QuicSpdySession, public QuicFilterMana MOCK_METHOD(quic::QuicConsumedData, WritevData, (quic::QuicStreamId id, size_t write_length, quic::QuicStreamOffset offset, quic::StreamSendingState state, quic::TransmissionType type, - quiche::QuicheOptional level)); + absl::optional level)); MOCK_METHOD(bool, ShouldYield, (quic::QuicStreamId id)); absl::string_view requestedServerName() const override { @@ -90,7 +90,7 @@ class MockEnvoyQuicClientSession : public quic::QuicSpdyClientSession, MOCK_METHOD(quic::QuicConsumedData, WritevData, (quic::QuicStreamId id, size_t write_length, quic::QuicStreamOffset offset, quic::StreamSendingState state, quic::TransmissionType type, - quiche::QuicheOptional level)); + absl::optional level)); MOCK_METHOD(bool, ShouldYield, (quic::QuicStreamId id)); absl::string_view requestedServerName() const override { From cd684e76bda80e140ab90573815f1990ec6f2a6f Mon Sep 17 00:00:00 2001 From: Wayne Zhang Date: Mon, 16 Nov 2020 12:45:15 -0800 Subject: [PATCH 104/117] jwt_authn: update to jwt_verify_lib with 1 minute clock skew (#13872) When verifying Jwt clock constraint, it is recommend to use some clock skew. grpc is using 1 minute clock [skew](https://github.com/grpc/grpc/blob/4645da201ae2c7d0b15fe56d86b41354fa4af0ca/src/core/lib/security/credentials/jwt/jwt_verifier.cc#L388-L389). [jwt_verify_lib](https://github.com/google/jwt_verify_lib/pull/57) has been updated to add 1 minute clock skew. In the old code, time constraint verification is done in jwt_authn filter, and jwt_verify_lib::verifyJwt() is doing time constraint verification again. Change jwt_verify_lib to split the time constraint verification to Jwt class so it can be called separately. And call verify() without the time checking. Risk Level: None Testing: unit-test is done in jwt_verify_lib repo Docs Changes: None Release Notes: Added Signed-off-by: Wayne Zhang --- .../filters/http/jwt_authn/v3/config.proto | 6 ++- .../http/jwt_authn/v4alpha/config.proto | 6 ++- bazel/repository_locations.bzl | 6 +-- docs/root/version_history/current.rst | 1 + .../filters/http/jwt_authn/v3/config.proto | 6 ++- .../http/jwt_authn/v4alpha/config.proto | 6 ++- .../filters/http/jwt_authn/authenticator.cc | 37 +++++++------------ .../http/jwt_authn/authenticator_test.cc | 17 +++++++++ 8 files changed, 55 insertions(+), 30 deletions(-) diff --git a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 0e4294608384..a10fa68f3043 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // cache_duration: // seconds: 300 // -// [#next-free-field: 10] +// [#next-free-field: 11] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.jwt_authn.v2alpha.JwtProvider"; @@ -191,6 +191,10 @@ message JwtProvider { // exp: 1501281058 // string payload_in_metadata = 9; + + // Specify the clock skew in seconds when verifying JWT time constraint, + // such as `exp`, and `nbf`. If not specified, default is 60 seconds. + uint32 clock_skew_seconds = 10; } // This message specifies how to fetch JWKS from remote and how to cache it. diff --git a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto index 53ee84fd65ea..2746640fa738 100644 --- a/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/api/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // cache_duration: // seconds: 300 // -// [#next-free-field: 10] +// [#next-free-field: 11] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtProvider"; @@ -191,6 +191,10 @@ message JwtProvider { // exp: 1501281058 // string payload_in_metadata = 9; + + // Specify the clock skew in seconds when verifying JWT time constraint, + // such as `exp`, and `nbf`. If not specified, default is 60 seconds. + uint32 clock_skew_seconds = 10; } // This message specifies how to fetch JWKS from remote and how to cache it. diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 211f52da0f5f..06e2eed0f5a7 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -408,13 +408,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "jwt_verify_lib", project_desc = "JWT verification library for C++", project_url = "https://github.com/google/jwt_verify_lib", - version = "7276a339af8426724b744216f619c99152f8c141", - sha256 = "f1fde4f3ebb3b2d841332c7a02a4b50e0529a19709934c63bc6208d1bbe28fb1", + version = "28efec2e4df1072db0ed03597591360ec9f80aac", + sha256 = "7a5c35b7cbf633398503ae12cad8c2833e92b3a796eed68b6256d22d51ace5e1", strip_prefix = "jwt_verify_lib-{version}", urls = ["https://github.com/google/jwt_verify_lib/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.filters.http.jwt_authn"], - release_date = "2020-07-10", + release_date = "2020-11-04", cpe = "N/A", ), com_github_nodejs_http_parser = dict( diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index f72b7f9842d0..e7d46f418bee 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -17,6 +17,7 @@ Minor Behavior Changes * ext_authz filter: disable `envoy.reloadable_features.ext_authz_measure_timeout_on_check_created` by default. * ext_authz filter: the deprecated field :ref:`use_alpha ` is no longer supported and cannot be set anymore. * grpc_web filter: if a `grpc-accept-encoding` header is present it's passed as-is to the upstream and if it isn't `grpc-accept-encoding:identity` is sent instead. The header was always overwriten with `grpc-accept-encoding:identity,deflate,gzip` before. +* jwt_authn filter: added support of Jwt time constraint verification with a clock skew (default to 60 seconds) and added a filter config field :ref:`clock_skew_seconds ` to configure it. * memory: enable new tcmalloc with restartable sequences for aarch64 builds. * tls: removed RSA key transport and SHA-1 cipher suites from the client-side defaults. * watchdog: the watchdog action :ref:`abort_action ` is now the default action to terminate the process if watchdog kill / multikill is enabled. diff --git a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto index 0e4294608384..a10fa68f3043 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v3/config.proto @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // cache_duration: // seconds: 300 // -// [#next-free-field: 10] +// [#next-free-field: 11] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.jwt_authn.v2alpha.JwtProvider"; @@ -191,6 +191,10 @@ message JwtProvider { // exp: 1501281058 // string payload_in_metadata = 9; + + // Specify the clock skew in seconds when verifying JWT time constraint, + // such as `exp`, and `nbf`. If not specified, default is 60 seconds. + uint32 clock_skew_seconds = 10; } // This message specifies how to fetch JWKS from remote and how to cache it. diff --git a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto index 53ee84fd65ea..2746640fa738 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/jwt_authn/v4alpha/config.proto @@ -51,7 +51,7 @@ option (udpa.annotations.file_status).package_version_status = NEXT_MAJOR_VERSIO // cache_duration: // seconds: 300 // -// [#next-free-field: 10] +// [#next-free-field: 11] message JwtProvider { option (udpa.annotations.versioning).previous_message_type = "envoy.extensions.filters.http.jwt_authn.v3.JwtProvider"; @@ -191,6 +191,10 @@ message JwtProvider { // exp: 1501281058 // string payload_in_metadata = 9; + + // Specify the clock skew in seconds when verifying JWT time constraint, + // such as `exp`, and `nbf`. If not specified, default is 60 seconds. + uint32 clock_skew_seconds = 10; } // This message specifies how to fetch JWKS from remote and how to cache it. diff --git a/source/extensions/filters/http/jwt_authn/authenticator.cc b/source/extensions/filters/http/jwt_authn/authenticator.cc index 1b73eeaf08b2..cf447f68bdaa 100644 --- a/source/extensions/filters/http/jwt_authn/authenticator.cc +++ b/source/extensions/filters/http/jwt_authn/authenticator.cc @@ -141,7 +141,7 @@ void AuthenticatorImpl::startVerify() { jwt_ = std::make_unique<::google::jwt_verify::Jwt>(); ENVOY_LOG(debug, "{}: Parse Jwt {}", name(), curr_token_->token()); - const Status status = jwt_->parseFromString(curr_token_->token()); + Status status = jwt_->parseFromString(curr_token_->token()); if (status != Status::Ok) { doneWithStatus(status); return; @@ -163,33 +163,23 @@ void AuthenticatorImpl::startVerify() { } } - // TODO(qiwzhang): Cross-platform-wise the below unix_timestamp code is wrong as the - // epoch is not guaranteed to be defined as the unix epoch. We should use - // the abseil time functionality instead or use the jwt_verify_lib to check - // the validity of a JWT. - // Check "exp" claim. - const uint64_t unix_timestamp = - std::chrono::duration_cast(timeSource().systemTime().time_since_epoch()) - .count(); - // If the nbf claim does *not* appear in the JWT, then the nbf field is defaulted - // to 0. - if (jwt_->nbf_ > unix_timestamp) { - doneWithStatus(Status::JwtNotYetValid); - return; - } - // If the exp claim does *not* appear in the JWT then the exp field is defaulted - // to 0. - if (jwt_->exp_ > 0 && jwt_->exp_ < unix_timestamp) { - doneWithStatus(Status::JwtExpired); - return; - } - // Check the issuer is configured or not. jwks_data_ = provider_ ? jwks_cache_.findByProvider(provider_.value()) : jwks_cache_.findByIssuer(jwt_->iss_); // isIssuerSpecified() check already make sure the issuer is in the cache. ASSERT(jwks_data_ != nullptr); + // Default is 60 seconds + uint64_t clock_skew_seconds = ::google::jwt_verify::kClockSkewInSecond; + if (jwks_data_->getJwtProvider().clock_skew_seconds() > 0) { + clock_skew_seconds = jwks_data_->getJwtProvider().clock_skew_seconds(); + } + status = jwt_->verifyTimeConstraint(absl::ToUnixSeconds(absl::Now()), clock_skew_seconds); + if (status != Status::Ok) { + doneWithStatus(status); + return; + } + // Check if audience is allowed bool is_allowed = check_audience_ ? check_audience_->areAudiencesAllowed(jwt_->audiences_) : jwks_data_->areAudiencesAllowed(jwt_->audiences_); @@ -247,7 +237,8 @@ void AuthenticatorImpl::onDestroy() { // Verify with a specific public key. void AuthenticatorImpl::verifyKey() { - const Status status = ::google::jwt_verify::verifyJwt(*jwt_, *jwks_data_->getJwksObj()); + const Status status = + ::google::jwt_verify::verifyJwtWithoutTimeChecking(*jwt_, *jwks_data_->getJwksObj()); if (status != Status::Ok) { doneWithStatus(status); return; diff --git a/test/extensions/filters/http/jwt_authn/authenticator_test.cc b/test/extensions/filters/http/jwt_authn/authenticator_test.cc index e9ceb23cbbbd..fbb732a63298 100644 --- a/test/extensions/filters/http/jwt_authn/authenticator_test.cc +++ b/test/extensions/filters/http/jwt_authn/authenticator_test.cc @@ -303,6 +303,23 @@ TEST_F(AuthenticatorTest, TestExpiredJWT) { expectVerifyStatus(Status::JwtExpired, headers); } +// This test verifies when a JWT is expired but with a big clock skew. +TEST_F(AuthenticatorTest, TestExpiredJWTWithABigClockSkew) { + auto& provider = (*proto_config_.mutable_providers())[std::string(ProviderName)]; + // Token is expired at 1205005587, but add clock skew at another 1205005587. + provider.set_clock_skew_seconds(1205005587); + createAuthenticator(); + + EXPECT_CALL(*raw_fetcher_, fetch(_, _, _)) + .WillOnce(Invoke([this](const envoy::config::core::v3::HttpUri&, Tracing::Span&, + JwksFetcher::JwksReceiver& receiver) { + receiver.onJwksSuccess(std::move(jwks_)); + })); + + Http::TestRequestHeaderMapImpl headers{{"Authorization", "Bearer " + std::string(ExpiredToken)}}; + expectVerifyStatus(Status::Ok, headers); +} + // This test verifies when a JWT is not yet valid, JwtNotYetValid status is returned. TEST_F(AuthenticatorTest, TestNotYetValidJWT) { EXPECT_CALL(*raw_fetcher_, fetch(_, _, _)).Times(0); From 5b1bdbed26c53a1921b1c4a064c7557c4ff01fbf Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Mon, 16 Nov 2020 23:02:02 +0100 Subject: [PATCH 105/117] [test host utils] use make_shared to avoid memory leaks (#14042) Commit Message: Switch to using std::make_shared in host util test code. Clang tidy is unhappy about current state of code and has been complaining about potential memory leaks, sample log here: https://dev.azure.com/cncf/envoy/_build/results?buildId=57303&view=logs&jobId=b7634614-24f3-5416-e791-4f3affaaed6c&j=b7634614-24f3-5416-e791-4f3affaaed6c&t=21e6aa7d-f369-5abd-5e4e-e888cac18e9c. Issue discovered in this PR. We may need to create dedicated issue to fix problem globally. Risk Level: Low Testing: Covered by existing tests Docs Changes: NA Release Notes: NA Signed-off-by: Kateryna Nezdolii --- test/common/upstream/utility.h | 40 +++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/test/common/upstream/utility.h b/test/common/upstream/utility.h index ef3dd210adf3..cd92eb9d37a3 100644 --- a/test/common/upstream/utility.h +++ b/test/common/upstream/utility.h @@ -79,48 +79,48 @@ inline envoy::config::cluster::v3::Cluster defaultStaticCluster(const std::strin inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& hostname, const std::string& url, uint32_t weight = 1) { - return HostSharedPtr{ - new HostImpl(cluster, hostname, Network::Utility::resolveUrl(url), nullptr, weight, - envoy::config::core::v3::Locality(), - envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, - envoy::config::core::v3::UNKNOWN)}; + return std::make_shared( + cluster, hostname, Network::Utility::resolveUrl(url), nullptr, weight, + envoy::config::core::v3::Locality(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN); } inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& url, uint32_t weight = 1, uint32_t priority = 0) { - return HostSharedPtr{ - new HostImpl(cluster, "", Network::Utility::resolveUrl(url), nullptr, weight, - envoy::config::core::v3::Locality(), - envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), - priority, envoy::config::core::v3::UNKNOWN)}; + return std::make_shared( + cluster, "", Network::Utility::resolveUrl(url), nullptr, weight, + envoy::config::core::v3::Locality(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), priority, + envoy::config::core::v3::UNKNOWN); } inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& url, const envoy::config::core::v3::Metadata& metadata, uint32_t weight = 1) { - return HostSharedPtr{ - new HostImpl(cluster, "", Network::Utility::resolveUrl(url), - std::make_shared(metadata), weight, - envoy::config::core::v3::Locality(), - envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, - envoy::config::core::v3::UNKNOWN)}; + return std::make_shared( + cluster, "", Network::Utility::resolveUrl(url), + std::make_shared(metadata), weight, + envoy::config::core::v3::Locality(), + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN); } inline HostSharedPtr makeTestHost(ClusterInfoConstSharedPtr cluster, const std::string& url, const envoy::config::endpoint::v3::Endpoint::HealthCheckConfig& health_check_config, uint32_t weight = 1) { - return HostSharedPtr{new HostImpl(cluster, "", Network::Utility::resolveUrl(url), nullptr, weight, + return std::make_shared(cluster, "", Network::Utility::resolveUrl(url), nullptr, weight, envoy::config::core::v3::Locality(), health_check_config, 0, - envoy::config::core::v3::UNKNOWN)}; + envoy::config::core::v3::UNKNOWN); } inline HostDescriptionConstSharedPtr makeTestHostDescription(ClusterInfoConstSharedPtr cluster, const std::string& url) { - return HostDescriptionConstSharedPtr{new HostDescriptionImpl( + return std::make_shared( cluster, "", Network::Utility::resolveUrl(url), nullptr, envoy::config::core::v3::Locality().default_instance(), - envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0)}; + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0); } inline HostsPerLocalitySharedPtr makeHostsPerLocality(std::vector&& locality_hosts, From e9ade46a66e9ea49a325d5384011638bf8d80f58 Mon Sep 17 00:00:00 2001 From: AngusDavis <945722+AngusDavis@users.noreply.github.com> Date: Mon, 16 Nov 2020 17:51:13 -0500 Subject: [PATCH 106/117] Build: Propagate user-supplied tags to external headers library. (#14016) This change applies user supplied tags to the _with_external_headers cc_library created by envoy_cc_library. This is useful for cases where builds are filtered or modified by tag (e.g., tags = manual, no-remmote, etc). Signed-off-by: Angus Davis --- bazel/envoy_library.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazel/envoy_library.bzl b/bazel/envoy_library.bzl index adcee0750790..a2f7c6b0ae02 100644 --- a/bazel/envoy_library.bzl +++ b/bazel/envoy_library.bzl @@ -144,7 +144,7 @@ def envoy_cc_library( hdrs = hdrs, copts = envoy_copts(repository) + copts, visibility = visibility, - tags = ["nocompdb"], + tags = ["nocompdb"] + tags, deps = [":" + name], strip_include_prefix = strip_include_prefix, ) From 0743c31c2f963d1039bf21deb9532a07cbe7c1d8 Mon Sep 17 00:00:00 2001 From: htuch Date: Mon, 16 Nov 2020 18:16:22 -0500 Subject: [PATCH 107/117] config: fix crash when type URL doesn't match proto. (#14031) Fixes #13681. Risk level: Low Testing: Unit and integration regression tests added. Signed-off-by: Harvey Tuch --- source/common/config/version_converter.cc | 20 ++++++++++++++------ test/common/config/version_converter_test.cc | 12 ++++++++++++ test/integration/ads_integration_test.cc | 17 +++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/source/common/config/version_converter.cc b/source/common/config/version_converter.cc index db2bd1cfc216..0477c29bcf86 100644 --- a/source/common/config/version_converter.cc +++ b/source/common/config/version_converter.cc @@ -78,20 +78,28 @@ void VersionConverter::annotateWithOriginalType(const Protobuf::Descriptor& prev void onMessage(Protobuf::Message& message, const void* ctxt) override { const Protobuf::Descriptor* descriptor = message.GetDescriptor(); const Protobuf::Reflection* reflection = message.GetReflection(); - const Protobuf::Descriptor& prev_descriptor = *static_cast(ctxt); + const Protobuf::Descriptor* prev_descriptor = static_cast(ctxt); + // If there is no previous descriptor for this message, we don't need to annotate anything. + if (prev_descriptor == nullptr) { + return; + } // If they are the same type, there's no possibility of any different type // further down, so we're done. - if (descriptor->full_name() == prev_descriptor.full_name()) { + if (descriptor->full_name() == prev_descriptor->full_name()) { return; } auto* unknown_field_set = reflection->MutableUnknownFields(&message); unknown_field_set->AddLengthDelimited(ProtobufWellKnown::OriginalTypeFieldNumber, - prev_descriptor.full_name()); + prev_descriptor->full_name()); } const void* onField(Protobuf::Message&, const Protobuf::FieldDescriptor& field, const void* ctxt) override { - const Protobuf::Descriptor& prev_descriptor = *static_cast(ctxt); + const Protobuf::Descriptor* prev_descriptor = static_cast(ctxt); + // If there is no previous descriptor for this field, we don't need to annotate anything. + if (prev_descriptor == nullptr) { + return nullptr; + } // TODO(htuch): This is a terrible hack, there should be no per-resource // business logic in this file. The reason this is required is that // endpoints, when captured in configuration such as inlined hosts in @@ -101,13 +109,13 @@ void VersionConverter::annotateWithOriginalType(const Protobuf::Descriptor& prev // In theory, we should be able to just clean up these annotations in // ClusterManagerImpl with type erasure, but protobuf doesn't free up memory // as expected, we probably need some arena level trick to address this. - if (prev_descriptor.full_name() == "envoy.api.v2.Cluster" && + if (prev_descriptor->full_name() == "envoy.api.v2.Cluster" && (field.name() == "hidden_envoy_deprecated_hosts" || field.name() == "load_assignment")) { // This will cause the sub-message visit to abort early. return field.message_type(); } const Protobuf::FieldDescriptor* prev_field = - prev_descriptor.FindFieldByNumber(field.number()); + prev_descriptor->FindFieldByNumber(field.number()); return prev_field != nullptr ? prev_field->message_type() : nullptr; } }; diff --git a/test/common/config/version_converter_test.cc b/test/common/config/version_converter_test.cc index 65bb66145fc7..1c7e949fb625 100644 --- a/test/common/config/version_converter_test.cc +++ b/test/common/config/version_converter_test.cc @@ -71,6 +71,18 @@ TEST(VersionConverterProto, UpgradeNextVersion) { VersionConverter::upgrade(source, dst); } +// Validate that even if we pass in a newer proto version that is being passed off as an older +// version (e.g. via a type URL mistake), we don't crash. This is a regression test for +// https://github.com/envoyproxy/envoy/issues/13681. +TEST(VersionConverterProto, UpgradeWithConfusedTypes) { + test::common::config::NextVersion source_next; + source_next.mutable_new_message_in_this_version(); + test::common::config::PreviousVersion source; + ASSERT_TRUE(source.ParseFromString(source_next.SerializeAsString())); + test::common::config::NextVersion dst; + VersionConverter::upgrade(source, dst); +} + // Bad UTF-8 can fail wire cast during upgrade. TEST(VersionConverterTest, UpgradeException) { API_NO_BOOST(envoy::api::v2::Cluster) source; diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index a9d96e44a8a2..24468b45f20f 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -1521,4 +1521,21 @@ TEST_P(AdsClusterV2Test, XdsBatching) { initialize(); } +// Regression test for https://github.com/envoyproxy/envoy/issues/13681. +TEST_P(AdsClusterV2Test, TypeUrlAnnotationRegression) { + initialize(); + const auto cds_type_url = Config::getTypeUrl( + envoy::config::core::v3::ApiVersion::V2); + + EXPECT_TRUE(compareDiscoveryRequest(cds_type_url, "", {}, {}, {}, true)); + auto cluster = buildCluster("cluster_0"); + auto* bias = cluster.mutable_least_request_lb_config()->mutable_active_request_bias(); + bias->set_default_value(1.1); + bias->set_runtime_key("foo"); + sendDiscoveryResponse(cds_type_url, {cluster}, {cluster}, {}, + "1", true); + + test_server_->waitForCounterGe("cluster_manager.cds.update_rejected", 1); +} + } // namespace Envoy From 81b25eb7c988bc64b9278721d7eb8f4ea745b7df Mon Sep 17 00:00:00 2001 From: Taylor Barrella Date: Mon, 16 Nov 2020 16:04:35 -0800 Subject: [PATCH 108/117] ci: fix CodeQL-build by removing deprecated set-env command (#14046) Signed-off-by: Taylor Barrella --- .github/workflows/codeql-push.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index d6110bbddca2..fbe091a90ec0 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -24,7 +24,9 @@ jobs: - name: Get build targets run: | . .github/workflows/get_build_targets.sh - echo ::set-env name=BUILD_TARGETS::$(echo $BUILD_TARGETS_LOCAL) + echo 'BUILD_TARGETS<> $GITHUB_ENV + echo $BUILD_TARGETS_LOCAL >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV # If this run was triggered by a pull request event, then checkout # the head of the pull request instead of the merge commit. - run: git checkout HEAD^2 From 39e43e1abb45c96993f2df3323df12bc94937910 Mon Sep 17 00:00:00 2001 From: Ruslan Nigmatullin Date: Mon, 16 Nov 2020 16:58:58 -0800 Subject: [PATCH 109/117] grpc-json-transcoder: Add support for configuring unescaping behavior (#14009) Functionality is implemented in grpc-ecosystem/grpc-httpjson-transcoding#45, add configuration options and wire them into filter. The reasoning is provided in grpc-ecosystem/grpc-httpjson-transcoding#44. Risk Level: Low Testing: added unit test Docs Changes: added proto docs Release Notes: updated Signed-off-by: Ruslan Nigmatullin --- .../grpc_json_transcoder/v3/transcoder.proto | 25 ++++++- bazel/repository_locations.bzl | 6 +- docs/root/version_history/current.rst | 1 + .../grpc_json_transcoder/v3/transcoder.proto | 25 ++++++- .../json_transcoder_filter.cc | 18 +++++ .../json_transcoder_filter_test.cc | 72 +++++++++++++++++++ test/proto/bookstore.proto | 5 ++ 7 files changed, 147 insertions(+), 5 deletions(-) diff --git a/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto b/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto index 3082089202ee..007ccabc3e47 100644 --- a/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto +++ b/api/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto @@ -15,11 +15,27 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // gRPC-JSON transcoder :ref:`configuration overview `. // [#extension: envoy.filters.http.grpc_json_transcoder] -// [#next-free-field: 10] +// [#next-free-field: 11] message GrpcJsonTranscoder { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder"; + enum UrlUnescapeSpec { + // URL path parameters will not decode RFC 6570 reserved characters. + // For example, segment `%2f%23/%20%2523` is unescaped to `%2f%23/ %23`. + ALL_CHARACTERS_EXCEPT_RESERVED = 0; + + // URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // For example, segment `%2f%23/%20%2523` is unescaped to `%2f#/ %23`. + ALL_CHARACTERS_EXCEPT_SLASH = 1; + + // URL path parameters will be fully URI-decoded. + // For example, segment `%2f%23/%20%2523` is unescaped to `/#/ %23`. + ALL_CHARACTERS = 2; + } + message PrintOptions { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder.PrintOptions"; @@ -160,4 +176,11 @@ message GrpcJsonTranscoder { // the ``google/rpc/error_details.proto`` should be included in the configured // :ref:`proto descriptor set `. bool convert_grpc_status = 9; + + // URL unescaping policy. + // This spec is only applied when extracting variable with multiple segments. + // For example, in case of `/foo/{x=*}/bar/{y=prefix/*}/{z=**}` `x` variable is single segment and `y` and `z` are multiple segments. + // For a path with `/foo/first/bar/prefix/second/third/fourth`, `x=first`, `y=prefix/second`, `z=third/fourth`. + // If this setting is not specified, the value defaults to :ref:`ALL_CHARACTERS_EXCEPT_RESERVED`. + UrlUnescapeSpec url_unescape_spec = 10 [(validate.rules).enum = {defined_only: true}]; } diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 06e2eed0f5a7..643e31c693a1 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -507,13 +507,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "grpc-httpjson-transcoding", project_desc = "Library that supports transcoding so that HTTP/JSON can be converted to gRPC", project_url = "https://github.com/grpc-ecosystem/grpc-httpjson-transcoding", - version = "b48d8aa15b3825e146168146755475ab918e95b7", - sha256 = "4147e992ec239fb78c435fdd9f68e8d93d89106f67278bf2995f3672dddba52b", + version = "4d095f048889d4fc3b8d4579aa80ca4290319802", + sha256 = "7af66e0674340932683ab4f04ea6f03e2550849a54741738d94310b84d396a2c", strip_prefix = "grpc-httpjson-transcoding-{version}", urls = ["https://github.com/grpc-ecosystem/grpc-httpjson-transcoding/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], extensions = ["envoy.filters.http.grpc_json_transcoder"], - release_date = "2020-11-05", + release_date = "2020-11-12", cpe = "N/A", ), io_bazel_rules_go = dict( diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index e7d46f418bee..bed554bd8f03 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -51,6 +51,7 @@ New Features ------------ * config: added new runtime feature `envoy.features.enable_all_deprecated_features` that allows the use of all deprecated features. * grpc: implemented header value syntax support when defining :ref:`initial metadata ` for gRPC-based `ext_authz` :ref:`HTTP ` and :ref:`network ` filters, and :ref:`ratelimit ` filters. +* grpc-json: added support for configuring :ref:`unescaping behavior ` for path components. * hds: added support for delta updates in the :ref:`HealthCheckSpecifier `, making only the Endpoints and Health Checkers that changed be reconstructed on receiving a new message, rather than the entire HDS. * health_check: added option to use :ref:`no_traffic_healthy_interval ` which allows a different no traffic interval when the host is healthy. * http: added HCM :ref:`timeout config field ` to control how long a downstream has to finish sending headers before the stream is cancelled. diff --git a/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto b/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto index 3082089202ee..007ccabc3e47 100644 --- a/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto +++ b/generated_api_shadow/envoy/extensions/filters/http/grpc_json_transcoder/v3/transcoder.proto @@ -15,11 +15,27 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // gRPC-JSON transcoder :ref:`configuration overview `. // [#extension: envoy.filters.http.grpc_json_transcoder] -// [#next-free-field: 10] +// [#next-free-field: 11] message GrpcJsonTranscoder { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder"; + enum UrlUnescapeSpec { + // URL path parameters will not decode RFC 6570 reserved characters. + // For example, segment `%2f%23/%20%2523` is unescaped to `%2f%23/ %23`. + ALL_CHARACTERS_EXCEPT_RESERVED = 0; + + // URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // For example, segment `%2f%23/%20%2523` is unescaped to `%2f#/ %23`. + ALL_CHARACTERS_EXCEPT_SLASH = 1; + + // URL path parameters will be fully URI-decoded. + // For example, segment `%2f%23/%20%2523` is unescaped to `/#/ %23`. + ALL_CHARACTERS = 2; + } + message PrintOptions { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.transcoder.v2.GrpcJsonTranscoder.PrintOptions"; @@ -160,4 +176,11 @@ message GrpcJsonTranscoder { // the ``google/rpc/error_details.proto`` should be included in the configured // :ref:`proto descriptor set `. bool convert_grpc_status = 9; + + // URL unescaping policy. + // This spec is only applied when extracting variable with multiple segments. + // For example, in case of `/foo/{x=*}/bar/{y=prefix/*}/{z=**}` `x` variable is single segment and `y` and `z` are multiple segments. + // For a path with `/foo/first/bar/prefix/second/third/fourth`, `x=first`, `y=prefix/second`, `z=third/fourth`. + // If this setting is not specified, the value defaults to :ref:`ALL_CHARACTERS_EXCEPT_RESERVED`. + UrlUnescapeSpec url_unescape_spec = 10 [(validate.rules).enum = {defined_only: true}]; } diff --git a/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc b/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc index e2998a3f5866..d770b9feb51a 100644 --- a/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc +++ b/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc @@ -188,6 +188,24 @@ JsonTranscoderConfig::JsonTranscoderConfig( } } + switch (proto_config.url_unescape_spec()) { + case envoy::extensions::filters::http::grpc_json_transcoder::v3::GrpcJsonTranscoder:: + ALL_CHARACTERS_EXCEPT_RESERVED: + pmb.SetUrlUnescapeSpec( + google::grpc::transcoding::UrlUnescapeSpec::kAllCharactersExceptReserved); + break; + case envoy::extensions::filters::http::grpc_json_transcoder::v3::GrpcJsonTranscoder:: + ALL_CHARACTERS_EXCEPT_SLASH: + pmb.SetUrlUnescapeSpec(google::grpc::transcoding::UrlUnescapeSpec::kAllCharactersExceptSlash); + break; + case envoy::extensions::filters::http::grpc_json_transcoder::v3::GrpcJsonTranscoder:: + ALL_CHARACTERS: + pmb.SetUrlUnescapeSpec(google::grpc::transcoding::UrlUnescapeSpec::kAllCharacters); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + path_matcher_ = pmb.Build(); const auto& print_config = proto_config.print_options(); diff --git a/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc b/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc index 1ec37be541b4..8082a846db89 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc @@ -1270,6 +1270,78 @@ INSTANTIATE_TEST_SUITE_P( })", R"({"id":"101","gender":"MALE","last_name":"Shakespeare"})"})); +struct GrpcJsonTranscoderFilterUnescapeTestParam { + std::string config_json_; + std::string expected_arg_; +}; + +class GrpcJsonTranscoderFilterUnescapeTest + : public testing::TestWithParam, + public GrpcJsonTranscoderFilterTestBase { +protected: + GrpcJsonTranscoderFilterUnescapeTest() { + envoy::extensions::filters::http::grpc_json_transcoder::v3::GrpcJsonTranscoder proto_config; + TestUtility::loadFromJson(TestEnvironment::substitute(GetParam().config_json_), proto_config); + config_ = std::make_unique(proto_config, *api_); + filter_ = std::make_unique(*config_); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + filter_->setEncoderFilterCallbacks(encoder_callbacks_); + } + + std::unique_ptr config_; + std::unique_ptr filter_; + NiceMock decoder_callbacks_; + NiceMock encoder_callbacks_; +}; + +TEST_P(GrpcJsonTranscoderFilterUnescapeTest, UnescapeSpec) { + Http::TestRequestHeaderMapImpl request_headers{ + {"content-type", "text/plain"}, {":method", "POST"}, {":path", "/wildcard/%2f%23/%20%2523"}}; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + + Buffer::OwnedImpl request_data{"{}"}; + EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(request_data, true)); + + Grpc::Decoder decoder; + std::vector frames; + decoder.decode(request_data, frames); + + EXPECT_EQ(1, frames.size()); + + bookstore::EchoBodyRequest expected_request; + expected_request.set_arg(GetParam().expected_arg_); + + bookstore::EchoBodyRequest request; + request.ParseFromString(frames[0].data_->toString()); + + EXPECT_EQ(expected_request.ByteSize(), frames[0].length_); + EXPECT_TRUE(MessageDifferencer::Equals(expected_request, request)); +} + +INSTANTIATE_TEST_SUITE_P(GrpcJsonTranscoderFilterUnescapeOptions, + GrpcJsonTranscoderFilterUnescapeTest, + ::testing::Values( + GrpcJsonTranscoderFilterUnescapeTestParam{ + R"({ + "proto_descriptor": "{{ test_rundir }}/test/proto/bookstore.descriptor", + "services": ["bookstore.Bookstore"] + })", + "%2f%23/ %23"}, + GrpcJsonTranscoderFilterUnescapeTestParam{ + R"({ + "proto_descriptor": "{{ test_rundir }}/test/proto/bookstore.descriptor", + "services": ["bookstore.Bookstore"], + "url_unescape_spec": "ALL_CHARACTERS_EXCEPT_SLASH" + })", + "%2f#/ %23"}, + GrpcJsonTranscoderFilterUnescapeTestParam{ + R"({ + "proto_descriptor": "{{ test_rundir }}/test/proto/bookstore.descriptor", + "services": ["bookstore.Bookstore"], + "url_unescape_spec": "ALL_CHARACTERS" + })", + "/#/ %23"})); + } // namespace } // namespace GrpcJsonTranscoder } // namespace HttpFilters diff --git a/test/proto/bookstore.proto b/test/proto/bookstore.proto index 62e697e219ee..559ec4da0ace 100644 --- a/test/proto/bookstore.proto +++ b/test/proto/bookstore.proto @@ -134,6 +134,11 @@ service Bookstore { get: "/bigbook" }; } + rpc PostWildcard(EchoBodyRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/wildcard/{arg=**}" + }; + } } service ServiceWithResponseBody { From 77e13728a69c1e4a210f4acfef8362a536c3b314 Mon Sep 17 00:00:00 2001 From: Lizan Zhou Date: Mon, 16 Nov 2020 17:57:00 -0800 Subject: [PATCH 110/117] wasm: use static registration for runtimes (#14014) Partially addresses #12574. Refactored test instantiate to removes many ifdefs. Commit Message: Additional Description: Risk Level: Low Testing: CI Docs Changes: Release Notes: Platform Specific Features: Signed-off-by: Lizan Zhou --- CODEOWNERS | 2 + api/envoy/extensions/wasm/v3/wasm.proto | 24 ++++++- bazel/repository_locations.bzl | 41 ++++++++++++ .../envoy/extensions/wasm/v3/wasm.proto | 24 ++++++- source/extensions/common/wasm/BUILD | 11 +++ .../common/wasm/wasm_runtime_factory.h | 26 +++++++ source/extensions/common/wasm/wasm_vm.cc | 45 +++---------- source/extensions/extensions_build_config.bzl | 9 +++ source/extensions/wasm_runtime/null/BUILD | 21 ++++++ source/extensions/wasm_runtime/null/config.cc | 25 +++++++ source/extensions/wasm_runtime/v8/BUILD | 20 ++++++ source/extensions/wasm_runtime/v8/config.cc | 27 ++++++++ source/extensions/wasm_runtime/wasmtime/BUILD | 20 ++++++ .../wasm_runtime/wasmtime/config.cc | 27 ++++++++ source/extensions/wasm_runtime/wavm/BUILD | 20 ++++++ source/extensions/wasm_runtime/wavm/config.cc | 27 ++++++++ test/extensions/access_loggers/wasm/BUILD | 1 + .../access_loggers/wasm/config_test.cc | 17 +---- test/extensions/bootstrap/wasm/BUILD | 3 + test/extensions/bootstrap/wasm/config_test.cc | 17 +---- test/extensions/bootstrap/wasm/wasm_test.cc | 67 +++---------------- test/extensions/common/wasm/BUILD | 16 +++++ test/extensions/common/wasm/wasm_runtime.cc | 41 ++++++++++++ test/extensions/common/wasm/wasm_runtime.h | 26 +++++++ test/extensions/common/wasm/wasm_test.cc | 19 ++---- test/extensions/filters/http/wasm/BUILD | 2 + .../filters/http/wasm/config_test.cc | 27 ++------ .../filters/http/wasm/wasm_filter_test.cc | 24 ++----- test/extensions/filters/network/wasm/BUILD | 2 + .../filters/network/wasm/config_test.cc | 17 +---- .../filters/network/wasm/wasm_filter_test.cc | 17 +---- test/extensions/stats_sinks/wasm/BUILD | 2 + .../stats_sinks/wasm/config_test.cc | 17 +---- .../stats_sinks/wasm/wasm_stat_sink_test.cc | 17 +---- test/per_file_coverage.sh | 3 + 35 files changed, 469 insertions(+), 235 deletions(-) create mode 100644 source/extensions/common/wasm/wasm_runtime_factory.h create mode 100644 source/extensions/wasm_runtime/null/BUILD create mode 100644 source/extensions/wasm_runtime/null/config.cc create mode 100644 source/extensions/wasm_runtime/v8/BUILD create mode 100644 source/extensions/wasm_runtime/v8/config.cc create mode 100644 source/extensions/wasm_runtime/wasmtime/BUILD create mode 100644 source/extensions/wasm_runtime/wasmtime/config.cc create mode 100644 source/extensions/wasm_runtime/wavm/BUILD create mode 100644 source/extensions/wasm_runtime/wavm/config.cc create mode 100644 test/extensions/common/wasm/wasm_runtime.cc create mode 100644 test/extensions/common/wasm/wasm_runtime.h diff --git a/CODEOWNERS b/CODEOWNERS index ab8af77e3401..c72b2900ec38 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -92,6 +92,8 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/filters/network/wasm @PiotrSikora @lizan # webassembly common extension /*/extensions/common/wasm @PiotrSikora @lizan +# webassembly runtimes +/*/extensions/wasm_runtime/ @PiotrSikora @lizan # common matcher /*/extensions/common/matcher @mattklein123 @yangminzhu # common crypto extension diff --git a/api/envoy/extensions/wasm/v3/wasm.proto b/api/envoy/extensions/wasm/v3/wasm.proto index b42fb75a0bf7..c6affb810611 100644 --- a/api/envoy/extensions/wasm/v3/wasm.proto +++ b/api/envoy/extensions/wasm/v3/wasm.proto @@ -28,7 +28,29 @@ message VmConfig { // See ref: "TODO: add ref" for details. string vm_id = 1; - // The Wasm runtime type (either "v8" or "null" for code compiled into Envoy). + // The Wasm runtime type. + // Available Wasm runtime types are registered as extensions. The following runtimes are included + // in Envoy code base: + // + // .. _extension_envoy.wasm.runtime.null: + // + // **envoy.wasm.runtime.null**: Null sandbox, the Wasm module must be compiled and linked into the + // Envoy binary. The registered name is given in the *code* field as *inline_string*. + // + // .. _extension_envoy.wasm.runtime.v8: + // + // **envoy.wasm.runtime.v8**: `V8 `_-based WebAssembly runtime. + // + // .. _extension_envoy.wasm.runtime.wavm: + // + // **envoy.wasm.runtime.wavm**: `WAVM `_-based WebAssembly runtime. + // This runtime is not enabled in the official build. + // + // .. _extension_envoy.wasm.runtime.wasmtime: + // + // **envoy.wasm.runtime.wasmtime**: `Wasmtime `_-based WebAssembly runtime. + // This runtime is not enabled in the official build. + // string runtime = 2 [(validate.rules).string = {min_len: 1}]; // The Wasm code that Envoy will execute. diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 643e31c693a1..1db2cb55e5ab 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -593,6 +593,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], cpe = "cpe:2.3:a:llvm:*:*", ), @@ -612,6 +615,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], cpe = "cpe:2.3:a:webassembly_virtual_machine_project:webassembly_virtual_machine:*", ), @@ -631,6 +637,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], cpe = "N/A", ), @@ -652,6 +661,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], cpe = "N/A", ), @@ -703,6 +715,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], release_date = "2020-10-27", cpe = "cpe:2.3:a:google:v8:*", @@ -750,6 +765,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.network.rbac", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.null", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], release_date = "2020-10-25", cpe = "N/A", @@ -769,6 +788,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.null", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], release_date = "2020-04-02", cpe = "N/A", @@ -862,6 +885,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.null", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], release_date = "2020-10-22", cpe = "N/A", @@ -881,6 +908,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.null", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], release_date = "2020-11-12", cpe = "N/A", @@ -911,6 +942,10 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.null", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], release_date = "2020-10-21", cpe = "N/A", @@ -931,6 +966,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], release_date = "2019-06-21", cpe = "N/A", @@ -950,6 +988,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", + "envoy.wasm.runtime.v8", + "envoy.wasm.runtime.wavm", + "envoy.wasm.runtime.wasmtime", ], release_date = "2018-12-18", cpe = "N/A", diff --git a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto index b42fb75a0bf7..c6affb810611 100644 --- a/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto +++ b/generated_api_shadow/envoy/extensions/wasm/v3/wasm.proto @@ -28,7 +28,29 @@ message VmConfig { // See ref: "TODO: add ref" for details. string vm_id = 1; - // The Wasm runtime type (either "v8" or "null" for code compiled into Envoy). + // The Wasm runtime type. + // Available Wasm runtime types are registered as extensions. The following runtimes are included + // in Envoy code base: + // + // .. _extension_envoy.wasm.runtime.null: + // + // **envoy.wasm.runtime.null**: Null sandbox, the Wasm module must be compiled and linked into the + // Envoy binary. The registered name is given in the *code* field as *inline_string*. + // + // .. _extension_envoy.wasm.runtime.v8: + // + // **envoy.wasm.runtime.v8**: `V8 `_-based WebAssembly runtime. + // + // .. _extension_envoy.wasm.runtime.wavm: + // + // **envoy.wasm.runtime.wavm**: `WAVM `_-based WebAssembly runtime. + // This runtime is not enabled in the official build. + // + // .. _extension_envoy.wasm.runtime.wasmtime: + // + // **envoy.wasm.runtime.wasmtime**: `Wasmtime `_-based WebAssembly runtime. + // This runtime is not enabled in the official build. + // string runtime = 2 [(validate.rules).string = {min_len: 1}]; // The Wasm code that Envoy will execute. diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index e2a03e72fc0f..2cbdfdc06908 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -16,6 +16,16 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "wasm_runtime_factory_interface", + hdrs = [ + "wasm_runtime_factory.h", + ], + deps = [ + ":wasm_hdr", + ], +) + # NB: Used to break the circular dependency between wasm_lib and null_plugin_lib. envoy_cc_library( name = "wasm_hdr", @@ -86,6 +96,7 @@ envoy_cc_library( deps = [ ":wasm_hdr", ":wasm_interoperation_lib", + ":wasm_runtime_factory_interface", "//external:abseil_base", "//external:abseil_node_hash_map", "//include/envoy/server:lifecycle_notifier_interface", diff --git a/source/extensions/common/wasm/wasm_runtime_factory.h b/source/extensions/common/wasm/wasm_runtime_factory.h new file mode 100644 index 000000000000..de00551b4443 --- /dev/null +++ b/source/extensions/common/wasm/wasm_runtime_factory.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "extensions/common/wasm/wasm_vm.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class WasmRuntimeFactory { +public: + virtual ~WasmRuntimeFactory() = default; + virtual WasmVmPtr createWasmVm() PURE; + + virtual absl::string_view name() PURE; + virtual absl::string_view shortName() PURE; + + std::string category() { return "envoy.wasm.runtime"; } +}; + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/wasm/wasm_vm.cc b/source/extensions/common/wasm/wasm_vm.cc index 5b482e6bb847..b7b8ec0c6734 100644 --- a/source/extensions/common/wasm/wasm_vm.cc +++ b/source/extensions/common/wasm/wasm_vm.cc @@ -6,21 +6,11 @@ #include "extensions/common/wasm/context.h" #include "extensions/common/wasm/ext/envoy_null_vm_wasm_api.h" #include "extensions/common/wasm/wasm_extension.h" +#include "extensions/common/wasm/wasm_runtime_factory.h" #include "extensions/common/wasm/well_known_names.h" -#include "include/proxy-wasm/null.h" #include "include/proxy-wasm/null_plugin.h" -#if defined(ENVOY_WASM_V8) -#include "include/proxy-wasm/v8.h" -#endif -#if defined(ENVOY_WASM_WAVM) -#include "include/proxy-wasm/wavm.h" -#endif -#if defined(ENVOY_WASM_WASMTIME) -#include "include/proxy-wasm/wasmtime.h" -#endif - using ContextBase = proxy_wasm::ContextBase; using Word = proxy_wasm::Word; @@ -70,36 +60,21 @@ WasmVmPtr createWasmVm(absl::string_view runtime, const Stats::ScopeSharedPtr& s ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::wasm), warn, "Failed to create Wasm VM with unspecified runtime"); return nullptr; - } else if (runtime == WasmRuntimeNames::get().Null) { - auto wasm = proxy_wasm::createNullVm(); - wasm->integration() = getWasmExtension()->createEnvoyWasmVmIntegration(scope, runtime, "null"); - return wasm; -#if defined(ENVOY_WASM_V8) - } else if (runtime == WasmRuntimeNames::get().V8) { - auto wasm = proxy_wasm::createV8Vm(); - wasm->integration() = getWasmExtension()->createEnvoyWasmVmIntegration(scope, runtime, "v8"); - return wasm; -#endif -#if defined(ENVOY_WASM_WAVM) - } else if (runtime == WasmRuntimeNames::get().Wavm) { - auto wasm = proxy_wasm::createWavmVm(); - wasm->integration() = getWasmExtension()->createEnvoyWasmVmIntegration(scope, runtime, "wavm"); - return wasm; -#endif -#if defined(ENVOY_WASM_WASMTIME) - } else if (runtime == WasmRuntimeNames::get().Wasmtime) { - auto wasm = proxy_wasm::createWasmtimeVm(); - wasm->integration() = - getWasmExtension()->createEnvoyWasmVmIntegration(scope, runtime, "wasmtime"); - return wasm; -#endif - } else { + } + + auto runtime_factory = Registry::FactoryRegistry::getFactory(runtime); + if (runtime_factory == nullptr) { ENVOY_LOG_TO_LOGGER( Envoy::Logger::Registry::getLog(Envoy::Logger::Id::wasm), warn, "Failed to create Wasm VM using {} runtime. Envoy was compiled without support for it", runtime); return nullptr; } + + auto wasm = runtime_factory->createWasmVm(); + wasm->integration() = getWasmExtension()->createEnvoyWasmVmIntegration( + scope, runtime_factory->name(), runtime_factory->shortName()); + return wasm; } } // namespace Wasm diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 664b561fb0d2..8a48fb678264 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -214,8 +214,17 @@ EXTENSIONS = { # # Watchdog actions # + "envoy.watchdog.profile_action": "//source/extensions/watchdog/profile_action:config", + # + # WebAssembly runtimes + # + + "envoy.wasm.runtime.null": "//source/extensions/wasm_runtime/null:config", + "envoy.wasm.runtime.v8": "//source/extensions/wasm_runtime/v8:config", + "envoy.wasm.runtime.wavm": "//source/extensions/wasm_runtime/wavm:config", + "envoy.wasm.runtime.wasmtime": "//source/extensions/wasm_runtime/wasmtime:config", } # These can be changed to ["//visibility:public"], for downstream builds which diff --git a/source/extensions/wasm_runtime/null/BUILD b/source/extensions/wasm_runtime/null/BUILD new file mode 100644 index 000000000000..fce183d76032 --- /dev/null +++ b/source/extensions/wasm_runtime/null/BUILD @@ -0,0 +1,21 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + security_posture = "unknown", + status = "alpha", + deps = [ + "//include/envoy/registry", + "//source/extensions/common/wasm:wasm_hdr", + "//source/extensions/common/wasm:wasm_runtime_factory_interface", + ], +) diff --git a/source/extensions/wasm_runtime/null/config.cc b/source/extensions/wasm_runtime/null/config.cc new file mode 100644 index 000000000000..3515c9462ce1 --- /dev/null +++ b/source/extensions/wasm_runtime/null/config.cc @@ -0,0 +1,25 @@ +#include "envoy/registry/registry.h" + +#include "extensions/common/wasm/wasm_runtime_factory.h" + +#include "include/proxy-wasm/null.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class NullRuntimeFactory : public WasmRuntimeFactory { +public: + WasmVmPtr createWasmVm() override { return proxy_wasm::createNullVm(); } + + absl::string_view name() override { return "envoy.wasm.runtime.null"; } + absl::string_view shortName() override { return "null"; } +}; + +REGISTER_FACTORY(NullRuntimeFactory, WasmRuntimeFactory); + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/wasm_runtime/v8/BUILD b/source/extensions/wasm_runtime/v8/BUILD new file mode 100644 index 000000000000..55d14af61a57 --- /dev/null +++ b/source/extensions/wasm_runtime/v8/BUILD @@ -0,0 +1,20 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + security_posture = "unknown", + status = "alpha", + deps = [ + "//include/envoy/registry", + "//source/extensions/common/wasm:wasm_lib", + ], +) diff --git a/source/extensions/wasm_runtime/v8/config.cc b/source/extensions/wasm_runtime/v8/config.cc new file mode 100644 index 000000000000..1061b17b2b9d --- /dev/null +++ b/source/extensions/wasm_runtime/v8/config.cc @@ -0,0 +1,27 @@ +#include "envoy/registry/registry.h" + +#include "extensions/common/wasm/wasm_runtime_factory.h" + +#include "include/proxy-wasm/v8.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class V8RuntimeFactory : public WasmRuntimeFactory { +public: + WasmVmPtr createWasmVm() override { return proxy_wasm::createV8Vm(); } + + absl::string_view name() override { return "envoy.wasm.runtime.v8"; } + absl::string_view shortName() override { return "v8"; } +}; + +#if defined(ENVOY_WASM_V8) +REGISTER_FACTORY(V8RuntimeFactory, WasmRuntimeFactory); +#endif + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/wasm_runtime/wasmtime/BUILD b/source/extensions/wasm_runtime/wasmtime/BUILD new file mode 100644 index 000000000000..55d14af61a57 --- /dev/null +++ b/source/extensions/wasm_runtime/wasmtime/BUILD @@ -0,0 +1,20 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + security_posture = "unknown", + status = "alpha", + deps = [ + "//include/envoy/registry", + "//source/extensions/common/wasm:wasm_lib", + ], +) diff --git a/source/extensions/wasm_runtime/wasmtime/config.cc b/source/extensions/wasm_runtime/wasmtime/config.cc new file mode 100644 index 000000000000..a407d847bdfd --- /dev/null +++ b/source/extensions/wasm_runtime/wasmtime/config.cc @@ -0,0 +1,27 @@ +#include "envoy/registry/registry.h" + +#include "extensions/common/wasm/wasm_runtime_factory.h" + +#include "include/proxy-wasm/wasmtime.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class WasmtimeRuntimeFactory : public WasmRuntimeFactory { +public: + WasmVmPtr createWasmVm() override { return proxy_wasm::createWasmtimeVm(); } + + absl::string_view name() override { return "envoy.wasm.runtime.wasmtime"; } + absl::string_view shortName() override { return "wasmtime"; } +}; + +#if defined(ENVOY_WASM_WASMTIME) +REGISTER_FACTORY(WasmtimeRuntimeFactory, WasmRuntimeFactory); +#endif + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/wasm_runtime/wavm/BUILD b/source/extensions/wasm_runtime/wavm/BUILD new file mode 100644 index 000000000000..55d14af61a57 --- /dev/null +++ b/source/extensions/wasm_runtime/wavm/BUILD @@ -0,0 +1,20 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + security_posture = "unknown", + status = "alpha", + deps = [ + "//include/envoy/registry", + "//source/extensions/common/wasm:wasm_lib", + ], +) diff --git a/source/extensions/wasm_runtime/wavm/config.cc b/source/extensions/wasm_runtime/wavm/config.cc new file mode 100644 index 000000000000..d50119cf784d --- /dev/null +++ b/source/extensions/wasm_runtime/wavm/config.cc @@ -0,0 +1,27 @@ +#include "envoy/registry/registry.h" + +#include "extensions/common/wasm/wasm_runtime_factory.h" + +#include "include/proxy-wasm/wavm.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +class WavmRuntimeFactory : public WasmRuntimeFactory { +public: + WasmVmPtr createWasmVm() override { return proxy_wasm::createWavmVm(); } + + absl::string_view name() override { return "envoy.wasm.runtime.wavm"; } + absl::string_view shortName() override { return "wavm"; } +}; + +#if defined(ENVOY_WASM_WAVM) +REGISTER_FACTORY(WavmRuntimeFactory, WasmRuntimeFactory); +#endif + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/access_loggers/wasm/BUILD b/test/extensions/access_loggers/wasm/BUILD index 54ab90482a91..fc56bc9b7c39 100644 --- a/test/extensions/access_loggers/wasm/BUILD +++ b/test/extensions/access_loggers/wasm/BUILD @@ -25,6 +25,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/access_loggers/wasm:config", "//test/extensions/access_loggers/wasm/test_data:test_cpp_plugin", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/access_loggers/wasm/config_test.cc b/test/extensions/access_loggers/wasm/config_test.cc index 744f074fdb8c..fb570453db30 100644 --- a/test/extensions/access_loggers/wasm/config_test.cc +++ b/test/extensions/access_loggers/wasm/config_test.cc @@ -9,6 +9,7 @@ #include "extensions/access_loggers/well_known_names.h" #include "extensions/common/wasm/wasm.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" @@ -39,20 +40,8 @@ class TestFactoryContext : public NiceMock {}; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif -#if defined(ENVOY_WASM_WASMTIME) - "wasmtime", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmAccessLogConfigTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmAccessLogConfigTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmAccessLogConfigTest, CreateWasmFromEmpty) { auto factory = diff --git a/test/extensions/bootstrap/wasm/BUILD b/test/extensions/bootstrap/wasm/BUILD index 6a6488e2b63b..b9c8282420c3 100644 --- a/test/extensions/bootstrap/wasm/BUILD +++ b/test/extensions/bootstrap/wasm/BUILD @@ -37,6 +37,7 @@ envoy_extension_cc_test( "//source/extensions/bootstrap/wasm:config", "//source/extensions/common/wasm:wasm_lib", "//test/extensions/bootstrap/wasm/test_data:stats_cpp_plugin", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/server:server_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", @@ -58,6 +59,7 @@ envoy_extension_cc_test( "//source/extensions/bootstrap/wasm:config", "//source/extensions/common/wasm:wasm_lib", "//test/extensions/bootstrap/wasm/test_data:start_cpp_plugin", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/event:event_mocks", "//test/mocks/server:server_mocks", "//test/mocks/thread_local:thread_local_mocks", @@ -85,6 +87,7 @@ envoy_extension_cc_test_binary( "//source/extensions/bootstrap/wasm:config", "//source/extensions/common/wasm:wasm_lib", "//test/extensions/bootstrap/wasm/test_data:speed_cpp_plugin", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/server:server_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", diff --git a/test/extensions/bootstrap/wasm/config_test.cc b/test/extensions/bootstrap/wasm/config_test.cc index bd5d4b947166..a0b7274e53fd 100644 --- a/test/extensions/bootstrap/wasm/config_test.cc +++ b/test/extensions/bootstrap/wasm/config_test.cc @@ -6,6 +6,7 @@ #include "extensions/bootstrap/wasm/config.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/event/mocks.h" #include "test/mocks/server/mocks.h" #include "test/mocks/thread_local/mocks.h" @@ -67,20 +68,8 @@ class WasmFactoryTest : public testing::TestWithParam { Server::BootstrapExtensionPtr extension_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif -#if defined(ENVOY_WASM_WASMTIME) - "wasmtime", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFactoryTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFactoryTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmFactoryTest, CreateWasmFromWasm) { auto factory = std::make_unique(); diff --git a/test/extensions/bootstrap/wasm/wasm_test.cc b/test/extensions/bootstrap/wasm/wasm_test.cc index 5384c66dcae8..757b086770b6 100644 --- a/test/extensions/bootstrap/wasm/wasm_test.cc +++ b/test/extensions/bootstrap/wasm/wasm_test.cc @@ -3,6 +3,7 @@ #include "extensions/common/wasm/wasm.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/server/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/environment.h" @@ -73,34 +74,14 @@ class WasmTestBase { std::shared_ptr wasm_; }; -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME) class WasmTest : public WasmTestBase, public testing::TestWithParam { public: void createWasm() { WasmTestBase::createWasm(GetParam()); } }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8" -#endif -#if defined(ENVOY_WASM_V8) && (defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME)) - , -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm" -#endif -#if (defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM)) && defined(ENVOY_WASM_WASMTIME) - , -#endif -#if defined(ENVOY_WASM_WASMTIME) - "wasmtime" -#endif -); - -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmTest, testing_values); -#endif +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmTest, + Envoy::Extensions::Common::Wasm::sandbox_runtime_values); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmTest); class WasmNullTest : public WasmTestBase, public testing::TestWithParam { public: @@ -116,22 +97,8 @@ class WasmNullTest : public WasmTestBase, public testing::TestWithParam> { public: @@ -151,24 +118,9 @@ class WasmTestMatrix : public WasmTestBase, }; INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmTestMatrix, - testing::Combine(testing::Values( -#if defined(ENVOY_WASM_V8) - "v8" -#endif -#if defined(ENVOY_WASM_V8) && (defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME)) - , -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm" -#endif -#if (defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM)) && defined(ENVOY_WASM_WASMTIME) - , -#endif -#if defined(ENVOY_WASM_WASMTIME) - "wasmtime" -#endif - ), + testing::Combine(Envoy::Extensions::Common::Wasm::sandbox_runtime_values, testing::Values("cpp", "rust"))); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmTestMatrix); TEST_P(WasmTestMatrix, Logging) { plugin_configuration_ = "configure-test"; @@ -202,9 +154,7 @@ TEST_P(WasmTestMatrix, Logging) { dispatcher_->run(Event::Dispatcher::RunType::NonBlock); dispatcher_->clearDeferredDeleteList(); } -#endif -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME) TEST_P(WasmTest, BadSignature) { createWasm(); const auto code = TestEnvironment::readFileToStringForTest(TestEnvironment::substitute( @@ -266,7 +216,6 @@ TEST_P(WasmTest, Asm2Wasm) { EXPECT_CALL(*context, log_(spdlog::level::info, Eq("out 0 0 0"))); EXPECT_TRUE(wasm_->configure(context, plugin_)); } -#endif TEST_P(WasmNullTest, Stats) { createWasm(); diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index 4e586cb7902f..b26be4cdb7e5 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -2,6 +2,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_test", "envoy_cc_test_binary", + "envoy_cc_test_library", "envoy_package", ) load( @@ -21,6 +22,7 @@ envoy_cc_test( ]), deps = [ "//source/extensions/common/wasm:wasm_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/test_common:environment_lib", "//test/test_common:registry_lib", "//test/test_common:utility_lib", @@ -44,6 +46,7 @@ envoy_cc_test( "//source/common/stats:stats_lib", "//source/extensions/common/crypto:utility_lib", "//source/extensions/common/wasm:wasm_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/common/wasm/test_data:test_context_cpp_plugin", "//test/extensions/common/wasm/test_data:test_cpp_plugin", "//test/mocks/server:server_mocks", @@ -63,8 +66,21 @@ envoy_cc_test_binary( deps = [ "//source/common/event:dispatcher_lib", "//source/extensions/common/wasm:wasm_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/server:server_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", ], ) + +envoy_cc_test_library( + name = "wasm_runtime", + srcs = ["wasm_runtime.cc"], + hdrs = ["wasm_runtime.h"], + deps = [ + "//source/extensions/wasm_runtime/null:config", + "//source/extensions/wasm_runtime/v8:config", + "//source/extensions/wasm_runtime/wasmtime:config", + "//source/extensions/wasm_runtime/wavm:config", + ], +) diff --git a/test/extensions/common/wasm/wasm_runtime.cc b/test/extensions/common/wasm/wasm_runtime.cc new file mode 100644 index 000000000000..a8451c70df64 --- /dev/null +++ b/test/extensions/common/wasm/wasm_runtime.cc @@ -0,0 +1,41 @@ +#include "test/extensions/common/wasm/wasm_runtime.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +std::vector runtimes() { + std::vector runtimes = sandboxRuntimes(); + runtimes.push_back("null"); + return runtimes; +} + +std::vector sandboxRuntimes() { + std::vector runtimes; +#if defined(ENVOY_WASM_V8) + runtimes.push_back("v8"); +#endif +#if defined(ENVOY_WASM_WAVM) + runtimes.push_back("wavm"); +#endif +#if defined(ENVOY_WASM_WASMTIME) + runtimes.push_back("wasmtime"); +#endif + return runtimes; +} + +std::vector> runtimesAndLanguages() { + std::vector> values; + for (const auto& runtime : sandboxRuntimes()) { + values.push_back(std::make_tuple(runtime, "cpp")); + values.push_back(std::make_tuple(runtime, "rust")); + } + values.push_back(std::make_tuple("null", "cpp")); + return values; +} + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/common/wasm/wasm_runtime.h b/test/extensions/common/wasm/wasm_runtime.h new file mode 100644 index 000000000000..ef248d85310b --- /dev/null +++ b/test/extensions/common/wasm/wasm_runtime.h @@ -0,0 +1,26 @@ +#pragma once + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { + +// All WASM runtimes. +std::vector runtimes(); + +// All sandboxed WASM runtimes. +std::vector sandboxRuntimes(); + +// Testable runtime and language combinations +std::vector> runtimesAndLanguages(); + +inline auto runtime_values = testing::ValuesIn(runtimes()); +inline auto sandbox_runtime_values = testing::ValuesIn(sandboxRuntimes()); +inline auto runtime_and_language_values = testing::ValuesIn(runtimesAndLanguages()); + +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index 6d903abdbec2..cd56089daf5f 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -6,6 +6,7 @@ #include "extensions/common/wasm/wasm.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/server/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/upstream/mocks.h" @@ -89,20 +90,7 @@ class WasmCommonTest : public testing::TestWithParam { } }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto test_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif -#if defined(ENVOY_WASM_WASMTIME) - "wasmtime", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonTest, test_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonTest, Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmCommonTest, EnvoyWasm) { auto envoy_wasm = std::make_unique(); @@ -969,7 +957,8 @@ class WasmCommonContextTest std::unique_ptr context_; }; -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonContextTest, test_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonContextTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmCommonContextTest, OnDnsResolve) { std::string code; diff --git a/test/extensions/filters/http/wasm/BUILD b/test/extensions/filters/http/wasm/BUILD index f8392be38a7a..055529c224ce 100644 --- a/test/extensions/filters/http/wasm/BUILD +++ b/test/extensions/filters/http/wasm/BUILD @@ -34,6 +34,7 @@ envoy_extension_cc_test( deps = [ "//source/common/http:message_lib", "//source/extensions/filters/http/wasm:wasm_filter_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/filters/http/wasm/test_data:test_cpp_plugin", "//test/mocks/network:connection_mocks", "//test/mocks/router:router_mocks", @@ -58,6 +59,7 @@ envoy_extension_cc_test( "//source/extensions/common/crypto:utility_lib", "//source/extensions/common/wasm:wasm_lib", "//source/extensions/filters/http/wasm:config", + "//test/extensions/common/wasm:wasm_runtime", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "@envoy_api//envoy/extensions/filters/http/wasm/v3:pkg_cc_proto", diff --git a/test/extensions/filters/http/wasm/config_test.cc b/test/extensions/filters/http/wasm/config_test.cc index 552aa61a9387..5d5d92afb6b5 100644 --- a/test/extensions/filters/http/wasm/config_test.cc +++ b/test/extensions/filters/http/wasm/config_test.cc @@ -11,6 +11,7 @@ #include "extensions/common/wasm/wasm.h" #include "extensions/filters/http/wasm/config.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/http/mocks.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" @@ -29,7 +30,6 @@ using Common::Wasm::WasmException; namespace HttpFilters { namespace Wasm { -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME) class WasmFilterConfigTest : public Event::TestUsingSimulatedTime, public testing::TestWithParam { protected: @@ -65,27 +65,9 @@ class WasmFilterConfigTest : public Event::TestUsingSimulatedTime, Event::TimerCb retry_timer_cb_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8" -#endif -#if defined(ENVOY_WASM_V8) && (defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME)) - , -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm" -#endif -#if (defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM)) && defined(ENVOY_WASM_WASMTIME) - , -#endif -#if defined(ENVOY_WASM_WASMTIME) - "wasmtime" -#endif -); - -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFilterConfigTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmFilterConfigTest, + Envoy::Extensions::Common::Wasm::sandbox_runtime_values); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WasmFilterConfigTest); TEST_P(WasmFilterConfigTest, JsonLoadFromFileWasm) { const std::string json = TestEnvironment::substitute(absl::StrCat(R"EOF( @@ -832,7 +814,6 @@ TEST_P(WasmFilterConfigTest, YamlLoadFromRemoteSuccessBadcodeFailOpen) { // The filter is not registered. cb(filter_callback); } -#endif } // namespace Wasm } // namespace HttpFilters diff --git a/test/extensions/filters/http/wasm/wasm_filter_test.cc b/test/extensions/filters/http/wasm/wasm_filter_test.cc index 9999d453c75f..fddde58db40b 100644 --- a/test/extensions/filters/http/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/http/wasm/wasm_filter_test.cc @@ -2,6 +2,7 @@ #include "extensions/filters/http/wasm/wasm_filter.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/network/connection.h" #include "test/mocks/router/mocks.h" #include "test/test_common/wasm_base.h" @@ -95,20 +96,8 @@ class WasmHttpFilterTest : public Common::Wasm::WasmHttpFilterTestBase< Grpc::MockAsyncClientManager async_client_manager_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - std::make_tuple("v8", "cpp"), std::make_tuple("v8", "rust"), -#endif -#if defined(ENVOY_WASM_WAVM) - std::make_tuple("wavm", "cpp"), std::make_tuple("wavm", "rust"), -#endif -#if defined(ENVOY_WASM_WASMTIME) - std::make_tuple("wasmtime", "cpp"), std::make_tuple("wasmtime", "rust"), -#endif - std::make_tuple("null", "cpp")); -INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, testing_values); +INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmHttpFilterTest, + Envoy::Extensions::Common::Wasm::runtime_and_language_values); // Bad code in initial config. TEST_P(WasmHttpFilterTest, BadCode) { @@ -1263,9 +1252,11 @@ TEST_P(WasmHttpFilterTest, GrpcStreamOpenAtShutdown) { } // Test metadata access including CEL expressions. -// TODO: re-enable this on Windows if and when the CEL `Antlr` parser compiles on Windows. -#if defined(ENVOY_WASM_V8) || defined(ENVOY_WASM_WAVM) || defined(ENVOY_WASM_WASMTIME) TEST_P(WasmHttpFilterTest, Metadata) { +#ifdef WIN32 + // TODO: re-enable this on Windows if and when the CEL `Antlr` parser compiles on Windows. + GTEST_SKIP() << "Skipping on Windows"; +#endif setupTest("", "metadata"); setupFilter(); envoy::config::core::v3::Node node_data; @@ -1319,7 +1310,6 @@ TEST_P(WasmHttpFilterTest, Metadata) { filter().onDestroy(); filter().onDestroy(); // Does nothing. } -#endif TEST_P(WasmHttpFilterTest, Property) { if (std::get<1>(GetParam()) == "rust") { diff --git a/test/extensions/filters/network/wasm/BUILD b/test/extensions/filters/network/wasm/BUILD index d21eba6c0853..bfbd34124d5f 100644 --- a/test/extensions/filters/network/wasm/BUILD +++ b/test/extensions/filters/network/wasm/BUILD @@ -29,6 +29,7 @@ envoy_extension_cc_test( "//source/extensions/common/crypto:utility_lib", "//source/extensions/common/wasm:wasm_lib", "//source/extensions/filters/network/wasm:config", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/filters/network/wasm/test_data:test_cpp_plugin", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", @@ -46,6 +47,7 @@ envoy_extension_cc_test( extension_name = "envoy.filters.network.wasm", deps = [ "//source/extensions/filters/network/wasm:wasm_filter_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/filters/network/wasm/test_data:test_cpp_plugin", "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", diff --git a/test/extensions/filters/network/wasm/config_test.cc b/test/extensions/filters/network/wasm/config_test.cc index 68541490a82c..6d93a167f674 100644 --- a/test/extensions/filters/network/wasm/config_test.cc +++ b/test/extensions/filters/network/wasm/config_test.cc @@ -8,6 +8,7 @@ #include "extensions/filters/network/wasm/config.h" #include "extensions/filters/network/wasm/wasm_filter.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" @@ -56,20 +57,8 @@ class WasmNetworkFilterConfigTest : public testing::TestWithParam { Event::TimerCb retry_timer_cb_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif -#if defined(ENVOY_WASM_WASMTIME) - "wasmtime", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmNetworkFilterConfigTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmNetworkFilterConfigTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmNetworkFilterConfigTest, YamlLoadFromFileWasm) { if (GetParam() == "null") { diff --git a/test/extensions/filters/network/wasm/wasm_filter_test.cc b/test/extensions/filters/network/wasm/wasm_filter_test.cc index 517d37cee3b5..0b602428083c 100644 --- a/test/extensions/filters/network/wasm/wasm_filter_test.cc +++ b/test/extensions/filters/network/wasm/wasm_filter_test.cc @@ -3,6 +3,7 @@ #include "extensions/common/wasm/wasm.h" #include "extensions/filters/network/wasm/wasm_filter.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/network/mocks.h" #include "test/mocks/server/mocks.h" #include "test/test_common/wasm_base.h" @@ -82,20 +83,8 @@ class WasmNetworkFilterTest : public Common::Wasm::WasmNetworkFilterTestBase< std::string code_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - std::make_tuple("v8", "cpp"), std::make_tuple("v8", "rust"), -#endif -#if defined(ENVOY_WASM_WAVM) - std::make_tuple("wavm", "cpp"), std::make_tuple("wavm", "rust"), -#endif -#if defined(ENVOY_WASM_WASMTIME) - std::make_tuple("wasmtime", "cpp"), std::make_tuple("wasmtime", "rust"), -#endif - std::make_tuple("null", "cpp")); -INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmNetworkFilterTest, testing_values); +INSTANTIATE_TEST_SUITE_P(RuntimesAndLanguages, WasmNetworkFilterTest, + Envoy::Extensions::Common::Wasm::runtime_and_language_values); // Bad code in initial config. TEST_P(WasmNetworkFilterTest, BadCode) { diff --git a/test/extensions/stats_sinks/wasm/BUILD b/test/extensions/stats_sinks/wasm/BUILD index 6135c8cfcf0a..b0911d7aaa9f 100644 --- a/test/extensions/stats_sinks/wasm/BUILD +++ b/test/extensions/stats_sinks/wasm/BUILD @@ -24,6 +24,7 @@ envoy_extension_cc_test( extension_name = "envoy.stat_sinks.wasm", deps = [ "//source/extensions/stat_sinks/wasm:config", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/stats_sinks/wasm/test_data:test_context_cpp_plugin", "//test/mocks/server:server_mocks", "@envoy_api//envoy/extensions/stat_sinks/wasm/v3:pkg_cc_proto", @@ -41,6 +42,7 @@ envoy_extension_cc_test( deps = [ "//source/common/stats:stats_lib", "//source/extensions/common/wasm:wasm_lib", + "//test/extensions/common/wasm:wasm_runtime", "//test/extensions/stats_sinks/wasm/test_data:test_context_cpp_plugin", "//test/mocks/stats:stats_mocks", "//test/test_common:wasm_lib", diff --git a/test/extensions/stats_sinks/wasm/config_test.cc b/test/extensions/stats_sinks/wasm/config_test.cc index d9b1263215af..012f4ecc2c98 100644 --- a/test/extensions/stats_sinks/wasm/config_test.cc +++ b/test/extensions/stats_sinks/wasm/config_test.cc @@ -8,6 +8,7 @@ #include "extensions/stat_sinks/wasm/wasm_stat_sink_impl.h" #include "extensions/stat_sinks/well_known_names.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" @@ -65,20 +66,8 @@ class WasmStatSinkConfigTest : public testing::TestWithParam { Stats::SinkPtr sink_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif -#if defined(ENVOY_WASM_WASMTIME) - "wasmtime", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmStatSinkConfigTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmStatSinkConfigTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmStatSinkConfigTest, CreateWasmFromEmpty) { envoy::extensions::stat_sinks::wasm::v3::Wasm config; diff --git a/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc b/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc index 716925bfd12f..db9f4108aedd 100644 --- a/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc +++ b/test/extensions/stats_sinks/wasm/wasm_stat_sink_test.cc @@ -2,6 +2,7 @@ #include "extensions/common/wasm/wasm.h" +#include "test/extensions/common/wasm/wasm_runtime.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/wasm_base.h" @@ -54,20 +55,8 @@ class WasmCommonContextTest std::unique_ptr context_; }; -// NB: this is required by VC++ which can not handle the use of macros in the macro definitions -// used by INSTANTIATE_TEST_SUITE_P. -auto testing_values = testing::Values( -#if defined(ENVOY_WASM_V8) - "v8", -#endif -#if defined(ENVOY_WASM_WAVM) - "wavm", -#endif -#if defined(ENVOY_WASM_WASMTIME) - "wasmtime", -#endif - "null"); -INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonContextTest, testing_values); +INSTANTIATE_TEST_SUITE_P(Runtimes, WasmCommonContextTest, + Envoy::Extensions::Common::Wasm::runtime_values); TEST_P(WasmCommonContextTest, OnStat) { std::string code; diff --git a/test/per_file_coverage.sh b/test/per_file_coverage.sh index 13526f0eebb2..8de0092d2981 100755 --- a/test/per_file_coverage.sh +++ b/test/per_file_coverage.sh @@ -67,6 +67,9 @@ declare -a KNOWN_LOW_COVERAGE=( "source/extensions/transport_sockets/tls:94.2" "source/extensions/transport_sockets/tls/ocsp:95.3" "source/extensions/transport_sockets/tls/private_key:76.9" +"source/extensions/wasm_runtime:50.0" +"source/extensions/wasm_runtime/wasmtime:0.0" # Not enabled in coverage build +"source/extensions/wasm_runtime/wavm:0.0" # Noe enabled in coverage build "source/extensions/watchdog:69.6" # Death tests within extensions "source/extensions/watchdog/profile_action:84.9" "source/server:94.6" From 00038ddc5f5d3593ad1f3e7a1dbc0666cd240f16 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Mon, 16 Nov 2020 22:16:28 -0500 Subject: [PATCH 111/117] stats: use RE2 and a better pattern to accelerate a single stats tag-extraction RE (#8831) Description: Improves startup latency by 10% on 10k clusters, and I see no reason for that not to scale, or to be expandable to other regexes that we find to be heavy over time. Risk Level: low Testing: //test/... Docs Changes: n/a Release Notes: n/a Signed-off-by: Joshua Marantz --- source/common/common/regex.h | 2 + source/common/config/BUILD | 1 + source/common/config/well_known_names.cc | 9 +- source/common/config/well_known_names.h | 5 +- source/common/stats/BUILD | 1 + source/common/stats/tag_extractor_impl.cc | 94 ++++++++++--- source/common/stats/tag_extractor_impl.h | 51 +++++-- source/common/stats/tag_producer_impl.cc | 12 +- test/common/common/BUILD | 11 ++ test/common/common/re_speed_test.cc | 128 ++++++++++++++++++ test/common/stats/tag_extractor_impl_test.cc | 34 +++-- .../stats/thread_local_store_speed_test.cc | 2 + 12 files changed, 304 insertions(+), 46 deletions(-) create mode 100644 test/common/common/re_speed_test.cc diff --git a/source/common/common/regex.h b/source/common/common/regex.h index 68cb7ff8074d..2fdcd52ebc1c 100644 --- a/source/common/common/regex.h +++ b/source/common/common/regex.h @@ -9,6 +9,8 @@ namespace Envoy { namespace Regex { +enum class Type { Re2, StdRegex }; + /** * Utilities for constructing regular expressions. */ diff --git a/source/common/config/BUILD b/source/common/config/BUILD index 7c5460df96bb..baf4f851e998 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -461,6 +461,7 @@ envoy_cc_library( hdrs = ["well_known_names.h"], deps = [ "//source/common/common:assert_lib", + "//source/common/common:regex_lib", "//source/common/singleton:const_singleton", ], ) diff --git a/source/common/config/well_known_names.cc b/source/common/config/well_known_names.cc index e2677cb742a4..e8fc767c41a3 100644 --- a/source/common/config/well_known_names.cc +++ b/source/common/config/well_known_names.cc @@ -92,7 +92,7 @@ TagNameValues::TagNameValues() { addRegex(RATELIMIT_PREFIX, R"(^ratelimit\.((.*?)\.)\w+?$)"); // cluster.(.)* - addRegex(CLUSTER_NAME, "^cluster\\.((.*?)\\.)"); + addRe2(CLUSTER_NAME, "^cluster\\.(([^\\.]+)\\.).*"); // listener.[
.]http.(.)* addRegex(HTTP_CONN_MANAGER_PREFIX, R"(^listener(?=\.).*?\.http\.((.*?)\.))", ".http."); @@ -119,7 +119,12 @@ TagNameValues::TagNameValues() { void TagNameValues::addRegex(const std::string& name, const std::string& regex, const std::string& substr) { - descriptor_vec_.emplace_back(Descriptor(name, regex, substr)); + descriptor_vec_.emplace_back(Descriptor{name, regex, substr, Regex::Type::StdRegex}); +} + +void TagNameValues::addRe2(const std::string& name, const std::string& regex, + const std::string& substr) { + descriptor_vec_.emplace_back(Descriptor{name, regex, substr, Regex::Type::Re2}); } } // namespace Config diff --git a/source/common/config/well_known_names.h b/source/common/config/well_known_names.h index 1d3cd09c51d8..97ce58fd7265 100644 --- a/source/common/config/well_known_names.h +++ b/source/common/config/well_known_names.h @@ -6,6 +6,7 @@ #include "envoy/common/exception.h" #include "common/common/assert.h" +#include "common/common/regex.h" #include "common/singleton/const_singleton.h" namespace Envoy { @@ -62,11 +63,10 @@ class TagNameValues { * tags, such as "_rq_(\\d)xx$", will probably stay as regexes. */ struct Descriptor { - Descriptor(const std::string& name, const std::string& regex, const std::string& substr = "") - : name_(name), regex_(regex), substr_(substr) {} const std::string name_; const std::string regex_; const std::string substr_; + const Regex::Type re_type_; }; // Cluster name tag @@ -130,6 +130,7 @@ class TagNameValues { private: void addRegex(const std::string& name, const std::string& regex, const std::string& substr = ""); + void addRe2(const std::string& name, const std::string& regex, const std::string& substr = ""); // Collection of tag descriptors. std::vector descriptor_vec_; diff --git a/source/common/stats/BUILD b/source/common/stats/BUILD index fb8528063934..edc68f4774e7 100644 --- a/source/common/stats/BUILD +++ b/source/common/stats/BUILD @@ -195,6 +195,7 @@ envoy_cc_library( hdrs = ["tag_extractor_impl.h"], deps = [ "//include/envoy/stats:stats_interface", + "//source/common/common:assert_lib", "//source/common/common:perf_annotation_lib", "//source/common/common:regex_lib", ], diff --git a/source/common/stats/tag_extractor_impl.cc b/source/common/stats/tag_extractor_impl.cc index 3c666a13e105..6aefbdf6cd25 100644 --- a/source/common/stats/tag_extractor_impl.cc +++ b/source/common/stats/tag_extractor_impl.cc @@ -5,6 +5,7 @@ #include "envoy/common/exception.h" +#include "common/common/assert.h" #include "common/common/fmt.h" #include "common/common/perf_annotation.h" #include "common/common/regex.h" @@ -23,12 +24,11 @@ bool regexStartsWithDot(absl::string_view regex) { } // namespace -TagExtractorImpl::TagExtractorImpl(const std::string& name, const std::string& regex, - const std::string& substr) - : name_(name), prefix_(std::string(extractRegexPrefix(regex))), substr_(substr), - regex_(Regex::Utility::parseStdRegex(regex)) {} +TagExtractorImplBase::TagExtractorImplBase(absl::string_view name, absl::string_view regex, + absl::string_view substr) + : name_(name), prefix_(std::string(extractRegexPrefix(regex))), substr_(substr) {} -std::string TagExtractorImpl::extractRegexPrefix(absl::string_view regex) { +std::string TagExtractorImplBase::extractRegexPrefix(absl::string_view regex) { std::string prefix; if (absl::StartsWith(regex, "^")) { for (absl::string_view::size_type i = 1; i < regex.size(); ++i) { @@ -47,10 +47,10 @@ std::string TagExtractorImpl::extractRegexPrefix(absl::string_view regex) { return prefix; } -TagExtractorPtr TagExtractorImpl::createTagExtractor(const std::string& name, - const std::string& regex, - const std::string& substr) { - +TagExtractorPtr TagExtractorImplBase::createTagExtractor(absl::string_view name, + absl::string_view regex, + absl::string_view substr, + Regex::Type re_type) { if (name.empty()) { throw EnvoyException("tag_name cannot be empty"); } @@ -59,19 +59,37 @@ TagExtractorPtr TagExtractorImpl::createTagExtractor(const std::string& name, throw EnvoyException(fmt::format( "No regex specified for tag specifier and no default regex for name: '{}'", name)); } - return TagExtractorPtr{new TagExtractorImpl(name, regex, substr)}; + switch (re_type) { + case Regex::Type::Re2: + return std::make_unique(name, regex, substr); + case Regex::Type::StdRegex: + return std::make_unique(name, regex, substr); + } + NOT_REACHED_GCOVR_EXCL_LINE; } -bool TagExtractorImpl::substrMismatch(absl::string_view stat_name) const { +bool TagExtractorImplBase::substrMismatch(absl::string_view stat_name) const { return !substr_.empty() && stat_name.find(substr_) == absl::string_view::npos; } -bool TagExtractorImpl::extractTag(absl::string_view stat_name, TagVector& tags, - IntervalSet& remove_characters) const { +TagExtractorStdRegexImpl::TagExtractorStdRegexImpl(absl::string_view name, absl::string_view regex, + absl::string_view substr) + : TagExtractorImplBase(name, regex, substr), + regex_(Regex::Utility::parseStdRegex(std::string(regex))) {} + +std::string& TagExtractorImplBase::addTag(std::vector& tags) const { + tags.emplace_back(); + Tag& tag = tags.back(); + tag.name_ = name_; + return tag.value_; +} + +bool TagExtractorStdRegexImpl::extractTag(absl::string_view stat_name, std::vector& tags, + IntervalSet& remove_characters) const { PERF_OPERATION(perf); if (substrMismatch(stat_name)) { - PERF_RECORD(perf, "re-skip-substr", name_); + PERF_RECORD(perf, "re-skip", name_); return false; } @@ -88,11 +106,7 @@ bool TagExtractorImpl::extractTag(absl::string_view stat_name, TagVector& tags, // from the string but also not necessary in the tag value ("." for example). If there is no // second submatch, then the value_subexpr is the same as the remove_subexpr. const auto& value_subexpr = match.size() > 2 ? match[2] : remove_subexpr; - - tags.emplace_back(); - Tag& tag = tags.back(); - tag.name_ = name_; - tag.value_ = value_subexpr.str(); + addTag(tags) = value_subexpr.str(); // Determines which characters to remove from stat_name to elide remove_subexpr. std::string::size_type start = remove_subexpr.first - stat_name.begin(); @@ -105,5 +119,47 @@ bool TagExtractorImpl::extractTag(absl::string_view stat_name, TagVector& tags, return false; } +TagExtractorRe2Impl::TagExtractorRe2Impl(absl::string_view name, absl::string_view regex, + absl::string_view substr) + : TagExtractorImplBase(name, regex, substr), regex_(regex) {} + +bool TagExtractorRe2Impl::extractTag(absl::string_view stat_name, std::vector& tags, + IntervalSet& remove_characters) const { + PERF_OPERATION(perf); + + if (substrMismatch(stat_name)) { + PERF_RECORD(perf, "re2-skip", name_); + return false; + } + + // remove_subexpr is the first submatch. It represents the portion of the string to be removed. + re2::StringPiece remove_subexpr, value_subexpr; + + // The regex must match and contain one or more subexpressions (all after the first are ignored). + if (re2::RE2::FullMatch(re2::StringPiece(stat_name.data(), stat_name.size()), regex_, + &remove_subexpr, &value_subexpr) && + !remove_subexpr.empty()) { + + // value_subexpr is the optional second submatch. It is usually inside the first submatch + // (remove_subexpr) to allow the expression to strip off extra characters that should be removed + // from the string but also not necessary in the tag value ("." for example). If there is no + // second submatch, then the value_subexpr is the same as the remove_subexpr. + if (value_subexpr.empty()) { + value_subexpr = remove_subexpr; + } + addTag(tags) = std::string(value_subexpr); + + // Determines which characters to remove from stat_name to elide remove_subexpr. + std::string::size_type start = remove_subexpr.data() - stat_name.data(); + std::string::size_type end = remove_subexpr.data() + remove_subexpr.size() - stat_name.data(); + remove_characters.insert(start, end); + + PERF_RECORD(perf, "re2-match", name_); + return true; + } + PERF_RECORD(perf, "re2-miss", name_); + return false; +} + } // namespace Stats } // namespace Envoy diff --git a/source/common/stats/tag_extractor_impl.h b/source/common/stats/tag_extractor_impl.h index a63c7e1e4626..f909868eb236 100644 --- a/source/common/stats/tag_extractor_impl.h +++ b/source/common/stats/tag_extractor_impl.h @@ -6,12 +6,15 @@ #include "envoy/stats/tag_extractor.h" +#include "common/common/regex.h" + #include "absl/strings/string_view.h" +#include "re2/re2.h" namespace Envoy { namespace Stats { -class TagExtractorImpl : public TagExtractor { +class TagExtractorImplBase : public TagExtractor { public: /** * Creates a tag extractor from the regex provided. name and regex must be non-empty. @@ -20,16 +23,16 @@ class TagExtractorImpl : public TagExtractor { * @param substr a substring that -- if provided -- must be present in a stat name * in order to match the regex. This is an optional performance tweak * to avoid large numbers of failed regex lookups. + * @param re_type the regular expression syntax used (Regex::Type::StdRegex or Regex::Type::Re2). * @return TagExtractorPtr newly constructed TagExtractor. */ - static TagExtractorPtr createTagExtractor(const std::string& name, const std::string& regex, - const std::string& substr = ""); + static TagExtractorPtr createTagExtractor(absl::string_view name, absl::string_view regex, + absl::string_view substr = "", + Regex::Type re_type = Regex::Type::StdRegex); - TagExtractorImpl(const std::string& name, const std::string& regex, - const std::string& substr = ""); + TagExtractorImplBase(absl::string_view name, absl::string_view regex, + absl::string_view substr = ""); std::string name() const override { return name_; } - bool extractTag(absl::string_view tag_extracted_name, TagVector& tags, - IntervalSet& remove_characters) const override; absl::string_view prefixToken() const override { return prefix_; } /** @@ -39,7 +42,7 @@ class TagExtractorImpl : public TagExtractor { */ bool substrMismatch(absl::string_view stat_name) const; -private: +protected: /** * Examines a regex string, looking for the pattern: ^alphanumerics_with_underscores\. * Returns "alphanumerics_with_underscores" if that pattern is found, empty-string otherwise. @@ -47,11 +50,43 @@ class TagExtractorImpl : public TagExtractor { * @return std::string the prefix, or "" if no prefix found. */ static std::string extractRegexPrefix(absl::string_view regex); + + /** + * Adds a new tag for the current name, returning a reference to the tag value. + * + * @param tags the list of tags + * @return a reference to the value of the tag that was added. + */ + std::string& addTag(std::vector& tags) const; + const std::string name_; const std::string prefix_; const std::string substr_; +}; + +class TagExtractorStdRegexImpl : public TagExtractorImplBase { +public: + TagExtractorStdRegexImpl(absl::string_view name, absl::string_view regex, + absl::string_view substr = ""); + + bool extractTag(absl::string_view tag_extracted_name, std::vector& tags, + IntervalSet& remove_characters) const override; + +private: const std::regex regex_; }; +class TagExtractorRe2Impl : public TagExtractorImplBase { +public: + TagExtractorRe2Impl(absl::string_view name, absl::string_view regex, + absl::string_view substr = ""); + + bool extractTag(absl::string_view tag_extracted_name, std::vector& tags, + IntervalSet& remove_characters) const override; + +private: + const re2::RE2 regex_; +}; + } // namespace Stats } // namespace Envoy diff --git a/source/common/stats/tag_producer_impl.cc b/source/common/stats/tag_producer_impl.cc index 255dfcaeed39..bfc35b52e40a 100644 --- a/source/common/stats/tag_producer_impl.cc +++ b/source/common/stats/tag_producer_impl.cc @@ -34,11 +34,11 @@ TagProducerImpl::TagProducerImpl(const envoy::config::metrics::v3::StatsConfig& "No regex specified for tag specifier and no default regex for name: '{}'", name)); } } else { - addExtractor(Stats::TagExtractorImpl::createTagExtractor(name, tag_specifier.regex())); + addExtractor(TagExtractorImplBase::createTagExtractor(name, tag_specifier.regex())); } } else if (tag_specifier.tag_value_case() == envoy::config::metrics::v3::TagSpecifier::TagValueCase::kFixedValue) { - default_tags_.emplace_back(Stats::Tag{name, tag_specifier.fixed_value()}); + default_tags_.emplace_back(Tag{name, tag_specifier.fixed_value()}); } } } @@ -47,8 +47,8 @@ int TagProducerImpl::addExtractorsMatching(absl::string_view name) { int num_found = 0; for (const auto& desc : Config::TagNames::get().descriptorVec()) { if (desc.name_ == name) { - addExtractor( - Stats::TagExtractorImpl::createTagExtractor(desc.name_, desc.regex_, desc.substr_)); + addExtractor(TagExtractorImplBase::createTagExtractor(desc.name_, desc.regex_, desc.substr_, + desc.re_type_)); ++num_found; } } @@ -103,8 +103,8 @@ TagProducerImpl::addDefaultExtractors(const envoy::config::metrics::v3::StatsCon if (!config.has_use_all_default_tags() || config.use_all_default_tags().value()) { for (const auto& desc : Config::TagNames::get().descriptorVec()) { names.emplace(desc.name_); - addExtractor( - Stats::TagExtractorImpl::createTagExtractor(desc.name_, desc.regex_, desc.substr_)); + addExtractor(TagExtractorImplBase::createTagExtractor(desc.name_, desc.regex_, desc.substr_, + desc.re_type_)); } } return names; diff --git a/test/common/common/BUILD b/test/common/common/BUILD index 8f9ec5324dc8..c002da716b0a 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -269,6 +269,17 @@ envoy_cc_test( deps = ["//source/common/common:callback_impl_lib"], ) +envoy_cc_benchmark_binary( + name = "re_speed_test", + srcs = ["re_speed_test.cc"], + external_deps = ["benchmark"], + deps = [ + "//source/common/common:assert_lib", + "//source/common/common:utility_lib", + "@com_googlesource_code_re2//:re2", + ], +) + envoy_cc_benchmark_binary( name = "utility_speed_test", srcs = ["utility_speed_test.cc"], diff --git a/test/common/common/re_speed_test.cc b/test/common/common/re_speed_test.cc new file mode 100644 index 000000000000..0db62906e281 --- /dev/null +++ b/test/common/common/re_speed_test.cc @@ -0,0 +1,128 @@ +// Note: this should be run with --compilation_mode=opt, and would benefit from +// a quiescent system with disabled cstate power management. + +#include + +#include "common/common/assert.h" + +#include "absl/strings/string_view.h" +#include "benchmark/benchmark.h" +#include "re2/re2.h" + +// NOLINT(namespace-envoy) + +static const char* ClusterInputs[] = { + "cluster.no_trailing_dot", + "cluster.match.", + "cluster.match.normal", + "cluster.match.and.a.whole.lot.of.things.coming.after.the.matches.really.too.much.stuff", +}; + +static const char ClusterRePattern[] = "^cluster\\.((.*?)\\.)"; +static const char ClusterReAltPattern[] = "^cluster\\.(([^\\.]+)\\.).*"; + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_StdRegex(benchmark::State& state) { + std::regex re(ClusterRePattern); + uint32_t passes = 0; + std::vector inputs; + for (const char* cluster_input : ClusterInputs) { + inputs.push_back(cluster_input); + } + + for (auto _ : state) { // NOLINT + for (const std::string& cluster_input : inputs) { + std::smatch match; + if (std::regex_search(cluster_input, match, re)) { + ASSERT(match.size() >= 3); + ASSERT(match[1] == "match."); + ASSERT(match[2] == "match"); + ++passes; + } + } + } + RELEASE_ASSERT(passes > 0, ""); +} +BENCHMARK(BM_StdRegex); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_StdRegexStringView(benchmark::State& state) { + std::regex re(ClusterRePattern); + std::vector inputs; + for (const char* cluster_input : ClusterInputs) { + inputs.push_back(cluster_input); + } + uint32_t passes = 0; + for (auto _ : state) { // NOLINT + for (absl::string_view cluster_input : inputs) { + std::match_results smatch; + if (std::regex_search(cluster_input.begin(), cluster_input.end(), smatch, re)) { + ASSERT(smatch.size() >= 3); + ASSERT(smatch[1] == "match."); + ASSERT(smatch[2] == "match"); + ++passes; + } + } + } + RELEASE_ASSERT(passes > 0, ""); +} +BENCHMARK(BM_StdRegexStringView); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_StdRegexStringViewAltPattern(benchmark::State& state) { + std::regex re(ClusterReAltPattern); + std::vector inputs; + for (const char* cluster_input : ClusterInputs) { + inputs.push_back(cluster_input); + } + uint32_t passes = 0; + for (auto _ : state) { // NOLINT + for (absl::string_view cluster_input : inputs) { + std::match_results smatch; + if (std::regex_search(cluster_input.begin(), cluster_input.end(), smatch, re)) { + ASSERT(smatch.size() >= 3); + ASSERT(smatch[1] == "match."); + ASSERT(smatch[2] == "match"); + ++passes; + } + } + } + RELEASE_ASSERT(passes > 0, ""); +} +BENCHMARK(BM_StdRegexStringViewAltPattern); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_RE2(benchmark::State& state) { + re2::RE2 re(ClusterRePattern); + uint32_t passes = 0; + for (auto _ : state) { // NOLINT + for (const char* cluster_input : ClusterInputs) { + re2::StringPiece match1, match2; + if (re2::RE2::PartialMatch(cluster_input, re, &match1, &match2)) { + ASSERT(match1 == "match."); + ASSERT(match2 == "match"); + ++passes; + } + } + } + RELEASE_ASSERT(passes > 0, ""); +} +BENCHMARK(BM_RE2); + +// NOLINTNEXTLINE(readability-identifier-naming) +static void BM_RE2_AltPattern(benchmark::State& state) { + re2::RE2 re(ClusterReAltPattern); + uint32_t passes = 0; + for (auto _ : state) { // NOLINT + for (const char* cluster_input : ClusterInputs) { + re2::StringPiece match1, match2; + if (re2::RE2::PartialMatch(cluster_input, re, &match1, &match2)) { + ASSERT(match1 == "match."); + ASSERT(match2 == "match"); + ++passes; + } + } + } + RELEASE_ASSERT(passes > 0, ""); +} +BENCHMARK(BM_RE2_AltPattern); diff --git a/test/common/stats/tag_extractor_impl_test.cc b/test/common/stats/tag_extractor_impl_test.cc index 18ff4d6ec88c..13fd4de172c9 100644 --- a/test/common/stats/tag_extractor_impl_test.cc +++ b/test/common/stats/tag_extractor_impl_test.cc @@ -15,7 +15,21 @@ namespace Envoy { namespace Stats { TEST(TagExtractorTest, TwoSubexpressions) { - TagExtractorImpl tag_extractor("cluster_name", "^cluster\\.((.+?)\\.)"); + TagExtractorStdRegexImpl tag_extractor("cluster_name", "^cluster\\.((.+?)\\.)"); + EXPECT_EQ("cluster_name", tag_extractor.name()); + std::string name = "cluster.test_cluster.upstream_cx_total"; + TagVector tags; + IntervalSetImpl remove_characters; + ASSERT_TRUE(tag_extractor.extractTag(name, tags, remove_characters)); + std::string tag_extracted_name = StringUtil::removeCharacters(name, remove_characters); + EXPECT_EQ("cluster.upstream_cx_total", tag_extracted_name); + ASSERT_EQ(1, tags.size()); + EXPECT_EQ("test_cluster", tags.at(0).value_); + EXPECT_EQ("cluster_name", tags.at(0).name_); +} + +TEST(TagExtractorTest, RE2Variants) { + TagExtractorRe2Impl tag_extractor("cluster_name", "^cluster\\.(([^\\.]+)\\.).*"); EXPECT_EQ("cluster_name", tag_extractor.name()); std::string name = "cluster.test_cluster.upstream_cx_total"; TagVector tags; @@ -29,7 +43,7 @@ TEST(TagExtractorTest, TwoSubexpressions) { } TEST(TagExtractorTest, SingleSubexpression) { - TagExtractorImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)"); + TagExtractorStdRegexImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)"); std::string name = "listener.80.downstream_cx_total"; TagVector tags; IntervalSetImpl remove_characters; @@ -42,24 +56,26 @@ TEST(TagExtractorTest, SingleSubexpression) { } TEST(TagExtractorTest, substrMismatch) { - TagExtractorImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)\\.foo\\.", ".foo."); + TagExtractorStdRegexImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)\\.foo\\.", + ".foo."); EXPECT_TRUE(tag_extractor.substrMismatch("listener.80.downstream_cx_total")); EXPECT_FALSE(tag_extractor.substrMismatch("listener.80.downstream_cx_total.foo.bar")); } TEST(TagExtractorTest, noSubstrMismatch) { - TagExtractorImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)\\.foo\\."); + TagExtractorStdRegexImpl tag_extractor("listner_port", "^listener\\.(\\d+?\\.)\\.foo\\."); EXPECT_FALSE(tag_extractor.substrMismatch("listener.80.downstream_cx_total")); EXPECT_FALSE(tag_extractor.substrMismatch("listener.80.downstream_cx_total.foo.bar")); } TEST(TagExtractorTest, EmptyName) { - EXPECT_THROW_WITH_MESSAGE(TagExtractorImpl::createTagExtractor("", "^listener\\.(\\d+?\\.)"), - EnvoyException, "tag_name cannot be empty"); + EXPECT_THROW_WITH_MESSAGE( + TagExtractorStdRegexImpl::createTagExtractor("", "^listener\\.(\\d+?\\.)"), EnvoyException, + "tag_name cannot be empty"); } TEST(TagExtractorTest, BadRegex) { - EXPECT_THROW_WITH_REGEX(TagExtractorImpl::createTagExtractor("cluster_name", "+invalid"), + EXPECT_THROW_WITH_REGEX(TagExtractorStdRegexImpl::createTagExtractor("cluster_name", "+invalid"), EnvoyException, "Invalid regex '\\+invalid':"); } @@ -361,7 +377,7 @@ TEST(TagExtractorTest, DefaultTagExtractors) { TEST(TagExtractorTest, ExtractRegexPrefix) { TagExtractorPtr tag_extractor; // Keep tag_extractor in this scope to prolong prefix lifetime. auto extractRegexPrefix = [&tag_extractor](const std::string& regex) -> absl::string_view { - tag_extractor = TagExtractorImpl::createTagExtractor("foo", regex); + tag_extractor = TagExtractorStdRegexImpl::createTagExtractor("foo", regex); return tag_extractor->prefixToken(); }; @@ -376,7 +392,7 @@ TEST(TagExtractorTest, ExtractRegexPrefix) { } TEST(TagExtractorTest, CreateTagExtractorNoRegex) { - EXPECT_THROW_WITH_REGEX(TagExtractorImpl::createTagExtractor("no such default tag", ""), + EXPECT_THROW_WITH_REGEX(TagExtractorStdRegexImpl::createTagExtractor("no such default tag", ""), EnvoyException, "^No regex specified for tag specifier and no default"); } diff --git a/test/common/stats/thread_local_store_speed_test.cc b/test/common/stats/thread_local_store_speed_test.cc index 3eff52a78d97..5208fdaed9f5 100644 --- a/test/common/stats/thread_local_store_speed_test.cc +++ b/test/common/stats/thread_local_store_speed_test.cc @@ -79,6 +79,7 @@ class ThreadLocalStorePerf { // Tests the single-threaded performance of the thread-local-store stats caches // without having initialized tls. +// NOLINTNEXTLINE(readability-identifier-naming) static void BM_StatsNoTls(benchmark::State& state) { Envoy::ThreadLocalStorePerf context; @@ -91,6 +92,7 @@ BENCHMARK(BM_StatsNoTls); // Tests the single-threaded performance of the thread-local-store stats caches // with tls. Note that this test is still single-threaded, and so there's only // one replica of the tls cache. +// NOLINTNEXTLINE(readability-identifier-naming) static void BM_StatsWithTls(benchmark::State& state) { Envoy::ThreadLocalStorePerf context; context.initThreading(); From 80e2b724f024751feeb06ecd931fd72404837905 Mon Sep 17 00:00:00 2001 From: Lizan Zhou Date: Mon, 16 Nov 2020 21:15:56 -0800 Subject: [PATCH 112/117] tidy: use last_github_commit script instead of target branch (#14052) Signed-off-by: Lizan Zhou --- .devcontainer/devcontainer.json | 3 +++ ci/run_clang_tidy.sh | 4 +--- ci/run_envoy_docker.sh | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 97c37be6a676..b6ee2969559f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,6 +12,9 @@ // Uncomment next line if you have devcontainer.env // "--env-file=.devcontainer/devcontainer.env" ], + "containerEnv": { + "ENVOY_SRCDIR": "${containerWorkspaceFolder}", + }, "settings": { "terminal.integrated.shell.linux": "/bin/bash", "bazel.buildifierFixOnFormat": true, diff --git a/ci/run_clang_tidy.sh b/ci/run_clang_tidy.sh index fe5d5fdd2047..b29a624c3f45 100755 --- a/ci/run_clang_tidy.sh +++ b/ci/run_clang_tidy.sh @@ -113,9 +113,7 @@ elif [[ "${RUN_FULL_CLANG_TIDY}" == 1 ]]; then run_clang_tidy else if [[ -z "${DIFF_REF}" ]]; then - if [[ "${BUILD_REASON}" == "PullRequest" ]]; then - DIFF_REF="remotes/origin/${SYSTEM_PULLREQUEST_TARGETBRANCH}" - elif [[ "${BUILD_REASON}" == *CI ]]; then + if [[ "${BUILD_REASON}" == *CI ]]; then DIFF_REF="HEAD^" else DIFF_REF=$("${ENVOY_SRCDIR}"/tools/git/last_github_commit.sh) diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index e0f204e67fcf..da5115db5274 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -83,7 +83,6 @@ docker run --rm \ -e ENVOY_BUILD_IMAGE \ -e ENVOY_SRCDIR \ -e ENVOY_BUILD_TARGET \ - -e SYSTEM_PULLREQUEST_TARGETBRANCH \ -e SYSTEM_PULLREQUEST_PULLREQUESTNUMBER \ -e GCS_ARTIFACT_BUCKET \ -e GITHUB_TOKEN \ From c39a22e1ee744f4e6031c0b53f7ccd2b6165e29f Mon Sep 17 00:00:00 2001 From: danzh Date: Tue, 17 Nov 2020 10:13:37 -0500 Subject: [PATCH 113/117] quiche: fix stream trailer decoding issue (#13871) Fix decoding trailer implementations in EnvoyQuicServer(Client)Stream for IETF quic. Previously assumption was IETF quic would always deliver trailers after finish delivering body, but it actually always deliver trailers before delivering body. Fix missing stream reset callback when QuicStream::ResetStream() is called. Risk Level: low Testing: Added trailer integration test, and more stream unit tests Part of #12930 Signed-off-by: Dan Zhang --- bazel/external/quiche.BUILD | 29 +++++ .../quiche/envoy_quic_client_stream.cc | 60 +++++---- .../quiche/envoy_quic_client_stream.h | 4 + .../quiche/envoy_quic_server_stream.cc | 63 +++++---- .../quiche/envoy_quic_server_stream.h | 6 +- .../quic_listeners/quiche/envoy_quic_utils.cc | 18 ++- .../quic_listeners/quiche/envoy_quic_utils.h | 5 +- test/extensions/quic_listeners/quiche/BUILD | 3 + .../quiche/envoy_quic_client_stream_test.cc | 120 +++++++++++++----- .../quiche/envoy_quic_server_stream_test.cc | 106 +++++++++++----- .../integration/quic_http_integration_test.cc | 6 + .../quic_listeners/quiche/test_utils.h | 25 ++++ tools/spelling/spelling_dictionary.txt | 1 + 13 files changed, 331 insertions(+), 115 deletions(-) diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index b6b208fc5bbf..a3c430cf9fa4 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -3654,6 +3654,35 @@ envoy_cc_test_library( ], ) +envoy_cc_test_library( + name = "quic_test_tools_qpack_qpack_encoder_test_utils_lib", + srcs = ["quiche/quic/test_tools/qpack/qpack_encoder_test_utils.cc"], + hdrs = ["quiche/quic/test_tools/qpack/qpack_encoder_test_utils.h"], + copts = quiche_copts, + repository = "@envoy", + tags = ["nofips"], + deps = [ + ":quic_core_qpack_qpack_encoder_lib", + ":quic_platform_test", + ":quic_test_tools_qpack_qpack_test_utils_lib", + ":spdy_core_header_block_lib", + ":spdy_core_hpack_hpack_lib", + ], +) + +envoy_cc_test_library( + name = "quic_test_tools_qpack_qpack_test_utils_lib", + srcs = ["quiche/quic/test_tools/qpack/qpack_test_utils.cc"], + hdrs = ["quiche/quic/test_tools/qpack/qpack_test_utils.h"], + copts = quiche_copts, + repository = "@envoy", + tags = ["nofips"], + deps = [ + ":quic_core_qpack_qpack_stream_sender_delegate_lib", + ":quic_platform_test", + ], +) + envoy_cc_test_library( name = "quic_test_tools_sent_packet_manager_peer_lib", srcs = ["quiche/quic/test_tools/quic_sent_packet_manager_peer.cc"], diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc index aeca6efdf198..79e442ea8628 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.cc @@ -72,6 +72,10 @@ Http::Status EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& void EnvoyQuicClientStream::encodeData(Buffer::Instance& data, bool end_stream) { ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, data.length()); + if (data.length() == 0 && !end_stream) { + return; + } + ASSERT(!local_end_stream_); local_end_stream_ = end_stream; // This is counting not serialized bytes in the send buffer. const uint64_t bytes_to_send_old = BufferedDataBytes(); @@ -112,8 +116,6 @@ void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*meta } void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { - // Higher layers expect calling resetStream() to immediately raise reset callbacks. - runResetCallbacks(reason); Reset(envoyResetReasonToQuicRstError(reason)); } @@ -130,13 +132,15 @@ void EnvoyQuicClientStream::switchStreamBlockState(bool should_block) { void EnvoyQuicClientStream::OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { - quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); if (rst_sent()) { return; } - ASSERT(headers_decompressed()); + quic::QuicSpdyStream::OnInitialHeadersComplete(fin, frame_len, header_list); + ASSERT(headers_decompressed() && !header_list.empty()); + response_decoder_->decodeHeaders( - quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); + quicHeadersToEnvoyHeaders(header_list), + /*end_stream=*/fin); if (fin) { end_stream_decoded_ = true; } @@ -165,18 +169,17 @@ void EnvoyQuicClientStream::OnBodyAvailable() { buffer->commit(&slice, 1); MarkConsumed(bytes_read); } + ASSERT(buffer->length() == 0 || !end_stream_decoded_); - // True if no trailer and FIN read. - bool finished_reading = IsDoneReading(); - bool empty_payload_with_fin = buffer->length() == 0 && fin_received(); + bool fin_read_and_no_trailers = IsDoneReading(); // If this call is triggered by an empty frame with FIN which is not from peer // but synthesized by stream itself upon receiving HEADERS with FIN or // TRAILERS, do not deliver end of stream here. Because either decodeHeaders // already delivered it or decodeTrailers will be called. - bool skip_decoding = empty_payload_with_fin && (end_stream_decoded_ || !finished_reading); + bool skip_decoding = (buffer->length() == 0 && !fin_read_and_no_trailers) || end_stream_decoded_; if (!skip_decoding) { - response_decoder_->decodeData(*buffer, finished_reading); - if (finished_reading) { + response_decoder_->decodeData(*buffer, fin_read_and_no_trailers); + if (fin_read_and_no_trailers) { end_stream_decoded_ = true; } } @@ -191,14 +194,10 @@ void EnvoyQuicClientStream::OnBodyAvailable() { return; } - if (!quic::VersionUsesHttp3(transport_version()) && !FinishedReadingTrailers()) { - // For Google QUIC implementation, trailers may arrived earlier and wait to - // be consumed after reading all the body. Consume it here. - // IETF QUIC shouldn't reach here because trailers are sent on same stream. - response_decoder_->decodeTrailers( - spdyHeaderBlockToEnvoyHeaders(received_trailers())); - MarkTrailersConsumed(); - } + // Trailers may arrived earlier and wait to be consumed after reading all the body. Consume it + // here. + maybeDecodeTrailers(); + OnFinRead(); in_decode_data_callstack_ = false; } @@ -207,20 +206,31 @@ void EnvoyQuicClientStream::OnTrailingHeadersComplete(bool fin, size_t frame_len const quic::QuicHeaderList& header_list) { quic::QuicSpdyStream::OnTrailingHeadersComplete(fin, frame_len, header_list); ASSERT(trailers_decompressed()); - if (session()->connection()->connected() && - (quic::VersionUsesHttp3(transport_version()) || sequencer()->IsClosed()) && - !FinishedReadingTrailers()) { - // Before QPack, trailers can arrive before body. Only decode trailers after finishing decoding - // body. + if (session()->connection()->connected() && !rst_sent()) { + maybeDecodeTrailers(); + } +} + +void EnvoyQuicClientStream::maybeDecodeTrailers() { + if (sequencer()->IsClosed() && !FinishedReadingTrailers()) { + ASSERT(!received_trailers().empty()); + // Only decode trailers after finishing decoding body. response_decoder_->decodeTrailers( spdyHeaderBlockToEnvoyHeaders(received_trailers())); + end_stream_decoded_ = true; MarkTrailersConsumed(); } } void EnvoyQuicClientStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { quic::QuicSpdyClientStream::OnStreamReset(frame); - runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); + runResetCallbacks(quicRstErrorToEnvoyRemoteResetReason(frame.error_code)); +} + +void EnvoyQuicClientStream::Reset(quic::QuicRstStreamErrorCode error) { + // Upper layers expect calling resetStream() to immediately raise reset callbacks. + runResetCallbacks(quicRstErrorToEnvoyLocalResetReason(error)); + quic::QuicSpdyClientStream::Reset(error); } void EnvoyQuicClientStream::OnConnectionClosed(quic::QuicErrorCode error, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h index 2446fbb0c3e9..2702b5f8fe79 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_client_stream.h @@ -46,6 +46,7 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, // quic::QuicSpdyStream void OnBodyAvailable() override; void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + void Reset(quic::QuicRstStreamErrorCode error) override; void OnClose() override; void OnCanWrite() override; // quic::Stream @@ -67,6 +68,9 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, private: QuicFilterManagerConnectionImpl* filterManagerConnection(); + // Deliver awaiting trailers if body has been delivered. + void maybeDecodeTrailers(); + Http::ResponseDecoder* response_decoder_{nullptr}; }; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc index d5e5726bf369..c7d25b8d47da 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -84,6 +84,10 @@ void EnvoyQuicServerStream::encodeHeaders(const Http::ResponseHeaderMap& headers void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) { ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, data.length()); + if (data.length() == 0 && !end_stream) { + return; + } + ASSERT(!local_end_stream_); local_end_stream_ = end_stream; // This is counting not serialized bytes in the send buffer. const uint64_t bytes_to_send_old = BufferedDataBytes(); @@ -121,8 +125,6 @@ void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*meta } void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { - // Upper layers expect calling resetStream() to immediately raise reset callbacks. - runResetCallbacks(reason); if (local_end_stream_ && !reading_stopped()) { // This is after 200 early response. Reset with QUIC_STREAM_NO_ERROR instead // of propagating original reset reason. In QUICHE if a stream stops reading @@ -146,10 +148,17 @@ void EnvoyQuicServerStream::switchStreamBlockState(bool should_block) { void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { + // TODO(danzh) Fix in QUICHE. If the stream has been reset in the call stack, + // OnInitialHeadersComplete() shouldn't be called. + if (rst_sent()) { + return; + } quic::QuicSpdyServerStreamBase::OnInitialHeadersComplete(fin, frame_len, header_list); - ASSERT(headers_decompressed()); + ASSERT(headers_decompressed() && !header_list.empty()); + request_decoder_->decodeHeaders( - quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); + quicHeadersToEnvoyHeaders(header_list), + /*end_stream=*/fin); if (fin) { end_stream_decoded_ = true; } @@ -179,17 +188,15 @@ void EnvoyQuicServerStream::OnBodyAvailable() { MarkConsumed(bytes_read); } - // True if no trailer and FIN read. - bool finished_reading = IsDoneReading(); - bool empty_payload_with_fin = buffer->length() == 0 && fin_received(); + bool fin_read_and_no_trailers = IsDoneReading(); // If this call is triggered by an empty frame with FIN which is not from peer // but synthesized by stream itself upon receiving HEADERS with FIN or // TRAILERS, do not deliver end of stream here. Because either decodeHeaders // already delivered it or decodeTrailers will be called. - bool skip_decoding = empty_payload_with_fin && (end_stream_decoded_ || !finished_reading); + bool skip_decoding = (buffer->length() == 0 && !fin_read_and_no_trailers) || end_stream_decoded_; if (!skip_decoding) { - request_decoder_->decodeData(*buffer, finished_reading); - if (finished_reading) { + request_decoder_->decodeData(*buffer, fin_read_and_no_trailers); + if (fin_read_and_no_trailers) { end_stream_decoded_ = true; } } @@ -204,14 +211,10 @@ void EnvoyQuicServerStream::OnBodyAvailable() { return; } - if (!quic::VersionUsesHttp3(transport_version()) && !FinishedReadingTrailers()) { - // For Google QUIC implementation, trailers may arrived earlier and wait to - // be consumed after reading all the body. Consume it here. - // IETF QUIC shouldn't reach here because trailers are sent on same stream. - request_decoder_->decodeTrailers( - spdyHeaderBlockToEnvoyHeaders(received_trailers())); - MarkTrailersConsumed(); - } + // Trailers may arrived earlier and wait to be consumed after reading all the body. Consume it + // here. + maybeDecodeTrailers(); + OnFinRead(); in_decode_data_callstack_ = false; } @@ -219,20 +222,32 @@ void EnvoyQuicServerStream::OnBodyAvailable() { void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, const quic::QuicHeaderList& header_list) { quic::QuicSpdyServerStreamBase::OnTrailingHeadersComplete(fin, frame_len, header_list); - if (session()->connection()->connected() && - (quic::VersionUsesHttp3(transport_version()) || sequencer()->IsClosed()) && - !FinishedReadingTrailers()) { - // Before QPack trailers can arrive before body. Only decode trailers after finishing decoding - // body. + ASSERT(trailers_decompressed()); + if (session()->connection()->connected() && !rst_sent()) { + maybeDecodeTrailers(); + } +} + +void EnvoyQuicServerStream::maybeDecodeTrailers() { + if (sequencer()->IsClosed() && !FinishedReadingTrailers()) { + ASSERT(!received_trailers().empty()); + // Only decode trailers after finishing decoding body. request_decoder_->decodeTrailers( spdyHeaderBlockToEnvoyHeaders(received_trailers())); + end_stream_decoded_ = true; MarkTrailersConsumed(); } } void EnvoyQuicServerStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { quic::QuicSpdyServerStreamBase::OnStreamReset(frame); - runResetCallbacks(quicRstErrorToEnvoyResetReason(frame.error_code)); + runResetCallbacks(quicRstErrorToEnvoyRemoteResetReason(frame.error_code)); +} + +void EnvoyQuicServerStream::Reset(quic::QuicRstStreamErrorCode error) { + // Upper layers expect calling resetStream() to immediately raise reset callbacks. + runResetCallbacks(quicRstErrorToEnvoyLocalResetReason(error)); + quic::QuicSpdyServerStreamBase::Reset(error); } void EnvoyQuicServerStream::OnConnectionClosed(quic::QuicErrorCode error, diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h index b05a707751ff..acd4138db398 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h @@ -49,9 +49,10 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, // quic::QuicSpdyStream void OnBodyAvailable() override; void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + void Reset(quic::QuicRstStreamErrorCode error) override; void OnClose() override; void OnCanWrite() override; - // quic::QuicServerSessionBase + // quic::QuicSpdyServerStreamBase void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; protected: @@ -69,6 +70,9 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, private: QuicFilterManagerConnectionImpl* filterManagerConnection(); + // Deliver awaiting trailers if body has been delivered. + void maybeDecodeTrailers(); + Http::RequestDecoder* request_decoder_{nullptr}; }; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc index 473c01e617d6..bd0a8a657ccd 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -65,20 +65,32 @@ quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetRea case Http::StreamResetReason::LocalRefusedStreamReset: return quic::QUIC_REFUSED_STREAM; case Http::StreamResetReason::ConnectionFailure: + case Http::StreamResetReason::ConnectionTermination: return quic::QUIC_STREAM_CONNECTION_ERROR; case Http::StreamResetReason::LocalReset: return quic::QUIC_STREAM_CANCELLED; - case Http::StreamResetReason::ConnectionTermination: - return quic::QUIC_STREAM_NO_ERROR; default: return quic::QUIC_BAD_APPLICATION_PAYLOAD; } } -Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorCode rst_err) { +Http::StreamResetReason quicRstErrorToEnvoyLocalResetReason(quic::QuicRstStreamErrorCode rst_err) { + switch (rst_err) { + case quic::QUIC_REFUSED_STREAM: + return Http::StreamResetReason::LocalRefusedStreamReset; + case quic::QUIC_STREAM_CONNECTION_ERROR: + return Http::StreamResetReason::ConnectionFailure; + default: + return Http::StreamResetReason::LocalReset; + } +} + +Http::StreamResetReason quicRstErrorToEnvoyRemoteResetReason(quic::QuicRstStreamErrorCode rst_err) { switch (rst_err) { case quic::QUIC_REFUSED_STREAM: return Http::StreamResetReason::RemoteRefusedStreamReset; + case quic::QUIC_STREAM_CONNECTION_ERROR: + return Http::StreamResetReason::ConnectError; default: return Http::StreamResetReason::RemoteReset; } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h index 563e0960cbd9..3c29b28ef21f 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h @@ -66,7 +66,10 @@ spdy::SpdyHeaderBlock envoyHeadersToSpdyHeaderBlock(const Http::HeaderMap& heade quic::QuicRstStreamErrorCode envoyResetReasonToQuicRstError(Http::StreamResetReason reason); // Called when a RST_STREAM frame is received. -Http::StreamResetReason quicRstErrorToEnvoyResetReason(quic::QuicRstStreamErrorCode rst_err); +Http::StreamResetReason quicRstErrorToEnvoyLocalResetReason(quic::QuicRstStreamErrorCode rst_err); + +// Called when a QUIC stack reset the stream. +Http::StreamResetReason quicRstErrorToEnvoyRemoteResetReason(quic::QuicRstStreamErrorCode rst_err); // Called when underlying QUIC connection is closed either locally or by peer. Http::StreamResetReason quicErrorCodeToEnvoyResetReason(quic::QuicErrorCode error); diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 92aad3fe9a2e..4375b705ca7d 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -84,6 +84,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/test_common:utility_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + "@com_googlesource_quiche//:quic_test_tools_qpack_qpack_test_utils_lib", "@com_googlesource_quiche//:quic_test_tools_session_peer_lib", ], ) @@ -105,6 +106,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/test_common:utility_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + "@com_googlesource_quiche//:quic_test_tools_qpack_qpack_test_utils_lib", ], ) @@ -290,5 +292,6 @@ envoy_cc_test_library( "//test/test_common:environment_lib", "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", "@com_googlesource_quiche//:quic_test_tools_first_flight_lib", + "@com_googlesource_quiche//:quic_test_tools_qpack_qpack_encoder_test_utils_lib", ], ) diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc index f90d5c96b512..01871dcfff99 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_client_stream_test.cc @@ -69,6 +69,7 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { response_headers_.OnHeader(":status", "200"); response_headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + spdy_response_headers_[":status"] = "200"; trailers_.OnHeaderBlockStart(); trailers_.OnHeader("key1", "value1"); @@ -77,6 +78,7 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { trailers_.OnHeader(":final-offset", absl::StrCat("", response_body_.length())); } trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + spdy_trailers_["key1"] = "value1"; } void TearDown() override { @@ -87,6 +89,41 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { } } + std::string bodyToStreamPayload(const std::string& body) { + if (!quic::VersionUsesHttp3(quic_version_.transport_version)) { + return body; + } + return bodyToHttp3StreamPayload(body); + } + + size_t receiveResponse(const std::string& payload, bool fin) { + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([](const Http::ResponseHeaderMapPtr& headers, bool) { + EXPECT_EQ("200", headers->getStatusValue()); + })); + + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(payload, buffer.toString()); + EXPECT_EQ(fin, finished_reading); + })); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string data = absl::StrCat(spdyHeaderToHttp3StreamPayload(spdy_response_headers_), + bodyToStreamPayload(payload)); + quic::QuicStreamFrame frame(stream_id_, fin, 0, data); + quic_stream_->OnStreamFrame(frame); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + return data.length(); + } + quic_stream_->OnStreamHeaderList(/*fin=*/false, response_headers_.uncompressed_header_bytes(), + response_headers_); + + quic::QuicStreamFrame frame(stream_id_, fin, 0, payload); + quic_stream_->OnStreamFrame(frame); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + return payload.length(); + } + protected: Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; @@ -107,7 +144,9 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { Http::TestRequestHeaderMapImpl request_headers_; Http::TestRequestTrailerMapImpl request_trailers_; quic::QuicHeaderList response_headers_; + spdy::SpdyHeaderBlock spdy_response_headers_; quic::QuicHeaderList trailers_; + spdy::SpdyHeaderBlock spdy_trailers_; Buffer::OwnedImpl request_body_{"Hello world"}; std::string response_body_{"OK\n"}; }; @@ -115,44 +154,35 @@ class EnvoyQuicClientStreamTest : public testing::TestWithParam { INSTANTIATE_TEST_SUITE_P(EnvoyQuicClientStreamTests, EnvoyQuicClientStreamTest, testing::ValuesIn({true, false})); -TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { - EXPECT_EQ(absl::nullopt, quic_stream_->http1StreamEncoderOptions()); - const auto result = quic_stream_->encodeHeaders(request_headers_, false); +TEST_P(EnvoyQuicClientStreamTest, GetRequestAndHeaderOnlyResponse) { + const auto result = quic_stream_->encodeHeaders(request_headers_, /*end_stream=*/true); EXPECT_TRUE(result.ok()); - quic_stream_->encodeData(request_body_, false); - quic_stream_->encodeTrailers(request_trailers_); - EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/!quic::VersionUsesHttp3( + quic_version_.transport_version))) .WillOnce(Invoke([](const Http::ResponseHeaderMapPtr& headers, bool) { EXPECT_EQ("200", headers->getStatusValue()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/false, response_headers_.uncompressed_header_bytes(), - response_headers_); - EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); - - EXPECT_CALL(stream_decoder_, decodeData(_, _)) - .Times(testing::AtMost(2)) - .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { - EXPECT_EQ(response_body_, buffer.toString()); - EXPECT_FALSE(finished_reading); - })) - // Depends on QUIC version, there may be an empty STREAM_FRAME with FIN. But - // since there is trailers, finished_reading should always be false. - .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { - EXPECT_FALSE(finished_reading); - EXPECT_EQ(0, buffer.length()); - })); - std::string data = response_body_; if (quic::VersionUsesHttp3(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::QuicByteCount data_frame_header_length = - quic::HttpEncoder::SerializeDataFrameHeader(response_body_.length(), &data_buffer); - absl::string_view data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, response_body_); + EXPECT_CALL(stream_decoder_, decodeData(BufferStringEqual(""), /*end_stream=*/true)); + std::string payload = spdyHeaderToHttp3StreamPayload(spdy_response_headers_); + quic::QuicStreamFrame frame(stream_id_, true, 0, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/true, response_headers_.uncompressed_header_bytes(), + response_headers_); } - quic::QuicStreamFrame frame(stream_id_, false, 0, data); - quic_stream_->OnStreamFrame(frame); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); +} + +TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { + EXPECT_EQ(absl::nullopt, quic_stream_->http1StreamEncoderOptions()); + const auto result = quic_stream_->encodeHeaders(request_headers_, false); + EXPECT_TRUE(result.ok()); + quic_stream_->encodeData(request_body_, false); + quic_stream_->encodeTrailers(request_trailers_); + size_t offset = receiveResponse(response_body_, false); EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) .WillOnce(Invoke([](const Http::ResponseTrailerMapPtr& headers) { Http::LowerCaseString key1("key1"); @@ -160,7 +190,22 @@ TEST_P(EnvoyQuicClientStreamTest, PostRequestAndResponse) { EXPECT_EQ("value1", headers->get(key1)[0]->value().getStringView()); EXPECT_TRUE(headers->get(key2).empty()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string more_response_body{"bbb"}; + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(more_response_body, buffer.toString()); + EXPECT_EQ(false, finished_reading); + })); + std::string payload = absl::StrCat(bodyToStreamPayload(more_response_body), + spdyHeaderToHttp3StreamPayload(spdy_trailers_)); + quic::QuicStreamFrame frame(stream_id_, true, offset, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList( + /*fin=*/!quic::VersionUsesHttp3(quic_version_.transport_version), + trailers_.uncompressed_header_bytes(), trailers_); + } } TEST_P(EnvoyQuicClientStreamTest, OutOfOrderTrailers) { @@ -345,5 +390,18 @@ TEST_P(EnvoyQuicClientStreamTest, HeadersContributeToWatermarkIquic) { EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } +TEST_P(EnvoyQuicClientStreamTest, ResetStream) { + EXPECT_CALL(stream_callbacks_, onResetStream(Http::StreamResetReason::LocalReset, _)); + quic_stream_->resetStream(Http::StreamResetReason::LocalReset); + EXPECT_TRUE(quic_stream_->rst_sent()); +} + +TEST_P(EnvoyQuicClientStreamTest, ReceiveResetStream) { + EXPECT_CALL(stream_callbacks_, onResetStream(Http::StreamResetReason::RemoteReset, _)); + quic_stream_->OnStreamReset(quic::QuicRstStreamFrame( + quic::kInvalidControlFrameId, quic_stream_->id(), quic::QUIC_STREAM_NO_ERROR, 0)); + EXPECT_TRUE(quic_stream_->rst_received()); +} + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc index cea6897c4e1e..3e37ba3e1d39 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -89,14 +89,16 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { request_headers_.OnHeader(":path", "/"); request_headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + spdy_request_headers_[":authority"] = host_; + spdy_request_headers_[":method"] = "POST"; + spdy_request_headers_[":path"] = "/"; trailers_.OnHeaderBlockStart(); trailers_.OnHeader("key1", "value1"); - if (!quic::VersionUsesHttp3(quic_version_.transport_version)) { - // ":final-offset" is required and stripped off by quic. - trailers_.OnHeader(":final-offset", absl::StrCat("", request_body_.length())); - } + // ":final-offset" is required and stripped off by quic. + trailers_.OnHeader(":final-offset", absl::StrCat("", request_body_.length())); trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + spdy_trailers_["key1"] = "value1"; } void TearDown() override { @@ -106,27 +108,20 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { } std::string bodyToStreamPayload(const std::string& body) { - std::string data = body; - if (quic::VersionUsesHttp3(quic_version_.transport_version)) { - std::unique_ptr data_buffer; - quic::QuicByteCount data_frame_header_length = - quic::HttpEncoder::SerializeDataFrameHeader(body.length(), &data_buffer); - absl::string_view data_frame_header(data_buffer.get(), data_frame_header_length); - data = absl::StrCat(data_frame_header, body); + if (!quic::VersionUsesHttp3(quic_version_.transport_version)) { + return body; } - return data; + return bodyToHttp3StreamPayload(body); } - size_t sendRequest(const std::string& payload, bool fin, size_t decoder_buffer_high_watermark) { + size_t receiveRequest(const std::string& payload, bool fin, + size_t decoder_buffer_high_watermark) { EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) .WillOnce(Invoke([this](const Http::RequestHeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->getHostValue()); EXPECT_EQ("/", headers->getPathValue()); EXPECT_EQ(Http::Headers::get().MethodValues.Post, headers->getMethodValue()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), - request_headers_); - EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); EXPECT_CALL(stream_decoder_, decodeData(_, _)) .WillOnce(Invoke([&](Buffer::Instance& buffer, bool finished_reading) { @@ -136,10 +131,21 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { quic_stream_->readDisable(true); } })); - std::string data = bodyToStreamPayload(payload); - quic::QuicStreamFrame frame(stream_id_, fin, 0, data); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string data = absl::StrCat(spdyHeaderToHttp3StreamPayload(spdy_request_headers_), + bodyToStreamPayload(payload)); + quic::QuicStreamFrame frame(stream_id_, fin, 0, data); + quic_stream_->OnStreamFrame(frame); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + return data.length(); + } + quic_stream_->OnStreamHeaderList(/*fin=*/false, request_headers_.uncompressed_header_bytes(), + request_headers_); + + quic::QuicStreamFrame frame(stream_id_, fin, 0, payload); quic_stream_->OnStreamFrame(frame); - return data.length(); + EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); + return payload.length(); } protected: @@ -159,9 +165,11 @@ class EnvoyQuicServerStreamTest : public testing::TestWithParam { Http::MockRequestDecoder stream_decoder_; Http::MockStreamCallbacks stream_callbacks_; quic::QuicHeaderList request_headers_; + spdy::SpdyHeaderBlock spdy_request_headers_; Http::TestResponseHeaderMapImpl response_headers_; Http::TestResponseTrailerMapImpl response_trailers_; quic::QuicHeaderList trailers_; + spdy::SpdyHeaderBlock spdy_trailers_; std::string host_{"www.abc.com"}; std::string request_body_{"Hello world"}; }; @@ -178,27 +186,40 @@ TEST_P(EnvoyQuicServerStreamTest, GetRequestAndResponse) { request_headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); - EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/true)) + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/!quic::VersionUsesHttp3( + quic_version_.transport_version))) .WillOnce(Invoke([this](const Http::RequestHeaderMapPtr& headers, bool) { EXPECT_EQ(host_, headers->getHostValue()); EXPECT_EQ("/", headers->getPathValue()); EXPECT_EQ(Http::Headers::get().MethodValues.Get, headers->getMethodValue()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), - request_headers); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + EXPECT_CALL(stream_decoder_, decodeData(BufferStringEqual(""), /*end_stream=*/true)); + spdy::SpdyHeaderBlock spdy_headers; + spdy_headers[":authority"] = host_; + spdy_headers[":method"] = "GET"; + spdy_headers[":path"] = "/"; + std::string payload = spdyHeaderToHttp3StreamPayload(spdy_headers); + quic::QuicStreamFrame frame(stream_id_, true, 0, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/true, request_headers.uncompressed_header_bytes(), + request_headers); + } EXPECT_TRUE(quic_stream_->FinishedReadingHeaders()); quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/true); } TEST_P(EnvoyQuicServerStreamTest, PostRequestAndResponse) { EXPECT_EQ(absl::nullopt, quic_stream_->http1StreamEncoderOptions()); - sendRequest(request_body_, true, request_body_.size() * 2); + receiveRequest(request_body_, true, request_body_.size() * 2); quic_stream_->encodeHeaders(response_headers_, /*end_stream=*/false); quic_stream_->encodeTrailers(response_trailers_); } TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { - sendRequest(request_body_, false, request_body_.size() * 2); + size_t offset = receiveRequest(request_body_, false, request_body_.size() * 2); + EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) .WillOnce(Invoke([](const Http::RequestTrailerMapPtr& headers) { Http::LowerCaseString key1("key1"); @@ -206,7 +227,14 @@ TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { EXPECT_EQ("value1", headers->get(key1)[0]->value().getStringView()); EXPECT_TRUE(headers->get(key2).empty()); })); - quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + if (quic::VersionUsesHttp3(quic_version_.transport_version)) { + std::string payload = spdyHeaderToHttp3StreamPayload(spdy_trailers_); + quic::QuicStreamFrame frame(stream_id_, true, offset, payload); + quic_stream_->OnStreamFrame(frame); + } else { + quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), + trailers_); + } EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); } @@ -228,8 +256,7 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { // Trailer should be delivered to HCM later after body arrives. quic_stream_->OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); - std::string data = bodyToStreamPayload(request_body_); - quic::QuicStreamFrame frame(stream_id_, false, 0, data); + quic::QuicStreamFrame frame(stream_id_, false, 0, request_body_); EXPECT_CALL(stream_decoder_, decodeData(_, _)) .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { EXPECT_EQ(request_body_, buffer.toString()); @@ -246,10 +273,29 @@ TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { quic_stream_->OnStreamFrame(frame); } +TEST_P(EnvoyQuicServerStreamTest, ResetStreamByHCM) { + receiveRequest(request_body_, false, request_body_.size() * 2); + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); + quic_stream_->resetStream(Http::StreamResetReason::LocalReset); + EXPECT_TRUE(quic_stream_->rst_sent()); +} + +TEST_P(EnvoyQuicServerStreamTest, EarlyResponseWithReset) { + receiveRequest(request_body_, false, request_body_.size() * 2); + // Write response headers with FIN before finish receiving request. + quic_stream_->encodeHeaders(response_headers_, true); + // Resetting the stream now means stop reading and sending QUIC_STREAM_NO_ERROR. + EXPECT_CALL(stream_callbacks_, onResetStream(_, _)); + quic_stream_->resetStream(Http::StreamResetReason::LocalReset); + EXPECT_TRUE(quic_stream_->rst_sent()); + EXPECT_TRUE(quic_stream_->reading_stopped()); + EXPECT_EQ(quic::QUIC_STREAM_NO_ERROR, quic_stream_->stream_error()); +} + TEST_P(EnvoyQuicServerStreamTest, ReadDisableUponLargePost) { std::string large_request(1024, 'a'); // Sending such large request will cause read to be disabled. - size_t payload_offset = sendRequest(large_request, false, 512); + size_t payload_offset = receiveRequest(large_request, false, 512); EXPECT_FALSE(quic_stream_->HasBytesToRead()); // Disable reading one more time. quic_stream_->readDisable(true); @@ -325,7 +371,7 @@ TEST_P(EnvoyQuicServerStreamTest, ReadDisableAndReEnableImmediately) { // Tests that the stream with a send buffer whose high limit is 16k and low // limit is 8k sends over 32kB response. TEST_P(EnvoyQuicServerStreamTest, WatermarkSendBuffer) { - sendRequest(request_body_, true, request_body_.size() * 2); + receiveRequest(request_body_, true, request_body_.size() * 2); // Bump connection flow control window large enough not to cause connection // level flow control blocked. @@ -387,7 +433,7 @@ TEST_P(EnvoyQuicServerStreamTest, HeadersContributeToWatermarkIquic) { return; } - sendRequest(request_body_, true, request_body_.size() * 2); + receiveRequest(request_body_, true, request_body_.size() * 2); // Bump connection flow control window large enough not to cause connection level flow control // blocked diff --git a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc index d6121ff2ec6a..4c6309620f3d 100644 --- a/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc +++ b/test/extensions/quic_listeners/quiche/integration/quic_http_integration_test.cc @@ -531,5 +531,11 @@ TEST_P(QuicHttpIntegrationTest, CertVerificationFailure) { EXPECT_EQ(failure_reason, codec_client_->connection()->transportFailureReason()); } +TEST_P(QuicHttpIntegrationTest, RequestResponseWithTrailers) { + config_helper_.addConfigModifier(setEnableUpstreamTrailersHttp1()); + testTrailers(/*request_size=*/10, /*response_size=*/10, /*request_trailers_present=*/true, + /*response_trailers_present=*/true); +} + } // namespace Quic } // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/test_utils.h b/test/extensions/quic_listeners/quiche/test_utils.h index 7f0ea78e8766..f59720130c70 100644 --- a/test/extensions/quic_listeners/quiche/test_utils.h +++ b/test/extensions/quic_listeners/quiche/test_utils.h @@ -13,6 +13,8 @@ #include "quiche/quic/core/quic_utils.h" #include "quiche/quic/test_tools/crypto_test_utils.h" #include "quiche/quic/test_tools/quic_config_peer.h" +#include "quiche/quic/test_tools/qpack/qpack_test_utils.h" +#include "quiche/quic/test_tools/qpack/qpack_encoder_test_utils.h" #if defined(__GNUC__) #pragma GCC diagnostic pop @@ -167,6 +169,29 @@ enum class QuicVersionType { Iquic, }; +std::string spdyHeaderToHttp3StreamPayload(const spdy::SpdyHeaderBlock& header) { + quic::test::NoopQpackStreamSenderDelegate encoder_stream_sender_delegate; + quic::test::NoopDecoderStreamErrorDelegate decoder_stream_error_delegate; + auto qpack_encoder = std::make_unique(&decoder_stream_error_delegate); + qpack_encoder->set_qpack_stream_sender_delegate(&encoder_stream_sender_delegate); + // QpackEncoder does not use the dynamic table by default, + // therefore the value of |stream_id| does not matter. + std::string payload = qpack_encoder->EncodeHeaderList(/* stream_id = */ 0, header, nullptr); + std::unique_ptr headers_buffer; + quic::QuicByteCount headers_frame_header_length = + quic::HttpEncoder::SerializeHeadersFrameHeader(payload.length(), &headers_buffer); + absl::string_view headers_frame_header(headers_buffer.get(), headers_frame_header_length); + return absl::StrCat(headers_frame_header, payload); +} + +std::string bodyToHttp3StreamPayload(const std::string& body) { + std::unique_ptr data_buffer; + quic::QuicByteCount data_frame_header_length = + quic::HttpEncoder::SerializeDataFrameHeader(body.length(), &data_buffer); + absl::string_view data_frame_header(data_buffer.get(), data_frame_header_length); + return absl::StrCat(data_frame_header, body); +} + // A test suite with variation of ip version and a knob to turn on/off IETF QUIC implementation. class QuicMultiVersionTest : public testing::TestWithParam> {}; diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index b0c7a7c98d5e..7911b3134fd7 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -237,6 +237,7 @@ Postgre Postgres Prereq QDCOUNT +QPACK QUIC QoS RAII From fc9f014e70f1ece97dbe3dcafd55935a59f2ffae Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 17 Nov 2020 14:46:38 -0500 Subject: [PATCH 114/117] docs: updating 100-continue docs (#14040) Risk Level: n/a (test, docs only) Testing: new tests of 100-continue + logging Docs Changes: docs of how 100-continue works with logging Release Notes: n/a Fixes #14008 Signed-off-by: Alyssa Wilk --- .../observability/access_log/usage.rst | 4 ++++ test/integration/http_integration.cc | 14 +++++++++++++- test/integration/http_integration.h | 2 +- test/integration/protocol_integration_test.cc | 8 ++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/docs/root/configuration/observability/access_log/usage.rst b/docs/root/configuration/observability/access_log/usage.rst index 75c0630285fa..73ca330f7333 100644 --- a/docs/root/configuration/observability/access_log/usage.rst +++ b/docs/root/configuration/observability/access_log/usage.rst @@ -187,6 +187,10 @@ The following command operators are supported: HTTP response code. Note that a response code of '0' means that the server never sent the beginning of a response. This generally means that the (downstream) client disconnected. + Note that in the case of 100-continue responses, only the response code of the final headers + will be logged. If a 100-continue is followed by a 200, the logged response will be 200. + If a 100-continue results in a disconnect, the 100 will be logged. + TCP Not implemented ("-"). diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index bae6f3ba9acb..198ca37278d1 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -829,7 +829,9 @@ void HttpIntegrationTest::testGrpcRetry() { } void HttpIntegrationTest::testEnvoyHandling100Continue(bool additional_continue_from_upstream, - const std::string& via) { + const std::string& via, + bool disconnect_after_100) { + useAccessLog("%RESPONSE_CODE%"); initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -865,6 +867,15 @@ void HttpIntegrationTest::testEnvoyHandling100Continue(bool additional_continue_ upstream_request_->encode100ContinueHeaders( Http::TestResponseHeaderMapImpl{{":status", "100"}}); } + + if (disconnect_after_100) { + response->waitForContinueHeaders(); + codec_client_->close(); + ASSERT_TRUE(fake_upstream_connection_->close()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("100")); + return; + } + upstream_request_->encodeHeaders(default_response_headers_, false); upstream_request_->encodeData(12, true); @@ -879,6 +890,7 @@ void HttpIntegrationTest::testEnvoyHandling100Continue(bool additional_continue_ } else { EXPECT_EQ(via.c_str(), response->headers().getViaValue()); } + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("200")); } void HttpIntegrationTest::testEnvoyProxying1xx(bool continue_before_upstream_complete, diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index ae7652d59107..cf8afa9cfa6d 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -216,7 +216,7 @@ class HttpIntegrationTest : public BaseIntegrationTest { void testGrpcRetry(); void testEnvoyHandling100Continue(bool additional_continue_from_upstream = false, - const std::string& via = ""); + const std::string& via = "", bool disconnect_after_100 = false); void testEnvoyProxying1xx(bool continue_before_upstream_complete = false, bool with_encoder_filter = false, bool with_multiple_1xx_headers = false); diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 358ec1515cc0..acaf97b0ce64 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -993,6 +993,14 @@ TEST_P(ProtocolIntegrationTest, HittingEncoderFilterLimit) { test_server_->waitForCounterEq("http.config_test.downstream_rq_5xx", 1); } +// The downstream connection is closed when it is read disabled, and on OSX the +// connection error is not detected under these circumstances. +#if !defined(__APPLE__) +TEST_P(ProtocolIntegrationTest, 100ContinueAndClose) { + testEnvoyHandling100Continue(false, "", true); +} +#endif + TEST_P(ProtocolIntegrationTest, EnvoyHandling100Continue) { testEnvoyHandling100Continue(); } TEST_P(ProtocolIntegrationTest, EnvoyHandlingDuplicate100Continue) { From e72d3e58832b96f05cfaa32ad313d2362b10ce9f Mon Sep 17 00:00:00 2001 From: Lizan Zhou Date: Tue, 17 Nov 2020 18:23:44 -0800 Subject: [PATCH 115/117] wasm: make dependency clearer (#14062) Partially addresses #12574, makes split the monolithic wasm_lib into multiple libraries. Clear up dependency metadata due to select. Risk Level: Low Testing: CI Signed-off-by: Lizan Zhou --- bazel/external/proxy_wasm_cpp_host.BUILD | 88 ++++++++++++------- bazel/repository_locations.bzl | 81 ++--------------- source/extensions/common/wasm/BUILD | 5 +- .../common/wasm/wasm_runtime_factory.h | 7 +- source/extensions/wasm_runtime/null/BUILD | 2 +- source/extensions/wasm_runtime/v8/BUILD | 7 +- source/extensions/wasm_runtime/wasmtime/BUILD | 7 +- source/extensions/wasm_runtime/wavm/BUILD | 7 +- 8 files changed, 85 insertions(+), 119 deletions(-) diff --git a/bazel/external/proxy_wasm_cpp_host.BUILD b/bazel/external/proxy_wasm_cpp_host.BUILD index 6157654ed5ea..148635bc099d 100644 --- a/bazel/external/proxy_wasm_cpp_host.BUILD +++ b/bazel/external/proxy_wasm_cpp_host.BUILD @@ -1,10 +1,4 @@ load("@rules_cc//cc:defs.bzl", "cc_library") -load( - "@envoy//bazel:envoy_build_system.bzl", - "envoy_select_wasm_v8", - "envoy_select_wasm_wasmtime", - "envoy_select_wasm_wavm", -) licenses(["notice"]) # Apache 2 @@ -19,29 +13,14 @@ cc_library( ) cc_library( - name = "lib", - # Note that the select cannot appear in the glob. - srcs = glob( - [ - "src/**/*.h", - "src/**/*.cc", - ], - exclude = [ - "src/v8/*.cc", - "src/wavm/*.cc", - "src/wasmtime/*.cc", - ], - ) + envoy_select_wasm_v8(glob([ - "src/v8/*.cc", - ])) + envoy_select_wasm_wavm(glob([ - "src/wavm/*.cc", - ])) + envoy_select_wasm_wasmtime(glob([ - "src/wasmtime/*.cc", - ])), - copts = envoy_select_wasm_wavm([ - '-DWAVM_API=""', - "-Wno-non-virtual-dtor", - "-Wno-old-style-cast", + name = "common_lib", + srcs = glob([ + "src/*.h", + "src/*.cc", + "src/common/*.h", + "src/common/*.cc", + "src/third_party/*.h", + "src/third_party/*.cc", ]), deps = [ ":include", @@ -53,11 +32,54 @@ cc_library( "//external:zlib", "@proxy_wasm_cpp_sdk//:api_lib", "@proxy_wasm_cpp_sdk//:common_lib", - ] + envoy_select_wasm_v8([ + ], +) + +cc_library( + name = "null_lib", + srcs = glob([ + "src/null/*.cc", + ]), + deps = [ + ":common_lib", + ], +) + +cc_library( + name = "v8_lib", + srcs = glob([ + "src/v8/*.cc", + ]), + deps = [ + ":common_lib", "//external:wee8", - ]) + envoy_select_wasm_wavm([ + ], +) + +cc_library( + name = "wavm_lib", + srcs = glob([ + "src/wavm/*.cc", + ]), + copts = [ + '-DWAVM_API=""', + "-Wno-non-virtual-dtor", + "-Wno-old-style-cast", + ], + deps = [ + ":common_lib", "@envoy//bazel/foreign_cc:wavm", - ]) + envoy_select_wasm_wasmtime([ - "@com_github_wasm_c_api//:wasmtime_lib", + ], +) + +cc_library( + name = "wasmtime_lib", + srcs = glob([ + "src/wasmtime/*.h", + "src/wasmtime/*.cc", ]), + deps = [ + ":common_lib", + "@com_github_wasm_c_api//:wasmtime_lib", + ], ) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 1db2cb55e5ab..02d9a62ddeb8 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -587,16 +587,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/llvm-{version}.src.tar.xz"], release_date = "2020-03-23", use_category = ["dataplane_ext"], - extensions = [ - "envoy.access_loggers.wasm", - "envoy.bootstrap.wasm", - "envoy.filters.http.wasm", - "envoy.filters.network.wasm", - "envoy.stat_sinks.wasm", - "envoy.wasm.runtime.v8", - "envoy.wasm.runtime.wavm", - "envoy.wasm.runtime.wasmtime", - ], + extensions = ["envoy.wasm.runtime.wavm"], cpe = "cpe:2.3:a:llvm:*:*", ), com_github_wavm_wavm = dict( @@ -609,16 +600,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/WAVM/WAVM/archive/{version}.tar.gz"], release_date = "2020-09-17", use_category = ["dataplane_ext"], - extensions = [ - "envoy.access_loggers.wasm", - "envoy.bootstrap.wasm", - "envoy.filters.http.wasm", - "envoy.filters.network.wasm", - "envoy.stat_sinks.wasm", - "envoy.wasm.runtime.v8", - "envoy.wasm.runtime.wavm", - "envoy.wasm.runtime.wasmtime", - ], + extensions = ["envoy.wasm.runtime.wavm"], cpe = "cpe:2.3:a:webassembly_virtual_machine_project:webassembly_virtual_machine:*", ), com_github_wasmtime = dict( @@ -631,16 +613,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/bytecodealliance/wasmtime/archive/v{version}.tar.gz"], release_date = "2020-11-05", use_category = ["dataplane_ext"], - extensions = [ - "envoy.access_loggers.wasm", - "envoy.bootstrap.wasm", - "envoy.filters.http.wasm", - "envoy.filters.network.wasm", - "envoy.stat_sinks.wasm", - "envoy.wasm.runtime.v8", - "envoy.wasm.runtime.wavm", - "envoy.wasm.runtime.wasmtime", - ], + extensions = ["envoy.wasm.runtime.wasmtime"], cpe = "N/A", ), com_github_wasm_c_api = dict( @@ -655,16 +628,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( urls = ["https://github.com/WebAssembly/wasm-c-api/archive/{version}.tar.gz"], release_date = "2019-11-14", use_category = ["dataplane_ext"], - extensions = [ - "envoy.access_loggers.wasm", - "envoy.bootstrap.wasm", - "envoy.filters.http.wasm", - "envoy.filters.network.wasm", - "envoy.stat_sinks.wasm", - "envoy.wasm.runtime.v8", - "envoy.wasm.runtime.wavm", - "envoy.wasm.runtime.wasmtime", - ], + extensions = ["envoy.wasm.runtime.wasmtime"], cpe = "N/A", ), io_opencensus_cpp = dict( @@ -709,16 +673,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( sha256 = "f22734640e0515bc34d1ca3772513aef24374fafa44d0489d3a9a57cadec69fb", urls = ["https://storage.googleapis.com/envoyproxy-wee8/wee8-{version}.tar.gz"], use_category = ["dataplane_ext"], - extensions = [ - "envoy.access_loggers.wasm", - "envoy.bootstrap.wasm", - "envoy.filters.http.wasm", - "envoy.filters.network.wasm", - "envoy.stat_sinks.wasm", - "envoy.wasm.runtime.v8", - "envoy.wasm.runtime.wavm", - "envoy.wasm.runtime.wasmtime", - ], + extensions = ["envoy.wasm.runtime.v8"], release_date = "2020-10-27", cpe = "cpe:2.3:a:google:v8:*", ), @@ -765,10 +720,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.network.rbac", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", - "envoy.wasm.runtime.null", - "envoy.wasm.runtime.v8", - "envoy.wasm.runtime.wavm", - "envoy.wasm.runtime.wasmtime", ], release_date = "2020-10-25", cpe = "N/A", @@ -788,10 +739,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", - "envoy.wasm.runtime.null", - "envoy.wasm.runtime.v8", - "envoy.wasm.runtime.wavm", - "envoy.wasm.runtime.wasmtime", ], release_date = "2020-04-02", cpe = "N/A", @@ -936,17 +883,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( strip_prefix = "rules_rust-{version}", urls = ["https://github.com/bazelbuild/rules_rust/archive/{version}.tar.gz"], use_category = ["dataplane_ext"], - extensions = [ - "envoy.access_loggers.wasm", - "envoy.bootstrap.wasm", - "envoy.filters.http.wasm", - "envoy.filters.network.wasm", - "envoy.stat_sinks.wasm", - "envoy.wasm.runtime.null", - "envoy.wasm.runtime.v8", - "envoy.wasm.runtime.wavm", - "envoy.wasm.runtime.wasmtime", - ], + extensions = ["envoy.wasm.runtime.wasmtime"], release_date = "2020-10-21", cpe = "N/A", ), @@ -966,9 +903,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", - "envoy.wasm.runtime.v8", - "envoy.wasm.runtime.wavm", - "envoy.wasm.runtime.wasmtime", ], release_date = "2019-06-21", cpe = "N/A", @@ -988,9 +922,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.filters.http.wasm", "envoy.filters.network.wasm", "envoy.stat_sinks.wasm", - "envoy.wasm.runtime.v8", - "envoy.wasm.runtime.wavm", - "envoy.wasm.runtime.wasmtime", ], release_date = "2018-12-18", cpe = "N/A", diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index 2cbdfdc06908..0f351b56b5bd 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -22,7 +22,7 @@ envoy_cc_library( "wasm_runtime_factory.h", ], deps = [ - ":wasm_hdr", + "@proxy_wasm_cpp_host//:include", ], ) @@ -118,7 +118,8 @@ envoy_cc_library( "@com_google_cel_cpp//eval/public:cel_value", "@com_google_cel_cpp//eval/public:value_export_util", "@envoy_api//envoy/extensions/wasm/v3:pkg_cc_proto", - "@proxy_wasm_cpp_host//:lib", + "@proxy_wasm_cpp_host//:common_lib", + "@proxy_wasm_cpp_host//:null_lib", ] + select( { "//bazel:windows_x86_64": [], diff --git a/source/extensions/common/wasm/wasm_runtime_factory.h b/source/extensions/common/wasm/wasm_runtime_factory.h index de00551b4443..d0d589705f91 100644 --- a/source/extensions/common/wasm/wasm_runtime_factory.h +++ b/source/extensions/common/wasm/wasm_runtime_factory.h @@ -1,14 +1,17 @@ #pragma once -#include +#include "envoy/common/pure.h" -#include "extensions/common/wasm/wasm_vm.h" +#include "absl/strings/string_view.h" +#include "include/proxy-wasm/wasm_vm.h" namespace Envoy { namespace Extensions { namespace Common { namespace Wasm { +using WasmVmPtr = std::unique_ptr; + class WasmRuntimeFactory { public: virtual ~WasmRuntimeFactory() = default; diff --git a/source/extensions/wasm_runtime/null/BUILD b/source/extensions/wasm_runtime/null/BUILD index fce183d76032..63969c889c25 100644 --- a/source/extensions/wasm_runtime/null/BUILD +++ b/source/extensions/wasm_runtime/null/BUILD @@ -15,7 +15,7 @@ envoy_cc_extension( status = "alpha", deps = [ "//include/envoy/registry", - "//source/extensions/common/wasm:wasm_hdr", "//source/extensions/common/wasm:wasm_runtime_factory_interface", + "@proxy_wasm_cpp_host//:null_lib", ], ) diff --git a/source/extensions/wasm_runtime/v8/BUILD b/source/extensions/wasm_runtime/v8/BUILD index 55d14af61a57..8785616044d7 100644 --- a/source/extensions/wasm_runtime/v8/BUILD +++ b/source/extensions/wasm_runtime/v8/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_extension", "envoy_extension_package", ) +load("//bazel:envoy_select.bzl", "envoy_select_wasm_v8") licenses(["notice"]) # Apache 2 @@ -15,6 +16,8 @@ envoy_cc_extension( status = "alpha", deps = [ "//include/envoy/registry", - "//source/extensions/common/wasm:wasm_lib", - ], + "//source/extensions/common/wasm:wasm_runtime_factory_interface", + ] + envoy_select_wasm_v8([ + "@proxy_wasm_cpp_host//:v8_lib", + ]), ) diff --git a/source/extensions/wasm_runtime/wasmtime/BUILD b/source/extensions/wasm_runtime/wasmtime/BUILD index 55d14af61a57..d0adea5660c6 100644 --- a/source/extensions/wasm_runtime/wasmtime/BUILD +++ b/source/extensions/wasm_runtime/wasmtime/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_extension", "envoy_extension_package", ) +load("//bazel:envoy_select.bzl", "envoy_select_wasm_wasmtime") licenses(["notice"]) # Apache 2 @@ -15,6 +16,8 @@ envoy_cc_extension( status = "alpha", deps = [ "//include/envoy/registry", - "//source/extensions/common/wasm:wasm_lib", - ], + "//source/extensions/common/wasm:wasm_runtime_factory_interface", + ] + envoy_select_wasm_wasmtime([ + "@proxy_wasm_cpp_host//:wasmtime_lib", + ]), ) diff --git a/source/extensions/wasm_runtime/wavm/BUILD b/source/extensions/wasm_runtime/wavm/BUILD index 55d14af61a57..c9a5153efe31 100644 --- a/source/extensions/wasm_runtime/wavm/BUILD +++ b/source/extensions/wasm_runtime/wavm/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_extension", "envoy_extension_package", ) +load("//bazel:envoy_select.bzl", "envoy_select_wasm_wavm") licenses(["notice"]) # Apache 2 @@ -15,6 +16,8 @@ envoy_cc_extension( status = "alpha", deps = [ "//include/envoy/registry", - "//source/extensions/common/wasm:wasm_lib", - ], + "//source/extensions/common/wasm:wasm_runtime_factory_interface", + ] + envoy_select_wasm_wavm([ + "@proxy_wasm_cpp_host//:wavm_lib", + ]), ) From 52908446453ac40c652f2de4441a4f38e0364d24 Mon Sep 17 00:00:00 2001 From: asraa Date: Wed, 18 Nov 2020 07:54:49 -0500 Subject: [PATCH 116/117] [http1] fix H/1 response pipelining (#13983) * Fix HTTP/1 response pipelining. Extraneous data after a response complete when the connection is still open should not be processed. Signed-off-by: Asra Ali --- source/common/http/codec_client.cc | 4 +++ source/common/http/http1/codec_impl.cc | 13 ++++++++ source/common/http/http1/codec_impl.h | 1 + source/common/http/http1/codec_impl_legacy.cc | 10 ++++++ source/common/http/http1/codec_impl_legacy.h | 1 + test/common/http/codec_client_test.cc | 4 +++ test/integration/h1_corpus/alloc_headers | 12 +++++++ test/integration/integration_test.cc | 32 +++++++++++++++++++ 8 files changed, 77 insertions(+) create mode 100644 test/integration/h1_corpus/alloc_headers diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 0da14ae6992a..96c2769ac798 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -138,6 +138,10 @@ void CodecClient::onData(Buffer::Instance& data) { host_->cluster().stats().upstream_cx_protocol_error_.inc(); } } + + // All data should be consumed at this point if the connection remains open. + ASSERT(data.length() == 0 || connection_->state() != Network::Connection::State::Open, + absl::StrCat("extraneous bytes after response complete: ", data.length())); } CodecClientProd::CodecClientProd(Type type, Network::ClientConnectionPtr&& connection, diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index 6a803cca1e6c..f8c530cbcf48 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -552,6 +552,16 @@ Http::Status ConnectionImpl::dispatch(Buffer::Instance& data) { [&](Buffer::Instance& data) -> Http::Status { return innerDispatch(data); }, data); } +Http::Status ClientConnectionImpl::dispatch(Buffer::Instance& data) { + Http::Status status = ConnectionImpl::dispatch(data); + if (status.ok() && data.length() > 0) { + // The HTTP/1.1 codec pauses dispatch after a single response is complete. Extraneous data + // after a response is complete indicates an error. + return codecProtocolError("http/1.1 protocol error: extraneous data after response complete"); + } + return status; +} + Http::Status ConnectionImpl::innerDispatch(Buffer::Instance& data) { ENVOY_CONN_LOG(trace, "parsing {} bytes", connection_, data.length()); // Make sure that dispatching_ is set to false after dispatching, even when @@ -1285,6 +1295,9 @@ void ClientConnectionImpl::onMessageComplete() { pending_response_.reset(); headers_or_trailers_.emplace(nullptr); } + + // Pause the parser after a response is complete. Any remaining data indicates an error. + http_parser_pause(&parser_, 1); } void ClientConnectionImpl::onResetStream(StreamResetReason reason) { diff --git a/source/common/http/http1/codec_impl.h b/source/common/http/http1/codec_impl.h index 867cd603f57a..f63b777c6a44 100644 --- a/source/common/http/http1/codec_impl.h +++ b/source/common/http/http1/codec_impl.h @@ -580,6 +580,7 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { bool cannotHaveBody(); // ConnectionImpl + Http::Status dispatch(Buffer::Instance& data) override; void onEncodeComplete() override {} Status onMessageBegin() override { return okStatus(); } Status onUrl(const char*, size_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } diff --git a/source/common/http/http1/codec_impl_legacy.cc b/source/common/http/http1/codec_impl_legacy.cc index 474fd7ce1b0c..d82a6ae15b2f 100644 --- a/source/common/http/http1/codec_impl_legacy.cc +++ b/source/common/http/http1/codec_impl_legacy.cc @@ -526,6 +526,16 @@ Http::Status ConnectionImpl::dispatch(Buffer::Instance& data) { [&](Buffer::Instance& data) -> Http::Status { return innerDispatch(data); }, data); } +Http::Status ClientConnectionImpl::dispatch(Buffer::Instance& data) { + Http::Status status = ConnectionImpl::dispatch(data); + if (status.ok() && data.length() > 0) { + // The HTTP/1.1 codec pauses dispatch after a single response is complete. Extraneous data + // after a response is complete indicates an error. + return codecProtocolError("http/1.1 protocol error: extraneous data after response complete"); + } + return status; +} + Http::Status ConnectionImpl::innerDispatch(Buffer::Instance& data) { ENVOY_CONN_LOG(trace, "parsing {} bytes", connection_, data.length()); ASSERT(buffered_body_.length() == 0); diff --git a/source/common/http/http1/codec_impl_legacy.h b/source/common/http/http1/codec_impl_legacy.h index 48382473f0bd..6510116d7a36 100644 --- a/source/common/http/http1/codec_impl_legacy.h +++ b/source/common/http/http1/codec_impl_legacy.h @@ -555,6 +555,7 @@ class ClientConnectionImpl : public ClientConnection, public ConnectionImpl { bool cannotHaveBody(); // ConnectionImpl + Http::Status dispatch(Buffer::Instance& data) override; void onEncodeComplete() override {} void onMessageBegin() override {} void onUrl(const char*, size_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } diff --git a/test/common/http/codec_client_test.cc b/test/common/http/codec_client_test.cc index 48a1dad0da8e..25ba456a87fe 100644 --- a/test/common/http/codec_client_test.cc +++ b/test/common/http/codec_client_test.cc @@ -374,6 +374,7 @@ TEST_P(CodecNetworkTest, SendData) { upstream_connection_->write(data, false); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { EXPECT_EQ(full_data, data.toString()); + data.drain(data.length()); dispatcher_->exit(); return Http::okStatus(); })); @@ -397,10 +398,12 @@ TEST_P(CodecNetworkTest, SendHeadersAndClose) { .Times(2) .WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { EXPECT_EQ(full_data, data.toString()); + data.drain(data.length()); return Http::okStatus(); })) .WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { EXPECT_EQ("", data.toString()); + data.drain(data.length()); return Http::okStatus(); })); // Because the headers are not complete, the disconnect will reset the stream. @@ -435,6 +438,7 @@ TEST_P(CodecNetworkTest, SendHeadersAndCloseUnderReadDisable) { .Times(2) .WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { EXPECT_EQ(full_data, data.toString()); + data.drain(data.length()); return Http::okStatus(); })) .WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { diff --git a/test/integration/h1_corpus/alloc_headers b/test/integration/h1_corpus/alloc_headers new file mode 100644 index 000000000000..3e8a5e62f1a8 --- /dev/null +++ b/test/integration/h1_corpus/alloc_headers @@ -0,0 +1,12 @@ +events { + downstream_send_bytes: "POST /test/long/urlaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaattttttaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa448aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa HTTP/1.1\r\nhost: host\r\nx-lyft-user-id: 0\r\nx-forwaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaarded-for: -1113144117.0.0.1\r\ntransfer-encoding: chunked\r\n\r\n400\r\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaiaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234\234aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\344aaaaaaaaaaaaaaaaaaaaaa\r\n0\r\n\r\n" +} +events { + upstream_send_bytes: "HTTP/1.1 454 \002\002\002\002\002\002\002\002\002\002\002OK\r\ntransfer-encoding: chunked\r\n\r\n200\r\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_aaaaaaaaaaaaaaaaFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n0\r\n\r\n" +} +events { + upstream_send_bytes: "HTTP/1.1 654 \002\002\002\002\002\002\002\002\002\002\002OK\r\ntransfer-encoding: chunked\r\n\r\n200\r\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_aaaaaaaaaaaaaaaaFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n0\r\n\r\n" +} +events { + upstream_send_bytes: "HTTP/1.1 454 \002\002\002\002\002\002\002\002\002\002\002OK\r\ntransfer-encoding: chunked\r\n\r\n200\r\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa_aaaaaaaaaaaaaaaaFaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n0\r\n\r\n" +} diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index f76ad6f889e2..944ccfe81343 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -483,6 +483,38 @@ TEST_P(IntegrationTest, TestSmuggling) { } } +TEST_P(IntegrationTest, TestPipelinedResponses) { + initialize(); + auto tcp_client = makeTcpConnection(lookupPort("http")); + + ASSERT_TRUE(tcp_client->write( + "POST /test/long/url HTTP/1.1\r\nHost: host\r\ntransfer-encoding: chunked\r\n\r\n")); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + std::string data; + ASSERT_TRUE(fake_upstream_connection->waitForData( + FakeRawConnection::waitForInexactMatch("\r\n\r\n"), &data)); + ASSERT_THAT(data, HasSubstr("POST")); + + ASSERT_TRUE(fake_upstream_connection->write( + "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n0\r\n\r\n" + "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n0\r\n\r\n" + "HTTP/1.1 200 OK\r\ntransfer-encoding: chunked\r\n\r\n0\r\n\r\n")); + + tcp_client->waitForData("0\r\n\r\n", false); + std::string response = tcp_client->data(); + + EXPECT_THAT(response, HasSubstr("HTTP/1.1 200 OK\r\n")); + EXPECT_THAT(response, HasSubstr("transfer-encoding: chunked\r\n")); + EXPECT_THAT(response, EndsWith("0\r\n\r\n")); + + ASSERT_TRUE(fake_upstream_connection->close()); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + tcp_client->close(); + EXPECT_EQ(test_server_->counter("cluster.cluster_0.upstream_cx_protocol_error")->value(), 1); +} + TEST_P(IntegrationTest, TestServerAllowChunkedLength) { config_helper_.addConfigModifier( [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& From 9a65e98bac820b474f4d471a31cdb1a28210d5f7 Mon Sep 17 00:00:00 2001 From: Alex Konradi Date: Wed, 18 Nov 2020 10:23:04 -0500 Subject: [PATCH 117/117] vrp: allow supervisord to open its log file (#14066) Change the default location of the log file and give supervisord permissions to write to it. Signed-off-by: Alex Konradi --- ci/Dockerfile-envoy-google-vrp | 3 +++ configs/google-vrp/supervisor.conf | 1 + 2 files changed, 4 insertions(+) diff --git a/ci/Dockerfile-envoy-google-vrp b/ci/Dockerfile-envoy-google-vrp index 802e148851e0..abc84f1269ab 100644 --- a/ci/Dockerfile-envoy-google-vrp +++ b/ci/Dockerfile-envoy-google-vrp @@ -16,6 +16,9 @@ ADD configs/google-vrp/supervisor.conf /etc/supervisor.conf ADD test/config/integration/certs/serverkey.pem /etc/envoy/certs/serverkey.pem ADD test/config/integration/certs/servercert.pem /etc/envoy/certs/servercert.pem # ADD %local envoy bin% /usr/local/bin/envoy +RUN chmod 777 /var/log/supervisor +RUN chmod a+r /etc/supervisor.conf /etc/envoy/* /etc/envoy/certs/* +RUN chmod a+rx /usr/local/bin/launch_envoy.sh EXPOSE 10000 EXPOSE 10001 diff --git a/configs/google-vrp/supervisor.conf b/configs/google-vrp/supervisor.conf index e019581d079c..1e1d09f33660 100644 --- a/configs/google-vrp/supervisor.conf +++ b/configs/google-vrp/supervisor.conf @@ -1,5 +1,6 @@ [supervisord] nodaemon=true +logfile=/var/log/supervisor/supervisord.log [program:envoy-edge] command=launch_envoy.sh -c /etc/envoy/envoy-edge.yaml %(ENV_ENVOY_EDGE_EXTRA_ARGS)s