From 463e2299af804507a84e9ff20753f5607635a818 Mon Sep 17 00:00:00 2001 From: Florin Coras Date: Thu, 3 Mar 2022 11:29:29 -0800 Subject: [PATCH 01/68] vcl: update to vpp fe6d8a370 (#20186) Signed-off-by: Florin Coras --- 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 efa24cb15710..aae900478d48 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1112,13 +1112,13 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "VPP Comms Library", project_desc = "FD.io Vector Packet Processor (VPP) Comms Library", project_url = "https://fd.io/", - version = "7c3275e84b64ade4e20d00e4457bd4e437b1894f", - sha256 = "d456d37bbb7f90ec1ef166c1387e788b4c3078d38303f12ab41f1d0ac1a1cfc0", + version = "fe6d8a37062a28f28ee63c3e9177d18c10ed96e1", + sha256 = "2ae11c852f8c1967463251dcf9de983f9e4821abaeea6989fd2c1fb3020814b7", strip_prefix = "vpp-{version}", urls = ["https://github.com/FDio/vpp/archive/{version}.tar.gz"], use_category = ["other"], extensions = ["envoy.bootstrap.vcl"], - release_date = "2021-12-10", + release_date = "2022-03-02", cpe = "N/A", ), ) From 2002c87b2bec6c5c2db27d09a1c906e6a41ddb31 Mon Sep 17 00:00:00 2001 From: xuhj Date: Fri, 4 Mar 2022 03:43:46 +0800 Subject: [PATCH 02/68] listener: ensure `getBalancedHandlerByAddress` works with ipv4_compat (#19716) Signed-off-by: He Jie Xu --- docs/root/version_history/current.rst | 1 + envoy/network/address.h | 11 +- source/common/network/address_impl.cc | 37 +- source/common/network/address_impl.h | 3 +- source/common/runtime/runtime_features.cc | 2 + source/server/connection_handler_impl.cc | 57 ++- source/server/listener_impl.cc | 14 + test/server/connection_handler_test.cc | 586 ++++++++++++++++++++++ test/server/listener_manager_impl_test.cc | 71 +++ 9 files changed, 767 insertions(+), 15 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index c7ac40933be8..5be100a55e4a 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -20,6 +20,7 @@ Minor Behavior Changes * http: avoiding delay-close for HTTP/1.0 responses framed by connection: close as well as HTTP/1.1 if the request is fully read. This means for responses to such requests, the FIN will be sent immediately after the response. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.skip_delay_close`` to false. If clients are are seen to be receiving sporadic partial responses and flipping this flag fixes it, please notify the project immediately. * http: now the max concurrent streams of http2 connection can not only be adjusted down according to the SETTINGS frame but also can be adjusted up, of course, it can not exceed the configured upper bounds. This fix is guarded by ``envoy.reloadable_features.http2_allow_capacity_increase_by_settings``. * http: when writing custom filters, `injectEncodedDataToFilterChain` and `injectDecodedDataToFilterChain` now trigger sending of headers if they were not yet sent due to `StopIteration`. Previously, calling one of the inject functions in that state would trigger an assertion. See issue #19891 for more details. +* listener: the :ref:`ipv4_compat ` flag can only be set on Ipv6 address and Ipv4-mapped Ipv6 address. A runtime guard is added ``envoy.reloadable_features.strict_check_on_ipv4_compat`` and the default is true. * perf: ssl contexts are now tracked without scan based garbage collection and greatly improved the performance on secret update. * router: record upstream request timeouts for all the cases and not just for those requests which are awaiting headers. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.do_not_await_headers_on_upstream_timeout_to_emit_stats`` to false. * sip-proxy: add customized affinity support by adding :ref:`tra_service_config ` and :ref:`customized_affinity `. diff --git a/envoy/network/address.h b/envoy/network/address.h index 2b50b0728168..59f1bd54e0d3 100644 --- a/envoy/network/address.h +++ b/envoy/network/address.h @@ -21,6 +21,9 @@ class SocketInterface; namespace Address { +class Instance; +using InstanceConstSharedPtr = std::shared_ptr; + /** * Interface for an Ipv4 address. */ @@ -50,6 +53,12 @@ class Ipv6 { * @return true if address is Ipv6 and Ipv4 compatibility is disabled, false otherwise */ virtual bool v6only() const PURE; + + /** + * @return Ipv4 address from Ipv4-compatible Ipv6 address. Return `nullptr` + * if the Ipv6 address isn't Ipv4 mapped. + */ + virtual InstanceConstSharedPtr v4CompatibleAddress() const PURE; }; enum class IpVersion { v4, v6 }; // NOLINT(readability-identifier-naming) @@ -207,8 +216,6 @@ class Instance { virtual const Network::SocketInterface& socketInterface() const PURE; }; -using InstanceConstSharedPtr = std::shared_ptr; - } // namespace Address } // namespace Network } // namespace Envoy diff --git a/source/common/network/address_impl.cc b/source/common/network/address_impl.cc index 84775e01bfdb..56f3e0af9e49 100644 --- a/source/common/network/address_impl.cc +++ b/source/common/network/address_impl.cc @@ -46,6 +46,18 @@ InstanceConstSharedPtr throwOnError(StatusOr address) { } // namespace +void ipv6ToIpv4CompatibleAddress(const struct sockaddr_in6* sin6, struct sockaddr_in* sin) { +#if defined(__APPLE__) + *sin = {{}, AF_INET, sin6->sin6_port, {sin6->sin6_addr.__u6_addr.__u6_addr32[3]}, {}}; +#elif defined(WIN32) + struct in_addr in_v4 = {}; + in_v4.S_un.S_addr = reinterpret_cast(sin6->sin6_addr.u.Byte)[3]; + *sin = {AF_INET, sin6->sin6_port, in_v4, {}}; +#else + *sin = {AF_INET, sin6->sin6_port, {sin6->sin6_addr.s6_addr32[3]}, {}}; +#endif +} + StatusOr addressFromSockAddr(const sockaddr_storage& ss, socklen_t ss_len, bool v6only) { RELEASE_ASSERT(ss_len == 0 || static_cast(ss_len) >= sizeof(sa_family_t), ""); @@ -61,16 +73,8 @@ StatusOr addressFromSockAddr(const sockaddr_sto const struct sockaddr_in6* sin6 = reinterpret_cast(&ss); ASSERT(AF_INET6 == sin6->sin6_family); if (!v6only && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { -#if defined(__APPLE__) - struct sockaddr_in sin = { - {}, AF_INET, sin6->sin6_port, {sin6->sin6_addr.__u6_addr.__u6_addr32[3]}, {}}; -#elif defined(WIN32) - struct in_addr in_v4 = {}; - in_v4.S_un.S_addr = reinterpret_cast(sin6->sin6_addr.u.Byte)[3]; - struct sockaddr_in sin = {AF_INET, sin6->sin6_port, in_v4, {}}; -#else - struct sockaddr_in sin = {AF_INET, sin6->sin6_port, {sin6->sin6_addr.s6_addr32[3]}, {}}; -#endif + struct sockaddr_in sin; + ipv6ToIpv4CompatibleAddress(sin6, &sin); return Address::InstanceFactory::createInstancePtr(&sin); } else { return Address::InstanceFactory::createInstancePtr(*sin6, v6only); @@ -234,6 +238,16 @@ std::string Ipv6Instance::Ipv6Helper::makeFriendlyAddress() const { return ptr; } +InstanceConstSharedPtr Ipv6Instance::Ipv6Helper::v4CompatibleAddress() const { + if (!v6only_ && IN6_IS_ADDR_V4MAPPED(&address_.sin6_addr)) { + struct sockaddr_in sin; + ipv6ToIpv4CompatibleAddress(&address_, &sin); + auto addr = Address::InstanceFactory::createInstancePtr(&sin); + return addr.ok() ? addr.value() : nullptr; + } + return nullptr; +} + Ipv6Instance::Ipv6Instance(const sockaddr_in6& address, bool v6only, const SocketInterface* sock_interface) : InstanceBase(Type::Ip, sockInterfaceOrDefault(sock_interface)) { @@ -245,7 +259,7 @@ Ipv6Instance::Ipv6Instance(const std::string& address, const SocketInterface* so : Ipv6Instance(address, 0, sockInterfaceOrDefault(sock_interface)) {} Ipv6Instance::Ipv6Instance(const std::string& address, uint32_t port, - const SocketInterface* sock_interface) + const SocketInterface* sock_interface, bool v6only) : InstanceBase(Type::Ip, sockInterfaceOrDefault(sock_interface)) { throwOnError(validateProtocolSupported()); ip_.ipv6_.address_.sin6_family = AF_INET6; @@ -260,6 +274,7 @@ Ipv6Instance::Ipv6Instance(const std::string& address, uint32_t port, // Just in case address is in a non-canonical format, format from network address. ip_.friendly_address_ = ip_.ipv6_.makeFriendlyAddress(); friendly_name_ = fmt::format("[{}]:{}", ip_.friendly_address_, ip_.port()); + ip_.ipv6_.v6only_ = v6only; } Ipv6Instance::Ipv6Instance(uint32_t port, const SocketInterface* sock_interface) diff --git a/source/common/network/address_impl.h b/source/common/network/address_impl.h index 2ade85fc8998..cf8ddaee131f 100644 --- a/source/common/network/address_impl.h +++ b/source/common/network/address_impl.h @@ -196,7 +196,7 @@ class Ipv6Instance : public InstanceBase { * Construct from a string IPv6 address such as "12:34::5" as well as a port. */ Ipv6Instance(const std::string& address, uint32_t port, - const SocketInterface* sock_interface = nullptr); + const SocketInterface* sock_interface = nullptr, bool v6only = true); /** * Construct from a port. The IPv6 address will be set to "any" and is suitable for binding @@ -232,6 +232,7 @@ class Ipv6Instance : public InstanceBase { absl::uint128 address() const override; bool v6only() const override; uint32_t port() const; + InstanceConstSharedPtr v4CompatibleAddress() const override; std::string makeFriendlyAddress() const; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index ab63a8c5a79a..d996321f1ae9 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -47,6 +47,7 @@ RUNTIME_GUARD(envoy_reloadable_features_remove_legacy_json); RUNTIME_GUARD(envoy_reloadable_features_sanitize_http_header_referer); RUNTIME_GUARD(envoy_reloadable_features_skip_delay_close); RUNTIME_GUARD(envoy_reloadable_features_skip_dispatching_frames_for_closed_connection); +RUNTIME_GUARD(envoy_reloadable_features_strict_check_on_ipv4_compat); RUNTIME_GUARD(envoy_reloadable_features_support_locality_update_on_eds_cluster_endpoints); RUNTIME_GUARD(envoy_reloadable_features_test_feature_true); RUNTIME_GUARD(envoy_reloadable_features_udp_listener_updates_filter_chain_in_place); @@ -163,6 +164,7 @@ constexpr absl::Flag* runtime_features[] = { &FLAGS_envoy_reloadable_features_sanitize_http_header_referer, &FLAGS_envoy_reloadable_features_skip_delay_close, &FLAGS_envoy_reloadable_features_skip_dispatching_frames_for_closed_connection, + &FLAGS_envoy_reloadable_features_strict_check_on_ipv4_compat, &FLAGS_envoy_reloadable_features_support_locality_update_on_eds_cluster_endpoints, &FLAGS_envoy_reloadable_features_udp_listener_updates_filter_chain_in_place, &FLAGS_envoy_reloadable_features_unified_mux, diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc index 4aeafd42cfe7..b10884b703cc 100644 --- a/source/server/connection_handler_impl.cc +++ b/source/server/connection_handler_impl.cc @@ -95,6 +95,35 @@ void ConnectionHandlerImpl::addListener(absl::optional overridden_list details->typed_listener_)) { tcp_listener_map_by_address_.insert_or_assign( config.listenSocketFactory().localAddress()->asStringView(), details); + + auto& address = details->address_; + // If the address is Ipv6 and isn't v6only, parse out the ipv4 compatible address from the Ipv6 + // address and put an item to the map. Then this allows the `getBalancedHandlerByAddress` + // can match the Ipv4 request to Ipv4-mapped address also. + if (address->type() == Network::Address::Type::Ip && + address->ip()->version() == Network::Address::IpVersion::v6 && + !address->ip()->ipv6()->v6only()) { + if (address->ip()->isAnyAddress()) { + // Since both "::" with ipv4_compat and "0.0.0.0" can be supported. + // If there already one and it isn't shutdown for compatible addr, + // then won't insert a new one. + auto ipv4_any_address = Network::Address::Ipv4Instance(address->ip()->port()).asString(); + auto ipv4_any_listener = tcp_listener_map_by_address_.find(ipv4_any_address); + if (ipv4_any_listener == tcp_listener_map_by_address_.end() || + ipv4_any_listener->second->listener_->listener() == nullptr) { + tcp_listener_map_by_address_.insert_or_assign(ipv4_any_address, details); + } + } else { + auto v4_compatible_addr = address->ip()->ipv6()->v4CompatibleAddress(); + // Remove this check when runtime flag + // `envoy.reloadable_features.strict_check_on_ipv4_compat` deprecated. + // If this isn't a valid Ipv4-mapped address, then do nothing. + if (v4_compatible_addr != nullptr) { + tcp_listener_map_by_address_.insert_or_assign(v4_compatible_addr->asStringView(), + details); + } + } + } } else if (absl::holds_alternative>( details->typed_listener_)) { internal_listener_map_by_address_.insert_or_assign( @@ -107,11 +136,37 @@ void ConnectionHandlerImpl::removeListeners(uint64_t listener_tag) { listener_iter != listener_map_by_tag_.end()) { // listener_map_by_address_ may already update to the new listener. Compare it with the one // which find from listener_map_by_tag_, only delete it when it is same listener. - auto address_view = listener_iter->second->address_->asStringView(); + auto& address = listener_iter->second->address_; + auto address_view = address->asStringView(); if (tcp_listener_map_by_address_.contains(address_view) && tcp_listener_map_by_address_[address_view]->listener_tag_ == listener_iter->second->listener_tag_) { tcp_listener_map_by_address_.erase(address_view); + + // If the address is Ipv6 and isn't v6only, delete the corresponding Ipv4 item from the map. + if (address->type() == Network::Address::Type::Ip && + address->ip()->version() == Network::Address::IpVersion::v6 && + !address->ip()->ipv6()->v6only()) { + if (address->ip()->isAnyAddress()) { + auto ipv4_any_addr_iter = tcp_listener_map_by_address_.find( + Network::Address::Ipv4Instance(address->ip()->port()).asStringView()); + // Since both "::" with ipv4_compat and "0.0.0.0" can be supported, ensure they are same + // listener by tag. + if (ipv4_any_addr_iter != tcp_listener_map_by_address_.end() && + ipv4_any_addr_iter->second->listener_tag_ == listener_iter->second->listener_tag_) { + tcp_listener_map_by_address_.erase(ipv4_any_addr_iter); + } + } else { + auto v4_compatible_addr = address->ip()->ipv6()->v4CompatibleAddress(); + // Remove this check when runtime flag + // `envoy.reloadable_features.strict_check_on_ipv4_compat` deprecated. + if (v4_compatible_addr != nullptr) { + // both "::FFFF:" with ipv4_compat and "" isn't valid case, + // remove the v4 compatible addr item directly. + tcp_listener_map_by_address_.erase(v4_compatible_addr->asStringView()); + } + } + } } else if (internal_listener_map_by_address_.contains(address_view) && internal_listener_map_by_address_[address_view]->listener_tag_ == listener_iter->second->listener_tag_) { diff --git a/source/server/listener_impl.cc b/source/server/listener_impl.cc index cbe41f4c11c5..7ac6bd13606f 100644 --- a/source/server/listener_impl.cc +++ b/source/server/listener_impl.cc @@ -348,6 +348,20 @@ ListenerImpl::ListenerImpl(const envoy::config::listener::v3::Listener& config, validation_visitor_, parent_.server_.api(), parent_.server_.options())), quic_stat_names_(parent_.quicStatNames()) { + if ((address_->type() == Network::Address::Type::Ip && + config.address().socket_address().ipv4_compat()) && + (address_->ip()->version() != Network::Address::IpVersion::v6 || + address_->ip()->ipv6()->v4CompatibleAddress() == nullptr)) { + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_check_on_ipv4_compat")) { + throw EnvoyException(fmt::format( + "Only IPv6 address '::' or valid IPv4-mapped IPv6 address can set ipv4_compat: {}", + address_->asStringView())); + } else { + ENVOY_LOG(warn, "An invalid IPv4-mapped IPv6 address is used when ipv4_compat is set: {}", + address_->asStringView()); + } + } + const absl::optional runtime_val = listener_factory_context_->runtime().snapshot().get(cx_limit_runtime_key_); if (runtime_val && runtime_val->empty()) { diff --git a/test/server/connection_handler_test.cc b/test/server/connection_handler_test.cc index 9f86c5c76e42..810bbc764a23 100644 --- a/test/server/connection_handler_test.cc +++ b/test/server/connection_handler_test.cc @@ -1244,6 +1244,476 @@ TEST_F(ConnectionHandlerTest, MatchIPv6WildcardListener) { EXPECT_CALL(*access_log_, log(_, _, _, _)); } +// This tests the ConnectionHandler's `getBalancedHandlerByAddress` will match +// Ipv4 request to the listener which is listening on "::" with ipv4_compat flag. +// The `listener1` is the listener will balance the request to the `listener2`, +// the listener2 is listening on IPv6 any-address. And suppose the `listener2` +// will accept the ipv4 request since it has ipv4_compat flag. +TEST_F(ConnectionHandlerTest, MatchIPv6WildcardListenerWithAnyAddressAndIpv4CompatFlag) { + Network::TcpListenerCallbacks* listener_callbacks1; + auto listener1 = new NiceMock(); + TestListener* test_listener1 = + addListener(1, true, true, "test_listener1", listener1, &listener_callbacks1); + Network::Address::InstanceConstSharedPtr normal_address( + new Network::Address::Ipv4Instance("127.0.0.1", 10001)); + EXPECT_CALL(test_listener1->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(normal_address)); + handler_->addListener(absl::nullopt, *test_listener1, runtime_); + + auto ipv6_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv6_any_listener_callbacks; + auto listener2 = new NiceMock(); + TestListener* ipv6_any_listener = + addListener(2, false, false, "ipv6_any_test_listener", listener2, + &ipv6_any_listener_callbacks, nullptr, nullptr, Network::Socket::Type::Stream, + std::chrono::milliseconds(15000), false, ipv6_overridden_filter_chain_manager); + // Set the ipv6only as false. + Network::Address::InstanceConstSharedPtr any_address_ipv6( + new Network::Address::Ipv6Instance("::", 80, nullptr, false)); + EXPECT_CALL(ipv6_any_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(any_address_ipv6)); + handler_->addListener(absl::nullopt, *ipv6_any_listener, runtime_); + + Network::MockListenerFilter* test_filter = new Network::MockListenerFilter(); + EXPECT_CALL(*test_filter, destroy_()); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + bool redirected = false; + EXPECT_CALL(factory_, createListenerFilterChain(_)) + .WillRepeatedly(Invoke([&](Network::ListenerFilterManager& manager) -> bool { + // Insert the Mock filter. + if (!redirected) { + manager.addAcceptFilter(listener_filter_matcher_, + Network::ListenerFilterPtr{test_filter}); + redirected = true; + } + return true; + })); + + Network::Address::InstanceConstSharedPtr alt_address( + new Network::Address::Ipv4Instance("127.0.0.2", 80, nullptr)); + EXPECT_CALL(*test_filter, onAccept(_)) + .WillOnce(Invoke([&](Network::ListenerFilterCallbacks& cb) -> Network::FilterStatus { + cb.socket().connectionInfoProvider().restoreLocalAddress(alt_address); + return Network::FilterStatus::Continue; + })); + // The listener1 will balance the request to listener2. + EXPECT_CALL(manager_, findFilterChain(_)).Times(0); + // The listener2 gets the connection. + EXPECT_CALL(*ipv6_overridden_filter_chain_manager, findFilterChain(_)) + .WillOnce(Return(filter_chain_.get())); + auto* connection = new NiceMock(); + EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); + listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}); + EXPECT_EQ(1UL, handler_->numConnections()); + + EXPECT_CALL(*listener2, onDestroy()); + EXPECT_CALL(*listener1, onDestroy()); + EXPECT_CALL(*access_log_, log(_, _, _, _)); +} + +// This tests the ConnectionHandler's `getBalancedHandlerByAddress` will match +// Ipv4 request to the listener which is listening on Ipv4-compatible Ipv6 address with ipv4_compat +// flag. The `listener1` is the listener will balance the request to the `listener2`, the listener2 +// is listening on Ipv4-compatible Ipv6 address. And suppose the `listener2` will accept the ipv4 +// request since it has ipv4_compat flag. +TEST_F(ConnectionHandlerTest, MatchhIpv4CompatiableIPv6ListenerWithIpv4CompatFlag) { + Network::TcpListenerCallbacks* listener_callbacks1; + auto listener1 = new NiceMock(); + TestListener* test_listener1 = + addListener(1, true, true, "test_listener1", listener1, &listener_callbacks1); + Network::Address::InstanceConstSharedPtr normal_address( + new Network::Address::Ipv4Instance("127.0.0.1", 10001)); + EXPECT_CALL(test_listener1->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(normal_address)); + handler_->addListener(absl::nullopt, *test_listener1, runtime_); + + auto ipv6_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv6_listener_callbacks; + auto listener2 = new NiceMock(); + TestListener* ipv6_listener = + addListener(2, false, false, "ipv6_test_listener", listener2, &ipv6_listener_callbacks, + nullptr, nullptr, Network::Socket::Type::Stream, std::chrono::milliseconds(15000), + false, ipv6_overridden_filter_chain_manager); + // Set the ipv6only as false. + Network::Address::InstanceConstSharedPtr ipv4_mapped_ipv6_address( + new Network::Address::Ipv6Instance("::FFFF:192.168.0.1", 80, nullptr, false)); + EXPECT_CALL(ipv6_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(ipv4_mapped_ipv6_address)); + handler_->addListener(absl::nullopt, *ipv6_listener, runtime_); + + Network::MockListenerFilter* test_filter = new Network::MockListenerFilter(); + EXPECT_CALL(*test_filter, destroy_()); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + bool redirected = false; + EXPECT_CALL(factory_, createListenerFilterChain(_)) + .WillRepeatedly(Invoke([&](Network::ListenerFilterManager& manager) -> bool { + // Insert the Mock filter. + if (!redirected) { + manager.addAcceptFilter(listener_filter_matcher_, + Network::ListenerFilterPtr{test_filter}); + redirected = true; + } + return true; + })); + + Network::Address::InstanceConstSharedPtr alt_address( + new Network::Address::Ipv4Instance("192.168.0.1", 80, nullptr)); + EXPECT_CALL(*test_filter, onAccept(_)) + .WillOnce(Invoke([&](Network::ListenerFilterCallbacks& cb) -> Network::FilterStatus { + cb.socket().connectionInfoProvider().restoreLocalAddress(alt_address); + return Network::FilterStatus::Continue; + })); + // The listener1 will balance the request to listener2. + EXPECT_CALL(manager_, findFilterChain(_)).Times(0); + // The listener2 gets the connection. + EXPECT_CALL(*ipv6_overridden_filter_chain_manager, findFilterChain(_)) + .WillOnce(Return(filter_chain_.get())); + auto* connection = new NiceMock(); + EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); + listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}); + EXPECT_EQ(1UL, handler_->numConnections()); + + EXPECT_CALL(*listener2, onDestroy()); + EXPECT_CALL(*listener1, onDestroy()); + EXPECT_CALL(*access_log_, log(_, _, _, _)); +} + +// This tests the ConnectionHandler's `getBalancedHandlerByAddress` won't match +// Ipv4 request to the listener which is listening on Ipv6 any-address without ipv4_compat flag. +// The `listener1` is the listener will balance the request to the `listener2`, +// the listener2 is listening on IPv6 any-address. And suppose the `listener2` +// will accept the Ipv4 request since it has ipv4_compat flag. +TEST_F(ConnectionHandlerTest, NotMatchIPv6WildcardListenerWithoutIpv4CompatFlag) { + Network::TcpListenerCallbacks* listener_callbacks1; + auto listener1 = new NiceMock(); + TestListener* test_listener1 = + addListener(1, true, true, "test_listener1", listener1, &listener_callbacks1); + Network::Address::InstanceConstSharedPtr normal_address( + new Network::Address::Ipv4Instance("127.0.0.1", 10001)); + EXPECT_CALL(test_listener1->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(normal_address)); + handler_->addListener(absl::nullopt, *test_listener1, runtime_); + + auto ipv6_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv6_any_listener_callbacks; + auto listener2 = new NiceMock(); + TestListener* ipv6_any_listener = + addListener(2, false, false, "ipv6_any_test_listener", listener2, + &ipv6_any_listener_callbacks, nullptr, nullptr, Network::Socket::Type::Stream, + std::chrono::milliseconds(15000), false, ipv6_overridden_filter_chain_manager); + // not set the ipv6only flag, the default value is true. + Network::Address::InstanceConstSharedPtr any_address_ipv6( + new Network::Address::Ipv6Instance("::", 80)); + EXPECT_CALL(ipv6_any_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(any_address_ipv6)); + handler_->addListener(absl::nullopt, *ipv6_any_listener, runtime_); + + Network::MockListenerFilter* test_filter = new Network::MockListenerFilter(); + EXPECT_CALL(*test_filter, destroy_()); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + bool redirected = false; + EXPECT_CALL(factory_, createListenerFilterChain(_)) + .WillRepeatedly(Invoke([&](Network::ListenerFilterManager& manager) -> bool { + // Insert the Mock filter. + if (!redirected) { + manager.addAcceptFilter(listener_filter_matcher_, + Network::ListenerFilterPtr{test_filter}); + redirected = true; + } + return true; + })); + + Network::Address::InstanceConstSharedPtr alt_address( + new Network::Address::Ipv4Instance("127.0.0.2", 80, nullptr)); + EXPECT_CALL(*test_filter, onAccept(_)) + .WillOnce(Invoke([&](Network::ListenerFilterCallbacks& cb) -> Network::FilterStatus { + cb.socket().connectionInfoProvider().restoreLocalAddress(alt_address); + return Network::FilterStatus::Continue; + })); + // The listener1 gets the connection. + EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(filter_chain_.get())); + // The listener2 doesn't get the connection. + EXPECT_CALL(*ipv6_overridden_filter_chain_manager, findFilterChain(_)).Times(0); + auto* connection = new NiceMock(); + EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); + listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}); + EXPECT_EQ(1UL, handler_->numConnections()); + + EXPECT_CALL(*listener2, onDestroy()); + EXPECT_CALL(*listener1, onDestroy()); + EXPECT_CALL(*access_log_, log(_, _, _, _)); +} + +// This tests the case both "0.0.0.0" and "::" with ipv4_compat are added. The +// expectation is the ipv4 connection is going to ipv4 listener. +TEST_F(ConnectionHandlerTest, MatchhIpv4WhenBothIpv4AndIPv6WithIpv4CompatFlag) { + // Listener1 is response for redirect the connection. + Network::TcpListenerCallbacks* listener_callbacks1; + auto listener1 = new NiceMock(); + TestListener* test_listener1 = + addListener(1, true, true, "test_listener1", listener1, &listener_callbacks1); + Network::Address::InstanceConstSharedPtr normal_address( + new Network::Address::Ipv4Instance("127.0.0.1", 10001)); + EXPECT_CALL(test_listener1->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(normal_address)); + handler_->addListener(absl::nullopt, *test_listener1, runtime_); + + // Listener2 is listening on an ipv4-mapped ipv6 address. + auto ipv6_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv6_any_listener_callbacks; + auto listener2 = new NiceMock(); + TestListener* ipv6_listener = + addListener(2, false, false, "ipv6_test_listener", listener2, &ipv6_any_listener_callbacks, + nullptr, nullptr, Network::Socket::Type::Stream, std::chrono::milliseconds(15000), + false, ipv6_overridden_filter_chain_manager); + // Set the ipv6only as false. + Network::Address::InstanceConstSharedPtr ipv6_any_address( + new Network::Address::Ipv6Instance("::", 80, nullptr, false)); + EXPECT_CALL(ipv6_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(ipv6_any_address)); + handler_->addListener(absl::nullopt, *ipv6_listener, runtime_); + + // Listener3 is listening on an ipv4 address. + auto ipv4_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv4_listener_callbacks; + auto listener3 = new NiceMock(); + TestListener* ipv4_listener = + addListener(3, false, false, "ipv4_test_listener", listener3, &ipv4_listener_callbacks, + nullptr, nullptr, Network::Socket::Type::Stream, std::chrono::milliseconds(15000), + false, ipv4_overridden_filter_chain_manager); + Network::Address::InstanceConstSharedPtr ipv4_address( + new Network::Address::Ipv4Instance("0.0.0.0", 80, nullptr)); + EXPECT_CALL(ipv4_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(ipv4_address)); + handler_->addListener(absl::nullopt, *ipv4_listener, runtime_); + + Network::MockListenerFilter* test_filter = new Network::MockListenerFilter(); + EXPECT_CALL(*test_filter, destroy_()); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + bool redirected = false; + EXPECT_CALL(factory_, createListenerFilterChain(_)) + .WillRepeatedly(Invoke([&](Network::ListenerFilterManager& manager) -> bool { + // Insert the Mock filter. + if (!redirected) { + manager.addAcceptFilter(listener_filter_matcher_, + Network::ListenerFilterPtr{test_filter}); + redirected = true; + } + return true; + })); + + Network::Address::InstanceConstSharedPtr alt_address( + new Network::Address::Ipv4Instance("192.168.0.1", 80, nullptr)); + EXPECT_CALL(*test_filter, onAccept(_)) + .WillOnce(Invoke([&](Network::ListenerFilterCallbacks& cb) -> Network::FilterStatus { + cb.socket().connectionInfoProvider().restoreLocalAddress(alt_address); + return Network::FilterStatus::Continue; + })); + // The listener1 will balance the request to listener2. + EXPECT_CALL(manager_, findFilterChain(_)).Times(0); + // The listener2 won't get the connection. + EXPECT_CALL(*ipv6_overridden_filter_chain_manager, findFilterChain(_)).Times(0); + // The listener3 gets the connection. + EXPECT_CALL(*ipv4_overridden_filter_chain_manager, findFilterChain(_)) + .WillOnce(Return(filter_chain_.get())); + auto* connection = new NiceMock(); + EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); + listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}); + EXPECT_EQ(1UL, handler_->numConnections()); + + EXPECT_CALL(*listener3, onDestroy()); + EXPECT_CALL(*listener2, onDestroy()); + EXPECT_CALL(*listener1, onDestroy()); + EXPECT_CALL(*access_log_, log(_, _, _, _)); +} + +// This test is same as above except the Ipv4 listener is added first, then Ipv6 +// listener is added. +TEST_F(ConnectionHandlerTest, MatchhIpv4WhenBothIpv4AndIPv6WithIpv4CompatFlag2) { + // Listener1 is response for redirect the connection. + Network::TcpListenerCallbacks* listener_callbacks1; + auto listener1 = new NiceMock(); + TestListener* test_listener1 = + addListener(1, true, true, "test_listener1", listener1, &listener_callbacks1); + Network::Address::InstanceConstSharedPtr normal_address( + new Network::Address::Ipv4Instance("127.0.0.1", 10001)); + EXPECT_CALL(test_listener1->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(normal_address)); + handler_->addListener(absl::nullopt, *test_listener1, runtime_); + + // Listener3 is listening on an ipv4 address. + auto ipv4_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv4_listener_callbacks; + auto listener3 = new NiceMock(); + TestListener* ipv4_listener = + addListener(3, false, false, "ipv4_test_listener", listener3, &ipv4_listener_callbacks, + nullptr, nullptr, Network::Socket::Type::Stream, std::chrono::milliseconds(15000), + false, ipv4_overridden_filter_chain_manager); + Network::Address::InstanceConstSharedPtr ipv4_address( + new Network::Address::Ipv4Instance("0.0.0.0", 80, nullptr)); + EXPECT_CALL(ipv4_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(ipv4_address)); + handler_->addListener(absl::nullopt, *ipv4_listener, runtime_); + + // Listener2 is listening on an ipv4-mapped ipv6 address. + auto ipv6_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv6_any_listener_callbacks; + auto listener2 = new NiceMock(); + TestListener* ipv6_listener = + addListener(2, false, false, "ipv6_test_listener", listener2, &ipv6_any_listener_callbacks, + nullptr, nullptr, Network::Socket::Type::Stream, std::chrono::milliseconds(15000), + false, ipv6_overridden_filter_chain_manager); + // Set the ipv6only as false. + Network::Address::InstanceConstSharedPtr ipv4_mapped_ipv6_address( + new Network::Address::Ipv6Instance("::", 80, nullptr, false)); + EXPECT_CALL(ipv6_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(ipv4_mapped_ipv6_address)); + handler_->addListener(absl::nullopt, *ipv6_listener, runtime_); + + Network::MockListenerFilter* test_filter = new Network::MockListenerFilter(); + EXPECT_CALL(*test_filter, destroy_()); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + bool redirected = false; + EXPECT_CALL(factory_, createListenerFilterChain(_)) + .WillRepeatedly(Invoke([&](Network::ListenerFilterManager& manager) -> bool { + // Insert the Mock filter. + if (!redirected) { + manager.addAcceptFilter(listener_filter_matcher_, + Network::ListenerFilterPtr{test_filter}); + redirected = true; + } + return true; + })); + + Network::Address::InstanceConstSharedPtr alt_address( + new Network::Address::Ipv4Instance("192.168.0.1", 80, nullptr)); + EXPECT_CALL(*test_filter, onAccept(_)) + .WillOnce(Invoke([&](Network::ListenerFilterCallbacks& cb) -> Network::FilterStatus { + cb.socket().connectionInfoProvider().restoreLocalAddress(alt_address); + return Network::FilterStatus::Continue; + })); + // The listener1 will balance the request to listener2. + EXPECT_CALL(manager_, findFilterChain(_)).Times(0); + // The listener2 won't get the connection. + EXPECT_CALL(*ipv6_overridden_filter_chain_manager, findFilterChain(_)).Times(0); + // The listener3 gets the connection. + EXPECT_CALL(*ipv4_overridden_filter_chain_manager, findFilterChain(_)) + .WillOnce(Return(filter_chain_.get())); + auto* connection = new NiceMock(); + EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); + listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}); + EXPECT_EQ(1UL, handler_->numConnections()); + + EXPECT_CALL(*listener3, onDestroy()); + EXPECT_CALL(*listener2, onDestroy()); + EXPECT_CALL(*listener1, onDestroy()); + EXPECT_CALL(*access_log_, log(_, _, _, _)); +} + +// This test the case of there is stopped "0.0.0.0" listener, then add +// "::" listener with ipv4_compat. Ensure the ipv6 listener will take over the +// ipv4 listener. +TEST_F(ConnectionHandlerTest, AddIpv4MappedListenerAfterIpv4ListenerStopped) { + // Listener1 is response for redirect the connection. + Network::TcpListenerCallbacks* listener_callbacks1; + auto listener1 = new NiceMock(); + TestListener* test_listener1 = + addListener(1, true, true, "test_listener1", listener1, &listener_callbacks1); + Network::Address::InstanceConstSharedPtr normal_address( + new Network::Address::Ipv4Instance("127.0.0.1", 10001)); + EXPECT_CALL(test_listener1->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(normal_address)); + handler_->addListener(absl::nullopt, *test_listener1, runtime_); + + // Listener2 is listening on an Ipv4 address. + auto ipv4_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv4_listener_callbacks; + auto listener2 = new NiceMock(); + TestListener* ipv4_listener = + addListener(2, false, false, "ipv4_test_listener", listener2, &ipv4_listener_callbacks, + nullptr, nullptr, Network::Socket::Type::Stream, std::chrono::milliseconds(15000), + false, ipv4_overridden_filter_chain_manager); + Network::Address::InstanceConstSharedPtr ipv4_address( + new Network::Address::Ipv4Instance("0.0.0.0", 80, nullptr)); + EXPECT_CALL(ipv4_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(ipv4_address)); + handler_->addListener(absl::nullopt, *ipv4_listener, runtime_); + + EXPECT_CALL(*listener2, onDestroy()); + // Stop the ipv4 listener. + handler_->stopListeners(2); + + // Listener3 is listening on an Ipv4-mapped Ipv6 address. + auto ipv6_overridden_filter_chain_manager = + std::make_shared>(); + Network::TcpListenerCallbacks* ipv6_any_listener_callbacks; + auto listener3 = new NiceMock(); + TestListener* ipv6_listener = + addListener(3, false, false, "ipv6_test_listener", listener3, &ipv6_any_listener_callbacks, + nullptr, nullptr, Network::Socket::Type::Stream, std::chrono::milliseconds(15000), + false, ipv6_overridden_filter_chain_manager); + // Set the ipv6only as false. + Network::Address::InstanceConstSharedPtr ipv4_mapped_ipv6_address( + new Network::Address::Ipv6Instance("::", 80, nullptr, false)); + EXPECT_CALL(ipv6_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(ipv4_mapped_ipv6_address)); + handler_->addListener(absl::nullopt, *ipv6_listener, runtime_); + + Network::MockListenerFilter* test_filter = new Network::MockListenerFilter(); + EXPECT_CALL(*test_filter, destroy_()); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + bool redirected = false; + EXPECT_CALL(factory_, createListenerFilterChain(_)) + .WillRepeatedly(Invoke([&](Network::ListenerFilterManager& manager) -> bool { + // Insert the Mock filter. + if (!redirected) { + manager.addAcceptFilter(listener_filter_matcher_, + Network::ListenerFilterPtr{test_filter}); + redirected = true; + } + return true; + })); + + Network::Address::InstanceConstSharedPtr alt_address( + new Network::Address::Ipv4Instance("192.168.0.1", 80, nullptr)); + EXPECT_CALL(*test_filter, onAccept(_)) + .WillOnce(Invoke([&](Network::ListenerFilterCallbacks& cb) -> Network::FilterStatus { + cb.socket().connectionInfoProvider().restoreLocalAddress(alt_address); + return Network::FilterStatus::Continue; + })); + + // The listener1 will balance the request to listener2. + EXPECT_CALL(manager_, findFilterChain(_)).Times(0); + // The listener2 won't get the connection since it is stopped. + EXPECT_CALL(*ipv4_overridden_filter_chain_manager, findFilterChain(_)).Times(0); + // The listener3 gets the connection. + EXPECT_CALL(*ipv6_overridden_filter_chain_manager, findFilterChain(_)) + .WillOnce(Return(filter_chain_.get())); + + auto* connection = new NiceMock(); + EXPECT_CALL(dispatcher_, createServerConnection_()).WillOnce(Return(connection)); + EXPECT_CALL(factory_, createNetworkFilterChain(_, _)).WillOnce(Return(true)); + listener_callbacks1->onAccept(Network::ConnectionSocketPtr{accepted_socket}); + EXPECT_EQ(1UL, handler_->numConnections()); + + EXPECT_CALL(*listener3, onDestroy()); + EXPECT_CALL(*listener1, onDestroy()); + EXPECT_CALL(*access_log_, log(_, _, _, _)); +} + TEST_F(ConnectionHandlerTest, WildcardListenerWithOriginalDstInbound) { Network::TcpListenerCallbacks* listener_callbacks1; auto listener1 = new NiceMock(); @@ -1784,6 +2254,122 @@ TEST_F(ConnectionHandlerTest, TcpListenerRemoveListener) { handler_->removeListeners(0); } +TEST_F(ConnectionHandlerTest, TcpListenerRemoveIpv6AnyAddressWithIpv4CompatListener) { + InSequence s; + + Network::TcpListenerCallbacks* listener_callbacks; + auto listener = new NiceMock(); + TestListener* test_listener = + addListener(1, true, false, "test_listener", listener, &listener_callbacks); + Network::Address::InstanceConstSharedPtr any_address_ipv6( + new Network::Address::Ipv6Instance("::", 80, nullptr, false)); + EXPECT_CALL(test_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(any_address_ipv6)); + handler_->addListener(absl::nullopt, *test_listener, runtime_); + + Network::MockConnectionSocket* connection = new NiceMock(); + EXPECT_CALL(*access_log_, log(_, _, _, _)); + listener_callbacks->onAccept(Network::ConnectionSocketPtr{connection}); + EXPECT_EQ(0UL, handler_->numConnections()); + + EXPECT_CALL(*listener, onDestroy()); + handler_->stopListeners(1); + + EXPECT_CALL(dispatcher_, clearDeferredDeleteList()); + handler_->removeListeners(1); + + // Ensure both the Ipv6 and Ipv4 address was removed. + EXPECT_FALSE(handler_->getBalancedHandlerByAddress(*any_address_ipv6).has_value()); + Network::Address::InstanceConstSharedPtr any_address_ipv4( + new Network::Address::Ipv4Instance("0.0.0.0", 80, nullptr)); + EXPECT_FALSE(handler_->getBalancedHandlerByAddress(*any_address_ipv4).has_value()); +} + +TEST_F(ConnectionHandlerTest, TcpListenerRemoveIpv4CompatAddressListener) { + InSequence s; + + Network::TcpListenerCallbacks* listener_callbacks; + auto listener = new NiceMock(); + TestListener* test_listener = + addListener(1, true, false, "test_listener", listener, &listener_callbacks); + Network::Address::InstanceConstSharedPtr address_ipv6( + new Network::Address::Ipv6Instance("::FFFF:192.168.0.1", 80, nullptr, false)); + EXPECT_CALL(test_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(address_ipv6)); + handler_->addListener(absl::nullopt, *test_listener, runtime_); + + Network::MockConnectionSocket* connection = new NiceMock(); + EXPECT_CALL(*access_log_, log(_, _, _, _)); + listener_callbacks->onAccept(Network::ConnectionSocketPtr{connection}); + EXPECT_EQ(0UL, handler_->numConnections()); + + EXPECT_CALL(*listener, onDestroy()); + handler_->stopListeners(1); + + EXPECT_CALL(dispatcher_, clearDeferredDeleteList()); + handler_->removeListeners(1); + + // Ensure both the ipv6 and ipv4 address was removed. + EXPECT_FALSE(handler_->getBalancedHandlerByAddress(*address_ipv6).has_value()); + Network::Address::InstanceConstSharedPtr address_ipv4( + new Network::Address::Ipv4Instance("192.168.0.1", 80, nullptr)); + EXPECT_FALSE(handler_->getBalancedHandlerByAddress(*address_ipv4).has_value()); +} + +TEST_F(ConnectionHandlerTest, TcpListenerRemoveWithBothIpv4AnyAndIpv6Any) { + InSequence s; + + Network::TcpListenerCallbacks* listener_callbacks; + auto listener = new NiceMock(); + TestListener* test_listener = + addListener(1, true, false, "test_listener", listener, &listener_callbacks); + Network::Address::InstanceConstSharedPtr address_ipv6( + new Network::Address::Ipv6Instance("::", 80, nullptr, false)); + EXPECT_CALL(test_listener->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(address_ipv6)); + handler_->addListener(absl::nullopt, *test_listener, runtime_); + + Network::TcpListenerCallbacks* listener_callbacks2; + auto listener2 = new NiceMock(); + TestListener* test_listener2 = + addListener(2, true, false, "test_listener2", listener2, &listener_callbacks2); + Network::Address::InstanceConstSharedPtr address_ipv4( + new Network::Address::Ipv4Instance("0.0.0.0", 80, nullptr)); + EXPECT_CALL(test_listener2->socket_factory_, localAddress()) + .WillRepeatedly(ReturnRef(address_ipv4)); + handler_->addListener(absl::nullopt, *test_listener2, runtime_); + + Network::MockConnectionSocket* connection = new NiceMock(); + EXPECT_CALL(*access_log_, log(_, _, _, _)); + listener_callbacks->onAccept(Network::ConnectionSocketPtr{connection}); + EXPECT_EQ(0UL, handler_->numConnections()); + + Network::MockConnectionSocket* connection2 = new NiceMock(); + EXPECT_CALL(*access_log_, log(_, _, _, _)); + listener_callbacks2->onAccept(Network::ConnectionSocketPtr{connection2}); + EXPECT_EQ(0UL, handler_->numConnections()); + + // Remove Listener1 first. + EXPECT_CALL(*listener, onDestroy()); + handler_->stopListeners(1); + + EXPECT_CALL(dispatcher_, clearDeferredDeleteList()); + handler_->removeListeners(1); + + // Ensure only ipv6 address was removed. + EXPECT_FALSE(handler_->getBalancedHandlerByAddress(*address_ipv6).has_value()); + EXPECT_TRUE(handler_->getBalancedHandlerByAddress(*address_ipv4).has_value()); + + // Now remove Listener2. + EXPECT_CALL(*listener2, onDestroy()); + handler_->stopListeners(2); + + EXPECT_CALL(dispatcher_, clearDeferredDeleteList()); + handler_->removeListeners(2); + // Ensure the listener2 is gone. + EXPECT_FALSE(handler_->getBalancedHandlerByAddress(*address_ipv4).has_value()); +} + TEST_F(ConnectionHandlerTest, TcpListenerGlobalCxLimitReject) { Network::TcpListenerCallbacks* listener_callbacks; auto listener = new NiceMock(); diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 5d67dca2f1da..eb207528468a 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -546,6 +546,77 @@ bind_to_port: false EXPECT_EQ(1UL, server_.stats_store_.counterFromString("listener.127.0.0.1_1234.foo").value()); } +TEST_F(ListenerManagerImplTest, RejectIpv4CompatOnIpv4Address) { + const std::string yaml = R"EOF( + name: "foo" + address: + socket_address: + address: "0.0.0.0" + port_value: 13333 + ipv4_compat: true + filter_chains: + - filters: [] + )EOF"; + + EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), + EnvoyException, + "Only IPv6 address '::' or valid IPv4-mapped IPv6 address can set " + "ipv4_compat: 0.0.0.0:13333"); +} + +TEST_F(ListenerManagerImplTest, AcceptIpv4CompatOnIpv4Address) { + auto scoped_runtime_guard = std::make_unique(); + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.strict_check_on_ipv4_compat", "false"}}); + const std::string yaml = R"EOF( + name: "foo" + address: + socket_address: + address: "0.0.0.0" + port_value: 13333 + ipv4_compat: true + filter_chains: + - filters: [] + )EOF"; + + EXPECT_TRUE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true)); +} + +TEST_F(ListenerManagerImplTest, RejectIpv4CompatOnNonIpv4MappedIpv6address) { + const std::string yaml = R"EOF( + name: "foo" + address: + socket_address: + address: "::1" + port_value: 13333 + ipv4_compat: true + filter_chains: + - filters: [] + )EOF"; + + EXPECT_THROW_WITH_MESSAGE( + manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true), EnvoyException, + "Only IPv6 address '::' or valid IPv4-mapped IPv6 address can set ipv4_compat: [::1]:13333"); +} + +TEST_F(ListenerManagerImplTest, AcceptIpv4CompatOnNonIpv4MappedIpv6address) { + auto scoped_runtime_guard = std::make_unique(); + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.strict_check_on_ipv4_compat", "false"}}); + const std::string yaml = R"EOF( + name: "foo" + address: + socket_address: + address: "::1" + port_value: 13333 + ipv4_compat: true + filter_chains: + - filters: [] + )EOF"; + + EXPECT_TRUE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true)); +} + TEST_F(ListenerManagerImplTest, UnsupportedInternalListener) { auto scoped_runtime = std::make_unique(); // Workaround of triggering death at windows platform. From 74afc22efd2c0f98584d3ff54cb8e117f3fbcfea Mon Sep 17 00:00:00 2001 From: Rei Shimizu Date: Fri, 4 Mar 2022 05:49:11 +0900 Subject: [PATCH 03/68] access log: cleanup arguments in gRPC access log (#20033) Signed-off-by: Shikugawa --- source/extensions/access_loggers/common/BUILD | 10 +++++++ .../common/grpc_access_logger.h | 25 +++++++--------- .../common/grpc_access_logger_utils.cc | 19 ++++++++++++ .../common/grpc_access_logger_utils.h | 17 +++++++++++ .../grpc/grpc_access_log_impl.cc | 23 +++----------- .../grpc/grpc_access_log_impl.h | 5 +--- .../open_telemetry/grpc_access_log_impl.cc | 15 +++------- .../open_telemetry/grpc_access_log_impl.h | 5 +--- .../common/grpc_access_logger_test.cc | 30 +++++++------------ .../grpc/grpc_access_log_impl_test.cc | 9 ++++-- .../grpc_access_log_impl_test.cc | 9 ++++-- 11 files changed, 90 insertions(+), 77 deletions(-) create mode 100644 source/extensions/access_loggers/common/grpc_access_logger_utils.cc create mode 100644 source/extensions/access_loggers/common/grpc_access_logger_utils.h diff --git a/source/extensions/access_loggers/common/BUILD b/source/extensions/access_loggers/common/BUILD index d7968b6e0560..8b8060d4c74a 100644 --- a/source/extensions/access_loggers/common/BUILD +++ b/source/extensions/access_loggers/common/BUILD @@ -22,6 +22,15 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "grpc_access_logger_utils_lib", + srcs = ["grpc_access_logger_utils.cc"], + hdrs = ["grpc_access_logger_utils.h"], + deps = [ + "@envoy_api//envoy/extensions/access_loggers/grpc/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "file_access_log_lib", srcs = ["file_access_log_impl.cc"], @@ -49,6 +58,7 @@ envoy_cc_library( name = "grpc_access_logger", hdrs = ["grpc_access_logger.h"], deps = [ + ":grpc_access_logger_utils_lib", "//envoy/event:dispatcher_interface", "//envoy/grpc:async_client_manager_interface", "//envoy/singleton:instance_interface", diff --git a/source/extensions/access_loggers/common/grpc_access_logger.h b/source/extensions/access_loggers/common/grpc_access_logger.h index 13ff2b4347bb..613cd86f2239 100644 --- a/source/extensions/access_loggers/common/grpc_access_logger.h +++ b/source/extensions/access_loggers/common/grpc_access_logger.h @@ -13,6 +13,7 @@ #include "source/common/grpc/typed_async_client.h" #include "source/common/http/utility.h" #include "source/common/protobuf/utility.h" +#include "source/extensions/access_loggers/common/grpc_access_logger_utils.h" #include "absl/container/flat_hash_map.h" #include "absl/types/optional.h" @@ -172,19 +173,19 @@ class GrpcAccessLogger : public Detail::GrpcAccessLogger; - GrpcAccessLogger(const Grpc::RawAsyncClientSharedPtr& client, - std::chrono::milliseconds buffer_flush_interval_msec, - uint64_t max_buffer_size_bytes, Event::Dispatcher& dispatcher, - Stats::Scope& scope, std::string access_log_prefix, - const Protobuf::MethodDescriptor& service_method, - OptRef retry_policy) - : client_(client, service_method, retry_policy), - buffer_flush_interval_msec_(buffer_flush_interval_msec), + GrpcAccessLogger( + const Grpc::RawAsyncClientSharedPtr& client, + const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, + Event::Dispatcher& dispatcher, Stats::Scope& scope, std::string access_log_prefix, + const Protobuf::MethodDescriptor& service_method) + : client_(client, service_method, GrpcCommon::optionalRetryPolicy(config)), + buffer_flush_interval_msec_( + PROTOBUF_GET_MS_OR_DEFAULT(config, buffer_flush_interval, 1000)), flush_timer_(dispatcher.createTimer([this]() { flush(); flush_timer_->enableTimer(buffer_flush_interval_msec_); })), - max_buffer_size_bytes_(max_buffer_size_bytes), + max_buffer_size_bytes_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, buffer_size_bytes, 16384)), stats_({ALL_GRPC_ACCESS_LOGGER_STATS(POOL_COUNTER_PREFIX(scope, access_log_prefix))}) { flush_timer_->enableTimer(buffer_flush_interval_msec_); } @@ -290,10 +291,7 @@ class GrpcAccessLoggerCache : public Singleton::Instance, // the main thread if necessary. auto client = async_client_manager_.factoryForGrpcService(config.grpc_service(), scope_, true) ->createUncachedRawAsyncClient(); - const auto logger = createLogger( - config, std::move(client), - std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(config, buffer_flush_interval, 1000)), - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, buffer_size_bytes, 16384), cache.dispatcher_); + const auto logger = createLogger(config, std::move(client), cache.dispatcher_); cache.access_loggers_.emplace(cache_key, logger); return logger; } @@ -318,7 +316,6 @@ class GrpcAccessLoggerCache : public Singleton::Instance, // Create the specific logger type for this cache. virtual typename GrpcAccessLogger::SharedPtr createLogger(const ConfigProto& config, const Grpc::RawAsyncClientSharedPtr& client, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, Event::Dispatcher& dispatcher) PURE; Grpc::AsyncClientManager& async_client_manager_; diff --git a/source/extensions/access_loggers/common/grpc_access_logger_utils.cc b/source/extensions/access_loggers/common/grpc_access_logger_utils.cc new file mode 100644 index 000000000000..b5b67bbab583 --- /dev/null +++ b/source/extensions/access_loggers/common/grpc_access_logger_utils.cc @@ -0,0 +1,19 @@ +#include "source/extensions/access_loggers/common/grpc_access_logger_utils.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace GrpcCommon { + +OptRef optionalRetryPolicy( + const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config) { + if (!config.has_grpc_stream_retry_policy()) { + return {}; + } + return config.grpc_stream_retry_policy(); +} + +} // namespace GrpcCommon +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/common/grpc_access_logger_utils.h b/source/extensions/access_loggers/common/grpc_access_logger_utils.h new file mode 100644 index 000000000000..a874bfa41712 --- /dev/null +++ b/source/extensions/access_loggers/common/grpc_access_logger_utils.h @@ -0,0 +1,17 @@ +#pragma once + +#include "envoy/common/optref.h" +#include "envoy/extensions/access_loggers/grpc/v3/als.pb.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace GrpcCommon { + +OptRef optionalRetryPolicy( + const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config); + +} +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc index 4abd5480fa09..a50aa499ff2b 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc @@ -15,24 +15,13 @@ namespace Extensions { namespace AccessLoggers { namespace GrpcCommon { -OptRef optionalRetryPolicy( - const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config) { - if (!config.has_grpc_stream_retry_policy()) { - return {}; - } - return config.grpc_stream_retry_policy(); -} - GrpcAccessLoggerImpl::GrpcAccessLoggerImpl( const Grpc::RawAsyncClientSharedPtr& client, const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, Event::Dispatcher& dispatcher, const LocalInfo::LocalInfo& local_info, Stats::Scope& scope) - : GrpcAccessLogger(std::move(client), buffer_flush_interval_msec, max_buffer_size_bytes, - dispatcher, scope, GRPC_LOG_STATS_PREFIX, + : GrpcAccessLogger(std::move(client), config, dispatcher, scope, GRPC_LOG_STATS_PREFIX, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "envoy.service.accesslog.v3.AccessLogService.StreamAccessLogs"), - optionalRetryPolicy(config)), + "envoy.service.accesslog.v3.AccessLogService.StreamAccessLogs")), log_name_(config.log_name()), local_info_(local_info) {} void GrpcAccessLoggerImpl::addEntry(envoy::data::accesslog::v3::HTTPAccessLogEntry&& entry) { @@ -61,12 +50,8 @@ GrpcAccessLoggerCacheImpl::GrpcAccessLoggerCacheImpl(Grpc::AsyncClientManager& a GrpcAccessLoggerImpl::SharedPtr GrpcAccessLoggerCacheImpl::createLogger( const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, - const Grpc::RawAsyncClientSharedPtr& client, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, - Event::Dispatcher& dispatcher) { - return std::make_shared(client, config, buffer_flush_interval_msec, - max_buffer_size_bytes, dispatcher, local_info_, - scope_); + const Grpc::RawAsyncClientSharedPtr& client, Event::Dispatcher& dispatcher) { + return std::make_shared(client, config, dispatcher, local_info_, scope_); } } // namespace GrpcCommon diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_impl.h b/source/extensions/access_loggers/grpc/grpc_access_log_impl.h index 34aab4cf6edc..30d7c636d634 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_impl.h +++ b/source/extensions/access_loggers/grpc/grpc_access_log_impl.h @@ -26,7 +26,6 @@ class GrpcAccessLoggerImpl GrpcAccessLoggerImpl( const Grpc::RawAsyncClientSharedPtr& client, const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, Event::Dispatcher& dispatcher, const LocalInfo::LocalInfo& local_info, Stats::Scope& scope); private: @@ -53,9 +52,7 @@ class GrpcAccessLoggerCacheImpl // Common::GrpcAccessLoggerCache GrpcAccessLoggerImpl::SharedPtr createLogger(const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, - const Grpc::RawAsyncClientSharedPtr& client, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, - Event::Dispatcher& dispatcher) override; + const Grpc::RawAsyncClientSharedPtr& client, Event::Dispatcher& dispatcher) override; const LocalInfo::LocalInfo& local_info_; }; diff --git a/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc b/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc index 0c08ce1194e1..3979c4d27aad 100644 --- a/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc @@ -23,13 +23,10 @@ namespace OpenTelemetry { GrpcAccessLoggerImpl::GrpcAccessLoggerImpl( const Grpc::RawAsyncClientSharedPtr& client, const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, Event::Dispatcher& dispatcher, const LocalInfo::LocalInfo& local_info, Stats::Scope& scope) - : GrpcAccessLogger(client, buffer_flush_interval_msec, max_buffer_size_bytes, dispatcher, scope, - GRPC_LOG_STATS_PREFIX, + : GrpcAccessLogger(client, config, dispatcher, scope, GRPC_LOG_STATS_PREFIX, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "opentelemetry.proto.collector.logs.v1.LogsService.Export"), - config.grpc_stream_retry_policy()) { + "opentelemetry.proto.collector.logs.v1.LogsService.Export")) { initMessageRoot(config.log_name(), local_info); } @@ -76,12 +73,8 @@ GrpcAccessLoggerCacheImpl::GrpcAccessLoggerCacheImpl(Grpc::AsyncClientManager& a GrpcAccessLoggerImpl::SharedPtr GrpcAccessLoggerCacheImpl::createLogger( const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, - const Grpc::RawAsyncClientSharedPtr& client, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, - Event::Dispatcher& dispatcher) { - return std::make_shared(client, config, buffer_flush_interval_msec, - max_buffer_size_bytes, dispatcher, local_info_, - scope_); + const Grpc::RawAsyncClientSharedPtr& client, Event::Dispatcher& dispatcher) { + return std::make_shared(client, config, dispatcher, local_info_, scope_); } } // namespace OpenTelemetry diff --git a/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.h b/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.h index 0fa389d75b1d..acd7f281dfc2 100644 --- a/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.h +++ b/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.h @@ -38,7 +38,6 @@ class GrpcAccessLoggerImpl GrpcAccessLoggerImpl( const Grpc::RawAsyncClientSharedPtr& client, const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, Event::Dispatcher& dispatcher, const LocalInfo::LocalInfo& local_info, Stats::Scope& scope); private: @@ -67,9 +66,7 @@ class GrpcAccessLoggerCacheImpl // Common::GrpcAccessLoggerCache GrpcAccessLoggerImpl::SharedPtr createLogger(const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, - const Grpc::RawAsyncClientSharedPtr& client, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, - Event::Dispatcher& dispatcher) override; + const Grpc::RawAsyncClientSharedPtr& client, Event::Dispatcher& dispatcher) override; const LocalInfo::LocalInfo& local_info_; }; diff --git a/test/extensions/access_loggers/common/grpc_access_logger_test.cc b/test/extensions/access_loggers/common/grpc_access_logger_test.cc index c310923ff3d7..e3c239e2d2a0 100644 --- a/test/extensions/access_loggers/common/grpc_access_logger_test.cc +++ b/test/extensions/access_loggers/common/grpc_access_logger_test.cc @@ -41,14 +41,6 @@ const Protobuf::MethodDescriptor& mockMethodDescriptor() { "envoy.service.accesslog.v3.AccessLogService.StreamAccessLogs"); } -OptRef optionalRetryPolicy( - const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config) { - if (!config.has_grpc_stream_retry_policy()) { - return {}; - } - return config.grpc_stream_retry_policy(); -} - // We don't care about the actual log entries, as this logger just adds them to the proto, but we // need to use a proto type because the ByteSizeLong() is used to determine the log size, so we use // standard Struct and Empty protos. @@ -59,12 +51,10 @@ class MockGrpcAccessLoggerImpl MockGrpcAccessLoggerImpl( const Grpc::RawAsyncClientSharedPtr& client, const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, Event::Dispatcher& dispatcher, Stats::Scope& scope, std::string access_log_prefix, const Protobuf::MethodDescriptor& service_method) - : GrpcAccessLogger(std::move(client), buffer_flush_interval_msec, max_buffer_size_bytes, - dispatcher, scope, access_log_prefix, service_method, - optionalRetryPolicy(config)) {} + : GrpcAccessLogger(std::move(client), config, dispatcher, scope, access_log_prefix, + service_method) {} int numInits() const { return num_inits_; } @@ -125,10 +115,13 @@ class GrpcAccessLogTest : public testing::Test { void initLogger(std::chrono::milliseconds buffer_flush_interval_msec, size_t buffer_size_bytes) { timer_ = new Event::MockTimer(&dispatcher_); EXPECT_CALL(*timer_, enableTimer(buffer_flush_interval_msec, _)); + config_.mutable_buffer_size_bytes()->set_value(buffer_size_bytes); + config_.mutable_buffer_flush_interval()->set_nanos( + std::chrono::duration_cast(buffer_flush_interval_msec).count()); + logger_ = std::make_unique( - Grpc::RawAsyncClientPtr{async_client_}, config_, buffer_flush_interval_msec, - buffer_size_bytes, dispatcher_, stats_store_, "mock_access_log_prefix.", - mockMethodDescriptor()); + Grpc::RawAsyncClientPtr{async_client_}, config_, dispatcher_, stats_store_, + "mock_access_log_prefix.", mockMethodDescriptor()); } void expectStreamStart(MockAccessLogStream& stream, AccessLogCallbacks** callbacks_to_set) { @@ -354,11 +347,10 @@ class MockGrpcAccessLoggerCache MockGrpcAccessLoggerImpl::SharedPtr createLogger(const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, const Grpc::RawAsyncClientSharedPtr& client, - std::chrono::milliseconds buffer_flush_interval_msec, uint64_t max_buffer_size_bytes, Event::Dispatcher& dispatcher) override { - return std::make_shared( - std::move(client), config, buffer_flush_interval_msec, max_buffer_size_bytes, dispatcher, - scope_, "mock_access_log_prefix.", mockMethodDescriptor()); + return std::make_shared(std::move(client), config, dispatcher, scope_, + "mock_access_log_prefix.", + mockMethodDescriptor()); } }; diff --git a/test/extensions/access_loggers/grpc/grpc_access_log_impl_test.cc b/test/extensions/access_loggers/grpc/grpc_access_log_impl_test.cc index 44f2d4f6f4d1..2827b2d41a17 100644 --- a/test/extensions/access_loggers/grpc/grpc_access_log_impl_test.cc +++ b/test/extensions/access_loggers/grpc/grpc_access_log_impl_test.cc @@ -1,3 +1,4 @@ +#include #include #include "envoy/config/core/v3/grpc_service.pb.h" @@ -71,9 +72,11 @@ class GrpcAccessLoggerImplTest : public testing::Test { grpc_access_logger_impl_test_helper_(local_info_, async_client_) { EXPECT_CALL(*timer_, enableTimer(_, _)); *config_.mutable_log_name() = "test_log_name"; - logger_ = std::make_unique(Grpc::RawAsyncClientPtr{async_client_}, - config_, FlushInterval, BUFFER_SIZE_BYTES, - dispatcher_, local_info_, stats_store_); + config_.mutable_buffer_size_bytes()->set_value(BUFFER_SIZE_BYTES); + config_.mutable_buffer_flush_interval()->set_nanos( + std::chrono::duration_cast(FlushInterval).count()); + logger_ = std::make_unique( + Grpc::RawAsyncClientPtr{async_client_}, config_, dispatcher_, local_info_, stats_store_); } Grpc::MockAsyncClient* async_client_; diff --git a/test/extensions/access_loggers/open_telemetry/grpc_access_log_impl_test.cc b/test/extensions/access_loggers/open_telemetry/grpc_access_log_impl_test.cc index 79059617eecc..6dda6b298eaa 100644 --- a/test/extensions/access_loggers/open_telemetry/grpc_access_log_impl_test.cc +++ b/test/extensions/access_loggers/open_telemetry/grpc_access_log_impl_test.cc @@ -1,3 +1,4 @@ +#include #include #include "envoy/config/core/v3/grpc_service.pb.h" @@ -81,9 +82,11 @@ class GrpcAccessLoggerImplTest : public testing::Test { grpc_access_logger_impl_test_helper_(local_info_, async_client_) { EXPECT_CALL(*timer_, enableTimer(_, _)); *config_.mutable_log_name() = "test_log_name"; - logger_ = std::make_unique(Grpc::RawAsyncClientPtr{async_client_}, - config_, FlushInterval, BUFFER_SIZE_BYTES, - dispatcher_, local_info_, stats_store_); + config_.mutable_buffer_size_bytes()->set_value(BUFFER_SIZE_BYTES); + config_.mutable_buffer_flush_interval()->set_nanos( + std::chrono::duration_cast(FlushInterval).count()); + logger_ = std::make_unique( + Grpc::RawAsyncClientPtr{async_client_}, config_, dispatcher_, local_info_, stats_store_); } Grpc::MockAsyncClient* async_client_; From 84523e59c9d12a2c29ea8e125a4d5583b33ec2ec Mon Sep 17 00:00:00 2001 From: Shintaro Murakami Date: Fri, 4 Mar 2022 05:50:59 +0900 Subject: [PATCH 04/68] cleanup: get stream info simply (#20029) Signed-off-by: mrkm4ntr --- source/common/tcp_proxy/tcp_proxy.cc | 48 ++++++++++++---------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index 04335ecadf1b..cc6da3ff0cab 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -362,34 +362,28 @@ Network::FilterStatus Filter::initializeUpstreamConnection() { return Network::FilterStatus::StopIteration; } - if (auto downstream_connection = downstreamConnection(); downstream_connection != nullptr) { - if (!read_callbacks_->connection() - .streamInfo() - .filterState() - ->hasData( - Network::ProxyProtocolFilterState::key())) { - read_callbacks_->connection().streamInfo().filterState()->setData( - Network::ProxyProtocolFilterState::key(), - std::make_shared(Network::ProxyProtocolData{ - downstream_connection->connectionInfoProvider().remoteAddress(), - downstream_connection->connectionInfoProvider().localAddress()}), - StreamInfo::FilterState::StateType::ReadOnly, - StreamInfo::FilterState::LifeSpan::Connection); - } - transport_socket_options_ = Network::TransportSocketOptionsUtility::fromFilterState( - read_callbacks_->connection().streamInfo().filterState()); - - if (auto typed_state = downstream_connection->streamInfo() - .filterState() - .getDataReadOnly( - Network::UpstreamSocketOptionsFilterState::key()); - typed_state != nullptr) { - auto downstream_options = typed_state->value(); - if (!upstream_options_) { - upstream_options_ = std::make_shared(); - } - Network::Socket::appendOptions(upstream_options_, downstream_options); + auto& downstream_connection = read_callbacks_->connection(); + auto& filter_state = downstream_connection.streamInfo().filterState(); + if (!filter_state->hasData( + Network::ProxyProtocolFilterState::key())) { + filter_state->setData( + Network::ProxyProtocolFilterState::key(), + std::make_shared(Network::ProxyProtocolData{ + downstream_connection.connectionInfoProvider().remoteAddress(), + downstream_connection.connectionInfoProvider().localAddress()}), + StreamInfo::FilterState::StateType::ReadOnly, + StreamInfo::FilterState::LifeSpan::Connection); + } + transport_socket_options_ = Network::TransportSocketOptionsUtility::fromFilterState(filter_state); + + if (auto typed_state = filter_state->getDataReadOnly( + Network::UpstreamSocketOptionsFilterState::key()); + typed_state != nullptr) { + auto downstream_options = typed_state->value(); + if (!upstream_options_) { + upstream_options_ = std::make_shared(); } + Network::Socket::appendOptions(upstream_options_, downstream_options); } if (!maybeTunnel(*thread_local_cluster)) { From b2d449eb1f130d9f8d01af4ef2da89416b2157db Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 4 Mar 2022 07:10:02 +0000 Subject: [PATCH 05/68] deps: Remove `com_github_bazel_buildtools` (#20182) Fix #19918 Signed-off-by: Ryan Northey --- api/bazel/repositories.bzl | 3 --- api/bazel/repository_locations.bzl | 11 ----------- 2 files changed, 14 deletions(-) diff --git a/api/bazel/repositories.bzl b/api/bazel/repositories.bzl index ef92aa45f006..7d51eb47104a 100644 --- a/api/bazel/repositories.bzl +++ b/api/bazel/repositories.bzl @@ -22,9 +22,6 @@ def api_dependencies(): external_http_archive( name = "com_google_googleapis", ) - external_http_archive( - name = "com_github_bazelbuild_buildtools", - ) external_http_archive( name = "com_github_cncf_udpa", ) diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index c47fd3f5dc2c..b56277201cfd 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -28,17 +28,6 @@ REPOSITORY_LOCATIONS_SPEC = dict( "org_golang_x_text", ], ), - com_github_bazelbuild_buildtools = dict( - project_name = "Bazel build tools", - project_desc = "Developer tools for working with Google's bazel buildtool.", - project_url = "https://github.com/bazelbuild/buildtools", - version = "4.2.5", - sha256 = "d368c47bbfc055010f118efb2962987475418737e901f7782d2a966d1dc80296", - release_date = "2022-01-13", - strip_prefix = "buildtools-{version}", - urls = ["https://github.com/bazelbuild/buildtools/archive/{version}.tar.gz"], - use_category = ["api"], - ), com_github_cncf_udpa = dict( project_name = "xDS API", project_desc = "xDS API Working Group (xDS-WG)", From 09d55e6e8b2e5c698e6bc4b614fb987913f3738f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Mar 2022 09:25:47 +0000 Subject: [PATCH 06/68] build(deps): bump slack-sdk in /.github/actions/pr_notifier (#20202) Bumps [slack-sdk](https://github.com/slackapi/python-slack-sdk) from 3.15.1 to 3.15.2. - [Release notes](https://github.com/slackapi/python-slack-sdk/releases) - [Commits](https://github.com/slackapi/python-slack-sdk/compare/v3.15.1...v3.15.2) --- updated-dependencies: - dependency-name: slack-sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/pr_notifier/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/pr_notifier/requirements.txt b/.github/actions/pr_notifier/requirements.txt index af248441eecd..b3f6b3477e46 100644 --- a/.github/actions/pr_notifier/requirements.txt +++ b/.github/actions/pr_notifier/requirements.txt @@ -111,9 +111,9 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via pynacl -slack-sdk==3.15.1 \ - --hash=sha256:149f11bdf1eddc446a2327acc28d77bd6d0c54a9f4b6c1433dec422f2cc1c940 \ - --hash=sha256:8fe074124254e16172bec679421a8dd587320b3221a36a61ff9e350cbe9f9add +slack-sdk==3.15.2 \ + --hash=sha256:128f3bb0b5b91454a3d5f140a61f3db370a0e1b50ffe0a8d9e9ebe0e894faed7 \ + --hash=sha256:e1fa26786169176e707676decc287fd9d3d547bbc43c0a1a4f99eb373b07da94 # via -r requirements.in urllib3==1.26.6 \ --hash=sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4 \ From 1f0feac80315b5105cdc8d610655defb7ecda40f Mon Sep 17 00:00:00 2001 From: David Peet Date: Fri, 4 Mar 2022 04:28:01 -0500 Subject: [PATCH 07/68] code_format: fix path matching (#20108) * code_format: fix path matching This change fixes an issue in the code formatter where explicitly excluded paths are incorrectly matched when the script is provided arguments that do not start with './' or that end with a trailing slash. To address this issue this change normalizes paths before checking if the path is excluded. * Check directory after prepending ./ Signed-off-by: David Peet --- tools/code_format/check_format.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index 2f2110931896..dc69546f57d5 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -1066,9 +1066,11 @@ def check_format_visitor(self, arg, dir_name, names): top_level = pathlib.PurePath('/', *pathlib.PurePath(dir_name).parts[:2], '/') self.check_owners(str(top_level), owned_directories, error_messages) + dir_name = normalize_path(dir_name) + for file_name in names: result = pool.apply_async( - self.check_format_return_trace_on_error, args=(dir_name + "/" + file_name,)) + self.check_format_return_trace_on_error, args=(dir_name + file_name,)) result_list.append(result) # check_error_messages iterates over the list with error messages and prints @@ -1084,6 +1086,18 @@ def whitelisted_for_memcpy(self, file_path): return file_path in MEMCPY_WHITELIST +def normalize_path(path): + """Convert path to form ./path/to/dir/ for directories and ./path/to/file otherwise""" + if not path.startswith("./"): + path = "./" + path + + isdir = os.path.isdir(path) + if isdir and not path.endswith("/"): + path += "/" + + return path + + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check or fix file format.") parser.add_argument( @@ -1228,9 +1242,7 @@ def owned_directories(error_messages): # All of our EXCLUDED_PREFIXES start with "./", but the provided # target path argument might not. Add it here if it is missing, # and use that normalized path for both lookup and `check_format`. - normalized_target_path = args.target_path - if not normalized_target_path.startswith("./"): - normalized_target_path = "./" + normalized_target_path + normalized_target_path = normalize_path(args.target_path) if not normalized_target_path.startswith( EXCLUDED_PREFIXES) and normalized_target_path.endswith(SUFFIXES): error_messages += format_checker.check_format(normalized_target_path) From 8da56b03499f81b06ffea25bd2a7d60383650346 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Fri, 4 Mar 2022 01:29:29 -0800 Subject: [PATCH 08/68] Add a name to the CodeQL workflow (#20218) Signed-off-by: Ryan Hamilton --- .github/workflows/codeql-push.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 7dd91c9a4f9c..583b16867802 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -1,3 +1,5 @@ +name: CodeQL + on: push: paths: From 6030e42074e63f5430c9a462be3f50a260259f1d Mon Sep 17 00:00:00 2001 From: "Aaron.Zhang" Date: Fri, 4 Mar 2022 18:46:36 +0800 Subject: [PATCH 09/68] Add more comments for clang-format (#20197) * Signed-off-by: bozhang --- bazel/README.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/bazel/README.md b/bazel/README.md index 2d5ec2fc3292..750456c0c450 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -916,7 +916,29 @@ Edit the paths shown here to reflect the installation locations on your system: export CLANG_FORMAT="$HOME/ext/clang+llvm-12.0.1-x86_64-linux-gnu-ubuntu-16.04/bin/clang-format" export BUILDIFIER_BIN="/usr/bin/buildifier" ``` - +The easiest way to use the correct `clang-format` in your host system is to copy the `clang-format` from the ci docker image. +* Run the ci docker image +```shell +ci/run_envoy_docker.sh bash +``` +* Get the docker container ID +```shell +dockerContainerID=$(docker ps | grep envoy-build-ubuntu | awk '{print $1}') +``` +* Copy the `clang-format` to host machine +```shell +docker copy $dockerContainerID:/opt/llvm/bin/clang-format clang-format-ci +``` +* Replace the host `clang-format` with the new one. Ensure that the copied `clang-format` is the default one. You can do this by ensuring it is in `$PATH`: +```shell +cp clang-format-ci /usr/local/bin/clang-format +``` +If you are a non-root user, alternatively you can use a bin dir and add that to `$PATH` +```shell +mkdir bin +mv clang-format-ci bin/clang-format +export PATH=$PATH:$PWD/bin/ +``` Once this is set up, you can run clang-format without docker: ```shell From 402db09af741b00a63ab6b3410e343c7f2fb98ed Mon Sep 17 00:00:00 2001 From: Kuat Date: Fri, 4 Mar 2022 05:08:31 -0800 Subject: [PATCH 10/68] matchers: implement on_no_match for custom matchers (#20122) Signed-off-by: Kuat Yessenov --- envoy/matcher/matcher.h | 9 ++-- source/common/matcher/matcher.h | 6 +-- .../extensions/common/matcher/trie_matcher.h | 18 ++++--- .../common/matcher/trie_matcher_test.cc | 52 +++++++++++++++++++ 4 files changed, 72 insertions(+), 13 deletions(-) diff --git a/envoy/matcher/matcher.h b/envoy/matcher/matcher.h index b2f665a4e5e5..e703363944ec 100644 --- a/envoy/matcher/matcher.h +++ b/envoy/matcher/matcher.h @@ -291,9 +291,12 @@ class CommonProtocolInputFactory : public Config::TypedFactory { */ template class CustomMatcherFactory : public Config::TypedFactory { public: - virtual MatchTreeFactoryCb createCustomMatcherFactoryCb( - const Protobuf::Message& config, Server::Configuration::ServerFactoryContext& factory_context, - DataInputFactoryCb data_input, OnMatchFactory& on_match_factory) PURE; + virtual MatchTreeFactoryCb + createCustomMatcherFactoryCb(const Protobuf::Message& config, + Server::Configuration::ServerFactoryContext& factory_context, + DataInputFactoryCb data_input, + absl::optional> on_no_match, + OnMatchFactory& on_match_factory) PURE; std::string category() const override { // Static assert to guide implementors to understand what is required. static_assert(std::is_convertible(), diff --git a/source/common/matcher/matcher.h b/source/common/matcher/matcher.h index 3402b1d50989..80e230af93e7 100644 --- a/source/common/matcher/matcher.h +++ b/source/common/matcher/matcher.h @@ -201,6 +201,8 @@ class MatchTreeFactory : public OnMatchFactory { template MatchTreeFactoryCb createTreeMatcher(const MatcherType& matcher) { auto data_input = createDataInput(matcher.matcher_tree().input()); + auto on_no_match = createOnMatch(matcher.on_no_match()); + switch (matcher.matcher_tree().tree_type_case()) { case MatcherType::MatcherTree::kExactMatchMap: { std::vector>> match_children; @@ -211,8 +213,6 @@ class MatchTreeFactory : public OnMatchFactory { std::make_pair(children.first, *MatchTreeFactory::createOnMatch(children.second))); } - auto on_no_match = createOnMatch(matcher.on_no_match()); - return [match_children, data_input, on_no_match]() { auto multimap_matcher = std::make_unique>( data_input(), on_no_match ? absl::make_optional((*on_no_match)()) : absl::nullopt); @@ -233,7 +233,7 @@ class MatchTreeFactory : public OnMatchFactory { matcher.matcher_tree().custom_match().typed_config(), server_factory_context_.messageValidationVisitor(), factory); return factory.createCustomMatcherFactoryCb(*message, server_factory_context_, data_input, - *this); + on_no_match, *this); } } PANIC_DUE_TO_CORRUPT_ENUM; diff --git a/source/extensions/common/matcher/trie_matcher.h b/source/extensions/common/matcher/trie_matcher.h index 6b4ea542bf8b..7a327fde97df 100644 --- a/source/extensions/common/matcher/trie_matcher.h +++ b/source/extensions/common/matcher/trie_matcher.h @@ -60,9 +60,9 @@ template struct TrieNodeComparator { */ template class TrieMatcher : public MatchTree { public: - TrieMatcher(DataInputPtr&& data_input, + TrieMatcher(DataInputPtr&& data_input, absl::optional> on_no_match, const std::shared_ptr>>& trie) - : data_input_(std::move(data_input)), trie_(trie) {} + : data_input_(std::move(data_input)), on_no_match_(std::move(on_no_match)), trie_(trie) {} typename MatchTree::MatchResult match(const DataType& data) override { const auto input = data_input_->get(data); @@ -70,12 +70,12 @@ template class TrieMatcher : public MatchTree { return {MatchState::UnableToMatch, absl::nullopt}; } if (!input.data_) { - return {MatchState::MatchComplete, absl::nullopt}; + return {MatchState::MatchComplete, on_no_match_}; } const Network::Address::InstanceConstSharedPtr addr = Network::Utility::parseInternetAddressNoThrow(*input.data_); if (!addr) { - return {MatchState::MatchComplete, absl::nullopt}; + return {MatchState::MatchComplete, on_no_match_}; } auto values = trie_->getData(addr); // The candidates returned by the LC trie are not in any specific order, so we @@ -101,11 +101,12 @@ template class TrieMatcher : public MatchTree { first = false; } } - return {MatchState::MatchComplete, absl::nullopt}; + return {MatchState::MatchComplete, on_no_match_}; } private: const DataInputPtr data_input_; + const absl::optional> on_no_match_; std::shared_ptr>> trie_; }; @@ -116,6 +117,7 @@ class TrieMatcherFactoryBase : public ::Envoy::Matcher::CustomMatcherFactory data_input, + absl::optional> on_no_match, OnMatchFactory& on_match_factory) override { const auto& typed_config = MessageUtil::downcastAndValidate( @@ -139,8 +141,10 @@ class TrieMatcherFactoryBase : public ::Envoy::Matcher::CustomMatcherFactory>>(data); - return [data_input, lc_trie]() { - return std::make_unique>(data_input(), lc_trie); + return [data_input, lc_trie, on_no_match]() { + return std::make_unique>( + data_input(), on_no_match ? absl::make_optional(on_no_match.value()()) : absl::nullopt, + lc_trie); }; }; ProtobufTypes::MessagePtr createEmptyConfigProto() override { diff --git a/test/extensions/common/matcher/trie_matcher_test.cc b/test/extensions/common/matcher/trie_matcher_test.cc index ddf41211576f..b95580a22e9d 100644 --- a/test/extensions/common/matcher/trie_matcher_test.cc +++ b/test/extensions/common/matcher/trie_matcher_test.cc @@ -134,6 +134,58 @@ TEST_F(TrieMatcherTest, TestMatcher) { } } +TEST_F(TrieMatcherTest, TestMatcherOnNoMatch) { + const std::string yaml = R"EOF( +matcher_tree: + input: + name: input + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + custom_match: + name: ip_matcher + typed_config: + "@type": type.googleapis.com/xds.type.matcher.v3.IPMatcher + range_matchers: + - ranges: + - address_prefix: 192.0.0.0 + prefix_len: 2 + on_match: + action: + name: test_action + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + value: foo +on_no_match: + action: + name: bar + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + value: bar + )EOF"; + loadConfig(yaml); + + { + auto input = TestDataInputFactory("input", "192.0.100.1"); + validateMatch("foo"); + } + { + // No range matches. + auto input = TestDataInputFactory("input", "128.0.0.1"); + validateMatch("bar"); + } + { + // Input is not a valid IP. + auto input = TestDataInputFactory("input", "xxx"); + validateMatch("bar"); + } + { + // Input is nullopt. + auto input = TestDataInputFactory( + "input", {DataInputGetResult::DataAvailability::AllDataAvailable, absl::nullopt}); + validateMatch("bar"); + } +} + TEST_F(TrieMatcherTest, OverlappingMatcher) { const std::string yaml = R"EOF( matcher_tree: From ef5fe28eab5a172f7370fe6d8a3bdf9d1a0f4546 Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Fri, 4 Mar 2022 05:46:02 -0800 Subject: [PATCH 11/68] Alt Service Cache: Alt srv-cache no longer modifies the keystore while iterating. (#20188) * Alt srv-cache should defer updates until no longer iterating the keystore otherwise we could rehash but we have no iterator stability. Signed-off-by: Kevin Baichoo --- bazel/repositories.bzl | 4 +++ source/common/common/BUILD | 3 +++ source/common/common/key_value_store_base.cc | 15 +++++++++-- .../http/alternate_protocols_cache_impl.cc | 21 ++++++++++----- .../http/alternate_protocols_cache_impl.h | 1 + .../alternate_protocols_cache_impl_test.cc | 8 ++++++ .../file_based/key_value_store_test.cc | 27 +++++++++++++++++++ 7 files changed, 71 insertions(+), 8 deletions(-) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 5085dc8229fc..a8cbd55ff7cc 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -681,6 +681,10 @@ def _com_google_absl(): name = "abseil_status", actual = "@com_google_absl//absl/status", ) + native.bind( + name = "abseil_cleanup", + actual = "@com_google_absl//absl/cleanup:cleanup", + ) def _com_google_protobuf(): external_http_archive( diff --git a/source/common/common/BUILD b/source/common/common/BUILD index 8ea9d219563b..685d111e0029 100644 --- a/source/common/common/BUILD +++ b/source/common/common/BUILD @@ -153,6 +153,9 @@ envoy_cc_library( name = "key_value_store_lib", srcs = ["key_value_store_base.cc"], hdrs = ["key_value_store_base.h"], + external_deps = [ + "abseil_cleanup", + ], deps = [ "//envoy/common:key_value_store_interface", "//envoy/event:dispatcher_interface", diff --git a/source/common/common/key_value_store_base.cc b/source/common/common/key_value_store_base.cc index c0944b1a7076..ac7787474a60 100644 --- a/source/common/common/key_value_store_base.cc +++ b/source/common/common/key_value_store_base.cc @@ -1,5 +1,7 @@ #include "source/common/common/key_value_store_base.h" +#include "absl/cleanup/cleanup.h" + namespace Envoy { namespace { @@ -63,8 +65,7 @@ bool KeyValueStoreBase::parseContents(absl::string_view contents, } void KeyValueStoreBase::addOrUpdate(absl::string_view key, absl::string_view value) { - store_.erase(key); - store_.emplace(key, value); + store_.insert_or_assign(key, std::string(value)); if (!flush_timer_->enabled()) { flush(); } @@ -86,6 +87,16 @@ absl::optional KeyValueStoreBase::get(absl::string_view key) } void KeyValueStoreBase::iterate(ConstIterateCb cb) const { +#ifndef NDEBUG + // When running in debug mode, verify we don't modify the underlying store + // while iterating. + absl::flat_hash_map store_before_iteration = store_; + absl::Cleanup verify_store_is_not_modified = [this, &store_before_iteration] { + ASSERT(store_ == store_before_iteration, + "Expected iterate to not modify the underlying store."); + }; +#endif + for (const auto& [key, value] : store_) { Iterate ret = cb(key, value); if (ret == Iterate::Break) { diff --git a/source/common/http/alternate_protocols_cache_impl.cc b/source/common/http/alternate_protocols_cache_impl.cc index 653ce00835e1..55e766018a9f 100644 --- a/source/common/http/alternate_protocols_cache_impl.cc +++ b/source/common/http/alternate_protocols_cache_impl.cc @@ -104,22 +104,26 @@ AlternateProtocolsCacheImpl::originDataFromString(absl::string_view origin_data_ AlternateProtocolsCacheImpl::AlternateProtocolsCacheImpl( TimeSource& time_source, std::unique_ptr&& key_value_store, size_t max_entries) - : time_source_(time_source), key_value_store_(std::move(key_value_store)), - max_entries_(max_entries > 0 ? max_entries : 1024) { - if (key_value_store_) { - KeyValueStore::ConstIterateCb load = [this](const std::string& key, const std::string& value) { + : time_source_(time_source), max_entries_(max_entries > 0 ? max_entries : 1024) { + if (key_value_store) { + KeyValueStore::ConstIterateCb load_protocols = [this](const std::string& key, + const std::string& value) { absl::optional origin_data = originDataFromString(value, time_source_, true); absl::optional origin = stringToOrigin(key); if (origin_data.has_value() && origin.has_value()) { + // We deferred transfering ownership into key_value_store_ prior, so + // that we won't end up doing redundant updates to the store while + // iterating. setAlternativesImpl(origin.value(), origin_data.value().protocols); - setSrtt(origin.value(), origin_data.value().srtt); + setSrttImpl(origin.value(), origin_data.value().srtt); } else { ENVOY_LOG(warn, fmt::format("Unable to parse cache entry with key: {} value: {}", key, value)); } return KeyValueStore::Iterate::Continue; }; - key_value_store_->iterate(load); + key_value_store->iterate(load_protocols); + key_value_store_ = std::move(key_value_store); } } @@ -136,6 +140,11 @@ void AlternateProtocolsCacheImpl::setAlternatives(const Origin& origin, } void AlternateProtocolsCacheImpl::setSrtt(const Origin& origin, std::chrono::microseconds srtt) { + setSrttImpl(origin, srtt); +} + +void AlternateProtocolsCacheImpl::setSrttImpl(const Origin& origin, + std::chrono::microseconds srtt) { auto entry_it = protocols_.find(origin); if (entry_it == protocols_.end()) { return; diff --git a/source/common/http/alternate_protocols_cache_impl.h b/source/common/http/alternate_protocols_cache_impl.h index 2cfbe0f92c2e..8555b73cd6e0 100644 --- a/source/common/http/alternate_protocols_cache_impl.h +++ b/source/common/http/alternate_protocols_cache_impl.h @@ -71,6 +71,7 @@ class AlternateProtocolsCacheImpl : public AlternateProtocolsCache, private: void setAlternativesImpl(const Origin& origin, std::vector& protocols); + void setSrttImpl(const Origin& origin, std::chrono::microseconds srtt); // Time source used to check expiration of entries. TimeSource& time_source_; diff --git a/test/common/http/alternate_protocols_cache_impl_test.cc b/test/common/http/alternate_protocols_cache_impl_test.cc index 248530f2044f..05569e89ae3b 100644 --- a/test/common/http/alternate_protocols_cache_impl_test.cc +++ b/test/common/http/alternate_protocols_cache_impl_test.cc @@ -284,6 +284,14 @@ TEST_F(AlternateProtocolsCacheImplTest, CacheLoad) { EXPECT_EQ(protocols1_, protocols.ref()); } +TEST_F(AlternateProtocolsCacheImplTest, ShouldNotUpdateStoreOnCacheLoad) { + EXPECT_CALL(*store_, addOrUpdate(_, _)).Times(0); + EXPECT_CALL(*store_, iterate(_)).WillOnce(Invoke([&](KeyValueStore::ConstIterateCb fn) { + fn("https://hostname1:1", "alpn1=\"hostname1:1\"; ma=5|0"); + })); + initialize(); +} + } // namespace } // namespace Http } // namespace Envoy diff --git a/test/extensions/key_value/file_based/key_value_store_test.cc b/test/extensions/key_value/file_based/key_value_store_test.cc index ec99e61b7b9f..cfa4130454fd 100644 --- a/test/extensions/key_value/file_based/key_value_store_test.cc +++ b/test/extensions/key_value/file_based/key_value_store_test.cc @@ -103,6 +103,33 @@ TEST_F(KeyValueStoreTest, Iterate) { EXPECT_EQ(1, stop_early_counter); } +#ifndef NDEBUG +TEST_F(KeyValueStoreTest, ShouldCrashIfIterateCallbackAddsOrUpdatesStore) { + store_->addOrUpdate("foo", "bar"); + store_->addOrUpdate("baz", "eep"); + KeyValueStore::ConstIterateCb update_value_callback = [this](const std::string& key, + const std::string&) { + EXPECT_TRUE(key == "foo" || key == "baz"); + store_->addOrUpdate("foo", "updated-bar"); + return KeyValueStore::Iterate::Continue; + }; + + EXPECT_DEATH(store_->iterate(update_value_callback), + "Expected iterate to not modify the underlying store."); + + KeyValueStore::ConstIterateCb add_key_callback = [this](const std::string& key, + const std::string&) { + EXPECT_TRUE(key == "foo" || key == "baz" || key == "new-key"); + if (key == "foo") { + store_->addOrUpdate("new-key", "new-value"); + } + return KeyValueStore::Iterate::Continue; + }; + EXPECT_DEATH(store_->iterate(add_key_callback), + "Expected iterate to not modify the underlying store."); +} +#endif + TEST_F(KeyValueStoreTest, HandleBadFile) { auto checkBadFile = [this](std::string file, std::string error) { TestEnvironment::writeStringToFileForTest(filename_, file, true); From b8c4d4bae0c48c2a97126e486651987ceab7b493 Mon Sep 17 00:00:00 2001 From: xuhj Date: Fri, 4 Mar 2022 23:21:41 +0800 Subject: [PATCH 12/68] listener: ensure Ipv6 any address is valid on ipv4_compat set (#20214) Signed-off-by: He Jie Xu --- source/server/listener_impl.cc | 3 ++- test/server/listener_manager_impl_test.cc | 30 +++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/source/server/listener_impl.cc b/source/server/listener_impl.cc index 7ac6bd13606f..09d967511192 100644 --- a/source/server/listener_impl.cc +++ b/source/server/listener_impl.cc @@ -351,7 +351,8 @@ ListenerImpl::ListenerImpl(const envoy::config::listener::v3::Listener& config, if ((address_->type() == Network::Address::Type::Ip && config.address().socket_address().ipv4_compat()) && (address_->ip()->version() != Network::Address::IpVersion::v6 || - address_->ip()->ipv6()->v4CompatibleAddress() == nullptr)) { + (!address_->ip()->isAnyAddress() && + address_->ip()->ipv6()->v4CompatibleAddress() == nullptr))) { if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_check_on_ipv4_compat")) { throw EnvoyException(fmt::format( "Only IPv6 address '::' or valid IPv4-mapped IPv6 address can set ipv4_compat: {}", diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index eb207528468a..39d951d558a7 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -599,6 +599,36 @@ TEST_F(ListenerManagerImplTest, RejectIpv4CompatOnNonIpv4MappedIpv6address) { "Only IPv6 address '::' or valid IPv4-mapped IPv6 address can set ipv4_compat: [::1]:13333"); } +TEST_F(ListenerManagerImplTest, AcceptIpv4CompatOnIpv6AnyAddress) { + const std::string yaml = R"EOF( + name: "foo" + address: + socket_address: + address: "::" + port_value: 13333 + ipv4_compat: true + filter_chains: + - filters: [] + )EOF"; + + EXPECT_TRUE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true)); +} + +TEST_F(ListenerManagerImplTest, AcceptIpv4CompatOnNonCanonicalIpv6AnyAddress) { + const std::string yaml = R"EOF( + name: "foo" + address: + socket_address: + address: "::0" + port_value: 13333 + ipv4_compat: true + filter_chains: + - filters: [] + )EOF"; + + EXPECT_TRUE(manager_->addOrUpdateListener(parseListenerFromV3Yaml(yaml), "", true)); +} + TEST_F(ListenerManagerImplTest, AcceptIpv4CompatOnNonIpv4MappedIpv6address) { auto scoped_runtime_guard = std::make_unique(); Runtime::LoaderSingleton::getExisting()->mergeValues( From 4e970d58db25cd5709871658ac5dfd981c6483c7 Mon Sep 17 00:00:00 2001 From: Aidan Hahn <87328374+aidanhahn@users.noreply.github.com> Date: Fri, 4 Mar 2022 13:12:30 -0800 Subject: [PATCH 13/68] formatter: escape percent sign in response format (#20093) Signed-off-by: Aidan Hahn --- .../formatter/substitution_formatter.cc | 12 +++++- .../formatter/substitution_formatter_test.cc | 42 ++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/source/common/formatter/substitution_formatter.cc b/source/common/formatter/substitution_formatter.cc index bda70ea22c3c..de65cd9f6b99 100644 --- a/source/common/formatter/substitution_formatter.cc +++ b/source/common/formatter/substitution_formatter.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -454,12 +455,21 @@ SubstitutionFormatParser::parse(const std::string& format, std::vector formatters; const std::regex command_w_args_regex(R"EOF(^%([A-Z]|[0-9]|_)+(\([^\)]*\))?(:[0-9]+)?(%))EOF"); - for (size_t pos = 0; pos < format.length(); ++pos) { + for (size_t pos = 0; pos < format.size(); ++pos) { if (format[pos] != '%') { current_token += format[pos]; continue; } + // escape '%%' + if (format.size() > pos + 1) { + if (format[pos + 1] == '%') { + current_token += '%'; + pos++; + continue; + } + } + if (!current_token.empty()) { formatters.emplace_back(FormatterProviderPtr{new PlainStringFormatter(current_token)}); current_token = ""; diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index 3f5884d75e09..7cbe15c939a3 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -3109,8 +3109,7 @@ TEST(SubstitutionFormatterTest, ParserFailures) { "RESP(FIRST)%", "%REQ(valid)% %NOT_VALID%", "%REQ(FIRST?SECOND%", - "%%", - "%%HOSTNAME%PROTOCOL%", + "%HOSTNAME%PROTOCOL%", "%protocol%", "%REQ(TEST):%", "%REQ(TEST):3q4%", @@ -3168,6 +3167,20 @@ TEST(SubstitutionFormatterTest, EmptyFormatParse) { stream_info, body)); } +TEST(SubstitutionFormatterTest, EscapingFormatParse) { + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/"}}; + Http::TestResponseHeaderMapImpl response_headers; + Http::TestResponseTrailerMapImpl response_trailers; + StreamInfo::MockStreamInfo stream_info; + std::string body; + + auto providers = SubstitutionFormatParser::parse("%%"); + + ASSERT_EQ(providers.size(), 1); + EXPECT_EQ("%", providers[0]->format(request_headers, response_headers, response_trailers, + stream_info, body)); +} + TEST(SubstitutionFormatterTest, FormatterExtension) { Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/"}}; Http::TestResponseHeaderMapImpl response_headers; @@ -3185,6 +3198,31 @@ TEST(SubstitutionFormatterTest, FormatterExtension) { response_trailers, stream_info, body)); } +TEST(SubstitutionFormatterTest, PercentEscapingEdgeCase) { + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, {":path", "/"}}; + Http::TestResponseHeaderMapImpl response_headers; + Http::TestResponseTrailerMapImpl response_trailers; + StreamInfo::MockStreamInfo stream_info; + std::string body; + NiceMock os_sys_calls; + TestThreadsafeSingletonInjector os_calls(&os_sys_calls); + EXPECT_CALL(os_sys_calls, gethostname(_, _)) + .WillOnce(Invoke([](char* name, size_t) -> Api::SysCallIntResult { + StringUtil::strlcpy(name, "myhostname", 11); + return {0, 0}; + })); + absl::optional protocol = Http::Protocol::Http11; + EXPECT_CALL(stream_info, protocol()).WillRepeatedly(Return(protocol)); + + auto providers = SubstitutionFormatParser::parse("%HOSTNAME%%PROTOCOL%"); + + ASSERT_EQ(providers.size(), 2); + EXPECT_EQ("myhostname", providers[0]->format(request_headers, response_headers, response_trailers, + stream_info, body)); + EXPECT_EQ("HTTP/1.1", providers[1]->format(request_headers, response_headers, response_trailers, + stream_info, body)); +} + } // namespace } // namespace Formatter } // namespace Envoy From 23ff23d0954eb9e6e4ed7ed60ec0e0d05174f6da Mon Sep 17 00:00:00 2001 From: Wanli Li Date: Fri, 4 Mar 2022 13:51:04 -0800 Subject: [PATCH 14/68] health_checker: Add request header options to GRPC health checker. (#20071) Upstream GRPC hosts may categorize requests by request headers. This change exposes request_headers_to_add and request_headers_to_remove options to GrpcHealthCheck in a way similar to HttpHealthCheck. Risk Level: Low Testing: Performed manual test, a new unit test is added to cover the new code path. Docs Changes: None Release Notes: Added to "Minor Behavior Changes" section. Signed-off-by: Wanli Li --- api/envoy/config/core/v3/health_check.proto | 6 + docs/root/version_history/current.rst | 1 + source/common/upstream/health_checker_impl.cc | 15 +- source/common/upstream/health_checker_impl.h | 2 + .../upstream/health_checker_impl_test.cc | 135 ++++++++++++++++++ 5 files changed, 157 insertions(+), 2 deletions(-) diff --git a/api/envoy/config/core/v3/health_check.proto b/api/envoy/config/core/v3/health_check.proto index fcae6eb697c4..99934bb45208 100644 --- a/api/envoy/config/core/v3/health_check.proto +++ b/api/envoy/config/core/v3/health_check.proto @@ -186,6 +186,12 @@ message HealthCheck { // the :ref:`hostname ` field. string authority = 2 [(validate.rules).string = {well_known_regex: HTTP_HEADER_VALUE strict: false}]; + + // Specifies a list of key-value pairs that should be added to the metadata of each GRPC call + // that is sent to the health checked cluster. For more information, including details on header value syntax, + // see the documentation on :ref:`custom request headers + // `. + repeated HeaderValueOption initial_metadata = 3 [(validate.rules).repeated = {max_items: 1000}]; } // Custom health check. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 5be100a55e4a..a3ef7113be43 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -17,6 +17,7 @@ Minor Behavior Changes * ext_authz: added requested server name in ext_authz network filter for auth review. * file: changed disk based files to truncate files which are not being appended to. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.append_or_truncate`` to false. * grpc: flip runtime guard ``envoy.reloadable_features.enable_grpc_async_client_cache`` to be default enabled. async grpc client created through getOrCreateRawAsyncClient will be cached by default. +* health_checker: exposing `initial_metadata` to GrpcHealthCheck in a way similar to `request_headers_to_add` of HttpHealthCheck. * http: avoiding delay-close for HTTP/1.0 responses framed by connection: close as well as HTTP/1.1 if the request is fully read. This means for responses to such requests, the FIN will be sent immediately after the response. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.skip_delay_close`` to false. If clients are are seen to be receiving sporadic partial responses and flipping this flag fixes it, please notify the project immediately. * http: now the max concurrent streams of http2 connection can not only be adjusted down according to the SETTINGS frame but also can be adjusted up, of course, it can not exceed the configured upper bounds. This fix is guarded by ``envoy.reloadable_features.http2_allow_capacity_increase_by_settings``. * http: when writing custom filters, `injectEncodedDataToFilterChain` and `injectDecodedDataToFilterChain` now trigger sending of headers if they were not yet sent due to `StopIteration`. Previously, calling one of the inject functions in that state would trigger an assertion. See issue #19891 for more details. diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index 718bba503611..16621b7fc0a9 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -601,7 +601,9 @@ GrpcHealthCheckerImpl::GrpcHealthCheckerImpl(const Cluster& cluster, : HealthCheckerImplBase(cluster, config, dispatcher, runtime, random, std::move(event_logger)), random_generator_(random), service_method_(*Protobuf::DescriptorPool::generated_pool()->FindMethodByName( - "grpc.health.v1.Health.Check")) { + "grpc.health.v1.Health.Check")), + request_headers_parser_( + Router::HeaderParser::configure(config.grpc_health_check().initial_metadata())) { if (!config.grpc_health_check().service_name().empty()) { service_name_ = config.grpc_health_check().service_name(); } @@ -613,7 +615,10 @@ GrpcHealthCheckerImpl::GrpcHealthCheckerImpl(const Cluster& cluster, GrpcHealthCheckerImpl::GrpcActiveHealthCheckSession::GrpcActiveHealthCheckSession( GrpcHealthCheckerImpl& parent, const HostSharedPtr& host) - : ActiveHealthCheckSession(parent, host), parent_(parent) {} + : ActiveHealthCheckSession(parent, host), parent_(parent), + local_connection_info_provider_(std::make_shared( + Network::Utility::getCanonicalIpv4LoopbackAddress(), + Network::Utility::getCanonicalIpv4LoopbackAddress())) {} GrpcHealthCheckerImpl::GrpcActiveHealthCheckSession::~GrpcActiveHealthCheckSession() { ASSERT(client_ == nullptr); @@ -738,6 +743,12 @@ void GrpcHealthCheckerImpl::GrpcActiveHealthCheckSession::onInterval() { headers_message->headers().setReferenceUserAgent( Http::Headers::get().UserAgentValues.EnvoyHealthChecker); + StreamInfo::StreamInfoImpl stream_info(Http::Protocol::Http2, parent_.dispatcher_.timeSource(), + local_connection_info_provider_); + stream_info.setUpstreamInfo(std::make_shared()); + stream_info.upstreamInfo()->setUpstreamHost(host_); + parent_.request_headers_parser_->evaluateHeaders(headers_message->headers(), stream_info); + Grpc::Common::toGrpcTimeout(parent_.timeout_, headers_message->headers()); Router::FilterUtility::setUpstreamScheme( diff --git a/source/common/upstream/health_checker_impl.h b/source/common/upstream/health_checker_impl.h index 3dd90a6dc140..2b1ae69f3557 100644 --- a/source/common/upstream/health_checker_impl.h +++ b/source/common/upstream/health_checker_impl.h @@ -386,6 +386,7 @@ class GrpcHealthCheckerImpl : public HealthCheckerImplBase { Http::RequestEncoder* request_encoder_; Grpc::Decoder decoder_; std::unique_ptr health_check_response_; + Network::ConnectionInfoProviderSharedPtr local_connection_info_provider_; // If true, stream reset was initiated by us (GrpcActiveHealthCheckSession), not by HTTP stack, // e.g. remote reset. In this case healthcheck status has already been reported, only state // cleanup is required. @@ -412,6 +413,7 @@ class GrpcHealthCheckerImpl : public HealthCheckerImplBase { const Protobuf::MethodDescriptor& service_method_; absl::optional service_name_; absl::optional authority_value_; + Router::HeaderParserPtr request_headers_parser_; }; /** diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 436a5dde4b6a..e8ec25a14a6e 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -4139,6 +4139,20 @@ class GrpcHealthCheckerImplTestBase : public Event::TestUsingSimulatedTime, addCompletionCallback(); } + void setupHCWithHeaders(const absl::flat_hash_map headers_to_add) { + auto config = createGrpcHealthCheckConfig(); + config.mutable_grpc_health_check()->set_service_name("service"); + for (const auto& pair : headers_to_add) { + auto header_value_option = config.mutable_grpc_health_check()->add_initial_metadata(); + header_value_option->mutable_append()->set_value(false); + auto header = header_value_option->mutable_header(); + header->set_key(pair.first); + header->set_value(pair.second); + } + allocHealthChecker(config); + addCompletionCallback(); + } + void setupNoReuseConnectionHC() { auto config = createGrpcHealthCheckConfig(); config.mutable_reuse_connection()->set_value(false); @@ -4423,6 +4437,127 @@ TEST_F(GrpcHealthCheckerImplTest, SuccessWithCustomAuthority) { testSingleHostSuccess(authority); } +// Test single host check success with additional headers. +TEST_F(GrpcHealthCheckerImplTest, SuccessWithAdditionalHeaders) { + const std::string ENVOY_OK_KEY = "x-envoy-ok"; + const std::string ENVOY_OK_VAL = "ok"; + const std::string ENVOY_COOL_KEY = "x-envoy-cool"; + const std::string ENVOY_COOL_VAL = "cool"; + const std::string ENVOY_AWESOME_KEY = "x-envoy-awesome"; + const std::string ENVOY_AWESOME_VAL = "awesome"; + const std::string USER_AGENT_KEY = "user-agent"; + const std::string USER_AGENT_VAL = "CoolEnvoy/HC"; + const std::string PROTOCOL_KEY = "x-protocol"; + const std::string PROTOCOL_VAL = "%PROTOCOL%"; + const std::string DOWNSTREAM_LOCAL_ADDRESS_KEY = "x-downstream-local-address"; + const std::string DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT_KEY = + "x-downstream-local-address-without-port"; + const std::string DOWNSTREAM_REMOTE_ADDRESS_KEY = "x-downstream-remote-address"; + const std::string DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT_KEY = + "x-downstream-remote-address-without-port"; + const std::string START_TIME_KEY = "x-start-time"; + const std::string UPSTREAM_METADATA_KEY = "x-upstream-metadata"; + + setupHCWithHeaders( + {{ENVOY_OK_KEY, ENVOY_OK_VAL}, + {ENVOY_COOL_KEY, ENVOY_COOL_VAL}, + {ENVOY_AWESOME_KEY, ENVOY_AWESOME_VAL}, + {USER_AGENT_KEY, USER_AGENT_VAL}, + {PROTOCOL_KEY, PROTOCOL_VAL}, + {DOWNSTREAM_LOCAL_ADDRESS_KEY, "%DOWNSTREAM_LOCAL_ADDRESS%"}, + {DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT_KEY, "%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT%"}, + {DOWNSTREAM_REMOTE_ADDRESS_KEY, "%DOWNSTREAM_REMOTE_ADDRESS%"}, + {DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT_KEY, "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"}, + {START_TIME_KEY, "%START_TIME(%s.%9f)%"}, + {UPSTREAM_METADATA_KEY, "%UPSTREAM_METADATA([\"namespace\", \"key\"])%"}}); + + auto metadata = TestUtility::parseYaml( + R"EOF( + filter_metadata: + namespace: + key: value + )EOF"); + + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", metadata, simTime())}; + + cluster_->info_->stats().upstream_cx_total_.inc(); + + expectSessionCreate(); + expectHealthcheckStart(0); + + EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, false)) + .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()); + EXPECT_NE(nullptr, headers.Method()); + EXPECT_EQ(cluster_->info_->name(), headers.getHostValue()); + EXPECT_EQ(ENVOY_OK_VAL, + headers.get(Http::LowerCaseString(ENVOY_OK_KEY))[0]->value().getStringView()); + EXPECT_EQ(ENVOY_COOL_VAL, + headers.get(Http::LowerCaseString(ENVOY_COOL_KEY))[0]->value().getStringView()); + EXPECT_EQ( + ENVOY_AWESOME_VAL, + headers.get(Http::LowerCaseString(ENVOY_AWESOME_KEY))[0]->value().getStringView()); + EXPECT_EQ(USER_AGENT_VAL, headers.getUserAgentValue()); + EXPECT_EQ("HTTP/2", + headers.get(Http::LowerCaseString(PROTOCOL_KEY))[0]->value().getStringView()); + EXPECT_EQ("127.0.0.1:0", + headers.get(Http::LowerCaseString(DOWNSTREAM_LOCAL_ADDRESS_KEY))[0] + ->value() + .getStringView()); + EXPECT_EQ("127.0.0.1", + headers.get(Http::LowerCaseString(DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT_KEY))[0] + ->value() + .getStringView()); + EXPECT_EQ("127.0.0.1:0", + headers.get(Http::LowerCaseString(DOWNSTREAM_REMOTE_ADDRESS_KEY))[0] + ->value() + .getStringView()); + EXPECT_EQ("127.0.0.1", + headers.get(Http::LowerCaseString(DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT_KEY))[0] + ->value() + .getStringView()); + Envoy::DateFormatter date_formatter("%s.%9f"); + std::string current_start_time = + date_formatter.fromTime(dispatcher_.timeSource().systemTime()); + EXPECT_EQ(current_start_time, + headers.get(Http::LowerCaseString(START_TIME_KEY))[0]->value().getStringView()); + EXPECT_EQ( + "value", + headers.get(Http::LowerCaseString(UPSTREAM_METADATA_KEY))[0]->value().getStringView()); + 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) { + std::vector decoded_frames; + Grpc::Decoder decoder; + ASSERT_TRUE(decoder.decode(data, decoded_frames)); + ASSERT_EQ(1U, decoded_frames.size()); + auto& frame = decoded_frames[0]; + Buffer::ZeroCopyInputStreamImpl stream(std::move(frame.data_)); + grpc::health::v1::HealthCheckRequest request; + ASSERT_TRUE(request.ParseFromZeroCopyStream(&stream)); + EXPECT_EQ("service", request.service()); + })); + + health_checker_->start(); + + EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); + EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) + .WillOnce(Return(45000)); + expectHealthcheckStop(0, 45000); + + // Host state should not be changed (remains healthy). + EXPECT_CALL(*this, onHostStatus(cluster_->prioritySet().getMockHostSet(0)->hosts_[0], + HealthTransition::Unchanged)); + respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); + expectHostHealthy(true); +} + // Test host check success when gRPC response payload is split between several incoming data chunks. TEST_F(GrpcHealthCheckerImplTest, SuccessResponseSplitBetweenChunks) { setupServiceNameHC(absl::nullopt); From bdd2f63fafd7688388fb7e32bb209f2687af46e2 Mon Sep 17 00:00:00 2001 From: Yuchen Dai Date: Fri, 4 Mar 2022 14:32:45 -0800 Subject: [PATCH 15/68] perf: some config message optimization (#20185) prefer ref and move to copy Risk Level: LOW Signed-off-by: Yuchen Dai --- source/common/upstream/upstream_impl.cc | 6 ++++-- source/server/listener_manager_impl.cc | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index d35d436c4a50..93d6a1efadd0 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -933,7 +933,9 @@ ClusterInfoImpl::ClusterInfoImpl( DurationUtil::durationToMilliseconds(common_lb_config_.update_merge_window()); // Create upstream filter factories - auto filters = config.filters(); + const auto& filters = config.filters(); + ASSERT(filter_factories_.empty()); + filter_factories_.reserve(filters.size()); for (ssize_t i = 0; i < filters.size(); i++) { const auto& proto_config = filters[i]; ENVOY_LOG(debug, " upstream filter #{}:", i); @@ -945,7 +947,7 @@ ClusterInfoImpl::ClusterInfoImpl( factory_context.messageValidationVisitor(), *message); Network::FilterFactoryCb callback = factory.createFilterFactoryFromProto(*message, *factory_context_); - filter_factories_.push_back(callback); + filter_factories_.push_back(std::move(callback)); } } diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index e0e4be5f280a..e945c7f7ceb0 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -80,6 +80,7 @@ std::vector ProdListenerComponentFactory::createNetwor const Protobuf::RepeatedPtrField& filters, Server::Configuration::FilterChainFactoryContext& filter_chain_factory_context) { std::vector ret; + ret.reserve(filters.size()); for (ssize_t i = 0; i < filters.size(); i++) { const auto& proto_config = filters[i]; ENVOY_LOG(debug, " filter #{}:", i); @@ -101,7 +102,7 @@ std::vector ProdListenerComponentFactory::createNetwor i == filters.size() - 1); Network::FilterFactoryCb callback = factory.createFilterFactoryFromProto(*message, filter_chain_factory_context); - ret.push_back(callback); + ret.push_back(std::move(callback)); } return ret; } From 55e9b316d2e7d17a05b68924d0dffc2635ab8e52 Mon Sep 17 00:00:00 2001 From: Felix Du Date: Sun, 6 Mar 2022 11:37:14 +0800 Subject: [PATCH 16/68] sip-proxy: get load address from context localInfo (#20119) Use the unique service ip from localinfo instead of connection info Risk Level: Low Testing: Ut/Mt Signed-off-by: Felix Du --- .../sip_proxy/filters/network/source/BUILD | 2 ++ .../filters/network/source/conn_manager.cc | 4 ++-- .../filters/network/source/conn_manager.h | 24 ------------------- .../filters/network/source/decoder.h | 1 - .../network/source/router/router_impl.cc | 6 ++--- .../network/source/router/router_impl.h | 9 ------- .../filters/network/source/utility.h | 13 ++++++++++ .../filters/network/test/conn_manager_test.cc | 8 ++++++- .../sip_proxy/filters/network/test/mocks.cc | 1 - .../sip_proxy/filters/network/test/mocks.h | 1 - 10 files changed, 26 insertions(+), 43 deletions(-) diff --git a/contrib/sip_proxy/filters/network/source/BUILD b/contrib/sip_proxy/filters/network/source/BUILD index c437db5cba98..4eee6673e073 100644 --- a/contrib/sip_proxy/filters/network/source/BUILD +++ b/contrib/sip_proxy/filters/network/source/BUILD @@ -126,6 +126,8 @@ envoy_cc_library( deps = [ ":utility_interface", "//contrib/sip_proxy/filters/network/source/filters:filter_interface", + "//envoy/server:factory_context_interface", + "//envoy/server:transport_socket_config_interface", ], ) diff --git a/contrib/sip_proxy/filters/network/source/conn_manager.cc b/contrib/sip_proxy/filters/network/source/conn_manager.cc index 1793e7f124ac..1b4d765d62b2 100644 --- a/contrib/sip_proxy/filters/network/source/conn_manager.cc +++ b/contrib/sip_proxy/filters/network/source/conn_manager.cc @@ -231,7 +231,7 @@ void ConnectionManager::sendLocalReply(MessageMetadata& metadata, const DirectRe Buffer::OwnedImpl buffer; - metadata.setEP(getLocalIp()); + metadata.setEP(Utility::localAddress(context_)); const DirectResponse::ResponseType result = response.encode(metadata, buffer); read_callbacks_->connection().write(buffer, end_stream); @@ -348,7 +348,7 @@ FilterStatus ConnectionManager::ResponseDecoder::transportEnd() { Buffer::OwnedImpl buffer; - metadata_->setEP(getLocalIp()); + metadata_->setEP(Utility::localAddress(cm.context_)); std::shared_ptr encoder = std::make_shared(); encoder->encode(metadata_, buffer); diff --git a/contrib/sip_proxy/filters/network/source/conn_manager.h b/contrib/sip_proxy/filters/network/source/conn_manager.h index 6bf75cf12df1..b55f5b846733 100644 --- a/contrib/sip_proxy/filters/network/source/conn_manager.h +++ b/contrib/sip_proxy/filters/network/source/conn_manager.h @@ -113,23 +113,6 @@ class ConnectionManager : public Network::ReadFilter, // DecoderCallbacks DecoderEventHandler& newDecoderEventHandler(MessageMetadataSharedPtr metadata) override; - absl::string_view getLocalIp() override { - // should return local address ip - // But after ORIGINAL_DEST, the local address update to upstream local address - // So here get downstream remote IP, which should in same pod car with envoy - ENVOY_LOG(debug, "Local ip: {}", - read_callbacks_->connection() - .connectionInfoProvider() - .localAddress() - ->ip() - ->addressAsString()); - return read_callbacks_->connection() - .connectionInfoProvider() - .localAddress() - ->ip() - ->addressAsString(); - } - std::shared_ptr settings() const override { return config_.settings(); } void continueHanding(const std::string& key); @@ -176,13 +159,6 @@ class ConnectionManager : public Network::ReadFilter, return *this; } - absl::string_view getLocalIp() override { - // should return local address ip - // But after ORIGINAL_DEST, the local address update to upstream local address - // So here get downstream remote IP, which should in same pod car with envoy - return parent_.parent_.getLocalIp(); - } - std::shared_ptr settings() const override { return parent_.parent_.settings(); } std::shared_ptr traHandler() { diff --git a/contrib/sip_proxy/filters/network/source/decoder.h b/contrib/sip_proxy/filters/network/source/decoder.h index dda9445e5b1e..3b5f08f56c4e 100644 --- a/contrib/sip_proxy/filters/network/source/decoder.h +++ b/contrib/sip_proxy/filters/network/source/decoder.h @@ -92,7 +92,6 @@ class DecoderCallbacks { * @return DecoderEventHandler& a new DecoderEventHandler for a message. */ virtual DecoderEventHandler& newDecoderEventHandler(MessageMetadataSharedPtr metadata) PURE; - virtual absl::string_view getLocalIp() PURE; virtual std::shared_ptr settings() const PURE; }; diff --git a/contrib/sip_proxy/filters/network/source/router/router_impl.cc b/contrib/sip_proxy/filters/network/source/router/router_impl.cc index 594ef2dffd9b..7c2454b707ed 100644 --- a/contrib/sip_proxy/filters/network/source/router/router_impl.cc +++ b/contrib/sip_proxy/filters/network/source/router/router_impl.cc @@ -428,8 +428,8 @@ FilterStatus Router::messageEnd() { Buffer::OwnedImpl transport_buffer; // set EP/Opaque, used in upstream - ENVOY_STREAM_LOG(debug, "set EP {}", *callbacks_, upstream_request_->localAddress()); - metadata_->setEP(upstream_request_->localAddress()); + ENVOY_STREAM_LOG(debug, "set EP {}", *callbacks_, Utility::localAddress(context_)); + metadata_->setEP(Utility::localAddress(context_)); std::shared_ptr encoder = std::make_shared(); ENVOY_STREAM_LOG(debug, "before encode", *callbacks_); @@ -659,8 +659,6 @@ FilterStatus ResponseDecoder::transportBegin(MessageMetadataSharedPtr metadata) return FilterStatus::Continue; } -absl::string_view ResponseDecoder::getLocalIp() { return parent_.localAddress(); } - std::shared_ptr ResponseDecoder::settings() const { return parent_.settings(); } } // namespace Router diff --git a/contrib/sip_proxy/filters/network/source/router/router_impl.h b/contrib/sip_proxy/filters/network/source/router/router_impl.h index 99d1567d0c19..c820dc577220 100644 --- a/contrib/sip_proxy/filters/network/source/router/router_impl.h +++ b/contrib/sip_proxy/filters/network/source/router/router_impl.h @@ -333,7 +333,6 @@ class ResponseDecoder : public DecoderCallbacks, UNREFERENCED_PARAMETER(metadata); return *this; } - absl::string_view getLocalIp() override; std::shared_ptr settings() const override; private: @@ -381,14 +380,6 @@ class UpstreamRequest : public Tcp::ConnectionPool::Callbacks, return conn_data_->connection().write(data, end_stream); } - absl::string_view localAddress() { - return conn_data_->connection() - .connectionInfoProvider() - .localAddress() - ->ip() - ->addressAsString(); - } - std::shared_ptr transactionInfo() { return transaction_info_; } void setMetadata(MessageMetadataSharedPtr metadata) { metadata_ = metadata; } MessageMetadataSharedPtr metadata() { return metadata_; } diff --git a/contrib/sip_proxy/filters/network/source/utility.h b/contrib/sip_proxy/filters/network/source/utility.h index c9d29efa85b8..ae70d1e5ab92 100644 --- a/contrib/sip_proxy/filters/network/source/utility.h +++ b/contrib/sip_proxy/filters/network/source/utility.h @@ -1,6 +1,8 @@ #pragma once #include "envoy/buffer/buffer.h" +#include "envoy/server/factory_context.h" +#include "envoy/server/transport_socket_config.h" #include "source/common/protobuf/protobuf.h" @@ -136,6 +138,17 @@ class PendingListHandler { virtual void eraseActiveTransFromPendingList(std::string& transaction_id) PURE; }; +class Utility { +public: + static const std::string& localAddress(Server::Configuration::FactoryContext& context) { + return context.getTransportSocketFactoryContext() + .localInfo() + .address() + ->ip() + ->addressAsString(); + } +}; + } // namespace SipProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/contrib/sip_proxy/filters/network/test/conn_manager_test.cc b/contrib/sip_proxy/filters/network/test/conn_manager_test.cc index d5f5a868f26c..4361978474a4 100644 --- a/contrib/sip_proxy/filters/network/test/conn_manager_test.cc +++ b/contrib/sip_proxy/filters/network/test/conn_manager_test.cc @@ -46,7 +46,11 @@ class SipConnectionManagerTest : public testing::Test { public: SipConnectionManagerTest() : stats_(SipFilterStats::generateStats("test.", store_)), - transaction_infos_(std::make_shared()) {} + transaction_infos_(std::make_shared()) { + EXPECT_CALL(context_, getTransportSocketFactoryContext()) + .WillRepeatedly(testing::ReturnRef(factory_context_)); + EXPECT_CALL(factory_context_, localInfo()).WillRepeatedly(testing::ReturnRef(local_info_)); + } ~SipConnectionManagerTest() override { filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); } @@ -440,6 +444,8 @@ stat_prefix: egress decoder->onReset(); } + NiceMock factory_context_; + NiceMock local_info_; NiceMock context_; std::shared_ptr decoder_filter_; Stats::TestUtil::TestStore store_; diff --git a/contrib/sip_proxy/filters/network/test/mocks.cc b/contrib/sip_proxy/filters/network/test/mocks.cc index ffa48ee77cec..7acfd9f5324e 100644 --- a/contrib/sip_proxy/filters/network/test/mocks.cc +++ b/contrib/sip_proxy/filters/network/test/mocks.cc @@ -32,7 +32,6 @@ MockDecoderCallbacks::MockDecoderCallbacks() { service1.set_domain("pcsf-cfed.cncs.svc.cluster.local"); local_services_.emplace_back(service1); local_services_.emplace_back(service2); */ - ON_CALL(*this, getLocalIp()).WillByDefault(Return("127.0.0.1")); } MockDecoderCallbacks::~MockDecoderCallbacks() = default; diff --git a/contrib/sip_proxy/filters/network/test/mocks.h b/contrib/sip_proxy/filters/network/test/mocks.h index c40aed56fa46..b7b07cf2dd5f 100644 --- a/contrib/sip_proxy/filters/network/test/mocks.h +++ b/contrib/sip_proxy/filters/network/test/mocks.h @@ -54,7 +54,6 @@ class MockDecoderCallbacks : public DecoderCallbacks { // SipProxy::DecoderCallbacks MOCK_METHOD(DecoderEventHandler&, newDecoderEventHandler, (MessageMetadataSharedPtr)); - MOCK_METHOD(absl::string_view, getLocalIp, ()); MOCK_METHOD(std::string, getDomainMatchParamName, ()); MOCK_METHOD(void, setMetadata, (MessageMetadataSharedPtr metadata)); MOCK_METHOD(std::shared_ptr, settings, (), (const)); From 80458835e87ba03019df319a1927ae0d17956dd3 Mon Sep 17 00:00:00 2001 From: Matt Klein Date: Sun, 6 Mar 2022 11:04:45 -0700 Subject: [PATCH 17/68] schema validator: multiple improvements (#20111) - Add ability to fail on deprecated or WiP - Add basic tests to the validator. - PGV checks will now recurse into all sub-messages, including Any messages, allowing for full verification of the message tree. Signed-off-by: Matt Klein --- api/bazel/repository_locations.bzl | 6 +- bazel/dependency_imports.bzl | 8 +- .../sip_proxy/filters/network/test/mocks.cc | 2 +- .../tools/schema_validator_check_tool.rst | 7 +- docs/root/version_history/current.rst | 13 ++- source/common/protobuf/BUILD | 24 +++- source/common/protobuf/utility.cc | 38 +++++-- source/common/protobuf/utility.h | 32 +++++- source/common/protobuf/visitor.cc | 84 +++++++++++++- source/common/protobuf/visitor.h | 19 ++-- .../filters/network/thrift_proxy/mocks.cc | 2 +- test/tools/schema_validator/BUILD | 21 ++-- .../schema_validator/schema_validator.cc | 5 +- test/tools/schema_validator/test/BUILD | 27 +++++ .../test/config/bootstrap.yaml | 36 ++++++ .../test/config/bootstrap_pgv_fail.yaml | 38 +++++++ .../schema_validator/test/config/lds.yaml | 25 +++++ .../test/config/lds_deprecated.yaml | 28 +++++ .../test/config/lds_invalid_typed_struct.yaml | 16 +++ .../config/lds_invalid_typed_struct_2.yaml | 16 +++ .../test/config/lds_pgv_fail.yaml | 20 ++++ .../test/config/lds_unknown.yaml | 26 +++++ .../schema_validator/test/config/lds_wip.yaml | 27 +++++ .../test/schema_validator_test.cc | 104 ++++++++++++++++++ test/tools/schema_validator/validator.cc | 65 +++++++++-- test/tools/schema_validator/validator.h | 26 ++++- tools/api/generate_go_protobuf.py | 16 ++- 27 files changed, 653 insertions(+), 78 deletions(-) create mode 100644 test/tools/schema_validator/test/BUILD create mode 100644 test/tools/schema_validator/test/config/bootstrap.yaml create mode 100644 test/tools/schema_validator/test/config/bootstrap_pgv_fail.yaml create mode 100644 test/tools/schema_validator/test/config/lds.yaml create mode 100644 test/tools/schema_validator/test/config/lds_deprecated.yaml create mode 100644 test/tools/schema_validator/test/config/lds_invalid_typed_struct.yaml create mode 100644 test/tools/schema_validator/test/config/lds_invalid_typed_struct_2.yaml create mode 100644 test/tools/schema_validator/test/config/lds_pgv_fail.yaml create mode 100644 test/tools/schema_validator/test/config/lds_unknown.yaml create mode 100644 test/tools/schema_validator/test/config/lds_wip.yaml create mode 100644 test/tools/schema_validator/test/schema_validator_test.cc diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index b56277201cfd..e1978c4706ab 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -14,9 +14,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "protoc-gen-validate (PGV)", project_desc = "protoc plugin to generate polyglot message validators", project_url = "https://github.com/envoyproxy/protoc-gen-validate", - version = "0.6.2", - sha256 = "b02da533c77023238c556982507b9a71afc850478b637a7a13ec13f311efa5c0", - release_date = "2021-10-21", + version = "0.6.7", + sha256 = "4c692c62e16c168049bca2b2972b0a25222870cf53e61be30b50d761e58728bd", + release_date = "2022-03-04", strip_prefix = "protoc-gen-validate-{version}", urls = ["https://github.com/envoyproxy/protoc-gen-validate/archive/v{version}.tar.gz"], use_category = ["api"], diff --git a/bazel/dependency_imports.bzl b/bazel/dependency_imports.bzl index ffe21c99d8f8..1ae7f3fa14ed 100644 --- a/bazel/dependency_imports.bzl +++ b/bazel/dependency_imports.bzl @@ -77,12 +77,12 @@ def envoy_dependency_imports(go_version = GO_VERSION): go_repository( name = "com_github_lyft_protoc_gen_star", importpath = "github.com/lyft/protoc-gen-star", - sum = "h1:sImehRT+p7lW9n6R7MQc5hVgzWGEkDVZU4AsBQ4Isu8=", - version = "v0.5.1", + sum = "h1:xOpFu4vwmIoUeUrRuAtdCrZZymT/6AkW/bsUWA506Fo=", + version = "v0.6.0", # project_url = "https://pkg.go.dev/github.com/lyft/protoc-gen-star", - # last_update = "2020-08-06" + # last_update = "2022-03-04" # use_category = ["api"], - # source = "https://github.com/envoyproxy/protoc-gen-validate/blob/v0.6.1/dependencies.bzl#L35-L40" + # source = "https://github.com/envoyproxy/protoc-gen-validate/blob/v0.6.7/dependencies.bzl#L35-L40" ) go_repository( name = "com_github_iancoleman_strcase", diff --git a/contrib/sip_proxy/filters/network/test/mocks.cc b/contrib/sip_proxy/filters/network/test/mocks.cc index 7acfd9f5324e..aeab0d269388 100644 --- a/contrib/sip_proxy/filters/network/test/mocks.cc +++ b/contrib/sip_proxy/filters/network/test/mocks.cc @@ -14,7 +14,7 @@ namespace Envoy { // Provide a specialization for ProtobufWkt::Struct (for MockFilterConfigFactory) template <> -void MessageUtil::validate(const ProtobufWkt::Struct&, ProtobufMessage::ValidationVisitor&) {} +void MessageUtil::validate(const ProtobufWkt::Struct&, ProtobufMessage::ValidationVisitor&, bool) {} namespace Extensions { namespace NetworkFilters { diff --git a/docs/root/operations/tools/schema_validator_check_tool.rst b/docs/root/operations/tools/schema_validator_check_tool.rst index 085c5966fb9e..90544683a5e6 100644 --- a/docs/root/operations/tools/schema_validator_check_tool.rst +++ b/docs/root/operations/tools/schema_validator_check_tool.rst @@ -9,7 +9,7 @@ config, please refer to the :ref:`config load check tool`. Input - The tool expects two inputs: + The tool expects two required inputs: 1. The schema type to check the passed in configuration against. The supported types are: @@ -19,6 +19,11 @@ Input 2. The path to the configuration file. + Optional inputs include: + + 1. ``--fail-on-deprecated``: Will force failure if any deprecated fields are used. + 2. ``--fail-on-wip``: Will force failure if any work-in-progress fields are used. + Output If the configuration conforms to the schema, the tool will exit with status EXIT_SUCCESS. If the configuration does not conform to the schema, an error diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index a3ef7113be43..c87b89495ec5 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -82,8 +82,17 @@ New Features * outlier_detection: :ref:`max_ejection_time_jitter` configuration added to allow adding a random value to the ejection time to prevent 'thundering herd' scenarios. Defaults to 0 so as to not break or change the behavior of existing deployments. * redis: support for hostnames returned in `cluster slots` response is now available. * schema_validator_tool: added ``bootstrap`` checking to the - :ref:`schema validator check tool `. Also fixed linking - of all extensions into the tool so that all typed configurations can be properly verified. + :ref:`schema validator check tool `. +* schema_validator_tool: added ``--fail-on-deprecated`` and ``--fail-on-wip`` to the + :ref:`schema validator check tool ` to allow failing + the check if either deprecated or work-in-progress fields are used. +* schema_validator_tool: fixed linking of all extensions into the + :ref:`schema validator check tool ` so that all typed + configurations can be properly verified. +* schema_validator_tool: the + :ref:`schema validator check tool ` will now recurse + into all sub messages, including Any messages, and perform full validation (deprecation, + work-in-progress, PGV, etc.). Previously only top-level messages were fully validated. * stats: histogram_buckets query parameter added to stats endpoint to change histogram output to show buckets. * tools: the project now ships a :ref:`tools docker image ` which contains tools useful in support systems such as CI, CD, etc. The diff --git a/source/common/protobuf/BUILD b/source/common/protobuf/BUILD index d3235a5757e9..f5e52af8a1ad 100644 --- a/source/common/protobuf/BUILD +++ b/source/common/protobuf/BUILD @@ -50,10 +50,20 @@ envoy_cc_library( deps = [":cc_wkt_protos"], ) +envoy_cc_library( + name = "utility_lib_header", + hdrs = ["utility.h"], + deps = [ + "//envoy/api:api_interface", + "//envoy/protobuf:message_validator_interface", + "//source/common/common:stl_helpers", + "@envoy_api//envoy/type/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "utility_lib", srcs = ["utility.cc"], - hdrs = ["utility.h"], external_deps = [ "protobuf", "yaml_cpp", @@ -61,13 +71,11 @@ envoy_cc_library( deps = [ ":message_validator_lib", ":protobuf", - "//envoy/api:api_interface", - "//envoy/protobuf:message_validator_interface", + ":utility_lib_header", "//envoy/runtime:runtime_interface", "//source/common/common:assert_lib", "//source/common/common:documentation_url_lib", "//source/common/common:hash_lib", - "//source/common/common:stl_helpers", "//source/common/common:utility_lib", "//source/common/protobuf:visitor_lib", "//source/common/runtime:runtime_features_lib", @@ -81,5 +89,11 @@ envoy_cc_library( name = "visitor_lib", srcs = ["visitor.cc"], hdrs = ["visitor.h"], - deps = [":protobuf"], + deps = [ + ":message_validator_lib", + ":protobuf", + ":utility_lib_header", + "@com_github_cncf_udpa//udpa/type/v1:pkg_cc_proto", + "@com_github_cncf_udpa//xds/type/v3:pkg_cc_proto", + ], ) diff --git a/source/common/protobuf/utility.cc b/source/common/protobuf/utility.cc index f37d16dc248d..cb2dd853f92b 100644 --- a/source/common/protobuf/utility.cc +++ b/source/common/protobuf/utility.cc @@ -18,6 +18,7 @@ #include "absl/strings/match.h" #include "udpa/annotations/sensitive.pb.h" #include "udpa/annotations/status.pb.h" +#include "validate/validate.h" #include "xds/annotations/v3/status.pb.h" #include "yaml-cpp/yaml.h" @@ -372,8 +373,7 @@ class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { Runtime::Loader* runtime) : validation_visitor_(validation_visitor), runtime_(runtime) {} - const void* onField(const Protobuf::Message& message, const Protobuf::FieldDescriptor& field, - const void*) override { + void onField(const Protobuf::Message& message, const Protobuf::FieldDescriptor& field) override { const Protobuf::Reflection* reflection = message.GetReflection(); absl::string_view filename = filenameFromPath(field.file()->name()); @@ -385,7 +385,7 @@ class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { // If this field is not in use, continue. if ((field.is_repeated() && reflection->FieldSize(message, &field) == 0) || (!field.is_repeated() && !reflection->HasField(message, &field))) { - return nullptr; + return; } const auto& field_status = field.options().GetExtension(xds::annotations::v3::field_status); @@ -406,10 +406,9 @@ class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { absl::StrCat("envoy.deprecated_features:", field.full_name()), warning, message, validation_visitor_); } - return nullptr; } - void onMessage(const Protobuf::Message& message, const void*) override { + void onMessage(const Protobuf::Message& message, bool) override { if (message.GetDescriptor() ->options() .GetExtension(xds::annotations::v3::message_status) @@ -450,12 +449,37 @@ class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { } // namespace void MessageUtil::checkForUnexpectedFields(const Protobuf::Message& message, - ProtobufMessage::ValidationVisitor& validation_visitor) { + ProtobufMessage::ValidationVisitor& validation_visitor, + bool recurse_into_any) { Runtime::Loader* runtime = validation_visitor.runtime().has_value() ? &validation_visitor.runtime().value().get() : nullptr; UnexpectedFieldProtoVisitor unexpected_field_visitor(validation_visitor, runtime); - ProtobufMessage::traverseMessage(unexpected_field_visitor, message, nullptr); + ProtobufMessage::traverseMessage(unexpected_field_visitor, message, recurse_into_any); +} + +namespace { + +class PgvCheckVisitor : public ProtobufMessage::ConstProtoVisitor { +public: + void onMessage(const Protobuf::Message& message, bool was_any_or_top_level) override { + std::string err; + // PGV verification is itself recursive up to the point at which it hits an Any message. As + // such, to avoid N^2 checking of the tree, we only perform an additional check at the point + // at which PGV would have stopped because it does not itself check within Any messages. + if (was_any_or_top_level && !pgv::BaseValidator::AbstractCheckMessage(message, &err)) { + ProtoExceptionUtil::throwProtoValidationException(err, message); + } + } + + void onField(const Protobuf::Message&, const Protobuf::FieldDescriptor&) override {} +}; + +} // namespace + +void MessageUtil::recursivePgvCheck(const Protobuf::Message& message) { + PgvCheckVisitor visitor; + ProtobufMessage::traverseMessage(visitor, message, true); } std::string MessageUtil::getYamlStringFromMessage(const Protobuf::Message& message, diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 079e77f737b1..67ecab7ff504 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -271,27 +271,51 @@ class MessageUtil { /** * Checks for use of deprecated fields in message and all sub-messages. * @param message message to validate. + * @param validation_visitor the validation visitor to use. + * @param recurse_into_any whether to recurse into Any messages during unexpected checking. * @throw ProtoValidationException if deprecated fields are used and listed * in disallowed_features in runtime_features.h */ static void checkForUnexpectedFields(const Protobuf::Message& message, - ProtobufMessage::ValidationVisitor& validation_visitor); + ProtobufMessage::ValidationVisitor& validation_visitor, + bool recurse_into_any = false); /** - * Validate protoc-gen-validate constraints on a given protobuf. + * Perform a PGV check on the entire message tree, recursing into Any messages as needed. + */ + static void recursivePgvCheck(const Protobuf::Message& message); + + /** + * Validate protoc-gen-validate constraints on a given protobuf as well as performing + * unexpected field validation. * Note the corresponding `.pb.validate.h` for the message has to be included in the source file * of caller. * @param message message to validate. + * @param validation_visitor the validation visitor to use. + * @param recurse_into_any whether to recurse into Any messages during unexpected checking. * @throw ProtoValidationException if the message does not satisfy its type constraints. */ template static void validate(const MessageType& message, - ProtobufMessage::ValidationVisitor& validation_visitor) { + ProtobufMessage::ValidationVisitor& validation_visitor, + bool recurse_into_any = false) { // Log warnings or throw errors if deprecated fields or unknown fields are in use. if (!validation_visitor.skipValidation()) { - checkForUnexpectedFields(message, validation_visitor); + checkForUnexpectedFields(message, validation_visitor, recurse_into_any); + } + + // TODO(mattklein123): This will recurse the message twice, once above and once for PGV. When + // we move to always recursing, satisfying the TODO below, we should merge into a single + // recursion for performance reasons. + if (recurse_into_any) { + return recursivePgvCheck(message); } + // TODO(mattklein123): Now that PGV is capable of doing recursive message checks on abstract + // types, we can remove bottom up validation from the entire codebase and only validate + // at top level ingestion (bootstrap, discovery response). This is a large change and will be + // done as a separate PR. This change will also allow removing templating from most/all of + // related functions. std::string err; if (!Validate(message, &err)) { ProtoExceptionUtil::throwProtoValidationException(err, message); diff --git a/source/common/protobuf/visitor.cc b/source/common/protobuf/visitor.cc index 0e22cb2f076a..a233ac3b2efc 100644 --- a/source/common/protobuf/visitor.cc +++ b/source/common/protobuf/visitor.cc @@ -1,29 +1,101 @@ #include "source/common/protobuf/visitor.h" +#include "source/common/protobuf/message_validator_impl.h" +#include "source/common/protobuf/utility.h" + +#include "udpa/type/v1/typed_struct.pb.h" +#include "xds/type/v3/typed_struct.pb.h" + namespace Envoy { namespace ProtobufMessage { +namespace { + +std::unique_ptr typeUrlToMessage(const std::string& type_url) { + const absl::string_view inner_type_name = TypeUtil::typeUrlToDescriptorFullName(type_url); + const Protobuf::Descriptor* inner_descriptor = + Protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName( + std::string(inner_type_name)); + if (inner_descriptor == nullptr) { + return nullptr; + } + auto* inner_message_prototype = + Protobuf::MessageFactory::generated_factory()->GetPrototype(inner_descriptor); + return std::unique_ptr(inner_message_prototype->New()); +} + +template +std::pair, absl::string_view> +convertTypedStruct(const Protobuf::Message& message) { + auto* typed_struct = Protobuf::DynamicCastToGenerated(&message); + auto inner_message = typeUrlToMessage(typed_struct->type_url()); + absl::string_view target_type_url = typed_struct->type_url(); + // inner_message might be invalid as we did not previously check type_url during loading. + if (inner_message != nullptr) { + MessageUtil::jsonConvert(typed_struct->value(), ProtobufMessage::getNullValidationVisitor(), + *inner_message); + } + return {std::move(inner_message), target_type_url}; +} + +void traverseMessageWorker(ConstProtoVisitor& visitor, const Protobuf::Message& message, + bool was_any_or_top_level, bool recurse_into_any) { + visitor.onMessage(message, was_any_or_top_level); + + // If told to recurse into Any messages, do that here and skip the rest of the function. + if (recurse_into_any) { + std::unique_ptr inner_message; + absl::string_view target_type_url; + + if (message.GetDescriptor()->full_name() == "google.protobuf.Any") { + auto* any_message = Protobuf::DynamicCastToGenerated(&message); + inner_message = typeUrlToMessage(any_message->type_url()); + target_type_url = any_message->type_url(); + // inner_message must be valid as parsing would have already failed to load if there was an + // invalid type_url. + MessageUtil::unpackTo(*any_message, *inner_message); + } else if (message.GetDescriptor()->full_name() == "xds.type.v3.TypedStruct") { + std::tie(inner_message, target_type_url) = + convertTypedStruct(message); + } else if (message.GetDescriptor()->full_name() == "udpa.type.v1.TypedStruct") { + std::tie(inner_message, target_type_url) = + convertTypedStruct(message); + } + + if (inner_message != nullptr) { + traverseMessageWorker(visitor, *inner_message, true, recurse_into_any); + return; + } else if (!target_type_url.empty()) { + throw EnvoyException(fmt::format("Invalid type_url '{}' during traversal", target_type_url)); + } + } -void traverseMessage(ConstProtoVisitor& visitor, const Protobuf::Message& message, - const void* ctxt) { - visitor.onMessage(message, ctxt); const Protobuf::Descriptor* descriptor = message.GetDescriptor(); const Protobuf::Reflection* reflection = message.GetReflection(); for (int i = 0; i < descriptor->field_count(); ++i) { const Protobuf::FieldDescriptor* field = descriptor->field(i); - const void* field_ctxt = visitor.onField(message, *field, ctxt); + visitor.onField(message, *field); // If this is a message, recurse to scrub deprecated fields in the sub-message. if (field->cpp_type() == Protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { if (field->is_repeated()) { const int size = reflection->FieldSize(message, field); for (int j = 0; j < size; ++j) { - traverseMessage(visitor, reflection->GetRepeatedMessage(message, field, j), field_ctxt); + traverseMessageWorker(visitor, reflection->GetRepeatedMessage(message, field, j), false, + recurse_into_any); } } else if (reflection->HasField(message, field)) { - traverseMessage(visitor, reflection->GetMessage(message, field), field_ctxt); + traverseMessageWorker(visitor, reflection->GetMessage(message, field), false, + recurse_into_any); } } } } +} // namespace + +void traverseMessage(ConstProtoVisitor& visitor, const Protobuf::Message& message, + bool recurse_into_any) { + traverseMessageWorker(visitor, message, true, recurse_into_any); +} + } // namespace ProtobufMessage } // namespace Envoy diff --git a/source/common/protobuf/visitor.h b/source/common/protobuf/visitor.h index 3c2b782a84e9..cf306acaaf40 100644 --- a/source/common/protobuf/visitor.h +++ b/source/common/protobuf/visitor.h @@ -1,5 +1,7 @@ #pragma once +#include "envoy/common/pure.h" + #include "source/common/protobuf/protobuf.h" namespace Envoy { @@ -9,19 +11,18 @@ class ConstProtoVisitor { public: virtual ~ConstProtoVisitor() = default; - // Invoked when a field is visited, with the message, field descriptor and context. Returns a new - // context for use when traversing the sub-message in a field. - virtual const void* onField(const Protobuf::Message&, const Protobuf::FieldDescriptor&, - const void* ctxt) { - return ctxt; - } + // Invoked when a field is visited, with the message, and field descriptor. + virtual void onField(const Protobuf::Message&, const Protobuf::FieldDescriptor&) PURE; - // Invoked when a message is visited, with the message and a context. - virtual void onMessage(const Protobuf::Message&, const void*){}; + // Invoked when a message is visited, with the message. + // @param was_any_or_top_level supplies whether the message was either the top level message or an + // Any before being unpacked for further recursion. The latter can + // only be achieved by using recurse_into_any. + virtual void onMessage(const Protobuf::Message&, bool was_any_or_top_level) PURE; }; void traverseMessage(ConstProtoVisitor& visitor, const Protobuf::Message& message, - const void* ctxt); + bool recurse_into_any); } // namespace ProtobufMessage } // namespace Envoy diff --git a/test/extensions/filters/network/thrift_proxy/mocks.cc b/test/extensions/filters/network/thrift_proxy/mocks.cc index e4a795a6fd0c..0153c42b6103 100644 --- a/test/extensions/filters/network/thrift_proxy/mocks.cc +++ b/test/extensions/filters/network/thrift_proxy/mocks.cc @@ -15,7 +15,7 @@ namespace Envoy { // Provide a specialization for ProtobufWkt::Struct (for MockFilterConfigFactory) template <> -void MessageUtil::validate(const ProtobufWkt::Struct&, ProtobufMessage::ValidationVisitor&) {} +void MessageUtil::validate(const ProtobufWkt::Struct&, ProtobufMessage::ValidationVisitor&, bool) {} namespace Extensions { namespace NetworkFilters { diff --git a/test/tools/schema_validator/BUILD b/test/tools/schema_validator/BUILD index 51ba9acc5188..6e911ac1af88 100644 --- a/test/tools/schema_validator/BUILD +++ b/test/tools/schema_validator/BUILD @@ -13,16 +13,18 @@ envoy_package() envoy_cc_test_binary( name = "schema_validator_tool", - deps = [":schema_validator_lib"], + srcs = ["schema_validator.cc"], + deps = [":schema_validator_lib"] + select({ + "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), + "//bazel:linux_ppc": envoy_all_extensions(PPC_SKIP_TARGETS), + "//conditions:default": envoy_all_extensions(), + }), ) envoy_cc_test_library( name = "schema_validator_lib", - srcs = [ - "schema_validator.cc", - "validator.cc", - "validator.h", - ], + srcs = ["validator.cc"], + hdrs = ["validator.h"], # TCLAP command line parser needs this to support int64_t/uint64_t in several build environments. copts = ["-DHAVE_LONG_LONG"], external_deps = ["tclap"], @@ -30,13 +32,10 @@ envoy_cc_test_library( "//envoy/api:api_interface", "//source/common/protobuf:utility_lib", "//source/common/stats:isolated_store_lib", + "//source/common/version:version_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/route/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", - ] + select({ - "//bazel:windows_x86_64": envoy_all_extensions(WINDOWS_SKIP_TARGETS), - "//bazel:linux_ppc": envoy_all_extensions(PPC_SKIP_TARGETS), - "//conditions:default": envoy_all_extensions(), - }), + ], ) diff --git a/test/tools/schema_validator/schema_validator.cc b/test/tools/schema_validator/schema_validator.cc index 77a9a9d40a88..c40e4306133d 100644 --- a/test/tools/schema_validator/schema_validator.cc +++ b/test/tools/schema_validator/schema_validator.cc @@ -7,11 +7,8 @@ #include "test/tools/schema_validator/validator.h" int main(int argc, char** argv) { - Envoy::Options options(argc, argv); - Envoy::Validator v; - try { - v.validate(options.configPath(), options.schemaType()); + Envoy::Validator::run(argc, argv); } catch (const Envoy::EnvoyException& ex) { std::cerr << ex.what() << std::endl; return EXIT_FAILURE; diff --git a/test/tools/schema_validator/test/BUILD b/test/tools/schema_validator/test/BUILD new file mode 100644 index 000000000000..dcbf5e7cccb2 --- /dev/null +++ b/test/tools/schema_validator/test/BUILD @@ -0,0 +1,27 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "schema_validator_test", + srcs = ["schema_validator_test.cc"], + data = [ + ":configs", + ], + deps = [ + "//test/tools/schema_validator:schema_validator_lib", + ], +) + +filegroup( + name = "configs", + srcs = glob([ + "config/*.yaml", + ]), +) diff --git a/test/tools/schema_validator/test/config/bootstrap.yaml b/test/tools/schema_validator/test/config/bootstrap.yaml new file mode 100644 index 000000000000..a74bbeda78f2 --- /dev/null +++ b/test/tools/schema_validator/test/config/bootstrap.yaml @@ -0,0 +1,36 @@ +node: + id: node_id + cluster: node_cluster + +admin: + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 9901 + +layered_runtime: + layers: + - name: rtds + rtds_layer: + name: rtds + rtds_config: + resource_api_version: V3 + path_config_source: + path: /config_map/rtds/rtds.yaml + watched_directory: + path: /config_map/rtds + +dynamic_resources: + cds_config: + resource_api_version: V3 + path_config_source: + path: /config_map/cds/cds.yaml + watched_directory: + path: /config_map/cds + lds_config: + resource_api_version: V3 + path_config_source: + path: /config_map/lds/lds.yaml + watched_directory: + path: /config_map/lds diff --git a/test/tools/schema_validator/test/config/bootstrap_pgv_fail.yaml b/test/tools/schema_validator/test/config/bootstrap_pgv_fail.yaml new file mode 100644 index 000000000000..040c4a987765 --- /dev/null +++ b/test/tools/schema_validator/test/config/bootstrap_pgv_fail.yaml @@ -0,0 +1,38 @@ +stats_flush_interval: 500s + +node: + id: node_id + cluster: node_cluster + +admin: + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 9901 + +layered_runtime: + layers: + - name: rtds + rtds_layer: + name: rtds + rtds_config: + resource_api_version: V3 + path_config_source: + path: /config_map/rtds/rtds.yaml + watched_directory: + path: /config_map/rtds + +dynamic_resources: + cds_config: + resource_api_version: V3 + path_config_source: + path: /config_map/cds/cds.yaml + watched_directory: + path: /config_map/cds + lds_config: + resource_api_version: V3 + path_config_source: + path: /config_map/lds/lds.yaml + watched_directory: + path: /config_map/lds diff --git a/test/tools/schema_validator/test/config/lds.yaml b/test/tools/schema_validator/test/config/lds.yaml new file mode 100644 index 000000000000..a42a6f840672 --- /dev/null +++ b/test/tools/schema_validator/test/config/lds.yaml @@ -0,0 +1,25 @@ +resources: +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: ingress_https + 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 + use_remote_address: true + rds: + route_config_name: ingress_http + config_source: + resource_api_version: V3 + path_config_source: + path: /config_map/rds/rds.yaml + watched_directory: + path: /config_map/rds + http_filters: + - name: envoy.filters.http.router diff --git a/test/tools/schema_validator/test/config/lds_deprecated.yaml b/test/tools/schema_validator/test/config/lds_deprecated.yaml new file mode 100644 index 000000000000..7d614021b221 --- /dev/null +++ b/test/tools/schema_validator/test/config/lds_deprecated.yaml @@ -0,0 +1,28 @@ +resources: +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: ingress_https + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - use_proxy_proto: true + filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/xds.type.v3.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + value: + stat_prefix: ingress_http + use_remote_address: true + rds: + route_config_name: ingress_http + config_source: + resource_api_version: V3 + path_config_source: + path: /config_map/rds/rds.yaml + watched_directory: + path: /config_map/rds + http_filters: + - name: envoy.filters.http.router diff --git a/test/tools/schema_validator/test/config/lds_invalid_typed_struct.yaml b/test/tools/schema_validator/test/config/lds_invalid_typed_struct.yaml new file mode 100644 index 000000000000..376d6416c15e --- /dev/null +++ b/test/tools/schema_validator/test/config/lds_invalid_typed_struct.yaml @@ -0,0 +1,16 @@ +resources: +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: ingress_https + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - use_proxy_proto: true + filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/xds.type.v3.TypedStruct + type_url: blah + value: diff --git a/test/tools/schema_validator/test/config/lds_invalid_typed_struct_2.yaml b/test/tools/schema_validator/test/config/lds_invalid_typed_struct_2.yaml new file mode 100644 index 000000000000..e1ffd0b76e4d --- /dev/null +++ b/test/tools/schema_validator/test/config/lds_invalid_typed_struct_2.yaml @@ -0,0 +1,16 @@ +resources: +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: ingress_https + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - use_proxy_proto: true + filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: bleh + value: diff --git a/test/tools/schema_validator/test/config/lds_pgv_fail.yaml b/test/tools/schema_validator/test/config/lds_pgv_fail.yaml new file mode 100644 index 000000000000..17936d8ec125 --- /dev/null +++ b/test/tools/schema_validator/test/config/lds_pgv_fail.yaml @@ -0,0 +1,20 @@ +resources: +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: ingress_https + 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 + use_remote_address: true + rds: + route_config_name: ingress_http + config_source: + resource_api_version: V3 + path_config_source: + path: /config_map/rds/rds.yaml + watched_directory: + path: /config_map/rds + http_filters: + - name: envoy.filters.http.router diff --git a/test/tools/schema_validator/test/config/lds_unknown.yaml b/test/tools/schema_validator/test/config/lds_unknown.yaml new file mode 100644 index 000000000000..623adae49e2f --- /dev/null +++ b/test/tools/schema_validator/test/config/lds_unknown.yaml @@ -0,0 +1,26 @@ +resources: +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: ingress_https + 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 + use_remote_address: true + foo: bar + rds: + route_config_name: ingress_http + config_source: + resource_api_version: V3 + path_config_source: + path: /config_map/rds/rds.yaml + watched_directory: + path: /config_map/rds + http_filters: + - name: envoy.filters.http.router diff --git a/test/tools/schema_validator/test/config/lds_wip.yaml b/test/tools/schema_validator/test/config/lds_wip.yaml new file mode 100644 index 000000000000..8df38f44959f --- /dev/null +++ b/test/tools/schema_validator/test/config/lds_wip.yaml @@ -0,0 +1,27 @@ +resources: +- "@type": type.googleapis.com/envoy.config.listener.v3.Listener + name: ingress_https + 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 + use_remote_address: true + http3_protocol_options: + allow_extended_connect: true + rds: + route_config_name: ingress_http + config_source: + resource_api_version: V3 + path_config_source: + path: /config_map/rds/rds.yaml + watched_directory: + path: /config_map/rds + http_filters: + - name: envoy.filters.http.router diff --git a/test/tools/schema_validator/test/schema_validator_test.cc b/test/tools/schema_validator/test/schema_validator_test.cc new file mode 100644 index 000000000000..869c157636b3 --- /dev/null +++ b/test/tools/schema_validator/test/schema_validator_test.cc @@ -0,0 +1,104 @@ +#include "test/test_common/environment.h" +#include "test/tools/schema_validator/validator.h" + +#include "gtest/gtest.h" + +namespace Envoy { + +class SchemaValidatorTest : public testing::Test { +public: + void run(const std::string& command_line, const std::string& config_file) { + const std::string final_command_line = TestEnvironment::runfilesPath( + absl::StrCat("test/tools/schema_validator/test/config/", config_file)); + // Splitting on ' ' is not always reliable but works fine for these tests. + const std::vector split_command_line = + absl::StrSplit(fmt::format(command_line, final_command_line), ' '); + std::vector c_command_line; + c_command_line.reserve(split_command_line.size()); + for (auto& part : split_command_line) { + c_command_line.push_back(part.c_str()); + } + + Validator::run(c_command_line.size(), c_command_line.data()); + } +}; + +// Basic success case. +TEST_F(SchemaValidatorTest, LdsSuccess) { + run("schema_validator_tool -c {} -t discovery_response", "lds.yaml"); +} + +// Basic success case with full checking. +TEST_F(SchemaValidatorTest, LdsDeprecationAndWiPSuccess) { + run("schema_validator_tool -c {} -t discovery_response --fail-on-wip --fail-on-deprecated", + "lds.yaml"); +} + +// No errors without fail on deprecated. +TEST_F(SchemaValidatorTest, LdsSuccessWithoutFailOnDeprecated) { + run("schema_validator_tool -c {} -t discovery_response", "lds_deprecated.yaml"); +} + +// Fail on deprecated. +TEST_F(SchemaValidatorTest, LdsFailOnDeprecated) { + EXPECT_THROW_WITH_REGEX( + run("schema_validator_tool -c {} -t discovery_response --fail-on-deprecated", + "lds_deprecated.yaml"), + EnvoyException, + "Using deprecated option 'envoy.config.listener.v3.FilterChain.use_proxy_proto' from file " + "listener_components.proto"); +} + +// Unknown field. +TEST_F(SchemaValidatorTest, LdsUnknownField) { + EXPECT_THROW_WITH_REGEX( + run("schema_validator_tool -c {} -t discovery_response", "lds_unknown.yaml"), EnvoyException, + "reason INVALID_ARGUMENT:foo: Cannot find field."); +} + +// Invalid type struct URL cases. +TEST_F(SchemaValidatorTest, LdsInvalidTypedStruct) { + EXPECT_THROW_WITH_REGEX( + run("schema_validator_tool -c {} -t discovery_response", "lds_invalid_typed_struct.yaml"), + EnvoyException, "Invalid type_url 'blah' during traversal"); + + EXPECT_THROW_WITH_REGEX( + run("schema_validator_tool -c {} -t discovery_response", "lds_invalid_typed_struct_2.yaml"), + EnvoyException, "Invalid type_url 'bleh' during traversal"); +} + +// No errors without fail on WiP. +TEST_F(SchemaValidatorTest, LdsSuccessWithoutFailOnWiP) { + run("schema_validator_tool -c {} -t discovery_response", "lds_wip.yaml"); +} + +// Fail on WiP. +TEST_F(SchemaValidatorTest, LdsFailOnWiP) { + EXPECT_THROW_WITH_REGEX( + run("schema_validator_tool -c {} -t discovery_response --fail-on-wip", "lds_wip.yaml"), + EnvoyException, + "field 'envoy.config.core.v3.Http3ProtocolOptions.allow_extended_connect' is marked as " + "work-in-progress"); +} + +// Basic success case. +TEST_F(SchemaValidatorTest, BootstrapSuccess) { + run("schema_validator_tool -c {} -t bootstrap", "bootstrap.yaml"); +} + +// Bootstrap with PGV failure. +TEST_F(SchemaValidatorTest, BootstrapPgvFail) { + EXPECT_THROW_WITH_REGEX( + run("schema_validator_tool -c {} -t bootstrap", "bootstrap_pgv_fail.yaml"), EnvoyException, + "Proto constraint validation failed \\(BootstrapValidationError.StatsFlushInterval: value " + "must be inside range"); +} + +// LDS with PGV failure that requires recursing into an Any. +TEST_F(SchemaValidatorTest, LdsRecursivePgvFail) { + EXPECT_THROW_WITH_REGEX( + run("schema_validator_tool -c {} -t discovery_response", "lds_pgv_fail.yaml"), EnvoyException, + "Proto constraint validation failed \\(ListenerValidationError.Address: value is required"); +} + +} // namespace Envoy diff --git a/test/tools/schema_validator/validator.cc b/test/tools/schema_validator/validator.cc index 0aefa49d1b00..674100b35da4 100644 --- a/test/tools/schema_validator/validator.cc +++ b/test/tools/schema_validator/validator.cc @@ -8,6 +8,7 @@ #include "envoy/service/discovery/v3/discovery.pb.validate.h" #include "source/common/protobuf/utility.h" +#include "source/common/version/version.h" #include "tclap/CmdLine.h" @@ -30,8 +31,8 @@ const std::string& Schema::toString(Type type) { PANIC("reached unexpected code"); } -Options::Options(int argc, char** argv) { - TCLAP::CmdLine cmd("schema_validator_tool", ' ', "none", false); +Options::Options(int argc, const char* const* argv) { + TCLAP::CmdLine cmd("schema_validator_tool", ' ', VersionInfo::version()); TCLAP::ValueArg config_path("c", "config-path", "Path to configuration file.", true, "", "string", cmd); TCLAP::ValueArg schema_type( @@ -39,6 +40,10 @@ Options::Options(int argc, char** argv) { "Type of schema to validate the configuration against. " "Supported schema is: 'route', 'discovery_response', 'bootstrap'.", true, "", "string", cmd); + TCLAP::SwitchArg fail_on_deprecated("", "fail-on-deprecated", + "Whether to fail if using deprecated fields.", cmd, false); + TCLAP::SwitchArg fail_on_wip("", "fail-on-wip", + "Whether to fail if using work-in-progress fields.", cmd, false); try { cmd.parse(argc, argv); @@ -59,30 +64,70 @@ Options::Options(int argc, char** argv) { } config_path_ = config_path.getValue(); + fail_on_deprecated_ = fail_on_deprecated.getValue(); + fail_on_wip_ = fail_on_wip.getValue(); } -void Validator::validate(const std::string& config_path, Schema::Type schema_type) { +namespace { +class Visitor : public ProtobufMessage::ValidationVisitor { +public: + Visitor(const Options& options) : options_(options) {} - switch (schema_type) { + // ProtobufMessage::ValidationVisitor + void onUnknownField(absl::string_view description) override { + throw ProtobufMessage::UnknownProtoFieldException( + absl::StrCat("Protobuf message (", description, ") has unknown fields")); + } + bool skipValidation() override { return false; } + void onDeprecatedField(absl::string_view description, bool) override { + if (options_.failOnDeprecated()) { + throw ProtobufMessage::DeprecatedProtoFieldException( + absl::StrCat("Failing due to deprecated field: ", description)); + } + } + void onWorkInProgress(absl::string_view description) override { + if (options_.failOnWip()) { + throw EnvoyException(absl::StrCat("Failing due to work-in-progress field: ", description)); + } + } + OptRef runtime() override { return OptRef(); } + +private: + const Options& options_; +}; +} // namespace + +void Validator::validate(const Options& options) { + Visitor visitor(options); + std::cerr << "Validating: " << options.configPath() << "\n"; + + switch (options.schemaType()) { case Schema::Type::DiscoveryResponse: { envoy::service::discovery::v3::DiscoveryResponse discovery_response_config; - TestUtility::loadFromFile(config_path, discovery_response_config, *api_); - TestUtility::validate(discovery_response_config); + TestUtility::loadFromFile(options.configPath(), discovery_response_config, *api_); + MessageUtil::validate(discovery_response_config, visitor, true); break; } case Schema::Type::Route: { envoy::config::route::v3::RouteConfiguration route_config; - TestUtility::loadFromFile(config_path, route_config, *api_); - TestUtility::validate(route_config); + TestUtility::loadFromFile(options.configPath(), route_config, *api_); + MessageUtil::validate(route_config, visitor, true); break; } case Schema::Type::Bootstrap: { envoy::config::bootstrap::v3::Bootstrap bootstrap; - TestUtility::loadFromFile(config_path, bootstrap, *api_); - TestUtility::validate(bootstrap); + TestUtility::loadFromFile(options.configPath(), bootstrap, *api_); + MessageUtil::validate(bootstrap, visitor, true); break; } } } +void Validator::run(int argc, const char* const* argv) { + Options options(argc, argv); + Validator v; + + v.validate(options); +} + } // namespace Envoy diff --git a/test/tools/schema_validator/validator.h b/test/tools/schema_validator/validator.h index 460445ff3adb..46a18e195ea3 100644 --- a/test/tools/schema_validator/validator.h +++ b/test/tools/schema_validator/validator.h @@ -38,7 +38,7 @@ class Schema { */ class Options { public: - Options(int argc, char** argv); + Options(int argc, const char* const* argv); /** * @return the schema type. @@ -50,9 +50,21 @@ class Options { */ const std::string& configPath() const { return config_path_; } + /** + * @return whether to fail on deprecated fields. + */ + bool failOnDeprecated() const { return fail_on_deprecated_; } + + /** + * @return whether to fail on WiP fields. + */ + bool failOnWip() const { return fail_on_wip_; } + private: Schema::Type schema_type_; std::string config_path_; + bool fail_on_deprecated_; + bool fail_on_wip_; }; /** @@ -61,15 +73,21 @@ class Options { class Validator { public: Validator() : api_(Api::createApiForTest(stats_)) {} + /** * Validates the configuration at config_path against schema_type. * An EnvoyException is thrown in several cases: * - Cannot load the configuration from config_path(invalid path or malformed data). * - A schema error from validating the configuration. - * @param config_path specifies the path to the configuration file. - * @param schema_type specifies the schema to validate the configuration against. + * - Use of deprecated/WiP fields if configured to fail in those cases. + * @param options supplies the validation options. + */ + void validate(const Options& options); + + /** + * Run the validator from command line arguments. */ - void validate(const std::string& config_path, Schema::Type schema_type); + static void run(int argc, const char* const* argv); private: Stats::IsolatedStoreImpl stats_; diff --git a/tools/api/generate_go_protobuf.py b/tools/api/generate_go_protobuf.py index 6444bef2df8e..a6975c4aa78e 100755 --- a/tools/api/generate_go_protobuf.py +++ b/tools/api/generate_go_protobuf.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 -from subprocess import check_output -from subprocess import check_call +from subprocess import check_output, STDOUT, CalledProcessError import argparse import glob import os @@ -33,10 +32,15 @@ def generate_protobufs(targets, output, api_repo): # Each rule has the form @envoy_api//foo/bar:baz_go_proto. # First build all the rules to ensure we have the output files. # We preserve source info so comments are retained on generated code. - check_call([ - 'bazel', 'build', '-c', 'fastbuild', - '--experimental_proto_descriptor_sets_include_source_info' - ] + BAZEL_BUILD_OPTIONS + go_protos) + try: + check_output([ + 'bazel', 'build', '-c', 'fastbuild', + '--experimental_proto_descriptor_sets_include_source_info' + ] + BAZEL_BUILD_OPTIONS + go_protos, + stderr=STDOUT) + except CalledProcessError as e: + print(e.output) + raise e for rule in go_protos: # Example rule: From f9b05bde6901203fde947d16c115329d3e9523cb Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 7 Mar 2022 09:01:11 -0500 Subject: [PATCH 18/68] runtime: deprecating global ints (#20168) Deprecating runtime guards envoy.http.headermap.lazy_map_min_size re2.max_program_size.error_level re2.max_program_size.warn_level They're accessing global integers, and It's unclear if anyone is using them. If anyone indicates they are in use, I will plumb proper config knobs. Risk Level: low (runtime guarded) Testing: existing tests Docs Changes: n/a Release Notes: inline Runtime guard: envoy.reloadable_features.deprecate_global_ints Signed-off-by: Alyssa Wilk --- docs/root/version_history/current.rst | 2 ++ source/common/runtime/runtime_features.cc | 36 +++++++++---------- source/common/runtime/runtime_impl.cc | 9 +++-- test/common/common/regex_test.cc | 10 ++++-- test/common/http/header_map_impl_fuzz_test.cc | 3 +- test/common/http/header_map_impl_test.cc | 3 +- 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index c87b89495ec5..a58a8d074ec9 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -104,5 +104,7 @@ Deprecated * config: deprecated :ref:`path ` in favor of :ref:`path_config_source ` +* http: deprecated ``envoy.http.headermap.lazy_map_min_size``. If you are using this config knob you can revert this temporarily by setting ``envoy.reloadable_features.deprecate_global_ints`` to true but you MUST file an upstream issue to ensure this feature remains available. * http: removing support for long-deprecated old style filter names, e.g. envoy.router, envoy.lua. * re2: removed undocumented histograms ``re2.program_size`` and ``re2.exceeded_warn_level``. +* re2: deprecated ``re2.max_program_size.error_level`` and ``re2.max_program_size.warn_level``. If you are using these config knobs you can revert this temporarily by setting ``envoy.reloadable_features.deprecate_global_ints`` to true but you MUST file an upstream issue to ensure this feature remains available. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index d996321f1ae9..6ffe3e92d88d 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -27,6 +27,7 @@ RUNTIME_GUARD(envoy_reloadable_features_conn_pool_delete_when_idle); RUNTIME_GUARD(envoy_reloadable_features_conn_pool_new_stream_with_early_data_and_http3); RUNTIME_GUARD(envoy_reloadable_features_correct_scheme_and_xfp); RUNTIME_GUARD(envoy_reloadable_features_correctly_validate_alpn); +RUNTIME_GUARD(envoy_reloadable_features_deprecate_global_ints); RUNTIME_GUARD(envoy_reloadable_features_disable_tls_inspector_injection); RUNTIME_GUARD(envoy_reloadable_features_do_not_await_headers_on_upstream_timeout_to_emit_stats); RUNTIME_GUARD(envoy_reloadable_features_enable_grpc_async_client_cache); @@ -68,12 +69,10 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_unified_mux); FALSE_RUNTIME_GUARD(envoy_restart_features_no_runtime_singleton); // Block of non-boolean flags. These are deprecated. Do not add more. -ABSL_FLAG(uint64_t, envoy_buffer_overflow_multiplier, 0, ""); // NOLINT -ABSL_FLAG(uint64_t, envoy_do_not_use_going_away_max_http2_outbound_response, 2, ""); // NOLINT -ABSL_FLAG(uint64_t, envoy_headermap_lazy_map_min_size, 3, ""); // NOLINT -ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT -ABSL_FLAG(uint64_t, re2_max_program_size_warn_level, // NOLINT - std::numeric_limits::max(), ""); // NOLINT +ABSL_FLAG(uint64_t, envoy_headermap_lazy_map_min_size, 3, ""); // NOLINT +ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT +ABSL_FLAG(uint64_t, re2_max_program_size_warn_level, // NOLINT + std::numeric_limits::max(), ""); // NOLINT namespace Envoy { namespace Runtime { @@ -94,11 +93,7 @@ bool runtimeFeatureEnabled(absl::string_view feature) { uint64_t getInteger(absl::string_view feature, uint64_t default_value) { if (absl::StartsWith(feature, "envoy.")) { // DO NOT ADD MORE FLAGS HERE. This function deprecated and being removed. - if (feature == "envoy.buffer.overflow_multiplier") { - return absl::GetFlag(FLAGS_envoy_buffer_overflow_multiplier); - } else if (feature == "envoy.do_not_use_going_away_max_http2_outbound_responses") { - return absl::GetFlag(FLAGS_envoy_do_not_use_going_away_max_http2_outbound_response); - } else if (feature == "envoy.http.headermap.lazy_map_min_size") { + if (feature == "envoy.http.headermap.lazy_map_min_size") { return absl::GetFlag(FLAGS_envoy_headermap_lazy_map_min_size); } } @@ -145,6 +140,7 @@ constexpr absl::Flag* runtime_features[] = { &FLAGS_envoy_reloadable_features_conn_pool_new_stream_with_early_data_and_http3, &FLAGS_envoy_reloadable_features_correct_scheme_and_xfp, &FLAGS_envoy_reloadable_features_correctly_validate_alpn, + &FLAGS_envoy_reloadable_features_deprecate_global_ints, &FLAGS_envoy_reloadable_features_disable_tls_inspector_injection, &FLAGS_envoy_reloadable_features_do_not_await_headers_on_upstream_timeout_to_emit_stats, &FLAGS_envoy_reloadable_features_enable_grpc_async_client_cache, @@ -192,18 +188,24 @@ void maybeSetDeprecatedInts(absl::string_view name, uint32_t value) { return; } + bool set = false; // DO NOT ADD MORE FLAGS HERE. This function deprecated and being removed. - if (name == "envoy.buffer.overflow_multiplier") { - absl::SetFlag(&FLAGS_envoy_buffer_overflow_multiplier, value); - } else if (name == "envoy.do_not_use_going_away_max_http2_outbound_responses") { - absl::SetFlag(&FLAGS_envoy_do_not_use_going_away_max_http2_outbound_response, value); - } else if (name == "envoy.http.headermap.lazy_map_min_size") { + if (name == "envoy.http.headermap.lazy_map_min_size") { + set = true; absl::SetFlag(&FLAGS_envoy_headermap_lazy_map_min_size, value); } else if (name == "re2.max_program_size.error_level") { + set = true; absl::SetFlag(&FLAGS_re2_max_program_size_error_level, value); } else if (name == "re2.max_program_size.warn_level") { + set = true; absl::SetFlag(&FLAGS_re2_max_program_size_warn_level, value); } + if (set && Runtime::runtimeFeatureEnabled("envoy.reloadable_features.deprecate_global_ints")) { + IS_ENVOY_BUG(absl::StrCat( + "The Envoy community is attempting to remove global integers. Given you use ", name, + " please immediately file an upstream issue to retain the functionality as it will " + "otherwise be removed following the usual deprecation cycle.")); + } } std::string swapPrefix(std::string name) { @@ -232,8 +234,6 @@ void RuntimeFeatures::restoreDefaults() const { for (const auto& feature : disabled_features_) { absl::SetFlag(feature.second, false); } - absl::SetFlag(&FLAGS_envoy_buffer_overflow_multiplier, 0); - absl::SetFlag(&FLAGS_envoy_do_not_use_going_away_max_http2_outbound_response, 2); absl::SetFlag(&FLAGS_envoy_headermap_lazy_map_min_size, 3); absl::SetFlag(&FLAGS_re2_max_program_size_error_level, 100); absl::SetFlag(&FLAGS_re2_max_program_size_warn_level, std::numeric_limits::max()); diff --git a/source/common/runtime/runtime_impl.cc b/source/common/runtime/runtime_impl.cc index 5fd2fa8c7bbe..2136b9b05d3a 100644 --- a/source/common/runtime/runtime_impl.cc +++ b/source/common/runtime/runtime_impl.cc @@ -55,13 +55,16 @@ void refreshReloadableFlags(const Snapshot::EntryMap& flag_map) { if (it.second.bool_value_.has_value() && isRuntimeFeature(it.first)) { maybeSetRuntimeGuard(it.first, it.second.bool_value_.value()); } - if (it.second.uint_value_.has_value()) { - maybeSetDeprecatedInts(it.first, it.second.uint_value_.value()); - } } #ifdef ENVOY_ENABLE_QUIC quiche::FlagRegistry::getInstance().updateReloadableFlags(quiche_flags_override); #endif + // Make sure ints are parsed after the flag allowing deprecated ints is parsed. + for (const auto& it : flag_map) { + if (it.second.uint_value_.has_value()) { + maybeSetDeprecatedInts(it.first, it.second.uint_value_.value()); + } + } } } // namespace diff --git a/test/common/common/regex_test.cc b/test/common/common/regex_test.cc index 7330b1050c79..762215fcf14f 100644 --- a/test/common/common/regex_test.cc +++ b/test/common/common/regex_test.cc @@ -73,7 +73,8 @@ TEST(Utility, ParseRegex) { // The deprecated field codepath precedes any runtime settings. { TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"re2.max_program_size.error_level", "3"}}); + scoped_runtime.mergeValues({{"envoy.reloadable_features.deprecate_global_ints", "false"}, + {"re2.max_program_size.error_level", "3"}}); envoy::type::matcher::v3::RegexMatcher matcher; matcher.set_regex("/asdf/.*"); matcher.mutable_google_re2()->mutable_max_program_size()->set_value(1); @@ -89,7 +90,8 @@ TEST(Utility, ParseRegex) { // Verify that an exception is thrown for the error level max program size. { TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"re2.max_program_size.error_level", "1"}}); + scoped_runtime.mergeValues({{"envoy.reloadable_features.deprecate_global_ints", "false"}, + {"re2.max_program_size.error_level", "1"}}); envoy::type::matcher::v3::RegexMatcher matcher; matcher.set_regex("/asdf/.*"); matcher.mutable_google_re2(); @@ -107,6 +109,7 @@ TEST(Utility, ParseRegex) { // Verify that the error level max program size defaults to 100 if not set by runtime. { TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.deprecate_global_ints", "false"}}); envoy::type::matcher::v3::RegexMatcher matcher; matcher.set_regex( "/asdf/.*/asdf/.*/asdf/.*/asdf/.*/asdf/.*/asdf/.*/asdf/.*/asdf/.*/asdf/.*/asdf/.*"); @@ -125,7 +128,8 @@ TEST(Utility, ParseRegex) { // Verify that a warning is logged for the warn level max program size. { TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues({{"re2.max_program_size.warn_level", "1"}}); + scoped_runtime.mergeValues({{"envoy.reloadable_features.deprecate_global_ints", "false"}, + {"re2.max_program_size.warn_level", "1"}}); envoy::type::matcher::v3::RegexMatcher matcher; matcher.set_regex("/asdf/.*"); matcher.mutable_google_re2(); diff --git a/test/common/http/header_map_impl_fuzz_test.cc b/test/common/http/header_map_impl_fuzz_test.cc index 62fc452a420d..f59794f98416 100644 --- a/test/common/http/header_map_impl_fuzz_test.cc +++ b/test/common/http/header_map_impl_fuzz_test.cc @@ -20,7 +20,8 @@ DEFINE_PROTO_FUZZER(const test::common::http::HeaderMapImplFuzzTestCase& input) TestScopedRuntime scoped_runtime; // Set the lazy header-map threshold if found. if (input.has_config()) { - scoped_runtime.mergeValues({{"envoy.http.headermap.lazy_map_min_size", + scoped_runtime.mergeValues({{"envoy.reloadable_features.deprecate_global_ints", "false"}, + {"envoy.http.headermap.lazy_map_min_size", absl::StrCat(input.config().lazy_map_min_size())}}); } diff --git a/test/common/http/header_map_impl_test.cc b/test/common/http/header_map_impl_test.cc index 57561ec98ead..8f1a45de4dbe 100644 --- a/test/common/http/header_map_impl_test.cc +++ b/test/common/http/header_map_impl_test.cc @@ -373,7 +373,8 @@ class HeaderMapImplTest : public testing::TestWithParam { HeaderMapImplTest() { // Set the lazy map threshold using the test parameter. scoped_runtime_.mergeValues( - {{"envoy.http.headermap.lazy_map_min_size", absl::StrCat(GetParam())}}); + {{"envoy.reloadable_features.deprecate_global_ints", "false"}, + {"envoy.http.headermap.lazy_map_min_size", absl::StrCat(GetParam())}}); } static std::string testParamsToString(const ::testing::TestParamInfo& params) { From 360f7892e9315263a0c93d421fdfcddb9c172479 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 7 Mar 2022 15:32:27 -0500 Subject: [PATCH 19/68] setec: adding folks (#20237) Signed-off-by: Alyssa Wilk --- OWNERS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OWNERS.md b/OWNERS.md index 0d4918e54dbf..d84cac63a47f 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -70,6 +70,9 @@ without further review. * William A Rowe Jr ([wrowe](https://github.com/wrowe)) (wrowe@vmware.com) * Otto van der Schaaf ([oschaaf](https://github.com/oschaaf)) (oschaaf@redhat.com) * Tim Walsh ([twghu](https://github.com/twghu)) (walsh@redhat.com) +* Ryan Northey ([phlax](https://github.com/phlax)) (ryan@synca.io) +* Pradeep Rao ([pradeepcrao](https://github.com/pradeepcrao)) (pcrao@google.com) +* Ryan Hamilton ([RyanTheOptimist](https://github.com/ryantheoptimist)) (rch@google.com) In addition to the permanent Envoy security team, we have additional temporary contributors to envoy-setec and relevant Slack channels from: From 1e9eaf321ffd6a071a16779b1b3ede72694f98e8 Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Mon, 7 Mar 2022 14:13:44 -0800 Subject: [PATCH 20/68] HTTP2: Implement buffering of H2 stream data and trailers. (#19447) * Implement buffering of H2 stream data and trailers. Signed-off-by: Kevin Baichoo --- .../http/http_conn_man/stats.rst | 2 +- envoy/http/codec.h | 2 +- source/common/http/http1/codec_impl.cc | 2 +- source/common/http/http1/codec_impl.h | 2 +- source/common/http/http2/codec_impl.cc | 212 +++++++++++-- source/common/http/http2/codec_impl.h | 65 +++- source/common/http/http2/codec_stats.h | 3 +- source/common/quic/envoy_quic_stream.h | 2 +- source/common/runtime/runtime_features.cc | 4 + source/common/runtime/runtime_features.h | 2 + source/docs/flow_control.md | 26 ++ test/common/http/codec_impl_fuzz_test.cc | 55 +++- test/common/http/http2/codec_impl_test.cc | 296 ++++++++++++++++-- test/config/utility.cc | 2 +- test/config/utility.h | 2 +- test/extensions/filters/http/fault/BUILD | 1 + .../filters/http/kill_request/BUILD | 1 + test/extensions/filters/http/rbac/BUILD | 1 + test/integration/BUILD | 12 +- .../buffer_accounting_integration_test.cc | 274 +++++++++++++++- test/integration/filters/BUILD | 21 ++ test/integration/filters/tee_filter.cc | 85 +++++ test/integration/filters/tee_filter.h | 55 ++++ .../http2_flood_integration_test.cc | 29 +- test/integration/http_protocol_integration.cc | 31 +- test/integration/http_protocol_integration.h | 4 + .../multiplexed_upstream_integration_test.cc | 9 + test/integration/protocol_integration_test.cc | 2 +- test/mocks/http/stream.h | 2 +- 29 files changed, 1111 insertions(+), 93 deletions(-) create mode 100644 test/integration/filters/tee_filter.cc create mode 100644 test/integration/filters/tee_filter.h diff --git a/docs/root/configuration/http/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst index efb77be6f17e..b6730d14ffcb 100644 --- a/docs/root/configuration/http/http_conn_man/stats.rst +++ b/docs/root/configuration/http/http_conn_man/stats.rst @@ -173,7 +173,7 @@ On the upstream side all http2 statistics are rooted at *cluster..http2.* keepalive_timeout, Counter, Total number of connections closed due to :ref:`keepalive timeout ` streams_active, Gauge, Active streams as observed by the codec pending_send_bytes, Gauge, Currently buffered body data in bytes waiting to be written when stream/connection window is opened. - + deferred_stream_close, Gauge, Number of HTTP/2 streams where the stream has been closed but processing of the stream close has been deferred due to network backup. This is expected to be incremented when a downstream stream is backed up and the corresponding upstream stream has received end stream but we defer processing of the upstream stream close due to downstream backup. This is decremented as we finally delete the stream when either the deferred close stream has its buffered data drained or receives a reset. .. attention:: The HTTP/2 `streams_active` gauge may be greater than the HTTP connection manager diff --git a/envoy/http/codec.h b/envoy/http/codec.h index 7445fb20eec5..d32a8690c6c5 100644 --- a/envoy/http/codec.h +++ b/envoy/http/codec.h @@ -329,7 +329,7 @@ class Stream : public StreamResetHandler { * configured. * @return uint32_t the stream's configured buffer limits. */ - virtual uint32_t bufferLimit() PURE; + virtual uint32_t bufferLimit() const PURE; /** * @return string_view optionally return the reason behind codec level errors. diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index 98e290b99259..7cad7ec80a9e 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -369,7 +369,7 @@ void StreamEncoderImpl::readDisable(bool disable) { connection_.readDisable(disable); } -uint32_t StreamEncoderImpl::bufferLimit() { return connection_.bufferLimit(); } +uint32_t StreamEncoderImpl::bufferLimit() const { return connection_.bufferLimit(); } const Network::Address::InstanceConstSharedPtr& StreamEncoderImpl::connectionLocalAddress() { return connection_.connection().connectionInfoProvider().localAddress(); diff --git a/source/common/http/http1/codec_impl.h b/source/common/http/http1/codec_impl.h index e1e508747fa0..161ef63b1164 100644 --- a/source/common/http/http1/codec_impl.h +++ b/source/common/http/http1/codec_impl.h @@ -60,7 +60,7 @@ class StreamEncoderImpl : public virtual StreamEncoder, // progress may be made with the codec. void resetStream(StreamResetReason reason) override; void readDisable(bool disable) override; - uint32_t bufferLimit() override; + uint32_t bufferLimit() const override; absl::string_view responseDetails() override { return details_; } const Network::Address::InstanceConstSharedPtr& connectionLocalAddress() override; void setFlushTimeout(std::chrono::milliseconds) override { diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 3fa1c835d56e..c2ffa00fb731 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -158,7 +158,9 @@ ConnectionImpl::StreamImpl::StreamImpl(ConnectionImpl& parent, uint32_t buffer_l local_end_stream_sent_(false), remote_end_stream_(false), data_deferred_(false), received_noninformational_headers_(false), pending_receive_buffer_high_watermark_called_(false), - pending_send_buffer_high_watermark_called_(false), reset_due_to_messaging_error_(false) { + pending_send_buffer_high_watermark_called_(false), reset_due_to_messaging_error_(false), + defer_processing_backedup_streams_( + Runtime::runtimeFeatureEnabled(Runtime::defer_processing_backedup_streams)) { parent_.stats_.streams_active_.inc(); if (buffer_limit > 0) { setWriteBufferWatermarks(buffer_limit); @@ -166,6 +168,9 @@ ConnectionImpl::StreamImpl::StreamImpl(ConnectionImpl& parent, uint32_t buffer_l } void ConnectionImpl::StreamImpl::destroy() { + // Cancel any pending buffered data callback for the stream. + process_buffered_data_callback_.reset(); + MultiplexedStreamImplBase::destroy(); parent_.stats_.streams_active_.dec(); parent_.stats_.pending_send_bytes_.sub(pending_send_data_->length()); @@ -349,6 +354,44 @@ void ConnectionImpl::StreamImpl::encodeMetadata(const MetadataMapVector& metadat } } +void ConnectionImpl::StreamImpl::processBufferedData() { + ENVOY_CONN_LOG(debug, "Stream {} processing buffered data.", parent_.connection_, stream_id_); + + if (stream_manager_.body_buffered_ && continueProcessingBufferedData()) { + decodeData(); + } + + if (stream_manager_.trailers_buffered_ && continueProcessingBufferedData()) { + ASSERT(!stream_manager_.body_buffered_); + decodeTrailers(); + ASSERT(!stream_manager_.trailers_buffered_); + } + + // Reset cases are handled by resetStream and directly invoke onStreamClose, + // which consumes the buffered_on_stream_close_ so we don't invoke + // onStreamClose twice. + if (stream_manager_.buffered_on_stream_close_ && !stream_manager_.hasBufferedBodyOrTrailers()) { + ASSERT(!reset_reason_.has_value()); + ENVOY_CONN_LOG(debug, "invoking onStreamClose for stream: {} via processBufferedData", + parent_.connection_, stream_id_); + // We only buffer the onStreamClose if we had no errors. + parent_.onStreamClose(this, 0); + } +} + +void ConnectionImpl::StreamImpl::grantPeerAdditionalStreamWindow() { + if (parent_.use_new_codec_wrapper_) { + parent_.adapter_->MarkDataConsumedForStream(stream_id_, unconsumed_bytes_); + } else { + nghttp2_session_consume(parent_.session_, stream_id_, unconsumed_bytes_); + } + unconsumed_bytes_ = 0; + if (parent_.sendPendingFramesAndHandleError()) { + // Intended to check through coverage that this error case is tested + return; + } +} + void ConnectionImpl::StreamImpl::readDisable(bool disable) { ENVOY_CONN_LOG(debug, "Stream {} {}, unconsumed_bytes {} read_disable_count {}", parent_.connection_, stream_id_, (disable ? "disabled" : "enabled"), @@ -359,32 +402,75 @@ void ConnectionImpl::StreamImpl::readDisable(bool disable) { ASSERT(read_disable_count_ > 0); --read_disable_count_; if (!buffersOverrun()) { - if (parent_.use_new_codec_wrapper_) { - parent_.adapter_->MarkDataConsumedForStream(stream_id_, unconsumed_bytes_); - } else { - nghttp2_session_consume(parent_.session_, stream_id_, unconsumed_bytes_); - } - unconsumed_bytes_ = 0; - if (parent_.sendPendingFramesAndHandleError()) { - // Intended to check through coverage that this error case is tested - return; - } + scheduleProcessingOfBufferedData(); + grantPeerAdditionalStreamWindow(); } } } +void ConnectionImpl::StreamImpl::scheduleProcessingOfBufferedData() { + if (defer_processing_backedup_streams_) { + if (!process_buffered_data_callback_) { + process_buffered_data_callback_ = parent_.connection_.dispatcher().createSchedulableCallback( + [this]() { processBufferedData(); }); + } + + // We schedule processing to occur in another callback to avoid + // reentrant and deep call stacks. + process_buffered_data_callback_->scheduleCallbackCurrentIteration(); + } +} + void ConnectionImpl::StreamImpl::pendingRecvBufferHighWatermark() { - ENVOY_CONN_LOG(debug, "recv buffer over limit ", parent_.connection_); - ASSERT(!pending_receive_buffer_high_watermark_called_); - pending_receive_buffer_high_watermark_called_ = true; - readDisable(true); + // If `defer_processing_backedup_streams_`, read disabling here can become + // dangerous as it can prevent us from processing buffered data. + if (!defer_processing_backedup_streams_) { + ENVOY_CONN_LOG(debug, "recv buffer over limit ", parent_.connection_); + ASSERT(!pending_receive_buffer_high_watermark_called_); + pending_receive_buffer_high_watermark_called_ = true; + readDisable(true); + } } void ConnectionImpl::StreamImpl::pendingRecvBufferLowWatermark() { - ENVOY_CONN_LOG(debug, "recv buffer under limit ", parent_.connection_); - ASSERT(pending_receive_buffer_high_watermark_called_); - pending_receive_buffer_high_watermark_called_ = false; - readDisable(false); + // If `defer_processing_backedup_streams_`, we don't read disable on + // high watermark, so we shouldn't read disable here. + if (defer_processing_backedup_streams_) { + if (shouldAllowPeerAdditionalStreamWindow()) { + // We should grant additional stream window here, in case the + // `pending_recv_buffer_` was blocking flow control updates + // from going to the peer. + grantPeerAdditionalStreamWindow(); + } + } else { + ENVOY_CONN_LOG(debug, "recv buffer under limit ", parent_.connection_); + ASSERT(pending_receive_buffer_high_watermark_called_); + pending_receive_buffer_high_watermark_called_ = false; + readDisable(false); + } +} + +void ConnectionImpl::StreamImpl::decodeData() { + if (defer_processing_backedup_streams_ && buffersOverrun()) { + ENVOY_CONN_LOG(trace, "Stream {} buffering decodeData() call.", parent_.connection_, + stream_id_); + stream_manager_.body_buffered_ = true; + return; + } + + // Any buffered data will be consumed. + stream_manager_.body_buffered_ = false; + + // It's possible that we are waiting to send a deferred reset, so only raise data if local + // is not complete. + if (!deferred_reset_) { + decoder().decodeData(*pending_recv_data_, sendEndStream()); + } + + // TODO(kbaichoo): If dumping buffered data, we should do so in default read + // size chunks rather than dumping the entire buffer, which can have fairness + // issues. + pending_recv_data_->drain(pending_recv_data_->length()); } void ConnectionImpl::ClientStreamImpl::decodeHeaders() { @@ -404,11 +490,35 @@ void ConnectionImpl::ClientStreamImpl::decodeHeaders() { ASSERT(!remote_end_stream_); response_decoder_.decode1xxHeaders(std::move(headers)); } else { - response_decoder_.decodeHeaders(std::move(headers), remote_end_stream_); + response_decoder_.decodeHeaders(std::move(headers), sendEndStream()); } } +bool ConnectionImpl::StreamImpl::maybeDeferDecodeTrailers() { + ASSERT(!deferred_reset_.has_value()); + // Buffer trailers if we're deferring processing and not flushing all data + // through and either + // 1) Buffers are overrun + // 2) There's buffered body which should get processed before these trailers + // to avoid losing data. + if (defer_processing_backedup_streams_ && (buffersOverrun() || stream_manager_.body_buffered_)) { + stream_manager_.trailers_buffered_ = true; + ENVOY_CONN_LOG(trace, "Stream {} buffering decodeTrailers() call.", parent_.connection_, + stream_id_); + return true; + } + + return false; +} + void ConnectionImpl::ClientStreamImpl::decodeTrailers() { + if (maybeDeferDecodeTrailers()) { + return; + } + + // Consume any buffered trailers. + stream_manager_.trailers_buffered_ = false; + response_decoder_.decodeTrailers( std::move(absl::get(headers_or_trailers_))); } @@ -418,10 +528,17 @@ void ConnectionImpl::ServerStreamImpl::decodeHeaders() { if (Http::Utility::isH2UpgradeRequest(*headers)) { Http::Utility::transformUpgradeRequestFromH2toH1(*headers); } - request_decoder_->decodeHeaders(std::move(headers), remote_end_stream_); + request_decoder_->decodeHeaders(std::move(headers), sendEndStream()); } void ConnectionImpl::ServerStreamImpl::decodeTrailers() { + if (maybeDeferDecodeTrailers()) { + return; + } + + // Consume any buffered trailers. + stream_manager_.trailers_buffered_ = false; + request_decoder_->decodeTrailers( std::move(absl::get(headers_or_trailers_))); } @@ -634,9 +751,23 @@ void ConnectionImpl::ServerStreamImpl::resetStream(StreamResetReason reason) { } void ConnectionImpl::StreamImpl::resetStream(StreamResetReason reason) { + reset_reason_ = reason; + // Higher layers expect calling resetStream() to immediately raise reset callbacks. runResetCallbacks(reason); + // If we've bufferedOnStreamClose for this stream, we shouldn't propagate this + // reset as nghttp2 will have forgotten about the stream. + if (stream_manager_.buffered_on_stream_close_) { + ENVOY_CONN_LOG( + trace, "Stopped propagating reset to nghttp2 as we've buffered onStreamClose for stream {}", + parent_.connection_, stream_id_); + // The stream didn't originally have an NGHTTP2 error, since we buffered + // its stream close. + parent_.onStreamClose(this, 0); + return; + } + // If we submit a reset, nghttp2 will cancel outbound frames that have not yet been sent. // We want these frames to go out so we defer the reset until we send all of the frames that // end the local stream. However, if we're resetting the stream due to @@ -890,7 +1021,7 @@ int ConnectionImpl::onData(int32_t stream_id, const uint8_t* data, size_t len) { stream->pending_recv_data_->add(data, len); // Update the window to the peer unless some consumer of this stream's data has hit a flow control // limit and disabled reads on this stream - if (!stream->buffersOverrun()) { + if (stream->shouldAllowPeerAdditionalStreamWindow()) { if (use_new_codec_wrapper_) { adapter_->MarkDataConsumedForStream(stream_id, len); } else { @@ -1070,14 +1201,7 @@ Status ConnectionImpl::onFrameReceived(const nghttp2_frame* frame) { } case NGHTTP2_DATA: { stream->remote_end_stream_ = frame->hd.flags & NGHTTP2_FLAG_END_STREAM; - - // It's possible that we are waiting to send a deferred reset, so only raise data if local - // is not complete. - if (!stream->deferred_reset_) { - stream->decoder().decodeData(*stream->pending_recv_data_, stream->remote_end_stream_); - } - - stream->pending_recv_data_->drain(stream->pending_recv_data_->length()); + stream->decodeData(); break; } case NGHTTP2_RST_STREAM: { @@ -1232,10 +1356,18 @@ ssize_t ConnectionImpl::onSend(const uint8_t* data, size_t length) { return length; } -int ConnectionImpl::onStreamClose(int32_t stream_id, uint32_t error_code) { - StreamImpl* stream = getStream(stream_id); +int ConnectionImpl::onStreamClose(StreamImpl* stream, uint32_t error_code) { if (stream) { - ENVOY_CONN_LOG(debug, "stream closed: {}", connection_, error_code); + const int32_t stream_id = stream->stream_id_; + + // Consume buffered on stream_close. + if (stream->stream_manager_.buffered_on_stream_close_) { + stream->stream_manager_.buffered_on_stream_close_ = false; + stats_.deferred_stream_close_.dec(); + } + + ENVOY_CONN_LOG(debug, "stream {} closed: {}", connection_, stream_id, error_code); + if (!stream->remote_end_stream_ || !stream->local_end_stream_) { StreamResetReason reason; if (stream->reset_due_to_messaging_error_) { @@ -1265,6 +1397,16 @@ int ConnectionImpl::onStreamClose(int32_t stream_id, uint32_t error_code) { } stream->runResetCallbacks(reason); + + } else if (stream->defer_processing_backedup_streams_ && !stream->reset_reason_.has_value() && + stream->stream_manager_.hasBufferedBodyOrTrailers()) { + ASSERT(error_code == NGHTTP2_NO_ERROR); + ENVOY_CONN_LOG(debug, "buffered onStreamClose for stream: {}", connection_, stream_id); + // Buffer the call, rely on the stream->process_buffered_data_callback_ + // to end up invoking. + stream->stream_manager_.buffered_on_stream_close_ = true; + stats_.deferred_stream_close_.inc(); + return 0; } stream->destroy(); @@ -1289,6 +1431,10 @@ int ConnectionImpl::onStreamClose(int32_t stream_id, uint32_t error_code) { return 0; } +int ConnectionImpl::onStreamClose(int32_t stream_id, uint32_t error_code) { + return onStreamClose(getStream(stream_id), error_code); +} + int ConnectionImpl::onMetadataReceived(int32_t stream_id, const uint8_t* data, size_t len) { ENVOY_CONN_LOG(trace, "recv {} bytes METADATA", connection_, len); diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 4f775f2cb7b4..9d5fbda1a3bf 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -255,7 +255,7 @@ class ConnectionImpl : public virtual Connection, void removeCallbacks(StreamCallbacks& callbacks) override { removeCallbacksHelper(callbacks); } void resetStream(StreamResetReason reason) override; void readDisable(bool disable) override; - uint32_t bufferLimit() override { return pending_recv_data_->highWatermark(); } + uint32_t bufferLimit() const override { return pending_recv_data_->highWatermark(); } const Network::Address::InstanceConstSharedPtr& connectionLocalAddress() override { return parent_.connection_.connectionInfoProvider().localAddress(); } @@ -299,6 +299,9 @@ class ConnectionImpl : public virtual Connection, // to the decoder_. virtual void decodeHeaders() PURE; virtual void decodeTrailers() PURE; + bool maybeDeferDecodeTrailers(); + // Consumes any decoded data, buffering if backed up. + void decodeData(); // Get MetadataEncoder for this stream. MetadataEncoder& getMetadataEncoderOld(); @@ -309,9 +312,14 @@ class ConnectionImpl : public virtual Connection, void onMetadataDecoded(MetadataMapPtr&& metadata_map_ptr); bool buffersOverrun() const { return read_disable_count_ > 0; } + bool shouldAllowPeerAdditionalStreamWindow() const { + return !buffersOverrun() && !pending_recv_data_->highWatermarkTriggered(); + } void encodeDataHelper(Buffer::Instance& data, bool end_stream, bool skip_encoding_empty_trailers); + // Called from either process_buffered_data_callback_. + void processBufferedData(); const StreamInfo::BytesMeterSharedPtr& bytesMeter() override { return bytes_meter_; } ConnectionImpl& parent_; @@ -324,8 +332,11 @@ class ConnectionImpl : public virtual Connection, // Note that in current implementation the watermark callbacks of the pending_recv_data_ are // never called. The watermark value is set to the size of the stream window. As a result this // watermark can never overflow because the peer can never send more bytes than the stream - // window without triggering protocol error and this buffer is drained after each DATA frame was - // dispatched through the filter chain. See source/docs/flow_control.md for more information. + // window without triggering protocol error. This buffer is drained after each DATA frame was + // dispatched through the filter chain unless + // envoy.reloadable_features.defer_processing_backedup_streams is enabled, + // in which case this buffer may accumulate data. + // See source/docs/flow_control.md for more information. Buffer::InstancePtr pending_recv_data_; Buffer::InstancePtr pending_send_data_; HeaderMapPtr pending_trailers_to_encode_; @@ -333,6 +344,9 @@ class ConnectionImpl : public virtual Connection, std::unique_ptr metadata_encoder_; std::unique_ptr metadata_encoder_old_; absl::optional deferred_reset_; + // Holds the reset reason for this stream. Useful if we have buffered data + // to determine whether we should continue processing that data. + absl::optional reset_reason_; HeaderString cookies_; bool local_end_stream_sent_ : 1; bool remote_end_stream_ : 1; @@ -341,13 +355,54 @@ class ConnectionImpl : public virtual Connection, bool pending_receive_buffer_high_watermark_called_ : 1; bool pending_send_buffer_high_watermark_called_ : 1; bool reset_due_to_messaging_error_ : 1; + bool defer_processing_backedup_streams_ : 1; absl::string_view details_; + /** + * Tracks buffering that may occur for a stream if it is backed up. + */ + struct BufferedStreamManager { + bool body_buffered_{false}; + bool trailers_buffered_{false}; + + // We received a call to onStreamClose for the stream, but deferred it + // as the stream had pending data to process and the stream was not reset. + bool buffered_on_stream_close_{false}; + + bool hasBufferedBodyOrTrailers() const { return body_buffered_ || trailers_buffered_; } + }; + + BufferedStreamManager stream_manager_; + Event::SchedulableCallbackPtr process_buffered_data_callback_; + protected: // Http::MultiplexedStreamImplBase bool hasPendingData() override { return pending_send_data_->length() > 0 || pending_trailers_to_encode_ != nullptr; } + bool continueProcessingBufferedData() const { + // We should stop processing buffered data if either + // 1) Buffers become overrun + // 2) The stream ends up getting reset + // Both of these can end up changing as a result of processing buffered data. + return !buffersOverrun() && !reset_reason_.has_value(); + } + + // Avoid inversion in the case where we saw trailers, acquiring the + // remote_end_stream_ being set to true, but the trailers ended up being + // buffered. + // All buffered body must be consumed before we send end stream. + bool sendEndStream() const { + return remote_end_stream_ && !stream_manager_.trailers_buffered_ && + !stream_manager_.body_buffered_; + } + + // Schedules a callback to process buffered data. + void scheduleProcessingOfBufferedData(); + + // Marks data consumed by the stream, granting the peer additional stream + // window. + void grantPeerAdditionalStreamWindow(); }; using StreamImplPtr = std::unique_ptr; @@ -593,7 +648,11 @@ class ConnectionImpl : public virtual Connection, int onError(absl::string_view error); virtual int onHeader(const nghttp2_frame* frame, HeaderString&& name, HeaderString&& value) PURE; int onInvalidFrame(int32_t stream_id, int error_code); + // Pass through invoking with the actual stream. int onStreamClose(int32_t stream_id, uint32_t error_code); + // Should be invoked directly in buffered onStreamClose scenarios + // where nghttp2 might have already forgotten about the stream. + int onStreamClose(StreamImpl* stream, uint32_t error_code); int onMetadataReceived(int32_t stream_id, const uint8_t* data, size_t len); int onMetadataFrameComplete(int32_t stream_id, bool end_metadata); // Called iff use_new_codec_wrapper_ is false. diff --git a/source/common/http/http2/codec_stats.h b/source/common/http/http2/codec_stats.h index 3bfcad7cc0b4..27babcb480dc 100644 --- a/source/common/http/http2/codec_stats.h +++ b/source/common/http/http2/codec_stats.h @@ -31,7 +31,8 @@ namespace Http2 { COUNTER(tx_reset) \ COUNTER(keepalive_timeout) \ GAUGE(streams_active, Accumulate) \ - GAUGE(pending_send_bytes, Accumulate) + GAUGE(pending_send_bytes, Accumulate) \ + GAUGE(deferred_stream_close, Accumulate) /** * Wrapper struct for the HTTP/2 codec stats. @see stats_macros.h diff --git a/source/common/quic/envoy_quic_stream.h b/source/common/quic/envoy_quic_stream.h index b1e8687b523b..95a6a905ad5f 100644 --- a/source/common/quic/envoy_quic_stream.h +++ b/source/common/quic/envoy_quic_stream.h @@ -77,7 +77,7 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, void removeCallbacks(Http::StreamCallbacks& callbacks) override { removeCallbacksHelper(callbacks); } - uint32_t bufferLimit() override { return send_buffer_simulation_.highWatermark(); } + uint32_t bufferLimit() const override { return send_buffer_simulation_.highWatermark(); } const Network::Address::InstanceConstSharedPtr& connectionLocalAddress() override { return connection()->connectionInfoProvider().localAddress(); } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 6ffe3e92d88d..221d9f840eff 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -67,6 +67,9 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_allow_multiple_dns_addresses); FALSE_RUNTIME_GUARD(envoy_reloadable_features_unified_mux); // TODO(alyssar) flip false once issue complete. FALSE_RUNTIME_GUARD(envoy_restart_features_no_runtime_singleton); +// TODO(kbaichoo): Make this enabled by default when fairness and chunking +// are implemented, and we've had more cpu time. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_defer_processing_backedup_streams); // Block of non-boolean flags. These are deprecated. Do not add more. ABSL_FLAG(uint64_t, envoy_headermap_lazy_map_min_size, 3, ""); // NOLINT @@ -140,6 +143,7 @@ constexpr absl::Flag* runtime_features[] = { &FLAGS_envoy_reloadable_features_conn_pool_new_stream_with_early_data_and_http3, &FLAGS_envoy_reloadable_features_correct_scheme_and_xfp, &FLAGS_envoy_reloadable_features_correctly_validate_alpn, + &FLAGS_envoy_reloadable_features_defer_processing_backedup_streams, &FLAGS_envoy_reloadable_features_deprecate_global_ints, &FLAGS_envoy_reloadable_features_disable_tls_inspector_injection, &FLAGS_envoy_reloadable_features_do_not_await_headers_on_upstream_timeout_to_emit_stats, diff --git a/source/common/runtime/runtime_features.h b/source/common/runtime/runtime_features.h index 983c58d295a7..6c0d1f6cbe42 100644 --- a/source/common/runtime/runtime_features.h +++ b/source/common/runtime/runtime_features.h @@ -21,6 +21,8 @@ void maybeSetRuntimeGuard(absl::string_view name, bool value); void maybeSetDeprecatedInts(absl::string_view name, uint32_t value); constexpr absl::string_view conn_pool_new_stream_with_early_data_and_http3 = "envoy.reloadable_features.conn_pool_new_stream_with_early_data_and_http3"; +constexpr absl::string_view defer_processing_backedup_streams = + "envoy.reloadable_features.defer_processing_backedup_streams"; class RuntimeFeatures { public: diff --git a/source/docs/flow_control.md b/source/docs/flow_control.md index e32bc4b2e850..e21b05c8ebba 100644 --- a/source/docs/flow_control.md +++ b/source/docs/flow_control.md @@ -101,6 +101,32 @@ upstream connections, the `readDisable(true)` calls are unwound in ClientConnectionImpl::onMessageComplete() to make sure that as connections are returned to the connection pool they are ready to read. +#### HTTP2 defer processing backed up streams + +This section will be further integrated with the rest of the document when it is +enabled by default (currently off by default). At a high level this change does +the following: + +* If a HTTP/2 stream is read disabled anywhere we will begin buffering body and + trailers in the receiving side of the codec to minimize work done on the + stream when it's read disabled. There will be a callback scheduled to drain + these when the stream is read enabled, it can also be drained if the stream is + read enabled, and the stream is invoked with additional data. +* The codec's recieve buffer high watermark is still used in consideration for + granting peer stream additional window (preserving existing protocol flow + control). The codec's recieve buffer high watermark was rarely used prior as we'd + eagerly dispatch data through the filter chain. An exceptions where it could be + triggered is if a filter in the filter chain either injects data triggering watermark. + The codec's recieve buffer no longer read disables the stream, as we could be + read disabled elsewhere, buffer data in codec's recieve buffer hitting high + watermark and never switch back to being read enabled. +* One side effect of deferring stream processing is the need to defer processing + stream close. See ``deferred_stream_close`` in + :ref:`config_http_conn_man_stats_per_codec` for additional details. + +As of now we push all buffered data through the other end, but will implement +chunking and fairness to avoid a stream starving the others. + ## HTTP/2 codec recv buffer Given the HTTP/2 `Envoy::Http::Http2::ConnectionImpl::StreamImpl::pending_recv_data_` is processed immediately diff --git a/test/common/http/codec_impl_fuzz_test.cc b/test/common/http/codec_impl_fuzz_test.cc index a9ad2219ff59..5083d0f43ff7 100644 --- a/test/common/http/codec_impl_fuzz_test.cc +++ b/test/common/http/codec_impl_fuzz_test.cc @@ -119,6 +119,7 @@ class HttpStream : public LinkedObject { bool local_closed_{false}; bool remote_closed_{false}; uint32_t read_disable_count_{}; + bool created_schedulable_callback_{false}; bool isLocalOpen() const { return !local_closed_; } @@ -144,11 +145,26 @@ class HttpStream : public LinkedObject { } request_, response_; + // Encapsulates configuration, connections information used in the HttpStream. + struct ConnectionContext { + MockConnectionManagerConfig* conn_manager_config_; + NiceMock& server_connection_; + NiceMock& client_connection_; + + ConnectionContext(MockConnectionManagerConfig* conn_manager_config, + NiceMock& server_connection, + NiceMock& client_connection) + : conn_manager_config_(conn_manager_config), server_connection_(server_connection), + client_connection_(client_connection) {} + }; + HttpStream(ClientConnection& client, const TestRequestHeaderMapImpl& request_headers, bool end_stream, StreamResetCallbackFn stream_reset_callback, - MockConnectionManagerConfig* config) - : stream_reset_callback_(stream_reset_callback), conn_manager_config_(config) { + ConnectionContext& context) + : http_protocol_(client.protocol()), stream_reset_callback_(stream_reset_callback), + context_(context) { request_.request_encoder_ = &client.newStream(response_.response_decoder_); + ON_CALL(request_.stream_callbacks_, onResetStream(_, _)) .WillByDefault(InvokeWithoutArgs([this] { ENVOY_LOG_MISC(trace, "reset request for stream index {}", stream_index_); @@ -225,8 +241,8 @@ class HttpStream : public LinkedObject { auto headers = fromSanitizedHeaders(directional_action.headers()); ConnectionManagerUtility::mutateResponseHeaders(headers, &request_.request_headers_, - *conn_manager_config_, /*via=*/"", - stream_info_, /*node_id=*/""); + *context_.conn_manager_config_, + /*via=*/"", stream_info_, /*node_id=*/""); if (headers.Status() == nullptr) { headers.setReferenceKey(Headers::get().Status, "200"); } @@ -324,12 +340,35 @@ class HttpStream : public LinkedObject { } else { --state.read_disable_count_; } + StreamEncoder* encoder; + Event::MockDispatcher* dispatcher{nullptr}; + if (response) { encoder = state.response_encoder_; + dispatcher = &context_.server_connection_.dispatcher_; } else { encoder = state.request_encoder_; + dispatcher = &context_.client_connection_.dispatcher_; } + + // With this feature enabled for http2 we end up creating a schedulable + // callback the first time we re-enable reading as it's used to process + // the backed up data. + if (Runtime::runtimeFeatureEnabled(Runtime::defer_processing_backedup_streams)) { + const bool expecting_schedulable_callback_creation = + http_protocol_ == Protocol::Http2 && state.read_disable_count_ == 0 && !disable && + !state.created_schedulable_callback_; + + if (expecting_schedulable_callback_creation) { + ASSERT(dispatcher != nullptr); + state.created_schedulable_callback_ = true; + // The unique pointer of this object will be returned in createSchedulableCallback_ of + // dispatcher, so there is no risk of object leak. + new Event::MockSchedulableCallback(dispatcher); + } + } + encoder->getStream().readDisable(disable); } break; @@ -397,9 +436,10 @@ class HttpStream : public LinkedObject { response_.stream_state_ != StreamState::Closed; } + Protocol http_protocol_; int32_t stream_index_{-1}; StreamResetCallbackFn stream_reset_callback_; - MockConnectionManagerConfig* conn_manager_config_; + ConnectionContext context_; testing::NiceMock stream_info_; }; @@ -495,6 +535,9 @@ void codecFuzz(const test::common::http::CodecImplFuzzTestCase& input, HttpVersi const envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action = envoy::config::core::v3::HttpProtocolOptions::ALLOW; + HttpStream::ConnectionContext connection_context(&conn_manager_config, server_connection, + client_connection); + Http1::CodecStats::AtomicPtr http1_stats; Http2::CodecStats::AtomicPtr http2_stats; ClientConnectionPtr client; @@ -616,7 +659,7 @@ void codecFuzz(const test::common::http::CodecImplFuzzTestCase& input, HttpVersi should_close_connection = true; } }, - &conn_manager_config); + connection_context); LinkedList::moveIntoListBack(std::move(stream), pending_streams); break; } diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index 6f3bc0fe3181..f64c0b4fffc4 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -49,7 +49,7 @@ namespace Http { namespace Http2 { using Http2SettingsTuple = ::testing::tuple; -using Http2SettingsTestParam = ::testing::tuple; +using Http2SettingsTestParam = ::testing::tuple; namespace CommonUtility = ::Envoy::Http2::Utility; class Http2CodecImplTestFixture { @@ -101,9 +101,10 @@ class Http2CodecImplTestFixture { Http2CodecImplTestFixture() = default; Http2CodecImplTestFixture(Http2SettingsTuple client_settings, Http2SettingsTuple server_settings, - bool enable_new_codec_wrapper) + bool enable_new_codec_wrapper, bool defer_processing_backedup_streams) : client_settings_(client_settings), server_settings_(server_settings), - enable_new_codec_wrapper_(enable_new_codec_wrapper) { + enable_new_codec_wrapper_(enable_new_codec_wrapper), + defer_processing_backedup_streams_(defer_processing_backedup_streams) { // Make sure we explicitly test for stream flush timer creation. EXPECT_CALL(client_connection_.dispatcher_, createTimer_(_)).Times(0); EXPECT_CALL(server_connection_.dispatcher_, createTimer_(_)).Times(0); @@ -138,6 +139,9 @@ class Http2CodecImplTestFixture { Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.reloadable_features.http2_new_codec_wrapper", enable_new_codec_wrapper_ ? "true" : "false"}}); + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{std::string(Runtime::defer_processing_backedup_streams), + defer_processing_backedup_streams_ ? "true" : "false"}}); http2OptionsFromTuple(client_http2_options_, client_settings_); http2OptionsFromTuple(server_http2_options_, server_settings_); client_ = std::make_unique( @@ -321,6 +325,7 @@ class Http2CodecImplTestFixture { absl::optional client_settings_; absl::optional server_settings_; bool enable_new_codec_wrapper_ = false; + bool defer_processing_backedup_streams_ = false; bool allow_metadata_ = false; bool stream_error_on_invalid_http_messaging_ = false; Stats::TestUtil::TestStore client_stats_store_; @@ -366,7 +371,7 @@ class Http2CodecImplTest : public ::testing::TestWithParam(GetParam()), ::testing::get<1>(GetParam()), - ::testing::get<2>(GetParam())) {} + ::testing::get<2>(GetParam()), ::testing::get<3>(GetParam())) {} protected: void priorityFlood() { @@ -1581,13 +1586,31 @@ TEST_P(Http2CodecImplFlowControlTest, TestFlowControlInPendingSendData) { // updates to be sent, and the client to flush all queued data. // For bonus corner case coverage, remove callback2 in the middle of runLowWatermarkCallbacks() // and ensure it is not called. + NiceMock* process_buffered_data_callback{nullptr}; + if (defer_processing_backedup_streams_) { + process_buffered_data_callback = + new NiceMock(&server_connection_.dispatcher_); + } + EXPECT_CALL(callbacks, onBelowWriteBufferLowWatermark()).WillOnce(Invoke([&]() -> void { request_encoder_->getStream().removeCallbacks(callbacks2); })); EXPECT_CALL(callbacks2, onBelowWriteBufferLowWatermark()).Times(0); EXPECT_CALL(callbacks3, onBelowWriteBufferLowWatermark()); + + if (defer_processing_backedup_streams_) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + EXPECT_FALSE(process_buffered_data_callback->enabled_); + } + server_->getStream(1)->readDisable(false); driveToCompletion(); + + if (defer_processing_backedup_streams_) { + EXPECT_TRUE(process_buffered_data_callback->enabled_); + process_buffered_data_callback->invokeCallback(); + } + EXPECT_EQ(0, client_->getStream(1)->pending_send_data_->length()); EXPECT_EQ(0, TestUtility::findGauge(client_stats_store_, "http2.pending_send_bytes")->value()); // The extra 1 byte sent won't trigger another window update, so the final window should be the @@ -1879,9 +1902,6 @@ TEST_P(Http2CodecImplFlowControlTest, WindowUpdateOnReadResumingFlood) { EXPECT_EQ(initial_stream_window, server_->getStream(1)->bufferLimit()); EXPECT_EQ(initial_stream_window, client_->getStream(1)->bufferLimit()); - auto* violation_callback = - new NiceMock(&server_connection_.dispatcher_); - // One large write gets broken into smaller frames. EXPECT_CALL(request_decoder_, decodeData(_, false)).Times(AnyNumber()); Buffer::OwnedImpl long_data(std::string(initial_stream_window / 2, 'a')); @@ -1901,14 +1921,28 @@ TEST_P(Http2CodecImplFlowControlTest, WindowUpdateOnReadResumingFlood) { } driveToCompletion(); + auto* violation_callback = + new NiceMock(&server_connection_.dispatcher_); + EXPECT_FALSE(violation_callback->enabled_); + NiceMock* process_buffered_data_callback{nullptr}; + if (defer_processing_backedup_streams_) { + process_buffered_data_callback = + new NiceMock(&server_connection_.dispatcher_); + + EXPECT_FALSE(process_buffered_data_callback->enabled_); + } + // Now unblock the server's stream. This will cause the bytes to be consumed, 2 flow control // updates to be sent, and overflow outbound frame queue. server_->getStream(1)->readDisable(false); driveToCompletion(); EXPECT_TRUE(violation_callback->enabled_); + if (defer_processing_backedup_streams_) { + EXPECT_TRUE(process_buffered_data_callback->enabled_); + } EXPECT_CALL(server_connection_, close(Envoy::Network::ConnectionCloseType::NoFlush)); violation_callback->invokeCallback(); @@ -2091,12 +2125,14 @@ TEST_P(Http2CodecImplStreamLimitTest, LazyDecreaseMaxConcurrentStreamsConsumeErr // Deferred reset tests use only small windows so that we can test certain conditions. INSTANTIATE_TEST_SUITE_P(Http2CodecImplDeferredResetTest, Http2CodecImplDeferredResetTest, ::testing::Combine(HTTP2SETTINGS_SMALL_WINDOW_COMBINE, - HTTP2SETTINGS_SMALL_WINDOW_COMBINE, ::testing::Bool())); + HTTP2SETTINGS_SMALL_WINDOW_COMBINE, ::testing::Bool(), + ::testing::Bool())); // Flow control tests only use only small windows so that we can test certain conditions. INSTANTIATE_TEST_SUITE_P(Http2CodecImplFlowControlTest, Http2CodecImplFlowControlTest, ::testing::Combine(HTTP2SETTINGS_SMALL_WINDOW_COMBINE, - HTTP2SETTINGS_SMALL_WINDOW_COMBINE, ::testing::Bool())); + HTTP2SETTINGS_SMALL_WINDOW_COMBINE, ::testing::Bool(), + ::testing::Bool())); // we separate default/edge cases here to avoid combinatorial explosion #define HTTP2SETTINGS_DEFAULT_COMBINE \ @@ -2110,11 +2146,13 @@ INSTANTIATE_TEST_SUITE_P(Http2CodecImplFlowControlTest, Http2CodecImplFlowContro // edge settings allow for the number of streams needed by the test. INSTANTIATE_TEST_SUITE_P(Http2CodecImplStreamLimitTest, Http2CodecImplStreamLimitTest, ::testing::Combine(HTTP2SETTINGS_DEFAULT_COMBINE, - HTTP2SETTINGS_DEFAULT_COMBINE, ::testing::Bool())); + HTTP2SETTINGS_DEFAULT_COMBINE, ::testing::Bool(), + ::testing::Bool())); INSTANTIATE_TEST_SUITE_P(Http2CodecImplTestDefaultSettings, Http2CodecImplTest, ::testing::Combine(HTTP2SETTINGS_DEFAULT_COMBINE, - HTTP2SETTINGS_DEFAULT_COMBINE, ::testing::Bool())); + HTTP2SETTINGS_DEFAULT_COMBINE, ::testing::Bool(), + ::testing::Bool())); #define HTTP2SETTINGS_EDGE_COMBINE \ ::testing::Combine( \ @@ -2127,17 +2165,18 @@ INSTANTIATE_TEST_SUITE_P(Http2CodecImplTestDefaultSettings, Http2CodecImplTest, ::testing::Values(CommonUtility::OptionsLimits::MIN_INITIAL_CONNECTION_WINDOW_SIZE, \ CommonUtility::OptionsLimits::MAX_INITIAL_CONNECTION_WINDOW_SIZE)) -// Make sure we have coverage for high and low values for various combinations and permutations +// Make sure we have coverage for high and low values for various combinations and permutations // of HTTP settings in at least one test fixture. // Use with caution as any test using this runs 255 times. using Http2CodecImplTestAll = Http2CodecImplTest; INSTANTIATE_TEST_SUITE_P(Http2CodecImplTestDefaultSettings, Http2CodecImplTestAll, ::testing::Combine(HTTP2SETTINGS_DEFAULT_COMBINE, - HTTP2SETTINGS_DEFAULT_COMBINE, ::testing::Bool())); + HTTP2SETTINGS_DEFAULT_COMBINE, ::testing::Bool(), + ::testing::Bool())); INSTANTIATE_TEST_SUITE_P(Http2CodecImplTestEdgeSettings, Http2CodecImplTestAll, ::testing::Combine(HTTP2SETTINGS_EDGE_COMBINE, HTTP2SETTINGS_EDGE_COMBINE, - ::testing::Bool())); + ::testing::Bool(), ::testing::Bool())); TEST(Http2CodecUtility, reconstituteCrumbledCookies) { { @@ -2191,8 +2230,9 @@ class Http2CustomSettingsTestBase : public Http2CodecImplTestFixture { Http2CustomSettingsTestBase(Http2SettingsTuple client_settings, Http2SettingsTuple server_settings, bool use_new_codec_wrapper, - bool validate_client) - : Http2CodecImplTestFixture(client_settings, server_settings, use_new_codec_wrapper), + bool defer_processing_backedup_streams, bool validate_client) + : Http2CodecImplTestFixture(client_settings, server_settings, use_new_codec_wrapper, + defer_processing_backedup_streams), validate_client_(validate_client) {} ~Http2CustomSettingsTestBase() override = default; @@ -2236,16 +2276,17 @@ class Http2CustomSettingsTestBase : public Http2CodecImplTestFixture { class Http2CustomSettingsTest : public Http2CustomSettingsTestBase, public ::testing::TestWithParam< - ::testing::tuple> { + ::testing::tuple> { public: Http2CustomSettingsTest() : Http2CustomSettingsTestBase(::testing::get<0>(GetParam()), ::testing::get<1>(GetParam()), - ::testing::get<2>(GetParam()), ::testing::get<3>(GetParam())) {} + ::testing::get<2>(GetParam()), ::testing::get<3>(GetParam()), + ::testing::get<4>(GetParam())) {} }; INSTANTIATE_TEST_SUITE_P(Http2CodecImplTestEdgeSettings, Http2CustomSettingsTest, ::testing::Combine(HTTP2SETTINGS_DEFAULT_COMBINE, HTTP2SETTINGS_DEFAULT_COMBINE, ::testing::Bool(), - ::testing::Bool())); + ::testing::Bool(), ::testing::Bool())); // Validates that custom parameters (those which are not explicitly named in the // envoy::config::core::v3::Http2ProtocolOptions proto) are properly sent and processed by @@ -3223,6 +3264,220 @@ TEST_P(Http2CodecImplTest, ConnectTest) { driveToCompletion(); } +TEST_P(Http2CodecImplTest, ShouldWaitForDeferredBodyToProcessBeforeProcessingTrailers) { + // We must initialize before dtor, otherwise we'll touch uninitialized + // members in dtor. + initialize(); + + // Test only makes sense if we have defer processing enabled. + if (!defer_processing_backedup_streams_) { + return; + } + + TestRequestHeaderMapImpl request_headers; + HttpTestUtility::addDefaultHeaders(request_headers); + EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); + driveToCompletion(); + + // Force the stream to buffer data at the receiving codec. + server_->getStream(1)->readDisable(true); + Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); + request_encoder_->encodeData(body, false); + driveToCompletion(); + + // Now re-enable the stream, and try dispatching trailers to the server. + // It should buffer those trailers until the buffered data is processed + // from the callback below. + auto* process_buffered_data_callback = + new NiceMock(&server_connection_.dispatcher_); + EXPECT_FALSE(process_buffered_data_callback->enabled_); + + server_->getStream(1)->readDisable(false); + + EXPECT_TRUE(process_buffered_data_callback->enabled_); + + // Trailers should be buffered by the codec since there is unprocessed body. + // Hence we shouldn't invoke decodeTrailers yet. + EXPECT_CALL(request_decoder_, decodeTrailers_(_)).Times(0); + request_encoder_->encodeTrailers(TestRequestTrailerMapImpl{{"trailing", "header"}}); + driveToCompletion(); + + // Now invoke the deferred processing callback. + { + InSequence seq; + EXPECT_CALL(request_decoder_, decodeData(_, false)); + EXPECT_CALL(request_decoder_, decodeTrailers_(_)); + process_buffered_data_callback->invokeCallback(); + } +} + +TEST_P(Http2CodecImplTest, ShouldBufferDeferredBodyNoEndstream) { + // We must initialize before dtor, otherwise we'll touch uninitialized + // members in dtor. + initialize(); + + // Test only makes sense if we have defer processing enabled. + if (!defer_processing_backedup_streams_) { + return; + } + + TestRequestHeaderMapImpl request_headers; + HttpTestUtility::addDefaultHeaders(request_headers); + EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); + driveToCompletion(); + + // Force the stream to buffer data at the receiving codec. + server_->getStream(1)->readDisable(true); + Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); + request_encoder_->encodeData(body, false); + driveToCompletion(); + + // Now re-enable the stream, we should just flush the buffered data without + // end stream to the upstream. + auto* process_buffered_data_callback = + new NiceMock(&server_connection_.dispatcher_); + EXPECT_FALSE(process_buffered_data_callback->enabled_); + + server_->getStream(1)->readDisable(false); + + EXPECT_TRUE(process_buffered_data_callback->enabled_); + + // Now invoke the deferred processing callback. + { + InSequence seq; + EXPECT_CALL(request_decoder_, decodeData(_, false)); + process_buffered_data_callback->invokeCallback(); + } +} + +TEST_P(Http2CodecImplTest, ShouldBufferDeferredBodyWithEndStream) { + // We must initialize before dtor, otherwise we'll touch uninitialized + // members in dtor. + initialize(); + + // Test only makes sense if we have defer processing enabled. + if (!defer_processing_backedup_streams_) { + return; + } + + TestRequestHeaderMapImpl request_headers; + HttpTestUtility::addDefaultHeaders(request_headers); + EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); + driveToCompletion(); + + // Force the stream to buffer data at the receiving codec. + server_->getStream(1)->readDisable(true); + Buffer::OwnedImpl first_part(std::string(1024, 'a')); + request_encoder_->encodeData(first_part, false); + driveToCompletion(); + + // Finish request in subsequent call. + Buffer::OwnedImpl final_part(std::string(1024, 'a')); + request_encoder_->encodeData(final_part, true); + driveToCompletion(); + + auto* process_buffered_data_callback = + new NiceMock(&server_connection_.dispatcher_); + EXPECT_FALSE(process_buffered_data_callback->enabled_); + + server_->getStream(1)->readDisable(false); + + EXPECT_TRUE(process_buffered_data_callback->enabled_); + + // Now invoke the deferred processing callback. + { + InSequence seq; + EXPECT_CALL(request_decoder_, decodeData(_, true)); + process_buffered_data_callback->invokeCallback(); + } +} + +TEST_P(Http2CodecImplTest, + ShouldGracefullyHandleBufferedDataConsumedByNetworkEventInsteadOfCallback) { + // We must initialize before dtor, otherwise we'll touch uninitialized + // members in dtor. + initialize(); + + // Test only makes sense if we have defer processing enabled. + if (!defer_processing_backedup_streams_) { + return; + } + + TestRequestHeaderMapImpl request_headers; + HttpTestUtility::addDefaultHeaders(request_headers); + EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); + driveToCompletion(); + + // Force the stream to buffer data at the receiving codec. + server_->getStream(1)->readDisable(true); + Buffer::OwnedImpl body(std::string(1024 * 1024, 'a')); + request_encoder_->encodeData(body, false); + driveToCompletion(); + + auto* process_buffered_data_callback = + new NiceMock(&server_connection_.dispatcher_); + EXPECT_FALSE(process_buffered_data_callback->enabled_); + server_->getStream(1)->readDisable(false); + EXPECT_TRUE(process_buffered_data_callback->enabled_); + + // Scoop the buffered data instead by this call to encodeData. + EXPECT_CALL(request_decoder_, decodeData(_, true)); + request_encoder_->encodeData(body, true); + driveToCompletion(); + + // Deferred processing callback should have nothing to consume. + { + InSequence seq; + EXPECT_CALL(request_decoder_, decodeData(_, _)).Times(0); + EXPECT_CALL(request_decoder_, decodeTrailers_(_)).Times(0); + process_buffered_data_callback->invokeCallback(); + } +} + +TEST_P(Http2CodecImplTest, CanHandleMultipleBufferedDataProcessingOnAStream) { + // We must initialize before dtor, otherwise we'll touch uninitialized + // members in dtor. + initialize(); + + // Test only makes sense if we have defer processing enabled. + if (!defer_processing_backedup_streams_) { + return; + } + + TestRequestHeaderMapImpl request_headers; + HttpTestUtility::addDefaultHeaders(request_headers); + EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); + driveToCompletion(); + + auto* process_buffered_data_callback = + new NiceMock(&server_connection_.dispatcher_); + + for (int i = 0; i < 10; ++i) { + // Repeatedly back up, clearing with the deferred processing callback. + const bool end_stream = i == 9; + server_->getStream(1)->readDisable(true); + Buffer::OwnedImpl body(std::string(1024, 'a')); + request_encoder_->encodeData(body, end_stream); + driveToCompletion(); + + EXPECT_FALSE(process_buffered_data_callback->enabled_); + server_->getStream(1)->readDisable(false); + EXPECT_TRUE(process_buffered_data_callback->enabled_); + + { + InSequence seq; + EXPECT_CALL(request_decoder_, decodeData(_, end_stream)); + process_buffered_data_callback->invokeCallback(); + EXPECT_FALSE(process_buffered_data_callback->enabled_); + } + } +} + class TestNghttp2SessionFactory; // Test client for H/2 METADATA frame edge cases. @@ -3347,6 +3602,9 @@ class Http2CodecMetadataTest : public Http2CodecImplTestFixture, public ::testin Runtime::LoaderSingleton::getExisting()->mergeValues( {{"envoy.reloadable_features.http2_new_codec_wrapper", enable_new_codec_wrapper_ ? "true" : "false"}}); + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{std::string(Runtime::defer_processing_backedup_streams), + defer_processing_backedup_streams_ ? "true" : "false"}}); allow_metadata_ = true; http2OptionsFromTuple(client_http2_options_, client_settings_); http2OptionsFromTuple(server_http2_options_, server_settings_); diff --git a/test/config/utility.cc b/test/config/utility.cc index d2d9bacc285e..af8131b2dcd3 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -841,7 +841,7 @@ void ConfigHelper::configureUpstreamTls( }); } -void ConfigHelper::addRuntimeOverride(const std::string& key, const std::string& value) { +void ConfigHelper::addRuntimeOverride(absl::string_view key, absl::string_view value) { auto* static_layer = bootstrap_.mutable_layered_runtime()->mutable_layers(0)->mutable_static_layer(); (*static_layer->mutable_fields())[std::string(key)] = ValueUtil::stringValue(std::string(value)); diff --git a/test/config/utility.h b/test/config/utility.h index be4d0f1d042f..1aefcc8c62c1 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -322,7 +322,7 @@ class ConfigHelper { void skipPortUsageValidation() { skip_port_usage_validation_ = true; } // Add this key value pair to the static runtime. - void addRuntimeOverride(const std::string& key, const std::string& value); + void addRuntimeOverride(absl::string_view key, absl::string_view value); // Add typed_filter_metadata to the first listener. void addListenerTypedMetadata(absl::string_view key, ProtobufWkt::Any& packed_value); diff --git a/test/extensions/filters/http/fault/BUILD b/test/extensions/filters/http/fault/BUILD index 5c161d7eb673..428463ff15bc 100644 --- a/test/extensions/filters/http/fault/BUILD +++ b/test/extensions/filters/http/fault/BUILD @@ -54,6 +54,7 @@ envoy_extension_cc_test( name = "fault_filter_integration_test", srcs = ["fault_filter_integration_test.cc"], extension_names = ["envoy.filters.http.fault"], + shard_count = 2, deps = [ "//source/extensions/filters/http/fault:config", "//test/integration:http_protocol_integration_lib", diff --git a/test/extensions/filters/http/kill_request/BUILD b/test/extensions/filters/http/kill_request/BUILD index 98398e0bd613..1747fa2540cc 100644 --- a/test/extensions/filters/http/kill_request/BUILD +++ b/test/extensions/filters/http/kill_request/BUILD @@ -44,6 +44,7 @@ envoy_extension_cc_test( name = "kill_request_filter_integration_test", srcs = ["kill_request_filter_integration_test.cc"], extension_names = ["envoy.filters.http.kill_request"], + shard_count = 2, deps = [ "//source/extensions/filters/http/kill_request:kill_request_config", "//test/integration:http_protocol_integration_lib", diff --git a/test/extensions/filters/http/rbac/BUILD b/test/extensions/filters/http/rbac/BUILD index 0e2cf13fc8cc..9a1da74d8218 100644 --- a/test/extensions/filters/http/rbac/BUILD +++ b/test/extensions/filters/http/rbac/BUILD @@ -46,6 +46,7 @@ envoy_extension_cc_test( name = "rbac_filter_integration_test", srcs = ["rbac_filter_integration_test.cc"], extension_names = ["envoy.filters.http.rbac"], + shard_count = 2, deps = [ "//source/extensions/clusters/dynamic_forward_proxy:cluster", "//source/extensions/filters/http/dynamic_forward_proxy:config", diff --git a/test/integration/BUILD b/test/integration/BUILD index 27ad677a7867..801fc8b0470e 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -407,13 +407,14 @@ envoy_cc_test( srcs = [ "buffer_accounting_integration_test.cc", ], - shard_count = 2, + shard_count = 4, deps = [ ":base_overload_integration_test_lib", ":http_integration_lib", ":http_protocol_integration_lib", ":socket_interface_swap_lib", ":tracked_watermark_buffer_lib", + "//test/integration/filters:tee_filter_lib", "//test/mocks/http:http_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", @@ -548,7 +549,7 @@ 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 = 10, + shard_count = 16, deps = [ ":protocol_integration_test_lib", ], @@ -569,7 +570,7 @@ envoy_cc_test( srcs = [ "multiplexed_upstream_integration_test.cc", ], - shard_count = 2, + shard_count = 4, deps = [ ":http_protocol_integration_lib", "//source/common/http:header_map_lib", @@ -674,7 +675,7 @@ envoy_cc_test( srcs = ["idle_timeout_integration_test.cc"], # 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, + shard_count = 4, deps = [ ":http_protocol_integration_lib", "//test/integration/filters:backpressure_filter_config_lib", @@ -1005,6 +1006,7 @@ envoy_cc_test( srcs = [ "redirect_integration_test.cc", ], + shard_count = 2, deps = [ ":http_protocol_integration_lib", "//source/common/http:header_map_lib", @@ -1148,7 +1150,7 @@ envoy_cc_test_library( envoy_cc_test( name = "overload_integration_test", srcs = ["overload_integration_test.cc"], - shard_count = 2, + shard_count = 4, deps = [ ":base_overload_integration_test_lib", ":http_protocol_integration_lib", diff --git a/test/integration/buffer_accounting_integration_test.cc b/test/integration/buffer_accounting_integration_test.cc index d996dbdf601f..85ee8e20bb6d 100644 --- a/test/integration/buffer_accounting_integration_test.cc +++ b/test/integration/buffer_accounting_integration_test.cc @@ -9,6 +9,7 @@ #include "test/integration/autonomous_upstream.h" #include "test/integration/base_overload_integration_test.h" +#include "test/integration/filters/tee_filter.h" #include "test/integration/http_protocol_integration.h" #include "test/integration/tracked_watermark_buffer.h" #include "test/integration/utility.h" @@ -112,6 +113,9 @@ class Http2BufferWatermarksTest std::shared_ptr buffer_factory_; bool streamBufferAccounting() { return std::get<1>(GetParam()); } + bool deferProcessingBackedUpStreams() { + return Runtime::runtimeFeatureEnabled(Runtime::defer_processing_backedup_streams); + } std::string printAccounts() { std::stringstream stream; @@ -670,8 +674,15 @@ TEST_P(Http2OverloadManagerIntegrationTest, CanResetStreamIfEnvoyLevelStreamEnde upstream_request_for_response->encodeData(response_size, true); if (streamBufferAccounting()) { - // Wait for access log to know the Envoy level stream has been deleted. - EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("200")); + if (deferProcessingBackedUpStreams()) { + // Wait for an accumulation of data, as we cannot rely on the access log + // output since we're deferring the processing of the stream data. + EXPECT_TRUE(buffer_factory_->waitUntilTotalBufferedExceeds(10 * 10 * 1024)); + + } else { + // Wait for access log to know the Envoy level stream has been deleted. + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("200")); + } } // Set the pressure so the overload action kills the response if doing stream @@ -704,4 +715,263 @@ TEST_P(Http2OverloadManagerIntegrationTest, CanResetStreamIfEnvoyLevelStreamEnde } } +class Http2DeferredProcessingIntegrationTest : public Http2BufferWatermarksTest { +public: + Http2DeferredProcessingIntegrationTest() : registered_tee_factory_(tee_filter_factory_) { + config_helper_.prependFilter(R"EOF( + name: stream-tee-filter + )EOF"); + } + +protected: + StreamTeeFilterConfig tee_filter_factory_; + Registry::InjectFactory + registered_tee_factory_; +}; + +// We run with buffer accounting in order to verify the amount of data in the +// system. Buffer accounting isn't necessary for deferring http2 processing. +INSTANTIATE_TEST_SUITE_P( + IpVersions, Http2DeferredProcessingIntegrationTest, + testing::Combine(testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( + {Http::CodecType::HTTP2}, {FakeHttpConnection::Type::HTTP2})), + testing::Values(true), testing::Bool()), + protocolTestParamsAndBoolToString); + +TEST_P(Http2DeferredProcessingIntegrationTest, CanBufferInDownstreamCodec) { + config_helper_.setBufferLimits(1000, 1000); + initialize(); + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{std::string(Runtime::defer_processing_backedup_streams), "true"}}); + + // Stop writes to the upstream. + write_matcher_->setDestinationPort(fake_upstreams_[0]->localAddress()->ip()->port()); + write_matcher_->setWriteReturnsEgain(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto [request_encoder, response_decoder] = codec_client_->startRequest(default_request_headers_); + codec_client_->sendData(request_encoder, 1000, false); + // Wait for an upstream request to have our reach its buffer limit and read + // disable. + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_backed_up_total", 1); + test_server_->waitForCounterEq("http.config_test.downstream_flow_control_paused_reading_total", + 1); + + codec_client_->sendData(request_encoder, 1000, true); + + // Verify codec received but is buffered as we're still read disabled. + buffer_factory_->waitUntilTotalBufferedExceeds(2000); + test_server_->waitForCounterEq("http.config_test.downstream_flow_control_resumed_reading_total", + 0); + EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(0, [](const StreamTee& tee) { + absl::MutexLock l{&tee.mutex_}; + EXPECT_EQ(tee.request_body_.length(), 1000); + })); + + // Allow draining to the upstream, and complete the stream. + write_matcher_->setResumeWrites(); + + waitForNextUpstreamRequest(); + FakeStreamPtr upstream_request = std::move(upstream_request_); + upstream_request->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, true); + ASSERT_TRUE(response_decoder->waitForEndStream()); + ASSERT_TRUE(upstream_request->complete()); + test_server_->waitForCounterEq("http.config_test.downstream_flow_control_resumed_reading_total", + 1); +} + +TEST_P(Http2DeferredProcessingIntegrationTest, CanBufferInUpstreamCodec) { + config_helper_.setBufferLimits(1000, 1000); + initialize(); + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{std::string(Runtime::defer_processing_backedup_streams), "true"}}); + + // Stop writes to the downstream. + write_matcher_->setSourcePort(lookupPort("http")); + codec_client_ = makeHttpConnection(lookupPort("http")); + write_matcher_->setWriteReturnsEgain(); + + auto response_decoder = codec_client_->makeRequestWithBody(default_request_headers_, 1); + waitForNextUpstreamRequest(); + FakeStreamPtr upstream_request = std::move(upstream_request_); + upstream_request->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request->encodeData(1000, false); + + // Wait for an upstream response to have our reach its buffer limit and read + // disable. + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + + upstream_request->encodeData(500, false); + + // Verify codec received but is buffered as we're still read disabled. + buffer_factory_->waitUntilTotalBufferedExceeds(1500); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + 0); + EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(0, [](const StreamTee& tee) { + absl::MutexLock l{&tee.mutex_}; + EXPECT_EQ(tee.response_body_.length(), 1000); + })); + + // Allow draining to the downstream, and complete the stream. + write_matcher_->setResumeWrites(); + response_decoder->waitForBodyData(1500); + + upstream_request->encodeData(1, true); + ASSERT_TRUE(response_decoder->waitForEndStream()); + ASSERT_TRUE(upstream_request->complete()); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + 1); +} + +TEST_P(Http2DeferredProcessingIntegrationTest, CanDeferOnStreamCloseForUpstream) { + config_helper_.setBufferLimits(1000, 1000); + initialize(); + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{std::string(Runtime::defer_processing_backedup_streams), "true"}}); + + // Stop writes to the downstream. + write_matcher_->setSourcePort(lookupPort("http")); + codec_client_ = makeHttpConnection(lookupPort("http")); + write_matcher_->setWriteReturnsEgain(); + + auto response_decoder = codec_client_->makeRequestWithBody(default_request_headers_, 1); + waitForNextUpstreamRequest(); + FakeStreamPtr upstream_request = std::move(upstream_request_); + upstream_request->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request->encodeData(1000, false); + + // Wait for an upstream response to have our reach its buffer limit and read + // disable. + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + upstream_request->encodeData(500, true); + + // Verify codec received and has buffered onStreamClose for upstream as we're still read disabled. + buffer_factory_->waitUntilTotalBufferedExceeds(1500); + test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 1); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + 0); + EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(0, [](const StreamTee& tee) { + absl::MutexLock l{&tee.mutex_}; + EXPECT_EQ(tee.response_body_.length(), 1000); + })); + + // Allow draining to the downstream. + write_matcher_->setResumeWrites(); + + ASSERT_TRUE(response_decoder->waitForEndStream()); + ASSERT_TRUE(upstream_request->complete()); + test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 0); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + 1); +} + +TEST_P(Http2DeferredProcessingIntegrationTest, + ShouldCloseDeferredUpstreamOnStreamCloseIfLocalReply) { + config_helper_.setBufferLimits(9000, 9000); + initialize(); + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{std::string(Runtime::defer_processing_backedup_streams), "true"}}); + + // Stop writes to the downstream. + write_matcher_->setSourcePort(lookupPort("http")); + codec_client_ = makeHttpConnection(lookupPort("http")); + write_matcher_->setWriteReturnsEgain(); + + auto response_decoder = codec_client_->makeRequestWithBody(default_request_headers_, 1); + waitForNextUpstreamRequest(); + FakeStreamPtr upstream_request = std::move(upstream_request_); + upstream_request->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request->encodeData(9000, false); + + // Wait for an upstream response to have our reach its buffer limit and read + // disable. + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + + auto close_if_over_9000 = + [](StreamTee& tee, Http::StreamEncoderFilterCallbacks* encoder_cbs) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(tee.mutex_) -> Http::FilterDataStatus { + if (tee.response_body_.length() > 9000) { + encoder_cbs->sendLocalReply(Http::Code::InternalServerError, "Response size was over 9000!", + nullptr, absl::nullopt, ""); + return Http::FilterDataStatus::StopIterationNoBuffer; + } + return Http::FilterDataStatus::Continue; + }; + + EXPECT_TRUE(tee_filter_factory_.setEncodeDataCallback(0, close_if_over_9000)); + + upstream_request->encodeData(1, true); + + // Verify codec received and has buffered onStreamClose for upstream as we're still read disabled. + buffer_factory_->waitUntilTotalBufferedExceeds(9001); + test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 1); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + 0); + EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(0, [](const StreamTee& tee) { + absl::MutexLock l{&tee.mutex_}; + EXPECT_EQ(tee.response_body_.length(), 9000); + })); + + // Allow draining to the downstream, which should trigger a local reply. + write_matcher_->setResumeWrites(); + + ASSERT_TRUE(response_decoder->waitForReset()); + ASSERT_TRUE(upstream_request->complete()); + test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 0); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + 1); +} + +TEST_P(Http2DeferredProcessingIntegrationTest, + ShouldCloseDeferredUpstreamOnStreamCloseIfResetByDownstream) { + config_helper_.setBufferLimits(1000, 1000); + initialize(); + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{std::string(Runtime::defer_processing_backedup_streams), "true"}}); + + // Stop writes to the downstream. + write_matcher_->setSourcePort(lookupPort("http")); + codec_client_ = makeHttpConnection(lookupPort("http")); + write_matcher_->setWriteReturnsEgain(); + + auto [request_encoder, response_decoder] = codec_client_->startRequest(default_request_headers_); + codec_client_->sendData(request_encoder, 100, true); + + waitForNextUpstreamRequest(); + FakeStreamPtr upstream_request = std::move(upstream_request_); + + upstream_request->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request->encodeData(1000, false); + + // Wait for an upstream response to have our reach its buffer limit and read + // disable. + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_paused_reading_total", 1); + + upstream_request->encodeData(500, true); + ASSERT_TRUE(upstream_request->complete()); + + // Verify codec received and has buffered onStreamClose for upstream as we're still read disabled. + buffer_factory_->waitUntilTotalBufferedExceeds(1500); + test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 1); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + 0); + EXPECT_TRUE(tee_filter_factory_.inspectStreamTee(0, [](const StreamTee& tee) { + absl::MutexLock l{&tee.mutex_}; + EXPECT_EQ(tee.response_body_.length(), 1000); + })); + + // Downstream sends a RST, we should clean up the buffered upstream. + codec_client_->sendReset(request_encoder); + test_server_->waitForGaugeEq("cluster.cluster_0.http2.deferred_stream_close", 0); + // Resetting the upstream stream doesn't increment this count. + test_server_->waitForCounterEq("cluster.cluster_0.upstream_flow_control_resumed_reading_total", + 0); +} + } // namespace Envoy diff --git a/test/integration/filters/BUILD b/test/integration/filters/BUILD index e29fd05bd6e5..f495efca2a44 100644 --- a/test/integration/filters/BUILD +++ b/test/integration/filters/BUILD @@ -45,6 +45,27 @@ envoy_cc_test_library( ], ) +envoy_cc_test_library( + name = "tee_filter_lib", + srcs = [ + "tee_filter.cc", + ], + hdrs = [ + "tee_filter.h", + ], + external_deps = ["abseil_synchronization"], + deps = [ + "//envoy/http:filter_interface", + "//envoy/registry", + "//envoy/server:filter_config_interface", + "//source/common/buffer:buffer_lib", + "//source/common/common:minimal_logger_lib", + "//source/common/http:header_map_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", + "//test/extensions/filters/http/common:empty_http_filter_config_lib", + ], +) + envoy_cc_test_library( name = "local_reply_during_encoding_data_filter_lib", srcs = [ diff --git a/test/integration/filters/tee_filter.cc b/test/integration/filters/tee_filter.cc new file mode 100644 index 000000000000..8c6e5cf2d90c --- /dev/null +++ b/test/integration/filters/tee_filter.cc @@ -0,0 +1,85 @@ +#include "test/integration/filters/tee_filter.h" + +#include "envoy/registry/registry.h" + +#include "source/common/common/logger.h" +#include "source/common/http/header_map_impl.h" + +namespace Envoy { + +// A test filter that essentially tees the data flow through it. +class StreamTeeFilter : public Http::PassThroughFilter, public StreamTee { +public: + // Http::PassThroughFilter + Http::FilterDataStatus decodeData(Buffer::Instance& buffer, bool end_stream) override { + ENVOY_LOG_MISC(trace, "StreamTee decodeData {}", buffer.length()); + absl::MutexLock l{&mutex_}; + request_body_.add(buffer); + decode_end_stream_ = end_stream; + return Http::FilterDataStatus::Continue; + } + + Http::FilterTrailersStatus decodeTrailers(Http::RequestTrailerMap& request_trailers) override { + absl::MutexLock l{&mutex_}; + request_trailers_ = Http::createHeaderMap(request_trailers); + decode_end_stream_ = true; + return Http::FilterTrailersStatus::Continue; + } + + Http::FilterDataStatus encodeData(Buffer::Instance& buffer, bool end_stream) override { + ENVOY_LOG_MISC(trace, "StreamTee encodeData {}", buffer.length()); + absl::MutexLock l{&mutex_}; + response_body_.add(buffer); + encode_end_stream_ = end_stream; + if (on_encode_data_) { + return on_encode_data_(*this, encoder_callbacks_); + } + return Http::FilterDataStatus::Continue; + } + + Http::FilterTrailersStatus encodeTrailers(Http::ResponseTrailerMap& response_trailers) override { + absl::MutexLock l{&mutex_}; + response_trailers_ = Http::createHeaderMap(response_trailers); + encode_end_stream_ = true; + return Http::FilterTrailersStatus::Continue; + } +}; + +Http::FilterFactoryCb StreamTeeFilterConfig::createFilter(const std::string&, + Server::Configuration::FactoryContext&) { + return [this](Http::FilterChainFactoryCallbacks& callbacks) -> void { + auto filter = std::make_shared(); + // TODO(kbaichoo): support multiple streams. + current_tee_ = filter; + callbacks.addStreamFilter(std::move(filter)); + }; +} + +bool StreamTeeFilterConfig::inspectStreamTee(int /*stream_number*/, + std::function inspector) { + if (!current_tee_) { + ENVOY_LOG_MISC(warn, "No current stream_tee!"); + return false; + } + + // TODO(kbaichoo): support multiple streams. + inspector(*current_tee_); + return true; +} + +bool StreamTeeFilterConfig::setEncodeDataCallback( + int /*stream_number*/, + std::function + cb) { + if (!current_tee_) { + ENVOY_LOG_MISC(warn, "No current stream_tee!"); + return false; + } + + absl::MutexLock l{¤t_tee_->mutex_}; + current_tee_->on_encode_data_ = cb; + return true; +} + +} // namespace Envoy diff --git a/test/integration/filters/tee_filter.h b/test/integration/filters/tee_filter.h new file mode 100644 index 000000000000..3c1d8b697308 --- /dev/null +++ b/test/integration/filters/tee_filter.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include + +#include "envoy/http/filter.h" +#include "envoy/server/filter_config.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" + +#include "test/extensions/filters/http/common/empty_http_filter_config.h" + +#include "absl/synchronization/mutex.h" + +namespace Envoy { + +// Tees stream data. +struct StreamTee { + virtual ~StreamTee() = default; + mutable absl::Mutex mutex_; + Buffer::OwnedImpl request_body_ ABSL_GUARDED_BY(mutex_){}; + Buffer::OwnedImpl response_body_ ABSL_GUARDED_BY(mutex_){}; + bool decode_end_stream_ ABSL_GUARDED_BY(mutex_){false}; + bool encode_end_stream_ ABSL_GUARDED_BY(mutex_){false}; + Http::RequestHeaderMapPtr request_headers_ ABSL_GUARDED_BY(mutex_){nullptr}; + Http::RequestTrailerMapPtr request_trailers_ ABSL_GUARDED_BY(mutex_){nullptr}; + Http::ResponseHeaderMapPtr response_headers_ ABSL_GUARDED_BY(mutex_){nullptr}; + Http::ResponseTrailerMapPtr response_trailers_ ABSL_GUARDED_BY(mutex_){nullptr}; + + std::function + on_encode_data_ ABSL_GUARDED_BY(mutex_){nullptr}; +}; + +using StreamTeeSharedPtr = std::shared_ptr; + +// Inject a specific instance of this factory in order to leverage the same +// instance used by Envoy to inspect internally. +class StreamTeeFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig { +public: + StreamTeeFilterConfig() : EmptyHttpFilterConfig("stream-tee-filter") {} + + Http::FilterFactoryCb createFilter(const std::string&, + Server::Configuration::FactoryContext&) override; + bool inspectStreamTee(int /*stream_number*/, std::function inspector); + bool setEncodeDataCallback(int /*stream_number*/, + std::function + cb); + +private: + // TODO(kbaichoo): support multiple streams. + StreamTeeSharedPtr current_tee_; +}; + +} // namespace Envoy diff --git a/test/integration/http2_flood_integration_test.cc b/test/integration/http2_flood_integration_test.cc index d2ba01e1fdff..89553858d1f1 100644 --- a/test/integration/http2_flood_integration_test.cc +++ b/test/integration/http2_flood_integration_test.cc @@ -30,14 +30,20 @@ namespace Envoy { namespace { const uint32_t ControlFrameFloodLimit = 100; const uint32_t AllFrameFloodLimit = 1000; + +bool deferredProcessing(std::tuple params) { + return std::get<2>(params); +} + } // namespace std::string testParamsToString( - const ::testing::TestParamInfo> params) { + const ::testing::TestParamInfo> params) { const bool is_v4 = (std::get<0>(params.param) == Network::Address::IpVersion::v4); const bool http2_new_codec_wrapper = std::get<1>(params.param); - return absl::StrCat(is_v4 ? "IPv4" : "IPv6", - http2_new_codec_wrapper ? "WrappedHttp2" : "BareHttp2"); + return absl::StrCat( + is_v4 ? "IPv4" : "IPv6", http2_new_codec_wrapper ? "WrappedHttp2" : "BareHttp2", + deferredProcessing(params.param) ? "WithDeferredProcessing" : "NoDeferredProcessing"); } // It is important that the new socket interface is installed before any I/O activity starts and @@ -47,7 +53,7 @@ std::string testParamsToString( // Http2FrameIntegrationTest destructor completes. class Http2FloodMitigationTest : public SocketInterfaceSwap, - public testing::TestWithParam>, + public testing::TestWithParam>, public Http2RawFrameIntegrationTest { public: Http2FloodMitigationTest() : Http2RawFrameIntegrationTest(std::get<0>(GetParam())) { @@ -58,6 +64,8 @@ class Http2FloodMitigationTest const bool enable_new_wrapper = std::get<1>(GetParam()); config_helper_.addRuntimeOverride("envoy.reloadable_features.http2_new_codec_wrapper", enable_new_wrapper ? "true" : "false"); + config_helper_.addRuntimeOverride(Runtime::defer_processing_backedup_streams, + deferredProcessing(GetParam()) ? "true" : "false"); } protected: @@ -78,8 +86,8 @@ class Http2FloodMitigationTest INSTANTIATE_TEST_SUITE_P( IpVersions, Http2FloodMitigationTest, - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - testing::ValuesIn({false, true})), + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), ::testing::Bool(), + ::testing::Bool()), testParamsToString); void Http2FloodMitigationTest::initializeUpstreamFloodTest() { @@ -595,6 +603,15 @@ TEST_P(Http2FloodMitigationTest, Trailers) { // Verify flood detection by the WINDOW_UPDATE frame when a decoder filter is resuming reading from // the downstream via DecoderFilterBelowWriteBufferLowWatermark. TEST_P(Http2FloodMitigationTest, WindowUpdateOnLowWatermarkFlood) { + // This test depends on data flowing through a backed up stream eagerly (e.g. the + // backpressure-filter triggers above watermark when it receives headers from + // the downstream, and only goes below watermark if the response body has + // passed through the filter.). With defer processing of backed up streams however + // the data won't be eagerly processed as the stream is backed up. + // TODO(kbaichoo): Remove this test when removing this feature tag. + if (deferredProcessing(GetParam())) { + return; + } config_helper_.prependFilter(R"EOF( name: backpressure-filter )EOF"); diff --git a/test/integration/http_protocol_integration.cc b/test/integration/http_protocol_integration.cc index 2eff0470845d..73266ae93fe2 100644 --- a/test/integration/http_protocol_integration.cc +++ b/test/integration/http_protocol_integration.cc @@ -8,28 +8,39 @@ std::vector HttpProtocolIntegrationTest::getProtocolTest const std::vector& upstream_protocols) { std::vector ret; + const auto addHttp2TestParametersWithNewCodecWrapperOrDeferredProcessing = + [&ret](Network::Address::IpVersion ip_version, Http::CodecType downstream_protocol, + Http::CodecType upstream_protocol) { + ret.push_back(HttpProtocolTestParams{ip_version, downstream_protocol, upstream_protocol, + true, false}); + ret.push_back( + HttpProtocolTestParams{ip_version, downstream_protocol, upstream_protocol, true, true}); + ret.push_back(HttpProtocolTestParams{ip_version, downstream_protocol, upstream_protocol, + false, true}); + }; + for (auto ip_version : TestEnvironment::getIpVersionsForTest()) { for (auto downstream_protocol : downstream_protocols) { for (auto upstream_protocol : upstream_protocols) { #ifdef ENVOY_ENABLE_QUIC - ret.push_back( - HttpProtocolTestParams{ip_version, downstream_protocol, upstream_protocol, false}); + ret.push_back(HttpProtocolTestParams{ip_version, downstream_protocol, upstream_protocol, + false, false}); if (downstream_protocol == Http::CodecType::HTTP2 || upstream_protocol == Http::CodecType::HTTP2) { - ret.push_back( - HttpProtocolTestParams{ip_version, downstream_protocol, upstream_protocol, true}); + addHttp2TestParametersWithNewCodecWrapperOrDeferredProcessing( + ip_version, downstream_protocol, upstream_protocol); } #else if (downstream_protocol == Http::CodecType::HTTP3 || upstream_protocol == Http::CodecType::HTTP3) { ENVOY_LOG_MISC(warn, "Skipping HTTP/3 as support is compiled out"); } else { - ret.push_back( - HttpProtocolTestParams{ip_version, downstream_protocol, upstream_protocol, false}); + ret.push_back(HttpProtocolTestParams{ip_version, downstream_protocol, upstream_protocol, + false, false}); if (downstream_protocol == Http::CodecType::HTTP2 || upstream_protocol == Http::CodecType::HTTP2) { - ret.push_back( - HttpProtocolTestParams{ip_version, downstream_protocol, upstream_protocol, true}); + addHttp2TestParametersWithNewCodecWrapperOrDeferredProcessing( + ip_version, downstream_protocol, upstream_protocol); } } #endif @@ -68,7 +79,9 @@ std::string HttpProtocolIntegrationTest::protocolTestParamsToString( return absl::StrCat((params.param.version == Network::Address::IpVersion::v4 ? "IPv4_" : "IPv6_"), downstreamToString(params.param.downstream_protocol), upstreamToString(params.param.upstream_protocol), - params.param.http2_new_codec_wrapper ? "WrappedHttp2" : "BareHttp2"); + params.param.http2_new_codec_wrapper ? "WrappedHttp2" : "BareHttp2", + params.param.defer_processing_backedup_streams ? "WithDeferredProcessing" + : "NoDeferredProcessing"); } void HttpProtocolIntegrationTest::expectUpstreamBytesSentAndReceived( diff --git a/test/integration/http_protocol_integration.h b/test/integration/http_protocol_integration.h index d5bc1607ab6b..d288babeb2d9 100644 --- a/test/integration/http_protocol_integration.h +++ b/test/integration/http_protocol_integration.h @@ -11,6 +11,7 @@ struct HttpProtocolTestParams { Http::CodecType downstream_protocol; Http::CodecType upstream_protocol; bool http2_new_codec_wrapper; + bool defer_processing_backedup_streams; }; // Allows easy testing of Envoy code for HTTP/HTTP2 upstream/downstream. @@ -56,6 +57,9 @@ class HttpProtocolIntegrationTest : public testing::TestWithParambody().size()); } -// Very similar set-up to testRetry but with a 16k request the request will not +// Very similar set-up to testRetry but with a 65k request the request will not // be buffered and the 503 will be returned to the user. TEST_P(ProtocolIntegrationTest, RetryHittingBufferLimit) { config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream. diff --git a/test/mocks/http/stream.h b/test/mocks/http/stream.h index a9d27e58ddef..2d0356abf812 100644 --- a/test/mocks/http/stream.h +++ b/test/mocks/http/stream.h @@ -18,7 +18,7 @@ class MockStream : public Stream { MOCK_METHOD(void, resetStream, (StreamResetReason reason)); MOCK_METHOD(void, readDisable, (bool disable)); MOCK_METHOD(void, setWriteBufferWatermarks, (uint32_t)); - MOCK_METHOD(uint32_t, bufferLimit, ()); + MOCK_METHOD(uint32_t, bufferLimit, (), (const)); MOCK_METHOD(const Network::Address::InstanceConstSharedPtr&, connectionLocalAddress, ()); MOCK_METHOD(void, setFlushTimeout, (std::chrono::milliseconds timeout)); MOCK_METHOD(void, setAccount, (Buffer::BufferMemoryAccountSharedPtr)); From d3bd9bbef64abbf1c02fe91231eac8841901131d Mon Sep 17 00:00:00 2001 From: danzh Date: Mon, 7 Mar 2022 20:19:02 -0500 Subject: [PATCH 21/68] conn pool: refactor connecting capacity accounting (#20125) Refactor decr|incrConnectingAndConnectedStreamCapacity() to take into consideration whether the client is connecting or not while updating cluster and conn pool connecting stream capacity. Also replace some call sites of effectiveConcurrentStreamLimit() with currentUnusedCapacity() where the client is connecting because that's when these two are the same. This change is preparing for upcoming 0-RTT support in #20167 which allows connecting client to open new streams and transit to other non-CONNECTING state. Signed-off-by: Dan Zhang --- source/common/conn_pool/conn_pool_base.cc | 60 ++++++++++++-------- source/common/conn_pool/conn_pool_base.h | 16 ++---- source/common/http/http3/conn_pool.h | 20 +++---- source/common/tcp/conn_pool.h | 2 +- test/common/conn_pool/conn_pool_base_test.cc | 2 +- 5 files changed, 51 insertions(+), 49 deletions(-) diff --git a/source/common/conn_pool/conn_pool_base.cc b/source/common/conn_pool/conn_pool_base.cc index 578b1a7d2b77..e1e5173cf88b 100644 --- a/source/common/conn_pool/conn_pool_base.cc +++ b/source/common/conn_pool/conn_pool_base.cc @@ -13,7 +13,7 @@ namespace { [[maybe_unused]] ssize_t connectingCapacity(const std::list& connecting_clients) { ssize_t ret = 0; for (const auto& client : connecting_clients) { - ret += client->effectiveConcurrentStreamLimit(); + ret += client->currentUnusedCapacity(); } return ret; } @@ -150,11 +150,10 @@ ConnPoolImplBase::tryCreateNewConnection(float global_preconnect_ratio) { } ASSERT(client->state() == ActiveClient::State::CONNECTING); ASSERT(std::numeric_limits::max() - connecting_stream_capacity_ >= - client->effectiveConcurrentStreamLimit()); + static_cast(client->currentUnusedCapacity())); ASSERT(client->real_host_description_); // Increase the connecting capacity to reflect the streams this connection can serve. - state_.incrConnectingAndConnectedStreamCapacity(client->effectiveConcurrentStreamLimit()); - connecting_stream_capacity_ += client->effectiveConcurrentStreamLimit(); + incrConnectingAndConnectedStreamCapacity(client->currentUnusedCapacity(), *client); LinkedList::moveIntoList(std::move(client), owningList(client->state())); return can_create_connection ? ConnectionResult::CreatedNewConnection : ConnectionResult::CreatedButRateLimited; @@ -192,7 +191,7 @@ void ConnPoolImplBase::attachStreamToClient(Envoy::ConnectionPool::ActiveClient& // Decrement the capacity, as there's one less stream available for serving. // For HTTP/3, the capacity is updated in newStreamEncoder. if (trackStreamCapacity()) { - state_.decrConnectingAndConnectedStreamCapacity(1); + decrConnectingAndConnectedStreamCapacity(1, client); } // Track the new active stream. state_.incrActiveStreams(1); @@ -227,7 +226,7 @@ void ConnPoolImplBase::onStreamClosed(Envoy::ConnectionPool::ActiveClient& clien // to avoid overflow. bool negative_capacity = client.concurrent_stream_limit_ < client.numActiveStreams() + 1; if (negative_capacity || limited_by_concurrency) { - state_.incrConnectingAndConnectedStreamCapacity(1); + incrConnectingAndConnectedStreamCapacity(1, client); } } if (client.state() == ActiveClient::State::DRAINING && client.numActiveStreams() == 0) { @@ -425,19 +424,14 @@ void ConnPoolImplBase::checkForIdleAndCloseIdleConnsIfDraining() { void ConnPoolImplBase::onConnectionEvent(ActiveClient& client, absl::string_view failure_reason, Network::ConnectionEvent event) { - if (client.state() == ActiveClient::State::CONNECTING) { - ASSERT(connecting_stream_capacity_ >= client.effectiveConcurrentStreamLimit()); - connecting_stream_capacity_ -= client.effectiveConcurrentStreamLimit(); - } - if (client.connect_timer_) { + ASSERT(!client.has_handshake_completed_); client.connect_timer_->disableTimer(); client.connect_timer_.reset(); } - if (event == Network::ConnectionEvent::RemoteClose || event == Network::ConnectionEvent::LocalClose) { - state_.decrConnectingAndConnectedStreamCapacity(client.currentUnusedCapacity()); + decrConnectingAndConnectedStreamCapacity(client.currentUnusedCapacity(), client); // Make sure that onStreamClosed won't double count. client.remaining_streams_ = 0; // The client died. @@ -449,7 +443,8 @@ void ConnPoolImplBase::onConnectionEvent(ActiveClient& client, absl::string_view Envoy::Upstream::reportUpstreamCxDestroyActiveRequest(host_, event); } - if (client.state() == ActiveClient::State::CONNECTING) { + if (!client.hasHandshakeCompleted()) { + client.has_handshake_completed_ = true; host_->cluster().stats().upstream_cx_connect_fail_.inc(); host_->stats().cx_connect_fail_.inc(); @@ -510,6 +505,9 @@ void ConnPoolImplBase::onConnectionEvent(ActiveClient& client, absl::string_view tryCreateNewConnections(); } } else if (event == Network::ConnectionEvent::Connected) { + ASSERT(connecting_stream_capacity_ >= client.currentUnusedCapacity()); + connecting_stream_capacity_ -= client.currentUnusedCapacity(); + client.has_handshake_completed_ = true; client.conn_connect_ms_->complete(); client.conn_connect_ms_.reset(); ASSERT(client.state() == ActiveClient::State::CONNECTING); @@ -568,8 +566,7 @@ void ConnPoolImplBase::purgePendingStreams( } bool ConnPoolImplBase::connectingConnectionIsExcess() const { - ASSERT(connecting_stream_capacity_ >= - connecting_clients_.front()->effectiveConcurrentStreamLimit()); + ASSERT(connecting_stream_capacity_ >= connecting_clients_.front()->currentUnusedCapacity()); // If perUpstreamPreconnectRatio is one, this simplifies to checking if there would still be // sufficient connecting stream capacity to serve all pending streams if the most recent client // were removed from the picture. @@ -578,8 +575,8 @@ bool ConnPoolImplBase::connectingConnectionIsExcess() const { // streams and active streams, and makes sure the connecting capacity would still be sufficient to // serve that even with the most recent client removed. return (pending_streams_.size() + num_active_streams_) * perUpstreamPreconnectRatio() <= - (connecting_stream_capacity_ - - connecting_clients_.front()->effectiveConcurrentStreamLimit() + num_active_streams_); + (connecting_stream_capacity_ - connecting_clients_.front()->currentUnusedCapacity() + + num_active_streams_); } void ConnPoolImplBase::onPendingStreamCancel(PendingStream& stream, @@ -606,6 +603,25 @@ void ConnPoolImplBase::onPendingStreamCancel(PendingStream& stream, checkForIdleAndCloseIdleConnsIfDraining(); } +void ConnPoolImplBase::decrConnectingAndConnectedStreamCapacity(uint32_t delta, + ActiveClient& client) { + state_.decrConnectingAndConnectedStreamCapacity(delta); + if (!client.hasHandshakeCompleted()) { + // If still doing handshake, it is contributing to the local connecting stream capacity. Update + // the capacity as well. + ASSERT(connecting_stream_capacity_ >= delta); + connecting_stream_capacity_ -= delta; + } +} + +void ConnPoolImplBase::incrConnectingAndConnectedStreamCapacity(uint32_t delta, + ActiveClient& client) { + state_.incrConnectingAndConnectedStreamCapacity(delta); + if (!client.hasHandshakeCompleted()) { + connecting_stream_capacity_ += delta; + } +} + namespace { // Translate zero to UINT64_MAX so that the zero/unlimited case doesn't // have to be handled specially. @@ -685,13 +701,7 @@ void ActiveClient::drain() { if (currentUnusedCapacity() <= 0) { return; } - if (state() == ActiveClient::State::CONNECTING) { - // If connecting, update both the cluster capacity and the local connecting - // capacity. - parent_.decrConnectingAndConnectedStreamCapacity(currentUnusedCapacity()); - } else { - parent_.state().decrConnectingAndConnectedStreamCapacity(currentUnusedCapacity()); - } + parent_.decrConnectingAndConnectedStreamCapacity(currentUnusedCapacity(), *this); remaining_streams_ = 0; } diff --git a/source/common/conn_pool/conn_pool_base.h b/source/common/conn_pool/conn_pool_base.h index d475ecf0988c..d870b1b6f96b 100644 --- a/source/common/conn_pool/conn_pool_base.h +++ b/source/common/conn_pool/conn_pool_base.h @@ -102,6 +102,8 @@ class ActiveClient : public LinkedObject, // Sets the remaining streams to 0, and updates pool and cluster capacity. virtual void drain(); + virtual bool hasHandshakeCompleted() const { return state_ != State::CONNECTING; } + ConnPoolImplBase& parent_; // The count of remaining streams allowed for this connection. // This will start out as the total number of streams per connection if capped @@ -124,6 +126,8 @@ class ActiveClient : public LinkedObject, Event::TimerPtr connection_duration_timer_; bool resources_released_{false}; bool timed_out_{false}; + // TODO(danzh) remove this once http codec exposes the handshake state for h3. + bool has_handshake_completed_{false}; private: State state_{State::CONNECTING}; @@ -275,16 +279,8 @@ class ConnPoolImplBase : protected Logger::Loggable { } Upstream::ClusterConnectivityState& state() { return state_; } - void decrConnectingAndConnectedStreamCapacity(uint32_t delta) { - state_.decrConnectingAndConnectedStreamCapacity(delta); - ASSERT(connecting_stream_capacity_ >= delta); - connecting_stream_capacity_ -= delta; - } - - void incrConnectingAndConnectedStreamCapacity(uint32_t delta) { - state_.incrConnectingAndConnectedStreamCapacity(delta); - connecting_stream_capacity_ += delta; - } + void decrConnectingAndConnectedStreamCapacity(uint32_t delta, ActiveClient& client); + void incrConnectingAndConnectedStreamCapacity(uint32_t delta, ActiveClient& client); // Called when an upstream is ready to serve pending streams. void onUpstreamReady(); diff --git a/source/common/http/http3/conn_pool.h b/source/common/http/http3/conn_pool.h index 5dfd0dec17f2..1cf5c356f8f3 100644 --- a/source/common/http/http3/conn_pool.h +++ b/source/common/http/http3/conn_pool.h @@ -53,6 +53,10 @@ class ActiveClient : public MultiplexedActiveClientBase { return std::min(quiche_capacity_, effectiveConcurrentStreamLimit()); } + // Overridden to return true as long as the client is doing handshake even when it is ready for + // early data streams. + bool hasHandshakeCompleted() const override { return has_handshake_completed_; } + void updateCapacity(uint64_t new_quiche_capacity) { // Each time we update the capacity make sure to reflect the update in the // connection pool. @@ -66,18 +70,10 @@ class ActiveClient : public MultiplexedActiveClientBase { quiche_capacity_ = new_quiche_capacity; uint64_t new_capacity = currentUnusedCapacity(); - if (connect_timer_) { - if (new_capacity < old_capacity) { - parent_.decrConnectingAndConnectedStreamCapacity(old_capacity - new_capacity); - } else if (old_capacity < new_capacity) { - parent_.incrConnectingAndConnectedStreamCapacity(new_capacity - old_capacity); - } - } else { - if (new_capacity < old_capacity) { - parent_.decrClusterStreamCapacity(old_capacity - new_capacity); - } else if (old_capacity < new_capacity) { - parent_.incrClusterStreamCapacity(new_capacity - old_capacity); - } + if (new_capacity < old_capacity) { + parent_.decrConnectingAndConnectedStreamCapacity(old_capacity - new_capacity, *this); + } else if (old_capacity < new_capacity) { + parent_.incrConnectingAndConnectedStreamCapacity(new_capacity - old_capacity, *this); } } diff --git a/source/common/tcp/conn_pool.h b/source/common/tcp/conn_pool.h index 58eb93e12df7..f9a8885591a3 100644 --- a/source/common/tcp/conn_pool.h +++ b/source/common/tcp/conn_pool.h @@ -164,7 +164,7 @@ class ConnPoolImpl : public Envoy::ConnectionPool::ConnPoolImplBase, connecting_client->remaining_streams_ = 1; if (connecting_client->effectiveConcurrentStreamLimit() < old_limit) { decrConnectingAndConnectedStreamCapacity( - old_limit - connecting_client->effectiveConcurrentStreamLimit()); + old_limit - connecting_client->effectiveConcurrentStreamLimit(), *connecting_client); } } } diff --git a/test/common/conn_pool/conn_pool_base_test.cc b/test/common/conn_pool/conn_pool_base_test.cc index 0faa7a06599e..2d5412a60a5a 100644 --- a/test/common/conn_pool/conn_pool_base_test.cc +++ b/test/common/conn_pool/conn_pool_base_test.cc @@ -431,7 +431,7 @@ TEST_F(ConnPoolImplDispatcherBaseTest, NoAvailableStreams) { stream_limit_ = 1; newConnectingClient(); clients_.back()->capacity_override_ = 0; - pool_.decrClusterStreamCapacity(stream_limit_); + pool_.decrConnectingAndConnectedStreamCapacity(stream_limit_, *clients_.back()); // Make sure that when the connected event is raised, there is no call to // onPoolReady, and the client is marked as busy. From 153c8255f7dc0ff64799d8a74a7bf49cf840d692 Mon Sep 17 00:00:00 2001 From: huntsman90 Date: Tue, 8 Mar 2022 07:50:52 -0800 Subject: [PATCH 22/68] redis: fix hostname resolution bug introduced in #19541 (#20217) Signed-off-by: Sai Teja Duthuluri --- .../clusters/redis/redis_cluster.cc | 8 +- .../clusters/redis/redis_cluster_test.cc | 78 +++++++++++++++++++ 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/source/extensions/clusters/redis/redis_cluster.cc b/source/extensions/clusters/redis/redis_cluster.cc index fb482bd7666c..4caa7cbba16e 100644 --- a/source/extensions/clusters/redis/redis_cluster.cc +++ b/source/extensions/clusters/redis/redis_cluster.cc @@ -340,8 +340,8 @@ void RedisCluster::RedisDiscoverySession::resolveClusterHostnames( parent_.dns_resolver_->resolve( slot.primary_hostname_, parent_.dns_lookup_family_, [this, slot_idx, slots, - &hostname_resolution_required_cnt](Network::DnsResolver::ResolutionStatus status, - std::list&& response) -> void { + hostname_resolution_required_cnt](Network::DnsResolver::ResolutionStatus status, + std::list&& response) -> void { auto& slot = (*slots)[slot_idx]; ENVOY_LOG(debug, "async DNS resolution complete for {}", slot.primary_hostname_); updateDnsStats(status, response.empty()); @@ -393,8 +393,8 @@ void RedisCluster::RedisDiscoverySession::resolveReplicas( parent_.dns_resolver_->resolve( replica.first, parent_.dns_lookup_family_, [this, index, slots, replica_idx, - &hostname_resolution_required_cnt](Network::DnsResolver::ResolutionStatus status, - std::list&& response) -> void { + hostname_resolution_required_cnt](Network::DnsResolver::ResolutionStatus status, + std::list&& response) -> void { auto& slot = (*slots)[index]; auto& replica = slot.replicas_to_resolve_[replica_idx]; ENVOY_LOG(debug, "async DNS resolution complete for {}", replica.first); diff --git a/test/extensions/clusters/redis/redis_cluster_test.cc b/test/extensions/clusters/redis/redis_cluster_test.cc index b9eda4278aa8..4019a7611484 100644 --- a/test/extensions/clusters/redis/redis_cluster_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_test.cc @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "envoy/common/callback.h" @@ -36,6 +37,7 @@ using testing::Eq; using testing::NiceMock; using testing::Ref; using testing::Return; +using testing::SaveArg; namespace Envoy { namespace Extensions { @@ -400,6 +402,50 @@ class RedisClusterTest : public testing::Test, return response; } + NetworkFilters::Common::Redis::RespValuePtr + twoSlotsPrimariesHostnames(const std::string& primary1, const std::string& primary2, + int64_t port) const { + std::vector primary_1(2); + primary_1[0].type(NetworkFilters::Common::Redis::RespType::BulkString); + primary_1[0].asString() = primary1; + primary_1[1].type(NetworkFilters::Common::Redis::RespType::Integer); + primary_1[1].asInteger() = port; + + std::vector primary_2(2); + primary_2[0].type(NetworkFilters::Common::Redis::RespType::BulkString); + primary_2[0].asString() = primary2; + primary_2[1].type(NetworkFilters::Common::Redis::RespType::Integer); + primary_2[1].asInteger() = port; + + std::vector slot_1(3); + slot_1[0].type(NetworkFilters::Common::Redis::RespType::Integer); + slot_1[0].asInteger() = 0; + slot_1[1].type(NetworkFilters::Common::Redis::RespType::Integer); + slot_1[1].asInteger() = 9999; + slot_1[2].type(NetworkFilters::Common::Redis::RespType::Array); + slot_1[2].asArray().swap(primary_1); + + std::vector slot_2(3); + slot_2[0].type(NetworkFilters::Common::Redis::RespType::Integer); + slot_2[0].asInteger() = 10000; + slot_2[1].type(NetworkFilters::Common::Redis::RespType::Integer); + slot_2[1].asInteger() = 16383; + slot_2[2].type(NetworkFilters::Common::Redis::RespType::Array); + slot_2[2].asArray().swap(primary_2); + + std::vector slots(2); + slots[0].type(NetworkFilters::Common::Redis::RespType::Array); + slots[0].asArray().swap(slot_1); + slots[1].type(NetworkFilters::Common::Redis::RespType::Array); + slots[1].asArray().swap(slot_2); + + NetworkFilters::Common::Redis::RespValuePtr response( + new NetworkFilters::Common::Redis::RespValue()); + response->type(NetworkFilters::Common::Redis::RespType::Array); + response->asArray().swap(slots); + return response; + } + NetworkFilters::Common::Redis::RespValue createStringField(bool is_correct_type, const std::string& correct_value) const { NetworkFilters::Common::Redis::RespValue respValue; @@ -778,6 +824,38 @@ TEST_F(RedisClusterTest, AddressAsHostname) { EXPECT_EQ(0U, cluster_->info()->stats().update_failure_.value()); } +TEST_F(RedisClusterTest, AddressAsHostnameParallelResolution) { + // This test specifically ensures that DNS resolution of different hostnames running parallel + // works as expected. + setupFromV3Yaml(BasicConfig); + const std::list resolved_addresses{"127.0.0.1", "127.0.0.2"}; + expectResolveDiscovery(Network::DnsLookupFamily::V4Only, "foo.bar.com", resolved_addresses); + + Network::DnsResolver::ResolveCb primary1_resolve_cb; + Network::DnsResolver::ResolveCb primary2_resolve_cb; + EXPECT_CALL(*dns_resolver_, resolve("primary1.com", Network::DnsLookupFamily::V4Only, _)) + .WillOnce(DoAll(SaveArg<2>(&primary1_resolve_cb), Return(&dns_resolver_->active_query_))); + EXPECT_CALL(*dns_resolver_, resolve("primary2.com", Network::DnsLookupFamily::V4Only, _)) + .WillOnce(DoAll(SaveArg<2>(&primary2_resolve_cb), Return(&dns_resolver_->active_query_))); + + expectRedisResolve(true); + + EXPECT_CALL(membership_updated_, ready()); + EXPECT_CALL(initialized_, ready()); + EXPECT_CALL(*cluster_callback_, onClusterSlotUpdate(_, _)); + cluster_->initialize([&]() -> void { initialized_.ready(); }); + expectClusterSlotResponse(twoSlotsPrimariesHostnames("primary1.com", "primary2.com", 22120)); + primary1_resolve_cb(Network::DnsResolver::ResolutionStatus::Success, + TestUtility::makeDnsResponse(std::list{"127.0.1.1"})); + primary2_resolve_cb(Network::DnsResolver::ResolutionStatus::Success, + TestUtility::makeDnsResponse(std::list{"127.0.1.2"})); + expectHealthyHosts(std::list({ + "127.0.1.1:22120", + "127.0.1.2:22120", + })); + EXPECT_EQ(0U, cluster_->info()->stats().update_failure_.value()); +} + TEST_F(RedisClusterTest, AddressAsHostnameFailure) { setupFromV3Yaml(BasicConfig); const std::list resolved_addresses{"127.0.0.1", "127.0.0.2"}; From 1e20ccd8687f4198de6300091162837b6516d43a Mon Sep 17 00:00:00 2001 From: xuhj Date: Tue, 8 Mar 2022 23:51:46 +0800 Subject: [PATCH 23/68] address: reuse InitHelper for Ipv6Instance (#20226) Signed-off-by: He Jie Xu --- source/common/network/address_impl.cc | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/source/common/network/address_impl.cc b/source/common/network/address_impl.cc index 56f3e0af9e49..330735fc100c 100644 --- a/source/common/network/address_impl.cc +++ b/source/common/network/address_impl.cc @@ -262,19 +262,18 @@ Ipv6Instance::Ipv6Instance(const std::string& address, uint32_t port, const SocketInterface* sock_interface, bool v6only) : InstanceBase(Type::Ip, sockInterfaceOrDefault(sock_interface)) { throwOnError(validateProtocolSupported()); - ip_.ipv6_.address_.sin6_family = AF_INET6; - ip_.ipv6_.address_.sin6_port = htons(port); + sockaddr_in6 addr_in; + memset(&addr_in, 0, sizeof(addr_in)); + addr_in.sin6_family = AF_INET6; + addr_in.sin6_port = htons(port); if (!address.empty()) { - if (1 != inet_pton(AF_INET6, address.c_str(), &ip_.ipv6_.address_.sin6_addr)) { + if (1 != inet_pton(AF_INET6, address.c_str(), &addr_in.sin6_addr)) { throw EnvoyException(fmt::format("invalid ipv6 address '{}'", address)); } } else { - ip_.ipv6_.address_.sin6_addr = in6addr_any; + addr_in.sin6_addr = in6addr_any; } - // Just in case address is in a non-canonical format, format from network address. - ip_.friendly_address_ = ip_.ipv6_.makeFriendlyAddress(); - friendly_name_ = fmt::format("[{}]:{}", ip_.friendly_address_, ip_.port()); - ip_.ipv6_.v6only_ = v6only; + initHelper(addr_in, v6only); } Ipv6Instance::Ipv6Instance(uint32_t port, const SocketInterface* sock_interface) From 4abc226c891ef0635340695df363bf2826982807 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 9 Mar 2022 02:31:48 +0000 Subject: [PATCH 24/68] ci: Use upstream `envoy.code.check` (#19737) This PR updates CI to use an upstream tool that integrates 4 of the current format CI checks: - flake8 - glint - shellcheck - yapf The tool can be run with: $ bazel run //tools/code:check # -- -c glint shellcheck python_yapf python_flake8 The -- --fix flag works for yapf only Also worth mentioning is that the following will only check changes against eg main (commits/branches/etc should work) $ bazel run //tools/code:check -- -s main This should allow us to use the tool in git hooks once enough of the checks have been integrated We can add more of the format checks to the tool after There are also various other planned improvements See https://github.com/envoyproxy/pytooling/milestones for general roadmap I would also like to rationalize the format check part of CI as we go (the pytest/tooling ci jobs can be removed fairly imminently) As the new tool produces quite a bit of logging ive had to make it run first - so as it doesnt bury the logs from other checkers For that reason, i added a message at the bottom of the log output to advise searching above A failing check run can be seen here: https://dev.azure.com/cncf/envoy/_build/results?buildId=103005&view=logs&j=c5dd2866-6ab3-5f3c-3a44-4cef0ec909b5&t=a9eb66d6-8944-5769-b3f7-476949dadcb8&l=7560 https://dev.azure.com/cncf/envoy/_build/results?buildId=103005&view=logs&j=c5dd2866-6ab3-5f3c-3a44-4cef0ec909b5&t=a9eb66d6-8944-5769-b3f7-476949dadcb8&l=7433 https://dev.azure.com/cncf/envoy/_build/results?buildId=103005&view=logs&j=c5dd2866-6ab3-5f3c-3a44-4cef0ec909b5&t=a9eb66d6-8944-5769-b3f7-476949dadcb8&l=7292 https://dev.azure.com/cncf/envoy/_build/results?buildId=103005&view=logs&j=c5dd2866-6ab3-5f3c-3a44-4cef0ec909b5&t=a9eb66d6-8944-5769-b3f7-476949dadcb8&l=124 https://dev.azure.com/cncf/envoy/_build/results?buildId=103005&view=logs&j=c5dd2866-6ab3-5f3c-3a44-4cef0ec909b5&t=a9eb66d6-8944-5769-b3f7-476949dadcb8&l=251 https://dev.azure.com/cncf/envoy/_build/results?buildId=103005&view=logs&j=c5dd2866-6ab3-5f3c-3a44-4cef0ec909b5&t=a9eb66d6-8944-5769-b3f7-476949dadcb8&l=7050 In addition to adding the envoy.code.check tool this PR also brings in newer versions of core libs that have been fixed/optimized/cleaned up significantly in this release release info/milestone for this PR https://github.com/envoyproxy/pytooling/milestone/1 https://github.com/envoyproxy/pytooling/releases/tag/2022-03-04.0 Next milestone is the next iteration of the dep.checker - mostly cleanups/fixes i think https://github.com/envoyproxy/pytooling/milestone/2 Signed-off-by: Ryan Northey --- .flake8 | 2 +- ci/format_pre.sh | 21 ++---- tools/base/requirements.in | 14 ++-- tools/base/requirements.txt | 63 ++++++++-------- tools/code/BUILD | 13 ++++ tools/code_format/BUILD | 11 --- tools/code_format/check_shellcheck_format.sh | 59 --------------- tools/code_format/glint.sh | 75 -------------------- tools/code_format/python_check.py | 55 -------------- 9 files changed, 62 insertions(+), 251 deletions(-) create mode 100644 tools/code/BUILD delete mode 100755 tools/code_format/check_shellcheck_format.sh delete mode 100755 tools/code_format/glint.sh delete mode 100755 tools/code_format/python_check.py diff --git a/.flake8 b/.flake8 index 691d9ad0460a..d4552574782e 100644 --- a/.flake8 +++ b/.flake8 @@ -5,4 +5,4 @@ ignore = W503,W504,E121,E126,E241,E125,E127,E129,E251,E265,E303,E306,E402,E501,E502,E711,E713,E722,E741,F523,F541,F841,N803,N806,N817,W605 # TODO(phlax): exclude less -exclude = build_docs,.git,generated,test,examples,venv +exclude = build_docs,.git,generated,test,examples,venv,tools/dev diff --git a/ci/format_pre.sh b/ci/format_pre.sh index 604ba166acad..6c8119c5b2d2 100755 --- a/ci/format_pre.sh +++ b/ci/format_pre.sh @@ -26,12 +26,12 @@ trap_errors () { FAILED+=(" > ${sub}@ ${file} :${line}") else FAILED+=("${sub}@ ${file} :${line}${command}") - if [[ "$CURRENT" == "glint" ]]; then + if [[ "$CURRENT" == "check" ]]; then + # shellcheck disable=SC2016 FAILED+=( - " Please fix your editor to ensure:" - " - no trailing whitespace" - " - no mixed tabs/spaces" - " - all files end with a newline") + "" + ' *Code formatting check failed*: please search above logs for `CodeChecker ERROR`' + "") fi fi ((frame++)) @@ -42,19 +42,12 @@ trap_errors () { trap trap_errors ERR trap exit 1 INT -# TODO: move these to bazel -CURRENT=glint -"${ENVOY_SRCDIR}"/tools/code_format/glint.sh - -CURRENT=shellcheck -"${ENVOY_SRCDIR}"/tools/code_format/check_shellcheck_format.sh check +CURRENT=check +time bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/code:check CURRENT=configs bazel run "${BAZEL_BUILD_OPTIONS[@]}" //configs:example_configs_validation -CURRENT=python -bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/code_format:python_check -- --diff-file="$DIFF_OUTPUT" -werror --fix - CURRENT=extensions bazel run "${BAZEL_BUILD_OPTIONS[@]}" //tools/extensions:extensions_check diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 8c43d2a74468..d4ef73dd4c2a 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -2,22 +2,22 @@ abstracts>=0.0.12 aio.api.bazel aio.api.github>=0.0.5 aio.api.nist -aio.core>=0.5.8 -aio.run.checker>=0.3.5 -aio.run.runner>=0.2.6 +aio.core>=0.8.1 +aio.run.checker>=0.5.1 +aio.run.runner>=0.3.2 colorama coloredlogs coverage envoy.base.utils>=0.1.0 -envoy.code_format.python_check>=0.0.8 +envoy.code.check envoy.dependency.check>=0.0.5 envoy.dependency.pip_check>=0.1.2 envoy.distribution.release>=0.0.7 envoy.distribution.repo>=0.0.5 envoy.distribution.verify>=0.0.8 -envoy.docs.sphinx-runner>=0.0.8 -envoy.gpg.identity>=0.0.6 -envoy.gpg.sign>=0.0.11 +envoy.docs.sphinx-runner>=0.1.0 +envoy.gpg.identity>=0.1.0 +envoy.gpg.sign>=0.1.0 flake8 frozendict gitpython diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 4e28d2642728..38fc1b1a9d2b 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -12,10 +12,9 @@ abstracts==0.0.12 \ # aio-api-github # aio-api-nist # aio-core - # aio-run-checker # aio-run-runner # envoy-base-utils - # envoy-code-format-python-check + # envoy-code-check # envoy-dependency-check # envoy-dependency-pip-check # envoy-distribution-release @@ -38,34 +37,35 @@ aio-api-nist==0.0.1 \ # via # -r requirements.in # envoy-dependency-check -aio-core==0.5.8 \ - --hash=sha256:3e027710040ce43548f5704a8cd45fbb9c2a0a609a3d65735db0da3f79fa8d3f \ - --hash=sha256:a5f09e74b726accba91abf3bd32d2072d9add833bd5d092a6d9545e5efe26e7c +aio-core==0.8.1 \ + --hash=sha256:085fb6573b7b8f07876f6f45d10692f20445cfe686ed43c46ed05d33379556b3 \ + --hash=sha256:136544fcc2f04a48fc9dd8d4413381bb1a6dd226c9eab733477167762a677854 # via # -r requirements.in # aio-api-github # aio-api-nist # aio-run-runner # envoy-base-utils - # envoy-code-format-python-check + # envoy-code-check # envoy-dependency-check # envoy-distribution-release # envoy-distribution-repo # envoy-github-abstract # envoy-github-release -aio-run-checker==0.3.5 \ - --hash=sha256:3ac33c5a8d5168d457c1fa2e874f76032d27615f57937c7a939123dec20cc8bf \ - --hash=sha256:3c8e09b4514dd33afdce21cae98b7fae90a9983973714d9935ba38726b084f07 + # envoy-gpg-identity +aio-run-checker==0.5.1 \ + --hash=sha256:07e0c39031afa5370740e98a7e1628123e56646610fd8b9cf983ad85205d3adf \ + --hash=sha256:c9dc570aa1bf16355a35aec2417d897ecb77d03b82bd5a4f56986966437ab947 # via # -r requirements.in - # envoy-code-format-python-check + # envoy-code-check # envoy-dependency-check # envoy-dependency-pip-check # envoy-distribution-distrotest # envoy-distribution-verify -aio-run-runner==0.2.6 \ - --hash=sha256:5512fbef70a1eae793455caa29653d95da850cabb8063a74241d94b110de32fe \ - --hash=sha256:fb350bc70a998ca07f9185c277d4d114cd6cc8de3eb4d95906407b49abaa26b1 +aio-run-runner==0.3.2 \ + --hash=sha256:75ad35f9090d7dade92846d6700b6afbfe5751afdd97c7cf5fd8e2cf4b45d8f9 \ + --hash=sha256:a19aa3607fb0b585f695c264df72d043658c5cf7d19b4f34f29cdf4af8b39fa1 # via # -r requirements.in # aio-run-checker @@ -299,6 +299,7 @@ envoy-base-utils==0.1.0 \ --hash=sha256:f5ca5e147e60af5c2f755cc61dcfc507b9253d4657869f923e56dfd7ac03b835 # via # -r requirements.in + # envoy-code-check # envoy-dependency-check # envoy-dependency-pip-check # envoy-distribution-distrotest @@ -308,9 +309,9 @@ envoy-base-utils==0.1.0 \ # envoy-docs-sphinx-runner # envoy-github-release # envoy-gpg-sign -envoy-code-format-python-check==0.0.8 \ - --hash=sha256:2257f718567e469993bacb6aaa08d1af8d5195fd1f6936dde621b4449da45c49 \ - --hash=sha256:acfd00e3bbd6615eaf50b3d6bad0510b967afd687911fa01da5322a3911005d0 +envoy-code-check==0.0.1 \ + --hash=sha256:cc8094e2451ba88a24af2bde05765253391b10125d13947f1bd9ca7afdc07f20 \ + --hash=sha256:e8da3d11a9a17beae55ba82c9b7107142d92742d64cbe772df860d1259cd9eb9 # via -r requirements.in envoy-dependency-check==0.0.5 \ --hash=sha256:39a8b25ae88e0305eaf38a90c3c7956db3b3c53e967464368319e41f3309df36 \ @@ -339,9 +340,9 @@ envoy-distribution-verify==0.0.8 \ envoy-docker-utils==0.0.2 \ --hash=sha256:a12cb57f0b6e204d646cbf94f927b3a8f5a27ed15f60d0576176584ec16a4b76 # via envoy-distribution-distrotest -envoy-docs-sphinx-runner==0.0.8 \ - --hash=sha256:b9d051aa7ee995b647d00ea5a86fe3be30f399e344837185edd53ff3b6fc6988 \ - --hash=sha256:fb2dc69067d9593adebda90794e912d84bb9a1c0e9c5d23ac6da608b79a8fc98 +envoy-docs-sphinx-runner==0.1.0 \ + --hash=sha256:8be22a71038dac272f20c43589155aa0adca64223d0c85ef8864d8b1ca272b4e \ + --hash=sha256:c0031fc261621d00cc03ed1de47d947a720a4649d6be34d3dcd34986137bdc6a # via -r requirements.in envoy-github-abstract==0.0.21 \ --hash=sha256:243dae9457243fb42e4643b1f45006c3b0d3151c808884217447d29a081b26a1 \ @@ -353,22 +354,22 @@ envoy-github-release==0.0.11 \ --hash=sha256:1bed7a829bd77391c33107a58057b89de34a3045895f276816e07ee0119d844f \ --hash=sha256:a3c117892b822b9f1d29bbc92f9c82c733d972316fbe2611c4895cf1e4b1113d # via envoy-distribution-release -envoy-gpg-identity==0.0.6 \ - --hash=sha256:2195e09aaacbaa8d6588378ad96d206708ffaf863aead38445eb3e54e4223c34 \ - --hash=sha256:93032dd82c9c422c2145dbf426fd93c3673c53ba03cca2eab13f5fb8cf24f47c +envoy-gpg-identity==0.1.0 \ + --hash=sha256:1df6989e1a6a3a9f5809ac056829b840f57326d41ae1181db415a722216bff48 \ + --hash=sha256:9e11625e25ad6a031f1c85c57067673f50c0427b0e71ef0a7e926ce77ebb75a9 # via # -r requirements.in # envoy-gpg-sign -envoy-gpg-sign==0.0.11 \ - --hash=sha256:6e7d1defb15eea4a64b0042271e1206a1e3892000e53990c5988c2b6e726734b \ - --hash=sha256:95c86ddd7031abf5249b292986b5de1279ca9ccc0dd1a5a5d977cd90a1a48122 +envoy-gpg-sign==0.1.0 \ + --hash=sha256:14b8efee80916fa78857198fb98357c9ddfaa9dec34eb8a453bda1a364f4b973 \ + --hash=sha256:36bb56534a6c5947c18bd10c43185345f092664de86b49318e2afc6cac7d9158 # via -r requirements.in flake8==4.0.1 \ --hash=sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d \ --hash=sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d # via # -r requirements.in - # envoy-code-format-python-check + # envoy-code-check # flake8-polyfill # pep8-naming flake8-polyfill==1.0.2 \ @@ -641,7 +642,9 @@ psutil==5.9.0 \ --hash=sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3 \ --hash=sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c \ --hash=sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3 - # via envoy-dependency-check + # via + # aio-core + # envoy-dependency-check py==1.10.0 \ --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 \ --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a @@ -896,7 +899,9 @@ types-orjson==3.6.2 \ types-psutil==5.8.20 \ --hash=sha256:35e16c5d58c21cd638b7a74a8b451b3c099e0f8140121f1d084174c60625a5c8 \ --hash=sha256:b9c5d6c1f8bd6154f845a79dfbe46e7628f8d58978c6cadde0afa4abe6400294 - # via envoy-dependency-check + # via + # aio-core + # envoy-dependency-check typing-extensions==3.10.0.2 \ --hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \ --hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \ @@ -950,7 +955,7 @@ yapf==0.32.0 \ --hash=sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b # via # -r requirements.in - # envoy-code-format-python-check + # envoy-code-check yarl==1.6.3 \ --hash=sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e \ --hash=sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434 \ diff --git a/tools/code/BUILD b/tools/code/BUILD new file mode 100644 index 000000000000..ed264805eaeb --- /dev/null +++ b/tools/code/BUILD @@ -0,0 +1,13 @@ +load("//bazel:envoy_build_system.bzl", "envoy_package") +load("//tools/base:envoy_python.bzl", "envoy_entry_point") +load("@envoy_repo//:path.bzl", "PATH") + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_entry_point( + name = "check", + args = ["--path=%s" % PATH], + pkg = "envoy.code.check", +) diff --git a/tools/code_format/BUILD b/tools/code_format/BUILD index 11416d9ca841..487a447a1563 100644 --- a/tools/code_format/BUILD +++ b/tools/code_format/BUILD @@ -1,5 +1,3 @@ -load("@base_pip3//:requirements.bzl", "requirement") -load("@rules_python//python:defs.bzl", "py_binary") load("//bazel:envoy_build_system.bzl", "envoy_package") licenses(["notice"]) # Apache 2 @@ -11,12 +9,3 @@ exports_files([ "header_order.py", "envoy_build_fixer.py", ]) - -py_binary( - name = "python_check", - srcs = ["python_check.py"], - deps = [ - "@envoy_repo", - requirement("envoy.code_format.python_check"), - ], -) diff --git a/tools/code_format/check_shellcheck_format.sh b/tools/code_format/check_shellcheck_format.sh deleted file mode 100755 index b09ba6cb08f0..000000000000 --- a/tools/code_format/check_shellcheck_format.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/bin/bash -e - -EXCLUDED_SHELLFILES=${EXCLUDED_SHELLFILES:-".rst$|.md$"} -SHEBANG_RE='^#!/bin/bash|^#!/bin/sh|^#!/usr/bin/env bash|^#!/usr/bin/env sh' - - -find_shell_files () { - local shellfiles - shellfiles=() - shellfiles+=("$(git grep -E "$SHEBANG_RE" | cut -d: -f1)") - shellfiles+=("$(git ls-files|grep '\.sh$')") - shellfiles=("$(echo "${shellfiles[@]}" | tr ' ' '\n' | sort | uniq)") - for file in "${shellfiles[@]}"; do - echo "$file" - done -} - -run_shellcheck_on () { - local file op - op="$1" - file="$2" - if [ "$op" != "fix" ]; then - echo "Shellcheck: ${file}" - fi - shellcheck -x "$file" -} - -run_shellchecks () { - local all_shellfiles=() failed=() failure \ - filtered_shellfiles=() found_shellfiles \ - line skipped_count success_count - - found_shellfiles=$(find_shell_files) - while read -r line; do all_shellfiles+=("$line"); done \ - <<< "$found_shellfiles" - while read -r line; do filtered_shellfiles+=("$line"); done \ - <<< "$(echo -e "$found_shellfiles" | grep -vE "${EXCLUDED_SHELLFILES}")" - - for file in "${filtered_shellfiles[@]}"; do - run_shellcheck_on "$1" "$file" || { - failed+=("$file") - } - done - if [[ "${#failed[@]}" -ne 0 ]]; then - echo -e "\nShellcheck failures:" >&2 - for failure in "${failed[@]}"; do - echo "$failure" >&2 - done - fi - skipped_count=$((${#all_shellfiles[@]} - ${#filtered_shellfiles[@]})) - success_count=$((${#filtered_shellfiles[@]} - ${#failed[@]})) - - echo -e "\nShellcheck totals (skipped/failed/success): ${skipped_count}/${#failed[@]}/${success_count}" - if [[ "${#failed[@]}" -ne 0 ]]; then - return 1 - fi -} - -run_shellchecks "${1:-check}" diff --git a/tools/code_format/glint.sh b/tools/code_format/glint.sh deleted file mode 100755 index 7e57cf266963..000000000000 --- a/tools/code_format/glint.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/bash - -# This script checks all files in the repo for basic format "hygiene", specifically -# -# - must have ending line -# - no trailing whitespace -# - no lines indented with a mixture of tabs and spaces -# - -NOLINT_RE="\.patch$|^test/.*_corpus/|^tools/.*_corpus/|password_protected_password.txt" -ERRORS= -MISSING_NEWLINE=0 -MIXED_TABS_AND_SPACES=0 -TRAILING_WHITESPACE=0 -# AZP appears to make lines with this prefix red -BASH_ERR_PREFIX="##[error]: " - - -# Checks whether a file has a mixture of indents starting with tabs and spaces -check_mixed_tabs_spaces () { - local spaced tabbed - tabbed=$(grep -cP "^\t" "$1") - spaced=$(grep -cP "^ " "$1") - if [[ $tabbed -gt 0 ]] && [[ $spaced -gt 0 ]]; then - echo "${BASH_ERR_PREFIX}mixed tabs and spaces: ${1}" >&2 - ERRORS=yes - ((MIXED_TABS_AND_SPACES=MIXED_TABS_AND_SPACES+1)) - fi -} - -# Checks whether a file has a terminating newline -check_new_line () { - test "$(tail -c 1 "$1" | wc -l)" -eq 0 && { - echo "${BASH_ERR_PREFIX}no newline at eof: ${1}" >&2 - ERRORS=yes - ((MISSING_NEWLINE=MISSING_NEWLINE+1)) - } -} - -# Checks whether a file contains lines ending in whitespace -check_trailing_whitespace () { - if grep -r '[[:blank:]]$' "$1" > /dev/null; then - echo "${BASH_ERR_PREFIX}trailing whitespace: ${1}" >&2 - ERRORS=yes - ((TRAILING_WHITESPACE=TRAILING_WHITESPACE+1)) - fi -} - -# Uses git grep to search for non-"binary" files from git's pov -# -# TODO(phlax): add hash/diff only filter for faster change linting -# this would also make it feasible to add as a commit/push hook -find_text_files () { - git grep --cached -Il '' | grep -vE "$NOLINT_RE" -} - -# Recurse text files linting language-independent checks -# -# note: we may want to use python if this grows in complexity -# -for file in $(find_text_files); do - check_new_line "$file" - check_mixed_tabs_spaces "$file" - check_trailing_whitespace "$file" -done - -if [[ -n "$ERRORS" ]]; then - echo >&2 - echo "${BASH_ERR_PREFIX}ERRORS found" >&2 - echo "${BASH_ERR_PREFIX}${MISSING_NEWLINE} files with missing newline" >&2 - echo "${BASH_ERR_PREFIX}${MIXED_TABS_AND_SPACES} files with mixed tabs and spaces" >&2 - echo "${BASH_ERR_PREFIX}${TRAILING_WHITESPACE} files with trailing whitespace" >&2 - echo >&2 - exit 1 -fi diff --git a/tools/code_format/python_check.py b/tools/code_format/python_check.py deleted file mode 100755 index 4b2b6fd59f9b..000000000000 --- a/tools/code_format/python_check.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 - -# usage -# -# with bazel: -# -# $ bazel run //tools/code_format:python_check -- -h -# -# $ bazel run //tools/code_format:python_check -# -# with pip: -# -# $ pip install envoy.code_format.python_check -# $ envoy.code_format.python_check -h -# -# usage with pip requires a path, eg -# -# $ envoy.code_format.python_check . -# -# The upstream lib is maintained here: -# -# https://github.com/envoyproxy/pytooling/tree/main/envoy.code_format.python_check -# -# Please submit issues/PRs to the pytooling repo: -# -# https://github.com/envoyproxy/pytooling -# - -import pathlib -import sys -from functools import cached_property - -import abstracts - -from envoy.code_format import python_check - -import envoy_repo - - -@abstracts.implementer(python_check.APythonChecker) -class EnvoyPythonChecker: - - @cached_property - def path(self) -> pathlib.Path: - if self.args.paths: - return pathlib.Path(self.args.paths[0]) - return pathlib.Path(envoy_repo.PATH) - - -def main(*args) -> int: - return EnvoyPythonChecker(*args)() - - -if __name__ == "__main__": - sys.exit(main(*sys.argv[1:])) From f1d0f9c6ecd0d5b90e209fa7a30a9c4f8e23c366 Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Wed, 9 Mar 2022 06:16:31 -0800 Subject: [PATCH 25/68] Increase shard count since csrf times out. (#20262) Signed-off-by: Kevin Baichoo --- test/extensions/filters/http/csrf/BUILD | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/extensions/filters/http/csrf/BUILD b/test/extensions/filters/http/csrf/BUILD index ee5a9a314f09..41af00ac23e9 100644 --- a/test/extensions/filters/http/csrf/BUILD +++ b/test/extensions/filters/http/csrf/BUILD @@ -31,6 +31,9 @@ envoy_extension_cc_test( name = "csrf_filter_integration_test", srcs = ["csrf_filter_integration_test.cc"], extension_names = ["envoy.filters.http.csrf"], + # TODO(kbaichoo): remove when deferred processing is enabled by default and the + # test is no longer parameterized by it. + shard_count = 2, deps = [ "//source/extensions/filters/http/csrf:config", "//test/config:utility_lib", From 8034d7694c3f7751545ecca375c54a2adc3d71fe Mon Sep 17 00:00:00 2001 From: Raven Black Date: Wed, 9 Mar 2022 09:18:04 -0500 Subject: [PATCH 26/68] Fix ASSERT's namespace issues with abort() (#20208) Signed-off-by: Raven Black --- source/common/common/assert.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/common/common/assert.h b/source/common/common/assert.h index 91edb4afb152..3eaa67a1232b 100644 --- a/source/common/common/assert.h +++ b/source/common/common/assert.h @@ -120,13 +120,13 @@ void resetEnvoyBugCountersForTest(); * RELEASE_ASSERT(foo == bar, "reason foo should actually be bar"); * new uses of RELEASE_ASSERT should supply a verbose explanation of what went wrong. */ -#define RELEASE_ASSERT(X, DETAILS) _ASSERT_IMPL(X, #X, abort(), DETAILS) +#define RELEASE_ASSERT(X, DETAILS) _ASSERT_IMPL(X, #X, ::abort(), DETAILS) /** * Assert macro intended for security guarantees. It has the same functionality * as RELEASE_ASSERT, but is intended for memory bounds-checking. */ -#define SECURITY_ASSERT(X, DETAILS) _ASSERT_IMPL(X, #X, abort(), DETAILS) +#define SECURITY_ASSERT(X, DETAILS) _ASSERT_IMPL(X, #X, ::abort(), DETAILS) // ENVOY_LOG_DEBUG_ASSERT_IN_RELEASE compiles all ASSERTs in release mode. #ifdef ENVOY_LOG_DEBUG_ASSERT_IN_RELEASE @@ -138,7 +138,7 @@ void resetEnvoyBugCountersForTest(); // This if condition represents any case where ASSERT()s are compiled in. #if !defined(NDEBUG) // If this is a debug build. -#define ASSERT_ACTION abort() +#define ASSERT_ACTION ::abort() #else // If this is not a debug build, but ENVOY_LOG_(FAST)_DEBUG_ASSERT_IN_RELEASE is defined. #define ASSERT_ACTION \ Envoy::Assert::invokeDebugAssertionFailureRecordActionForAssertMacroUseOnly( \ @@ -161,7 +161,7 @@ void resetEnvoyBugCountersForTest(); * should always pass but that sometimes fails for an unknown reason. The macro allows it to * be temporarily compiled out while the failure is triaged and investigated. */ -#define KNOWN_ISSUE_ASSERT(X, DETAILS) _ASSERT_IMPL(X, #X, abort(), DETAILS) +#define KNOWN_ISSUE_ASSERT(X, DETAILS) _ASSERT_IMPL(X, #X, ::abort(), DETAILS) #else // This non-implementation ensures that its argument is a valid expression that can be statically // casted to a bool, but the expression is never evaluated and will be compiled away. @@ -197,13 +197,13 @@ void resetEnvoyBugCountersForTest(); do { \ ENVOY_LOG_TO_LOGGER(Envoy::Logger::Registry::getLog(Envoy::Logger::Id::assert), critical, \ "panic: {}", X); \ - abort(); \ + ::abort(); \ } while (false) // We do not want to crash on failure in tests exercising ENVOY_BUGs while running coverage in debug // mode. Crashing causes flakes when forking to expect a debug death and reduces lines of coverage. #if !defined(NDEBUG) && !defined(ENVOY_CONFIG_COVERAGE) -#define ENVOY_BUG_ACTION abort() +#define ENVOY_BUG_ACTION ::abort() #else #define ENVOY_BUG_ACTION \ Envoy::Assert::invokeEnvoyBugFailureRecordActionForEnvoyBugMacroUseOnly(__FILE__ \ From 4644130d3594da84b121fc1f8b1f150385505880 Mon Sep 17 00:00:00 2001 From: xuhj Date: Wed, 9 Mar 2022 22:37:21 +0800 Subject: [PATCH 27/68] cleanup: zero out sockaddr_in/sockaddr_in6 (#20227) Commit Message: cleanup: zero out sockaddr_in/sockaddr_in6 Additional Description: Risk Level: low Testing: n/a Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: He Jie Xu --- source/common/network/cidr_range.cc | 2 ++ source/common/network/utility.cc | 1 + .../filters/udp/dns_filter/dns_parser.cc | 2 ++ .../extensions/transport_sockets/tls/utility.cc | 2 ++ test/common/network/address_impl_speed_test.cc | 10 ++++++---- test/common/network/address_impl_test.cc | 3 +++ .../dns_resolver/apple/apple_dns_impl_test.cc | 17 +++++++++++++++++ 7 files changed, 33 insertions(+), 4 deletions(-) diff --git a/source/common/network/cidr_range.cc b/source/common/network/cidr_range.cc index 9060324d0629..d41392ae31a6 100644 --- a/source/common/network/cidr_range.cc +++ b/source/common/network/cidr_range.cc @@ -157,6 +157,7 @@ InstanceConstSharedPtr CidrRange::truncateIpAddressAndLength(InstanceConstShared uint32_t ip4 = ntohl(address->ip()->ipv4()->address()); ip4 &= ~0U << (32 - length); sockaddr_in sa4; + memset(&sa4, 0, sizeof(sa4)); sa4.sin_family = AF_INET; sa4.sin_port = htons(0); sa4.sin_addr.s_addr = htonl(ip4); @@ -173,6 +174,7 @@ InstanceConstSharedPtr CidrRange::truncateIpAddressAndLength(InstanceConstShared return std::make_shared(uint32_t(0)); } sockaddr_in6 sa6; + memset(&sa6, 0, sizeof(sa6)); sa6.sin6_family = AF_INET6; sa6.sin6_port = htons(0); diff --git a/source/common/network/utility.cc b/source/common/network/utility.cc index e9be775aca01..2d662df1b57c 100644 --- a/source/common/network/utility.cc +++ b/source/common/network/utility.cc @@ -137,6 +137,7 @@ uint32_t Utility::portFromUdpUrl(const std::string& url) { Address::InstanceConstSharedPtr Utility::parseInternetAddressNoThrow(const std::string& ip_address, uint16_t port, bool v6only) { sockaddr_in sa4; + memset(&sa4, 0, sizeof(sa4)); if (inet_pton(AF_INET, ip_address.c_str(), &sa4.sin_addr) == 1) { sa4.sin_family = AF_INET; sa4.sin_port = htons(port); diff --git a/source/extensions/filters/udp/dns_filter/dns_parser.cc b/source/extensions/filters/udp/dns_filter/dns_parser.cc index 65397af75c19..e4a0d517189d 100644 --- a/source/extensions/filters/udp/dns_filter/dns_parser.cc +++ b/source/extensions/filters/udp/dns_filter/dns_parser.cc @@ -303,6 +303,7 @@ DnsAnswerRecordPtr DnsMessageParser::parseDnsARecord(DnsAnswerCtx& ctx) { case DNS_RECORD_TYPE_A: if (ctx.available_bytes_ >= sizeof(uint32_t)) { sockaddr_in sa4; + memset(&sa4, 0, sizeof(sa4)); sa4.sin_addr.s_addr = ctx.buffer_->peekLEInt(ctx.offset_); ip_addr = std::make_shared(&sa4); ctx.offset_ += ctx.data_length_; @@ -311,6 +312,7 @@ DnsAnswerRecordPtr DnsMessageParser::parseDnsARecord(DnsAnswerCtx& ctx) { case DNS_RECORD_TYPE_AAAA: if (ctx.available_bytes_ >= sizeof(absl::uint128)) { sockaddr_in6 sa6; + memset(&sa6, 0, sizeof(sa6)); uint8_t* address6_bytes = reinterpret_cast(&sa6.sin6_addr.s6_addr); static constexpr size_t count = sizeof(absl::uint128) / sizeof(uint8_t); for (size_t index = 0; index < count; index++) { diff --git a/source/extensions/transport_sockets/tls/utility.cc b/source/extensions/transport_sockets/tls/utility.cc index 3c89b6013930..52ffd9a349ad 100644 --- a/source/extensions/transport_sockets/tls/utility.cc +++ b/source/extensions/transport_sockets/tls/utility.cc @@ -224,6 +224,7 @@ std::string Utility::generalNameAsString(const GENERAL_NAME* general_name) { case GEN_IPADD: { if (general_name->d.ip->length == 4) { sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); sin.sin_port = 0; sin.sin_family = AF_INET; safeMemcpyUnsafeSrc(&sin.sin_addr, general_name->d.ip->data); @@ -231,6 +232,7 @@ std::string Utility::generalNameAsString(const GENERAL_NAME* general_name) { san = addr.ip()->addressAsString(); } else if (general_name->d.ip->length == 16) { sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(sin6)); sin6.sin6_port = 0; sin6.sin6_family = AF_INET6; safeMemcpyUnsafeSrc(&sin6.sin6_addr, general_name->d.ip->data); diff --git a/test/common/network/address_impl_speed_test.cc b/test/common/network/address_impl_speed_test.cc index c4005ac4c86c..9eea883a3cf9 100644 --- a/test/common/network/address_impl_speed_test.cc +++ b/test/common/network/address_impl_speed_test.cc @@ -7,8 +7,9 @@ namespace Envoy { namespace Network { namespace Address { -static void Ipv4InstanceCreate(benchmark::State& state) { +static void ipv4InstanceCreate(benchmark::State& state) { sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(443); static constexpr uint32_t Addr = 0xc00002ff; // From the RFC 5737 example range. @@ -18,10 +19,11 @@ static void Ipv4InstanceCreate(benchmark::State& state) { benchmark::DoNotOptimize(address.ip()); } } -BENCHMARK(Ipv4InstanceCreate); +BENCHMARK(ipv4InstanceCreate); -static void Ipv6InstanceCreate(benchmark::State& state) { +static void ipv6InstanceCreate(benchmark::State& state) { sockaddr_in6 addr; + memset(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; addr.sin6_port = htons(443); static const char* Addr = "2001:DB8::1234"; // From the RFC 3849 example range. @@ -31,7 +33,7 @@ static void Ipv6InstanceCreate(benchmark::State& state) { benchmark::DoNotOptimize(address.ip()); } } -BENCHMARK(Ipv6InstanceCreate); +BENCHMARK(ipv6InstanceCreate); } // namespace Address } // namespace Network diff --git a/test/common/network/address_impl_test.cc b/test/common/network/address_impl_test.cc index c82c87bff634..d1c00d65efad 100644 --- a/test/common/network/address_impl_test.cc +++ b/test/common/network/address_impl_test.cc @@ -134,6 +134,7 @@ TEST(Ipv4InstanceTest, SockaddrToString) { for (const auto address : addresses) { sockaddr_in addr4; + memset(&addr4, 0, sizeof(addr4)); addr4.sin_family = AF_INET; EXPECT_EQ(1, inet_pton(AF_INET, address, &addr4.sin_addr)); addr4.sin_port = 0; @@ -143,6 +144,7 @@ TEST(Ipv4InstanceTest, SockaddrToString) { TEST(Ipv4InstanceTest, SocketAddress) { sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -232,6 +234,7 @@ TEST(Ipv4InstanceTest, BadAddress) { TEST(Ipv6InstanceTest, SocketAddress) { sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); addr6.sin6_family = AF_INET6; EXPECT_EQ(1, inet_pton(AF_INET6, "01:023::00Ef", &addr6.sin6_addr)); addr6.sin6_port = htons(32000); diff --git a/test/extensions/network/dns_resolver/apple/apple_dns_impl_test.cc b/test/extensions/network/dns_resolver/apple/apple_dns_impl_test.cc index 4fb50f3398c8..0476228605d0 100644 --- a/test/extensions/network/dns_resolver/apple/apple_dns_impl_test.cc +++ b/test/extensions/network/dns_resolver/apple/apple_dns_impl_test.cc @@ -426,6 +426,7 @@ class AppleDnsImplFakeApiTest : public testing::Test { void completeWithError(DNSServiceErrorType error_code) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -462,12 +463,14 @@ class AppleDnsImplFakeApiTest : public testing::Test { uint32_t expected_address_size = 1) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); addr6.sin6_family = AF_INET6; EXPECT_EQ(1, inet_pton(AF_INET6, "102:304:506:708:90a:b0c:d0e:f00", &addr6.sin6_addr)); addr6.sin6_port = 0; @@ -563,6 +566,7 @@ class AppleDnsImplFakeApiTest : public testing::Test { TEST_F(AppleDnsImplFakeApiTest, ErrorInSocketAccess) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -598,6 +602,7 @@ TEST_F(AppleDnsImplFakeApiTest, InvalidFileEvent) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -633,6 +638,7 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResult) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -687,6 +693,7 @@ TEST_F(AppleDnsImplFakeApiTest, SynchronousTimeoutInGetAddrInfo) { TEST_F(AppleDnsImplFakeApiTest, QuerySynchronousCompletion) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -738,12 +745,14 @@ TEST_F(AppleDnsImplFakeApiTest, QueryCompletedWithTimeout) { TEST_F(AppleDnsImplFakeApiTest, MultipleAddresses) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); sockaddr_in addr4_2; + memset(&addr4_2, 0, sizeof(addr4_2)); addr4_2.sin_family = AF_INET; EXPECT_EQ(1, inet_pton(AF_INET, "5.6.7.8", &addr4_2.sin_addr)); addr4_2.sin_port = htons(6502); @@ -821,6 +830,7 @@ TEST_F(AppleDnsImplFakeApiTest, AllV4IfOnlyV4) { TEST_F(AppleDnsImplFakeApiTest, MultipleAddressesSecondOneFails) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -860,6 +870,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleAddressesSecondOneFails) { TEST_F(AppleDnsImplFakeApiTest, MultipleQueries) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -869,6 +880,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueries) { const std::string hostname2 = "foo2.com"; sockaddr_in addr4_2; + memset(&addr4_2, 0, sizeof(addr4_2)); addr4_2.sin_family = AF_INET; EXPECT_EQ(1, inet_pton(AF_INET, "5.6.7.8", &addr4_2.sin_addr)); addr4_2.sin_port = htons(6502); @@ -934,6 +946,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueries) { TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -1001,6 +1014,7 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { TEST_F(AppleDnsImplFakeApiTest, ResultWithOnlyNonAdditiveReplies) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -1035,6 +1049,7 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithOnlyNonAdditiveReplies) { TEST_F(AppleDnsImplFakeApiTest, ResultWithNullAddress) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); @@ -1062,12 +1077,14 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithNullAddress) { TEST_F(AppleDnsImplFakeApiTest, DeallocateOnDestruction) { const std::string hostname = "foo.com"; sockaddr_in addr4; + memset(&addr4, 0, sizeof(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); sockaddr_in addr4_2; + memset(&addr4_2, 0, sizeof(addr4_2)); addr4_2.sin_family = AF_INET; EXPECT_EQ(1, inet_pton(AF_INET, "5.6.7.8", &addr4_2.sin_addr)); addr4_2.sin_port = htons(6502); From 0d492f3a80e32f3b3e1a2f0cb3ca98eb24676315 Mon Sep 17 00:00:00 2001 From: Faseela K Date: Wed, 9 Mar 2022 18:30:53 +0100 Subject: [PATCH 28/68] deps: Bump com_github_curl -> curl-7_82_0 (from: curl-7_81_0) (#20234) Signed-off-by: Faseela K Fixes: #20231 --- bazel/repositories.bzl | 2 +- bazel/repository_locations.bzl | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index a8cbd55ff7cc..b7abfd8154ef 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -769,7 +769,7 @@ def _com_github_curl(): build_file_content = BUILD_ALL_CONTENT + """ cc_library(name = "curl", visibility = ["//visibility:public"], deps = ["@envoy//bazel/foreign_cc:curl"]) """, - # Patch curl 7.74.0 due to CMake's problematic implementation of policy `CMP0091` + # Patch curl 7.74.0 and later due to CMake's problematic implementation of policy `CMP0091` # and introduction of libidn2 dependency which is inconsistently available and must # not be a dynamic dependency on linux. # Upstream patches submitted: https://github.com/curl/curl/pull/6050 & 6362 diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index aae900478d48..6de829cd9f88 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -762,8 +762,8 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "curl", project_desc = "Library for transferring data with URLs", project_url = "https://curl.haxx.se", - version = "7.81.0", - sha256 = "ac8e1087711084548d788ef18b9b732c8de887457b81f616fc681d1044b32f98", + version = "7.82.0", + sha256 = "910cc5fe279dc36e2cca534172c94364cf3fcf7d6494ba56e6c61a390881ddce", strip_prefix = "curl-{version}", urls = ["https://github.com/curl/curl/releases/download/curl-{underscore_version}/curl-{version}.tar.gz"], use_category = ["dataplane_ext", "observability_ext"], @@ -773,7 +773,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( "envoy.grpc_credentials.aws_iam", "envoy.tracers.opencensus", ], - release_date = "2022-01-05", + release_date = "2022-03-05", cpe = "cpe:2.3:a:haxx:libcurl:*", ), v8 = dict( From e58c890ded5c13e60eba3285c96ffefd6f7215cf Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Wed, 9 Mar 2022 15:39:59 -0500 Subject: [PATCH 29/68] config: Adding config-validation interface, extension and CDS validator (#19857) * Adding custom config-validation interface, extension, and a CDS validator. Signed-off-by: Adi Suissa-Peleg --- CODEOWNERS | 2 + api/BUILD | 1 + api/envoy/config/core/v3/config_source.proto | 43 +++- api/envoy/config/core/v3/extension.proto | 31 --- .../listener/v3/listener_components.proto | 2 +- .../validators/minimum_clusters/v3/BUILD | 9 + .../v3/minimum_clusters.proto | 23 ++ api/versioning/BUILD | 1 + docs/root/api-v3/config/config.rst | 1 + .../config_validators/config_validators.rst | 10 + .../config_validation/config_validation.rst | 23 ++ .../configuration/operations/operations.rst | 1 + docs/root/version_history/current.rst | 1 + envoy/config/BUILD | 9 + envoy/config/config_validator.h | 75 ++++++ envoy/server/configuration.h | 6 + envoy/server/instance.h | 5 + envoy/upstream/cluster_manager.h | 12 +- source/common/config/BUILD | 26 ++ .../common/config/custom_config_validators.h | 46 ++++ .../config/custom_config_validators_impl.cc | 53 +++++ .../config/custom_config_validators_impl.h | 46 ++++ source/common/config/grpc_mux_impl.cc | 12 +- source/common/config/grpc_mux_impl.h | 5 +- source/common/config/new_grpc_mux_impl.cc | 10 +- source/common/config/new_grpc_mux_impl.h | 9 +- .../config/subscription_factory_impl.cc | 30 ++- .../common/config/subscription_factory_impl.h | 5 +- source/common/config/watch_map.cc | 8 + source/common/config/watch_map.h | 8 +- source/common/config/xds_mux/grpc_mux_impl.cc | 31 +-- source/common/config/xds_mux/grpc_mux_impl.h | 11 +- source/common/upstream/BUILD | 1 + .../common/upstream/cluster_manager_impl.cc | 25 +- source/common/upstream/cluster_manager_impl.h | 43 ++-- .../config/validators/minimum_clusters/BUILD | 34 +++ .../validators/minimum_clusters/config.cc | 45 ++++ .../validators/minimum_clusters/config.h | 30 +++ .../minimum_clusters_validator.cc | 85 +++++++ .../minimum_clusters_validator.h | 37 +++ source/extensions/extensions_build_config.bzl | 6 + source/extensions/extensions_metadata.yaml | 5 + .../config_validation/cluster_manager.cc | 2 +- .../config_validation/cluster_manager.h | 5 +- source/server/config_validation/server.cc | 2 +- source/server/config_validation/server.h | 3 + source/server/configuration_impl.h | 1 + source/server/server.cc | 7 +- source/server/server.h | 1 + test/common/config/BUILD | 17 ++ .../custom_config_validators_impl_test.cc | 184 ++++++++++++++ .../config/delta_subscription_impl_test.cc | 6 +- .../config/delta_subscription_test_harness.h | 7 +- test/common/config/grpc_mux_impl_test.cc | 13 +- .../config/grpc_subscription_test_harness.h | 9 +- test/common/config/new_grpc_mux_impl_test.cc | 7 +- .../config/subscription_factory_impl_test.cc | 4 +- test/common/config/watch_map_test.cc | 34 ++- test/common/config/xds_grpc_mux_impl_test.cc | 17 +- test/common/upstream/BUILD | 2 + .../upstream/cluster_manager_impl_test.cc | 6 +- test/common/upstream/eds_speed_test.cc | 9 +- .../upstream/load_stats_reporter_test.cc | 6 +- test/common/upstream/test_cluster_manager.h | 25 +- test/config_test/config_test.cc | 2 +- .../clusters/aggregate/cluster_update_test.cc | 6 +- .../config/validators/minimum_clusters/BUILD | 58 +++++ .../minimum_clusters/config_test.cc | 52 ++++ ...mum_clusters_validator_integration_test.cc | 202 ++++++++++++++++ .../minimum_clusters_validator_test.cc | 224 ++++++++++++++++++ test/mocks/config/BUILD | 9 + test/mocks/config/custom_config_validators.cc | 13 + test/mocks/config/custom_config_validators.h | 38 +++ test/mocks/server/instance.h | 1 + test/mocks/server/main.h | 1 + test/mocks/upstream/cluster_manager.h | 2 +- test/server/config_validation/BUILD | 1 + .../config_validation/cluster_manager_test.cc | 4 +- test/server/config_validation/server_test.cc | 30 +++ test/server/configuration_impl_test.cc | 2 +- tools/extensions/extensions_check.py | 26 +- 81 files changed, 1738 insertions(+), 166 deletions(-) create mode 100644 api/envoy/extensions/config/validators/minimum_clusters/v3/BUILD create mode 100644 api/envoy/extensions/config/validators/minimum_clusters/v3/minimum_clusters.proto create mode 100644 docs/root/api-v3/config/config_validators/config_validators.rst create mode 100644 docs/root/configuration/operations/config_validation/config_validation.rst create mode 100644 envoy/config/config_validator.h create mode 100644 source/common/config/custom_config_validators.h create mode 100644 source/common/config/custom_config_validators_impl.cc create mode 100644 source/common/config/custom_config_validators_impl.h create mode 100644 source/extensions/config/validators/minimum_clusters/BUILD create mode 100644 source/extensions/config/validators/minimum_clusters/config.cc create mode 100644 source/extensions/config/validators/minimum_clusters/config.h create mode 100644 source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.cc create mode 100644 source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.h create mode 100644 test/common/config/custom_config_validators_impl_test.cc create mode 100644 test/extensions/config/validators/minimum_clusters/BUILD create mode 100644 test/extensions/config/validators/minimum_clusters/config_test.cc create mode 100644 test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc create mode 100644 test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_test.cc create mode 100644 test/mocks/config/custom_config_validators.cc create mode 100644 test/mocks/config/custom_config_validators.h diff --git a/CODEOWNERS b/CODEOWNERS index cf21279c7e0a..58a35335fd7b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -203,6 +203,8 @@ extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp # DNS Resolver /*/extensions/network/dns_resolver/cares @junr03 @yanavlasov /*/extensions/network/dns_resolver/apple @junr03 @yanavlasov +# Config Validators +/*/extensions/config/validators/minimum_clusters @adisuissa @htuch # Contrib /contrib/exe/ @mattklein123 @lizan diff --git a/api/BUILD b/api/BUILD index 51248b22f805..4a8bfc14dcbe 100644 --- a/api/BUILD +++ b/api/BUILD @@ -131,6 +131,7 @@ proto_library( "//envoy/extensions/compression/brotli/decompressor/v3:pkg", "//envoy/extensions/compression/gzip/compressor/v3:pkg", "//envoy/extensions/compression/gzip/decompressor/v3:pkg", + "//envoy/extensions/config/validators/minimum_clusters/v3:pkg", "//envoy/extensions/filters/common/dependency/v3:pkg", "//envoy/extensions/filters/common/fault/v3:pkg", "//envoy/extensions/filters/common/matcher/action/v3:pkg", diff --git a/api/envoy/config/core/v3/config_source.proto b/api/envoy/config/core/v3/config_source.proto index cce94027cdf2..a49a05de8d46 100644 --- a/api/envoy/config/core/v3/config_source.proto +++ b/api/envoy/config/core/v3/config_source.proto @@ -3,8 +3,10 @@ syntax = "proto3"; package envoy.config.core.v3; import "envoy/config/core/v3/base.proto"; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/grpc_service.proto"; +import "google/protobuf/any.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; @@ -40,7 +42,7 @@ enum ApiVersion { // API configuration source. This identifies the API type and cluster that Envoy // will use to fetch an xDS API. -// [#next-free-field: 9] +// [#next-free-field: 10] message ApiConfigSource { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.ApiConfigSource"; @@ -108,6 +110,16 @@ message ApiConfigSource { // Skip the node identifier in subsequent discovery requests for streaming gRPC config types. bool set_node_on_first_message_only = 7; + + // A list of config validators that will be executed when a new update is + // received from the ApiConfigSource. Note that each validator handles a + // specific xDS service type, and only the validators corresponding to the + // type url (in `:ref: DiscoveryResponse` or `:ref: DeltaDiscoveryResponse`) + // will be invoked. + // If the validator returns false or throws an exception, the config will be rejected by + // the client, and a NACK will be sent. + // [#extension-category: envoy.config.validators] + repeated TypedExtensionConfig config_validators = 9; } // Aggregated Discovery Service (ADS) options. This is currently empty, but when @@ -240,3 +252,32 @@ message ConfigSource { // turn expect to be delivered. ApiVersion resource_api_version = 6 [(validate.rules).enum = {defined_only: true}]; } + +// Configuration source specifier for a late-bound extension configuration. The +// parent resource is warmed until all the initial extension configurations are +// received, unless the flag to apply the default configuration is set. +// Subsequent extension updates are atomic on a per-worker basis. Once an +// extension configuration is applied to a request or a connection, it remains +// constant for the duration of processing. If the initial delivery of the +// extension configuration fails, due to a timeout for example, the optional +// default configuration is applied. Without a default configuration, the +// extension is disabled, until an extension configuration is received. The +// behavior of a disabled extension depends on the context. For example, a +// filter chain with a disabled extension filter rejects all incoming streams. +message ExtensionConfigSource { + ConfigSource config_source = 1 [(validate.rules).any = {required: true}]; + + // Optional default configuration to use as the initial configuration if + // there is a failure to receive the initial extension configuration or if + // `apply_default_config_without_warming` flag is set. + google.protobuf.Any default_config = 2; + + // Use the default config as the initial configuration without warming and + // waiting for the first discovery response. Requires the default configuration + // to be supplied. + bool apply_default_config_without_warming = 3; + + // A set of permitted extension type URLs. Extension configuration updates are rejected + // if they do not match any type URL in the set. + repeated string type_urls = 4 [(validate.rules).repeated = {min_items: 1}]; +} diff --git a/api/envoy/config/core/v3/extension.proto b/api/envoy/config/core/v3/extension.proto index 12a37cd8c01f..80afce693cd7 100644 --- a/api/envoy/config/core/v3/extension.proto +++ b/api/envoy/config/core/v3/extension.proto @@ -2,8 +2,6 @@ syntax = "proto3"; package envoy.config.core.v3; -import "envoy/config/core/v3/config_source.proto"; - import "google/protobuf/any.proto"; import "udpa/annotations/status.proto"; @@ -32,32 +30,3 @@ message TypedExtensionConfig { // ` for further details. google.protobuf.Any typed_config = 2 [(validate.rules).any = {required: true}]; } - -// Configuration source specifier for a late-bound extension configuration. The -// parent resource is warmed until all the initial extension configurations are -// received, unless the flag to apply the default configuration is set. -// Subsequent extension updates are atomic on a per-worker basis. Once an -// extension configuration is applied to a request or a connection, it remains -// constant for the duration of processing. If the initial delivery of the -// extension configuration fails, due to a timeout for example, the optional -// default configuration is applied. Without a default configuration, the -// extension is disabled, until an extension configuration is received. The -// behavior of a disabled extension depends on the context. For example, a -// filter chain with a disabled extension filter rejects all incoming streams. -message ExtensionConfigSource { - ConfigSource config_source = 1 [(validate.rules).any = {required: true}]; - - // Optional default configuration to use as the initial configuration if - // there is a failure to receive the initial extension configuration or if - // `apply_default_config_without_warming` flag is set. - google.protobuf.Any default_config = 2; - - // Use the default config as the initial configuration without warming and - // waiting for the first discovery response. Requires the default configuration - // to be supplied. - bool apply_default_config_without_warming = 3; - - // A set of permitted extension type URLs. Extension configuration updates are rejected - // if they do not match any type URL in the set. - repeated string type_urls = 4 [(validate.rules).repeated = {min_items: 1}]; -} diff --git a/api/envoy/config/listener/v3/listener_components.proto b/api/envoy/config/listener/v3/listener_components.proto index 5d86b7977bfc..1116031caca9 100644 --- a/api/envoy/config/listener/v3/listener_components.proto +++ b/api/envoy/config/listener/v3/listener_components.proto @@ -4,7 +4,7 @@ package envoy.config.listener.v3; import "envoy/config/core/v3/address.proto"; import "envoy/config/core/v3/base.proto"; -import "envoy/config/core/v3/extension.proto"; +import "envoy/config/core/v3/config_source.proto"; import "envoy/type/v3/range.proto"; import "google/protobuf/any.proto"; diff --git a/api/envoy/extensions/config/validators/minimum_clusters/v3/BUILD b/api/envoy/extensions/config/validators/minimum_clusters/v3/BUILD new file mode 100644 index 000000000000..ee92fb652582 --- /dev/null +++ b/api/envoy/extensions/config/validators/minimum_clusters/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/config/validators/minimum_clusters/v3/minimum_clusters.proto b/api/envoy/extensions/config/validators/minimum_clusters/v3/minimum_clusters.proto new file mode 100644 index 000000000000..2a24d7a07ec5 --- /dev/null +++ b/api/envoy/extensions/config/validators/minimum_clusters/v3/minimum_clusters.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package envoy.extensions.config.validators.minimum_clusters.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.config.validators.minimum_clusters.v3"; +option java_outer_classname = "MinimumClustersProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/config/validators/minimum_clusters/v3;minimum_clustersv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Minimum Clusters] +// [#extension: envoy.config.validators.minimum_clusters] + +// Validates a CDS config, and ensures that the number of clusters is above the +// set threshold. +message MinimumClustersValidator { + // The minimal clusters threshold. Any CDS config update leading to less than + // this number will be rejected. + // Default value is 0. + uint32 min_clusters_num = 1; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index ec1462ad2d57..8da30302bc79 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -68,6 +68,7 @@ proto_library( "//envoy/extensions/compression/brotli/decompressor/v3:pkg", "//envoy/extensions/compression/gzip/compressor/v3:pkg", "//envoy/extensions/compression/gzip/decompressor/v3:pkg", + "//envoy/extensions/config/validators/minimum_clusters/v3:pkg", "//envoy/extensions/filters/common/dependency/v3:pkg", "//envoy/extensions/filters/common/fault/v3:pkg", "//envoy/extensions/filters/common/matcher/action/v3:pkg", diff --git a/docs/root/api-v3/config/config.rst b/docs/root/api-v3/config/config.rst index 220bac1d7961..c0c343f43839 100644 --- a/docs/root/api-v3/config/config.rst +++ b/docs/root/api-v3/config/config.rst @@ -36,3 +36,4 @@ Extensions formatter/formatter contrib/contrib rbac/matchers + config_validators/config_validators diff --git a/docs/root/api-v3/config/config_validators/config_validators.rst b/docs/root/api-v3/config/config_validators/config_validators.rst new file mode 100644 index 000000000000..2afc7bc3f712 --- /dev/null +++ b/docs/root/api-v3/config/config_validators/config_validators.rst @@ -0,0 +1,10 @@ +.. _v3_config_config_validators: + +Config validators +================= + +.. toctree:: + :glob: + :maxdepth: 2 + + ../../extensions/config/validators/*/v3/* diff --git a/docs/root/configuration/operations/config_validation/config_validation.rst b/docs/root/configuration/operations/config_validation/config_validation.rst new file mode 100644 index 000000000000..34fcd8664c2e --- /dev/null +++ b/docs/root/configuration/operations/config_validation/config_validation.rst @@ -0,0 +1,23 @@ +.. _config_config_validation: + +Config Validations +================== + +Envoy supports dynamic configuration using the :ref:`xDS protocol `. +When receiving a new configuration, Envoy first verifies that all fields are valid, +according to their protoc-gen-validate (PGV) constraints, and that the new config +keeps Envoy's internal state correct. If a given config violates a constraint, that +config is rejected (see :ref:`ACK/NACK and resource type instance version `). + +In addition to the above, Envoy also supports custom config validations where +verifications can be made, when using `gRPC-based subscriptions ` . +An example of such a validator is where an Envoy instance is expected to always have a +minimal number of clusters, then any config update that results with a number of +clusters which is less than the threshold should be rejected. + +Custom config validators are defined using Envoy's :ref:`extension ` framework. +Envoy's builtin config validators are listed :ref:`here `. + +To use a config validation extension, it needs to be added to the +:ref:`config_validators list ` +field of the API configuration source that will be validated. diff --git a/docs/root/configuration/operations/operations.rst b/docs/root/configuration/operations/operations.rst index faa9c0f37419..5359948acc76 100644 --- a/docs/root/configuration/operations/operations.rst +++ b/docs/root/configuration/operations/operations.rst @@ -6,4 +6,5 @@ Operations runtime overload_manager/overload_manager + config_validation/config_validation tools/router_check diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index a58a8d074ec9..e7c890f9160d 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -71,6 +71,7 @@ New Features :ref:`watched_directory ` can be used to setup an independent watch for when to reload the file path, for example when using Kubernetes ConfigMaps to deliver configuration. See the linked documentation for more information. +* config: added new :ref:`custom config validators ` to dynamically verify config updates. * cors: add dynamic support for headers ``access-control-allow-methods`` and ``access-control-allow-headers`` in cors. * http: added random_value_specifier in :ref:`weighted_clusters ` to allow random value to be specified from configuration proto. * http: added support for :ref:`proxy_status_config ` for configuring `Proxy-Status `_ HTTP response header fields. diff --git a/envoy/config/BUILD b/envoy/config/BUILD index 4b3c37ef9714..71bb10360edc 100644 --- a/envoy/config/BUILD +++ b/envoy/config/BUILD @@ -29,6 +29,15 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "config_validator_interface", + hdrs = ["config_validator.h"], + deps = [ + "//envoy/server:instance_interface", + "//source/common/protobuf", + ], +) + envoy_cc_library( name = "context_provider_interface", hdrs = ["context_provider.h"], diff --git a/envoy/config/config_validator.h b/envoy/config/config_validator.h new file mode 100644 index 000000000000..285995934a8f --- /dev/null +++ b/envoy/config/config_validator.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#include "envoy/common/optref.h" +#include "envoy/config/subscription.h" +#include "envoy/server/instance.h" + +#include "source/common/protobuf/protobuf.h" + +namespace Envoy { +namespace Config { + +/** + * The configuration validator interface. One can implement such validator + * to add custom constraints to the fetched config, and reject a config + * which violates these constraints. + * The validators will be extensions that can be dynamically configured. + * + * A validator example: a validator that prevents removing all Clusters + * from an Envoy (which may be caused by a bug in the config plane, and not + * intentional change). + */ +class ConfigValidator { +public: + virtual ~ConfigValidator() = default; + + /** + * Validates a given set of resources matching a State-of-the-World update. + * @param server A server instance to fetch the state before applying the config. + * @param resources List of decoded resources that reflect the new state. + * @throw EnvoyException if the config should be rejected. + */ + virtual void validate(const Server::Instance& server, + const std::vector& resources) PURE; + + /** + * Validates a given set of resources matching an Incremental update. + * @param server A server instance to fetch the state before applying the config. + * @param added_resources A list of decoded resources to add to the current state. + * @param removed_resources A list of resources to remove from the current state. + * @throw EnvoyException if the config should be rejected. + */ + virtual void validate(const Server::Instance& server, + const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources) PURE; +}; + +using ConfigValidatorPtr = std::unique_ptr; + +/** + * A factory abstract class for creating instances of ConfigValidators. + */ +class ConfigValidatorFactory : public Config::TypedFactory { +public: + ~ConfigValidatorFactory() override = default; + + /** + * Creates a ConfigValidator using the given config. + */ + virtual ConfigValidatorPtr + createConfigValidator(const ProtobufWkt::Any& config, + ProtobufMessage::ValidationVisitor& validation_visitor) PURE; + + std::string category() const override { return "envoy.config.validators"; } + + /** + * Returns the xDS service type url that the config validator expects to receive. + */ + virtual std::string typeUrl() const PURE; +}; + +} // namespace Config +} // namespace Envoy diff --git a/envoy/server/configuration.h b/envoy/server/configuration.h index 32c7e3f661b1..c716ca7393a0 100644 --- a/envoy/server/configuration.h +++ b/envoy/server/configuration.h @@ -99,6 +99,12 @@ class Main { */ virtual Upstream::ClusterManager* clusterManager() PURE; + /** + * @return const Upstream::ClusterManager* singleton for use by the entire server. + * This will be nullptr if the cluster manager has not initialized yet. + */ + virtual const Upstream::ClusterManager* clusterManager() const PURE; + /** * @return const StatsConfig& the configuration of server stats. */ diff --git a/envoy/server/instance.h b/envoy/server/instance.h index f779abb42a17..575c156c9061 100644 --- a/envoy/server/instance.h +++ b/envoy/server/instance.h @@ -61,6 +61,11 @@ class Instance { */ virtual Upstream::ClusterManager& clusterManager() PURE; + /** + * @return const Upstream::ClusterManager& singleton for use by the entire server. + */ + virtual const Upstream::ClusterManager& clusterManager() const PURE; + /** * @return Ssl::ContextManager& singleton for use by the entire server. */ diff --git a/envoy/upstream/cluster_manager.h b/envoy/upstream/cluster_manager.h index 51f714af5b7e..8643f623febc 100644 --- a/envoy/upstream/cluster_manager.h +++ b/envoy/upstream/cluster_manager.h @@ -248,13 +248,13 @@ class ClusterManager { warming_clusters_.find(cluster) != warming_clusters_.end(); } - ClusterConstOptRef getCluster(absl::string_view cluster) { + ClusterConstOptRef getCluster(absl::string_view cluster) const { auto active_cluster = active_clusters_.find(cluster); - if (active_cluster != active_clusters_.end()) { + if (active_cluster != active_clusters_.cend()) { return active_cluster->second; } auto warming_cluster = warming_clusters_.find(cluster); - if (warming_cluster != warming_clusters_.end()) { + if (warming_cluster != warming_clusters_.cend()) { return warming_cluster->second; } return absl::nullopt; @@ -262,6 +262,10 @@ class ClusterManager { ClusterInfoMap active_clusters_; ClusterInfoMap warming_clusters_; + + // Number of clusters that were dynamically added via API (xDS). This will be + // less than or equal to the number of `active_clusters_` and `warming_clusters_`. + uint32_t added_via_api_clusters_num_{0}; }; /** @@ -269,7 +273,7 @@ class ClusterManager { * * NOTE: This method is only thread safe on the main thread. It should not be called elsewhere. */ - virtual ClusterInfoMaps clusters() PURE; + virtual ClusterInfoMaps clusters() const PURE; using ClusterSet = absl::flat_hash_set; diff --git a/source/common/config/BUILD b/source/common/config/BUILD index ef91e5d5375f..7246182b1cd7 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -158,6 +158,7 @@ envoy_cc_library( hdrs = ["grpc_mux_impl.h"], deps = [ ":api_version_lib", + ":custom_config_validators_interface", ":decoded_resource_lib", ":grpc_stream_lib", ":ttl_lib", @@ -193,6 +194,7 @@ envoy_cc_library( srcs = ["new_grpc_mux_impl.cc"], hdrs = ["new_grpc_mux_impl.h"], deps = [ + ":custom_config_validators_interface", ":delta_subscription_state_lib", ":grpc_stream_lib", ":pausable_ack_queue_lib", @@ -315,6 +317,7 @@ envoy_cc_library( srcs = ["subscription_factory_impl.cc"], hdrs = ["subscription_factory_impl.h"], deps = [ + ":custom_config_validators_lib", ":filesystem_subscription_lib", ":grpc_subscription_lib", ":http_subscription_lib", @@ -324,6 +327,7 @@ envoy_cc_library( ":xds_resource_lib", "//envoy/config:subscription_factory_interface", "//envoy/config:subscription_interface", + "//envoy/server:instance_interface", "//envoy/upstream:cluster_manager_interface", "//source/common/common:minimal_logger_lib", "//source/common/config/xds_mux:grpc_mux_lib", @@ -412,6 +416,7 @@ envoy_cc_library( srcs = ["watch_map.cc"], hdrs = ["watch_map.h"], deps = [ + ":custom_config_validators_interface", ":decoded_resource_lib", ":utility_lib", ":xds_resource_lib", @@ -435,6 +440,27 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "custom_config_validators_interface", + hdrs = ["custom_config_validators.h"], + deps = [ + "//envoy/config:config_validator_interface", + ], +) + +envoy_cc_library( + name = "custom_config_validators_lib", + srcs = ["custom_config_validators_impl.cc"], + hdrs = ["custom_config_validators_impl.h"], + deps = [ + ":custom_config_validators_interface", + ":opaque_resource_decoder_lib", + ":resource_name_lib", + "//envoy/server:instance_interface", + "//source/common/config:utility_lib", + ], +) + envoy_cc_library( name = "watched_directory_lib", srcs = ["watched_directory.cc"], diff --git a/source/common/config/custom_config_validators.h b/source/common/config/custom_config_validators.h new file mode 100644 index 000000000000..3d1d5a3ebd4e --- /dev/null +++ b/source/common/config/custom_config_validators.h @@ -0,0 +1,46 @@ +#pragma once + +#include "envoy/config/config_validator.h" + +namespace Envoy { +namespace Config { + +// Represents a collection of config validators defined using Envoy extensions, +// for all xDS service types. This is different than running Envoy in +// "validation-mode", as these config validators will be executed whenever +// Envoy receives a new update. +class CustomConfigValidators { +public: + virtual ~CustomConfigValidators() = default; + + /** + * Executes the validators that receive the State-of-the-World resources. + * + * @param type_url the xDS type url of the incoming resources. + * @param resources the State-of-the-World resources from the new config + * update. + * @throw EnvoyException if the config is rejected by one of the validators. + */ + virtual void executeValidators(absl::string_view type_url, + const std::vector& resources) PURE; + + /** + * Executes the validators that receive the Incremental (delta-xDS) resources. + * + * @param type_url the xDS type url of the incoming resources. + * @param added_resources the added/modified resources from the new config + * update. + * @param removed_resources the resources to remove according to the new + * config update. + * @throw EnvoyException if the config is rejected by one of the validators. + */ + virtual void + executeValidators(absl::string_view type_url, + const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources) PURE; +}; + +using CustomConfigValidatorsPtr = std::unique_ptr; + +} // namespace Config +} // namespace Envoy diff --git a/source/common/config/custom_config_validators_impl.cc b/source/common/config/custom_config_validators_impl.cc new file mode 100644 index 000000000000..327982e3b8c5 --- /dev/null +++ b/source/common/config/custom_config_validators_impl.cc @@ -0,0 +1,53 @@ +#include "source/common/config/custom_config_validators_impl.h" + +#include "source/common/config/opaque_resource_decoder_impl.h" +#include "source/common/config/utility.h" + +namespace Envoy { +namespace Config { + +CustomConfigValidatorsImpl::CustomConfigValidatorsImpl( + ProtobufMessage::ValidationVisitor& validation_visitor, const Server::Instance& server, + const Protobuf::RepeatedPtrField& + validators_configs) + : server_(server) { + for (const auto& validator_config : validators_configs) { + auto& factory = + Config::Utility::getAndCheckFactory(validator_config); + const auto validator_type_url = factory.typeUrl(); + Config::ConfigValidatorPtr validator = + factory.createConfigValidator(validator_config.typed_config(), validation_visitor); + + // Insert a new vector for the type url if one doesn't exist. + auto pair = validators_map_.emplace(validator_type_url, 0); + pair.first->second.emplace_back(std::move(validator)); + } +} + +void CustomConfigValidatorsImpl::executeValidators( + absl::string_view type_url, const std::vector& resources) { + auto validators_it = validators_map_.find(type_url); + if (validators_it != validators_map_.end()) { + auto& validators = validators_it->second; + for (auto& validator : validators) { + // A validator can either succeed, or throw an EnvoyException. + validator->validate(server_, resources); + } + } +} + +void CustomConfigValidatorsImpl::executeValidators( + absl::string_view type_url, const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources) { + auto validators_it = validators_map_.find(type_url); + if (validators_it != validators_map_.end()) { + auto& validators = validators_it->second; + for (auto& validator : validators) { + // A validator can either succeed, or throw an EnvoyException. + validator->validate(server_, added_resources, removed_resources); + } + } +} + +} // namespace Config +} // namespace Envoy diff --git a/source/common/config/custom_config_validators_impl.h b/source/common/config/custom_config_validators_impl.h new file mode 100644 index 000000000000..0d764922beef --- /dev/null +++ b/source/common/config/custom_config_validators_impl.h @@ -0,0 +1,46 @@ +#pragma once + +#include "envoy/server/instance.h" + +#include "source/common/config/custom_config_validators.h" + +namespace Envoy { +namespace Config { + +class CustomConfigValidatorsImpl : public CustomConfigValidators { +public: + CustomConfigValidatorsImpl( + ProtobufMessage::ValidationVisitor& validation_visitor, const Server::Instance& server, + const Protobuf::RepeatedPtrField& + validators_configs); + + /** + * Executes the validators that receive the State-of-the-World resources. + * + * @param resources the State-of-the-World resources from the new config + * update. + * @throw EnvoyException if the config is rejected by one of the validators. + */ + void executeValidators(absl::string_view type_url, + const std::vector& resources) override; + + /** + * Executes the validators that receive the Incremental (delta-xDS) resources. + * + * @param added_resources the added/modified resources from the new config + * update. + * @param removed_resources the resources to remove according to the new + * config update. + * @throw EnvoyException if the config is rejected by one of the validators. + */ + void executeValidators(absl::string_view type_url, + const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources) override; + +private: + absl::flat_hash_map> validators_map_; + const Server::Instance& server_; +}; + +} // namespace Config +} // namespace Envoy diff --git a/source/common/config/grpc_mux_impl.cc b/source/common/config/grpc_mux_impl.cc index f886e9019d64..e49958292b84 100644 --- a/source/common/config/grpc_mux_impl.cc +++ b/source/common/config/grpc_mux_impl.cc @@ -36,11 +36,13 @@ GrpcMuxImpl::GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, Grpc::RawAsyncClientPtr async_client, Event::Dispatcher& dispatcher, const Protobuf::MethodDescriptor& service_method, Random::RandomGenerator& random, Stats::Scope& scope, - const RateLimitSettings& rate_limit_settings, bool skip_subsequent_node) + const RateLimitSettings& rate_limit_settings, bool skip_subsequent_node, + CustomConfigValidatorsPtr&& config_validators) : grpc_stream_(this, std::move(async_client), service_method, random, dispatcher, scope, rate_limit_settings), local_info_(local_info), skip_subsequent_node_(skip_subsequent_node), - first_stream_request_(true), dispatcher_(dispatcher), + config_validators_(std::move(config_validators)), first_stream_request_(true), + dispatcher_(dispatcher), dynamic_update_callback_handle_(local_info.contextProvider().addDynamicContextUpdateCallback( [this](absl::string_view resource_type_url) { onDynamicContextUpdate(resource_type_url); @@ -243,6 +245,12 @@ void GrpcMuxImpl::onDiscoveryResponse( } } + // Execute external config validators if there are any watches. + if (!api_state.watches_.empty()) { + config_validators_->executeValidators( + type_url, reinterpret_cast&>(resources)); + } + for (auto watch : api_state.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 diff --git a/source/common/config/grpc_mux_impl.h b/source/common/config/grpc_mux_impl.h index f1da3dd10a65..1dda15d727f6 100644 --- a/source/common/config/grpc_mux_impl.h +++ b/source/common/config/grpc_mux_impl.h @@ -17,6 +17,7 @@ #include "source/common/common/logger.h" #include "source/common/common/utility.h" #include "source/common/config/api_version.h" +#include "source/common/config/custom_config_validators.h" #include "source/common/config/grpc_stream.h" #include "source/common/config/ttl.h" #include "source/common/config/utility.h" @@ -35,7 +36,8 @@ class GrpcMuxImpl : public GrpcMux, GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, Grpc::RawAsyncClientPtr async_client, Event::Dispatcher& dispatcher, const Protobuf::MethodDescriptor& service_method, Random::RandomGenerator& random, Stats::Scope& scope, - const RateLimitSettings& rate_limit_settings, bool skip_subsequent_node); + const RateLimitSettings& rate_limit_settings, bool skip_subsequent_node, + CustomConfigValidatorsPtr&& config_validators); ~GrpcMuxImpl() override; @@ -168,6 +170,7 @@ class GrpcMuxImpl : public GrpcMux, grpc_stream_; const LocalInfo::LocalInfo& local_info_; const bool skip_subsequent_node_; + CustomConfigValidatorsPtr config_validators_; bool first_stream_request_; // Helper function for looking up and potentially allocating a new ApiState. diff --git a/source/common/config/new_grpc_mux_impl.cc b/source/common/config/new_grpc_mux_impl.cc index d05cfdd1a35d..eab01b0afec1 100644 --- a/source/common/config/new_grpc_mux_impl.cc +++ b/source/common/config/new_grpc_mux_impl.cc @@ -39,10 +39,11 @@ NewGrpcMuxImpl::NewGrpcMuxImpl(Grpc::RawAsyncClientPtr&& async_client, const Protobuf::MethodDescriptor& service_method, Random::RandomGenerator& random, Stats::Scope& scope, const RateLimitSettings& rate_limit_settings, - const LocalInfo::LocalInfo& local_info) + const LocalInfo::LocalInfo& local_info, + CustomConfigValidatorsPtr&& config_validators) : grpc_stream_(this, std::move(async_client), service_method, random, dispatcher, scope, rate_limit_settings), - local_info_(local_info), + local_info_(local_info), config_validators_(std::move(config_validators)), dynamic_update_callback_handle_(local_info.contextProvider().addDynamicContextUpdateCallback( [this](absl::string_view resource_type_url) { onDynamicContextUpdate(resource_type_url); @@ -232,8 +233,9 @@ 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, dispatcher_)); + subscriptions_.emplace( + type_url, std::make_unique(type_url, local_info_, use_namespace_matching, + dispatcher_, *config_validators_.get())); 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 84e5d223df63..b6c67488174f 100644 --- a/source/common/config/new_grpc_mux_impl.h +++ b/source/common/config/new_grpc_mux_impl.h @@ -33,7 +33,8 @@ class NewGrpcMuxImpl NewGrpcMuxImpl(Grpc::RawAsyncClientPtr&& async_client, Event::Dispatcher& dispatcher, const Protobuf::MethodDescriptor& service_method, Random::RandomGenerator& random, Stats::Scope& scope, const RateLimitSettings& rate_limit_settings, - const LocalInfo::LocalInfo& local_info); + const LocalInfo::LocalInfo& local_info, + CustomConfigValidatorsPtr&& config_validators); ~NewGrpcMuxImpl() override; @@ -81,8 +82,9 @@ class NewGrpcMuxImpl struct SubscriptionStuff { SubscriptionStuff(const std::string& type_url, const LocalInfo::LocalInfo& local_info, - const bool use_namespace_matching, Event::Dispatcher& dispatcher) - : watch_map_(use_namespace_matching), + const bool use_namespace_matching, Event::Dispatcher& dispatcher, + CustomConfigValidators& config_validators) + : watch_map_(use_namespace_matching, type_url, config_validators), sub_state_(type_url, watch_map_, local_info, dispatcher) {} WatchMap watch_map_; @@ -173,6 +175,7 @@ class NewGrpcMuxImpl grpc_stream_; const LocalInfo::LocalInfo& local_info_; + CustomConfigValidatorsPtr config_validators_; Common::CallbackHandlePtr dynamic_update_callback_handle_; Event::Dispatcher& dispatcher_; diff --git a/source/common/config/subscription_factory_impl.cc b/source/common/config/subscription_factory_impl.cc index a040a84bf3b3..68c8541281c8 100644 --- a/source/common/config/subscription_factory_impl.cc +++ b/source/common/config/subscription_factory_impl.cc @@ -2,6 +2,7 @@ #include "envoy/config/core/v3/config_source.pb.h" +#include "source/common/config/custom_config_validators_impl.h" #include "source/common/config/filesystem_subscription_impl.h" #include "source/common/config/grpc_mux_impl.h" #include "source/common/config/grpc_subscription_impl.h" @@ -21,9 +22,9 @@ namespace Config { SubscriptionFactoryImpl::SubscriptionFactoryImpl( const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher, Upstream::ClusterManager& cm, ProtobufMessage::ValidationVisitor& validation_visitor, - Api::Api& api) + Api::Api& api, const Server::Instance& server) : local_info_(local_info), dispatcher_(dispatcher), cm_(cm), - validation_visitor_(validation_visitor), api_(api) {} + validation_visitor_(validation_visitor), api_(api), server_(server) {} SubscriptionPtr SubscriptionFactoryImpl::subscriptionFromConfigSource( const envoy::config::core::v3::ConfigSource& config, absl::string_view type_url, @@ -70,6 +71,10 @@ SubscriptionPtr SubscriptionFactoryImpl::subscriptionFromConfigSource( validation_visitor_); case envoy::config::core::v3::ApiConfigSource::GRPC: { GrpcMuxSharedPtr mux; + CustomConfigValidatorsPtr custom_config_validators = + std::make_unique(validation_visitor_, server_, + api_config_source.config_validators()); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.unified_mux")) { mux = std::make_shared( Utility::factoryForGrpcApiConfigSource(cm_.grpcAsyncClientManager(), api_config_source, @@ -77,7 +82,8 @@ SubscriptionPtr SubscriptionFactoryImpl::subscriptionFromConfigSource( ->createUncachedRawAsyncClient(), dispatcher_, sotwGrpcMethod(type_url), api_.randomGenerator(), scope, Utility::parseRateLimitSettings(api_config_source), local_info_, - api_config_source.set_node_on_first_message_only()); + api_config_source.set_node_on_first_message_only(), + std::move(custom_config_validators)); } else { mux = std::make_shared( local_info_, @@ -86,7 +92,8 @@ SubscriptionPtr SubscriptionFactoryImpl::subscriptionFromConfigSource( ->createUncachedRawAsyncClient(), dispatcher_, sotwGrpcMethod(type_url), api_.randomGenerator(), scope, Utility::parseRateLimitSettings(api_config_source), - api_config_source.set_node_on_first_message_only()); + api_config_source.set_node_on_first_message_only(), + std::move(custom_config_validators)); } return std::make_unique( std::move(mux), callbacks, resource_decoder, stats, type_url, dispatcher_, @@ -95,6 +102,9 @@ SubscriptionPtr SubscriptionFactoryImpl::subscriptionFromConfigSource( } case envoy::config::core::v3::ApiConfigSource::DELTA_GRPC: { GrpcMuxSharedPtr mux; + CustomConfigValidatorsPtr custom_config_validators = + std::make_unique(validation_visitor_, server_, + api_config_source.config_validators()); if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.unified_mux")) { mux = std::make_shared( Utility::factoryForGrpcApiConfigSource(cm_.grpcAsyncClientManager(), api_config_source, @@ -102,14 +112,16 @@ SubscriptionPtr SubscriptionFactoryImpl::subscriptionFromConfigSource( ->createUncachedRawAsyncClient(), dispatcher_, deltaGrpcMethod(type_url), api_.randomGenerator(), scope, Utility::parseRateLimitSettings(api_config_source), local_info_, - api_config_source.set_node_on_first_message_only()); + api_config_source.set_node_on_first_message_only(), + std::move(custom_config_validators)); } else { mux = std::make_shared( Config::Utility::factoryForGrpcApiConfigSource(cm_.grpcAsyncClientManager(), api_config_source, scope, true) ->createUncachedRawAsyncClient(), dispatcher_, deltaGrpcMethod(type_url), api_.randomGenerator(), scope, - Utility::parseRateLimitSettings(api_config_source), local_info_); + Utility::parseRateLimitSettings(api_config_source), local_info_, + std::move(custom_config_validators)); } return std::make_unique( std::move(mux), callbacks, resource_decoder, stats, type_url, dispatcher_, @@ -156,6 +168,9 @@ SubscriptionPtr SubscriptionFactoryImpl::collectionSubscriptionFromUrl( config.api_config_source(); Utility::checkApiConfigSourceSubscriptionBackingCluster(cm_.primaryClusters(), api_config_source); + CustomConfigValidatorsPtr custom_config_validators = + std::make_unique(validation_visitor_, server_, + api_config_source.config_validators()); SubscriptionOptions options; // All Envoy collections currently are xDS resource graph roots and require node context @@ -171,7 +186,8 @@ SubscriptionPtr SubscriptionFactoryImpl::collectionSubscriptionFromUrl( api_config_source, scope, true) ->createUncachedRawAsyncClient(), dispatcher_, deltaGrpcMethod(type_url), api_.randomGenerator(), scope, - Utility::parseRateLimitSettings(api_config_source), local_info_), + Utility::parseRateLimitSettings(api_config_source), local_info_, + std::move(custom_config_validators)), callbacks, resource_decoder, stats, dispatcher_, Utility::configSourceInitialFetchTimeout(config), false, options); } diff --git a/source/common/config/subscription_factory_impl.h b/source/common/config/subscription_factory_impl.h index 07cfa5d17520..ba30cde20f7c 100644 --- a/source/common/config/subscription_factory_impl.h +++ b/source/common/config/subscription_factory_impl.h @@ -5,6 +5,7 @@ #include "envoy/config/core/v3/config_source.pb.h" #include "envoy/config/subscription.h" #include "envoy/config/subscription_factory.h" +#include "envoy/server/instance.h" #include "envoy/stats/scope.h" #include "envoy/upstream/cluster_manager.h" @@ -17,7 +18,8 @@ class SubscriptionFactoryImpl : public SubscriptionFactory, Logger::Loggable& resources, } } + // Execute external config validators. + config_validators_.executeValidators(type_url_, resources); + const bool map_is_single_wildcard = (watches_.size() == 1 && wildcard_watches_.size() == 1); // We just bundled up the updates into nice per-watch packages. Now, deliver them. for (auto& watch : watches_) { @@ -207,6 +210,11 @@ void WatchMap::onConfigUpdate( } } + // Execute external config validators. + config_validators_.executeValidators( + type_url_, reinterpret_cast&>(decoded_resources), + removed_resources); + // We just bundled up the updates into nice per-watch packages. Now, deliver them. for (const auto& [cur_watch, resource_to_add] : per_watch_added) { if (deferred_removed_during_update_->count(cur_watch) > 0) { diff --git a/source/common/config/watch_map.h b/source/common/config/watch_map.h index c7a9f14fef0e..7c0da75df7f5 100644 --- a/source/common/config/watch_map.h +++ b/source/common/config/watch_map.h @@ -9,6 +9,7 @@ #include "source/common/common/assert.h" #include "source/common/common/logger.h" +#include "source/common/config/custom_config_validators.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" @@ -60,7 +61,10 @@ struct Watch { // A WatchMap is assumed to be dedicated to a single type_url type of resource (EDS, CDS, etc). class WatchMap : public UntypedConfigUpdateCallbacks, public Logger::Loggable { public: - WatchMap(const bool use_namespace_matching) : use_namespace_matching_(use_namespace_matching) {} + WatchMap(const bool use_namespace_matching, const std::string& type_url, + CustomConfigValidators& config_validators) + : use_namespace_matching_(use_namespace_matching), type_url_(type_url), + config_validators_(config_validators) {} // Adds 'callbacks' to the WatchMap, with every possible resource being watched. // (Use updateWatchInterest() to narrow it down to some specific names). @@ -127,6 +131,8 @@ class WatchMap : public UntypedConfigUpdateCallbacks, public Logger::Loggable> watch_interest_; const bool use_namespace_matching_; + const std::string type_url_; + CustomConfigValidators& config_validators_; }; } // namespace Config diff --git a/source/common/config/xds_mux/grpc_mux_impl.cc b/source/common/config/xds_mux/grpc_mux_impl.cc index c3e2d496b7f0..a4864c0cbfd0 100644 --- a/source/common/config/xds_mux/grpc_mux_impl.cc +++ b/source/common/config/xds_mux/grpc_mux_impl.cc @@ -36,14 +36,12 @@ using AllMuxes = ThreadSafeSingleton; } // namespace template -GrpcMuxImpl::GrpcMuxImpl(std::unique_ptr subscription_state_factory, - bool skip_subsequent_node, - const LocalInfo::LocalInfo& local_info, - Grpc::RawAsyncClientPtr&& async_client, - Event::Dispatcher& dispatcher, - const Protobuf::MethodDescriptor& service_method, - Random::RandomGenerator& random, Stats::Scope& scope, - const RateLimitSettings& rate_limit_settings) +GrpcMuxImpl::GrpcMuxImpl( + std::unique_ptr subscription_state_factory, bool skip_subsequent_node, + const LocalInfo::LocalInfo& local_info, Grpc::RawAsyncClientPtr&& async_client, + Event::Dispatcher& dispatcher, const Protobuf::MethodDescriptor& service_method, + Random::RandomGenerator& random, Stats::Scope& scope, + const RateLimitSettings& rate_limit_settings, CustomConfigValidatorsPtr&& config_validators) : grpc_stream_(this, std::move(async_client), service_method, random, dispatcher, scope, rate_limit_settings), subscription_state_factory_(std::move(subscription_state_factory)), @@ -51,7 +49,8 @@ GrpcMuxImpl::GrpcMuxImpl(std::unique_ptr subscription_state_fac dynamic_update_callback_handle_(local_info.contextProvider().addDynamicContextUpdateCallback( [this](absl::string_view resource_type_url) { onDynamicContextUpdate(resource_type_url); - })) { + })), + config_validators_(std::move(config_validators)) { Config::Utility::checkLocalInfo("ads", local_info); AllMuxes::get().insert(this); } @@ -84,7 +83,9 @@ Config::GrpcMuxWatchPtr GrpcMuxImpl::addWatch( if (watch_map == watch_maps_.end()) { // We don't yet have a subscription for type_url! Make one! watch_map = - watch_maps_.emplace(type_url, std::make_unique(options.use_namespace_matching_)) + watch_maps_ + .emplace(type_url, std::make_unique(options.use_namespace_matching_, type_url, + *config_validators_.get())) .first; subscriptions_.emplace(type_url, subscription_state_factory_->makeSubscriptionState( type_url, *watch_maps_[type_url], resource_decoder)); @@ -359,10 +360,11 @@ GrpcMuxDelta::GrpcMuxDelta(Grpc::RawAsyncClientPtr&& async_client, Event::Dispat const Protobuf::MethodDescriptor& service_method, Random::RandomGenerator& random, Stats::Scope& scope, const RateLimitSettings& rate_limit_settings, - const LocalInfo::LocalInfo& local_info, bool skip_subsequent_node) + const LocalInfo::LocalInfo& local_info, bool skip_subsequent_node, + CustomConfigValidatorsPtr&& config_validators) : GrpcMuxImpl(std::make_unique(dispatcher), skip_subsequent_node, local_info, std::move(async_client), dispatcher, service_method, random, scope, - rate_limit_settings) {} + rate_limit_settings, std::move(config_validators)) {} // GrpcStreamCallbacks for GrpcMuxDelta void GrpcMuxDelta::requestOnDemandUpdate(const std::string& type_url, @@ -379,10 +381,11 @@ GrpcMuxSotw::GrpcMuxSotw(Grpc::RawAsyncClientPtr&& async_client, Event::Dispatch const Protobuf::MethodDescriptor& service_method, Random::RandomGenerator& random, Stats::Scope& scope, const RateLimitSettings& rate_limit_settings, - const LocalInfo::LocalInfo& local_info, bool skip_subsequent_node) + const LocalInfo::LocalInfo& local_info, bool skip_subsequent_node, + CustomConfigValidatorsPtr&& config_validators) : GrpcMuxImpl(std::make_unique(dispatcher), skip_subsequent_node, local_info, std::move(async_client), dispatcher, service_method, random, scope, - rate_limit_settings) {} + rate_limit_settings, std::move(config_validators)) {} Config::GrpcMuxWatchPtr NullGrpcMuxImpl::addWatch(const std::string&, const absl::flat_hash_set&, diff --git a/source/common/config/xds_mux/grpc_mux_impl.h b/source/common/config/xds_mux/grpc_mux_impl.h index 8e3fbf4ef23f..2821da7ce7fd 100644 --- a/source/common/config/xds_mux/grpc_mux_impl.h +++ b/source/common/config/xds_mux/grpc_mux_impl.h @@ -17,6 +17,7 @@ #include "source/common/common/logger.h" #include "source/common/common/utility.h" #include "source/common/config/api_version.h" +#include "source/common/config/custom_config_validators.h" #include "source/common/config/grpc_stream.h" #include "source/common/config/pausable_ack_queue.h" #include "source/common/config/watch_map.h" @@ -60,7 +61,8 @@ class GrpcMuxImpl : public GrpcStreamCallbacks, const LocalInfo::LocalInfo& local_info, Grpc::RawAsyncClientPtr&& async_client, Event::Dispatcher& dispatcher, const Protobuf::MethodDescriptor& service_method, Random::RandomGenerator& random, Stats::Scope& scope, - const RateLimitSettings& rate_limit_settings); + const RateLimitSettings& rate_limit_settings, + CustomConfigValidatorsPtr&& config_validators); ~GrpcMuxImpl() override; @@ -200,6 +202,7 @@ class GrpcMuxImpl : public GrpcStreamCallbacks, // this one is up to GrpcMux. const LocalInfo::LocalInfo& local_info_; Common::CallbackHandlePtr dynamic_update_callback_handle_; + CustomConfigValidatorsPtr config_validators_; // True iff Envoy is shutting down; no messages should be sent on the `grpc_stream_` when this is // true because it may contain dangling pointers. @@ -213,7 +216,8 @@ class GrpcMuxDelta : public GrpcMuxImpl&) override { diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index 0c1b847ec1fb..df90081e1558 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -99,6 +99,7 @@ envoy_cc_library( "//source/common/common:cleanup_lib", "//source/common/common:enum_to_int", "//source/common/common:utility_lib", + "//source/common/config:custom_config_validators_lib", "//source/common/config:grpc_mux_lib", "//source/common/config/xds_mux:grpc_mux_lib", "//source/common/config:subscription_factory_lib", diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 8d970fd6a7e6..470894e57d32 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -21,6 +21,7 @@ #include "source/common/common/enum_to_int.h" #include "source/common/common/fmt.h" #include "source/common/common/utility.h" +#include "source/common/config/custom_config_validators_impl.h" #include "source/common/config/new_grpc_mux_impl.h" #include "source/common/config/utility.h" #include "source/common/config/xds_mux/grpc_mux_impl.h" @@ -266,7 +267,8 @@ ClusterManagerImpl::ClusterManagerImpl( const LocalInfo::LocalInfo& local_info, AccessLog::AccessLogManager& log_manager, Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, ProtobufMessage::ValidationContext& validation_context, Api::Api& api, - Http::Context& http_context, Grpc::Context& grpc_context, Router::Context& router_context) + Http::Context& http_context, Grpc::Context& grpc_context, Router::Context& router_context, + const Server::Instance& server) : factory_(factory), runtime_(runtime), stats_(stats), tls_(tls), random_(api.randomGenerator()), bind_config_(bootstrap.cluster_manager().upstream_bind_config()), local_info_(local_info), @@ -285,7 +287,7 @@ ClusterManagerImpl::ClusterManagerImpl( cluster_request_response_size_stat_names_(stats.symbolTable()), cluster_timeout_budget_stat_names_(stats.symbolTable()), subscription_factory_(local_info, main_thread_dispatcher, *this, - validation_context.dynamicValidationVisitor(), api) { + validation_context.dynamicValidationVisitor(), api, server) { async_client_manager_ = std::make_unique( *this, tls, time_source_, api, grpc_context.statNames()); const auto& cm_config = bootstrap.cluster_manager(); @@ -337,6 +339,11 @@ ClusterManagerImpl::ClusterManagerImpl( // After here, we just have a GrpcMux interface held in ads_mux_, which hides // whether the backing implementation is delta or SotW. if (dyn_resources.has_ads_config()) { + Config::CustomConfigValidatorsPtr custom_config_validators = + std::make_unique( + validation_context.dynamicValidationVisitor(), server, + dyn_resources.ads_config().config_validators()); + if (dyn_resources.ads_config().api_type() == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC) { Config::Utility::checkTransportVersion(dyn_resources.ads_config()); @@ -351,7 +358,8 @@ ClusterManagerImpl::ClusterManagerImpl( "DeltaAggregatedResources"), random_, stats_, Envoy::Config::Utility::parseRateLimitSettings(dyn_resources.ads_config()), local_info, - dyn_resources.ads_config().set_node_on_first_message_only()); + dyn_resources.ads_config().set_node_on_first_message_only(), + std::move(custom_config_validators)); } else { ads_mux_ = std::make_shared( Config::Utility::factoryForGrpcApiConfigSource(*async_client_manager_, @@ -361,7 +369,8 @@ ClusterManagerImpl::ClusterManagerImpl( *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.DeltaAggregatedResources"), random_, stats_, - Envoy::Config::Utility::parseRateLimitSettings(dyn_resources.ads_config()), local_info); + Envoy::Config::Utility::parseRateLimitSettings(dyn_resources.ads_config()), local_info, + std::move(custom_config_validators)); } } else { Config::Utility::checkTransportVersion(dyn_resources.ads_config()); @@ -376,7 +385,8 @@ ClusterManagerImpl::ClusterManagerImpl( "StreamAggregatedResources"), random_, stats_, Envoy::Config::Utility::parseRateLimitSettings(dyn_resources.ads_config()), local_info, - bootstrap.dynamic_resources().ads_config().set_node_on_first_message_only()); + bootstrap.dynamic_resources().ads_config().set_node_on_first_message_only(), + std::move(custom_config_validators)); } else { ads_mux_ = std::make_shared( local_info, @@ -388,7 +398,8 @@ ClusterManagerImpl::ClusterManagerImpl( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), random_, stats_, Envoy::Config::Utility::parseRateLimitSettings(dyn_resources.ads_config()), - bootstrap.dynamic_resources().ads_config().set_node_on_first_message_only()); + bootstrap.dynamic_resources().ads_config().set_node_on_first_message_only(), + std::move(custom_config_validators)); } } } else { @@ -1807,7 +1818,7 @@ ClusterManagerPtr ProdClusterManagerFactory::clusterManagerFromProto( return ClusterManagerPtr{new ClusterManagerImpl( bootstrap, *this, stats_, tls_, context_.runtime(), context_.localInfo(), log_manager_, context_.mainThreadDispatcher(), context_.admin(), validation_context_, context_.api(), - http_context_, grpc_context_, router_context_)}; + http_context_, grpc_context_, router_context_, server_)}; } Http::ConnectionPool::InstancePtr ProdClusterManagerFactory::allocateConnPool( diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 01499c150000..086feab088ff 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -48,15 +48,18 @@ namespace Upstream { */ class ProdClusterManagerFactory : public ClusterManagerFactory { public: - ProdClusterManagerFactory( - Server::Admin& admin, Runtime::Loader& runtime, Stats::Store& stats, - ThreadLocal::Instance& tls, Network::DnsResolverSharedPtr dns_resolver, - Ssl::ContextManager& ssl_context_manager, Event::Dispatcher& main_thread_dispatcher, - const LocalInfo::LocalInfo& local_info, Secret::SecretManager& secret_manager, - ProtobufMessage::ValidationContext& validation_context, Api::Api& api, - Http::Context& http_context, Grpc::Context& grpc_context, Router::Context& router_context, - AccessLog::AccessLogManager& log_manager, Singleton::Manager& singleton_manager, - const Server::Options& options, Quic::QuicStatNames& quic_stat_names) + ProdClusterManagerFactory(Server::Admin& admin, Runtime::Loader& runtime, Stats::Store& stats, + ThreadLocal::Instance& tls, Network::DnsResolverSharedPtr dns_resolver, + Ssl::ContextManager& ssl_context_manager, + Event::Dispatcher& main_thread_dispatcher, + const LocalInfo::LocalInfo& local_info, + Secret::SecretManager& secret_manager, + ProtobufMessage::ValidationContext& validation_context, Api::Api& api, + Http::Context& http_context, Grpc::Context& grpc_context, + Router::Context& router_context, + AccessLog::AccessLogManager& log_manager, + Singleton::Manager& singleton_manager, const Server::Options& options, + Quic::QuicStatNames& quic_stat_names, const Server::Instance& server) : context_(options, main_thread_dispatcher, api, local_info, admin, runtime, singleton_manager, validation_context.staticValidationVisitor(), stats, tls), validation_context_(validation_context), http_context_(http_context), @@ -65,7 +68,8 @@ class ProdClusterManagerFactory : public ClusterManagerFactory { local_info_(local_info), secret_manager_(secret_manager), log_manager_(log_manager), singleton_manager_(singleton_manager), quic_stat_names_(quic_stat_names), alternate_protocols_cache_manager_factory_(singleton_manager, tls_, {context_}), - alternate_protocols_cache_manager_(alternate_protocols_cache_manager_factory_.get()) {} + alternate_protocols_cache_manager_(alternate_protocols_cache_manager_factory_.get()), + server_(server) {} // Upstream::ClusterManagerFactory ClusterManagerPtr @@ -111,6 +115,7 @@ class ProdClusterManagerFactory : public ClusterManagerFactory { Quic::QuicStatNames& quic_stat_names_; Http::AlternateProtocolsCacheManagerFactoryImpl alternate_protocols_cache_manager_factory_; Http::AlternateProtocolsCacheManagerSharedPtr alternate_protocols_cache_manager_; + const Server::Instance& server_; }; // For friend declaration in ClusterManagerInitHelper. @@ -242,7 +247,7 @@ class ClusterManagerImpl : public ClusterManager, Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, ProtobufMessage::ValidationContext& validation_context, Api::Api& api, Http::Context& http_context, Grpc::Context& grpc_context, - Router::Context& router_context); + Router::Context& router_context, const Server::Instance& server); std::size_t warmingClusterCount() const { return warming_clusters_.size(); } @@ -258,14 +263,24 @@ class ClusterManagerImpl : public ClusterManager, init_helper_.setInitializedCb(callback); } - ClusterInfoMaps clusters() override { + ClusterInfoMaps clusters() const override { ClusterInfoMaps clusters_maps; - for (auto& cluster : active_clusters_) { + for (const auto& cluster : active_clusters_) { clusters_maps.active_clusters_.emplace(cluster.first, *cluster.second->cluster_); + if (cluster.second->cluster_->info()->addedViaApi()) { + ++clusters_maps.added_via_api_clusters_num_; + } } - for (auto& cluster : warming_clusters_) { + for (const auto& cluster : warming_clusters_) { clusters_maps.warming_clusters_.emplace(cluster.first, *cluster.second->cluster_); + if (cluster.second->cluster_->info()->addedViaApi()) { + ++clusters_maps.added_via_api_clusters_num_; + } } + // The number of clusters that were added via API must be at most the number + // of active clusters + number of warming clusters. + ASSERT(clusters_maps.added_via_api_clusters_num_ <= + clusters_maps.active_clusters_.size() + clusters_maps.warming_clusters_.size()); return clusters_maps; } diff --git a/source/extensions/config/validators/minimum_clusters/BUILD b/source/extensions/config/validators/minimum_clusters/BUILD new file mode 100644 index 000000000000..a4e5eaae0edd --- /dev/null +++ b/source/extensions/config/validators/minimum_clusters/BUILD @@ -0,0 +1,34 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "minimum_clusters_validator", + srcs = ["minimum_clusters_validator.cc"], + hdrs = ["minimum_clusters_validator.h"], + deps = [ + "//envoy/config:config_validator_interface", + "//source/common/common:assert_lib", + "@envoy_api//envoy/extensions/config/validators/minimum_clusters/v3:pkg_cc_proto", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":minimum_clusters_validator", + "//envoy/config:config_validator_interface", + "//envoy/registry", + "//source/common/config:resource_name_lib", + "@envoy_api//envoy/extensions/config/validators/minimum_clusters/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/config/validators/minimum_clusters/config.cc b/source/extensions/config/validators/minimum_clusters/config.cc new file mode 100644 index 000000000000..03fd560edc41 --- /dev/null +++ b/source/extensions/config/validators/minimum_clusters/config.cc @@ -0,0 +1,45 @@ +#include "source/extensions/config/validators/minimum_clusters/config.h" + +#include "envoy/extensions/config/validators/minimum_clusters/v3/minimum_clusters.pb.h" +#include "envoy/extensions/config/validators/minimum_clusters/v3/minimum_clusters.pb.validate.h" +#include "envoy/registry/registry.h" + +#include "source/common/config/resource_name.h" + +namespace Envoy { +namespace Extensions { +namespace Config { +namespace Validators { + +Envoy::Config::ConfigValidatorPtr MinimumClustersValidatorFactory::createConfigValidator( + const ProtobufWkt::Any& config, ProtobufMessage::ValidationVisitor& validation_visitor) { + const auto& validator_config = MessageUtil::anyConvertAndValidate< + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator>( + config, validation_visitor); + + return std::make_unique(validator_config); +} + +Envoy::ProtobufTypes::MessagePtr MinimumClustersValidatorFactory::createEmptyConfigProto() { + return std::make_unique< + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator>(); +} + +std::string MinimumClustersValidatorFactory::name() const { + return absl::StrCat(category(), ".minimum_clusters_validator"); +} + +std::string MinimumClustersValidatorFactory::typeUrl() const { + return Envoy::Config::getTypeUrl(); +} + +/** + * Static registration for this config validator factory. @see RegisterFactory. + */ +REGISTER_FACTORY(MinimumClustersValidatorFactory, + Envoy::Config::ConfigValidatorFactory){"envoy.config.validators.minimum_clusters"}; + +} // namespace Validators +} // namespace Config +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/config/validators/minimum_clusters/config.h b/source/extensions/config/validators/minimum_clusters/config.h new file mode 100644 index 000000000000..b200892b81d6 --- /dev/null +++ b/source/extensions/config/validators/minimum_clusters/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include "envoy/config/config_validator.h" + +#include "source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.h" + +namespace Envoy { +namespace Extensions { +namespace Config { +namespace Validators { + +class MinimumClustersValidatorFactory : public Envoy::Config::ConfigValidatorFactory { +public: + MinimumClustersValidatorFactory() = default; + + Envoy::Config::ConfigValidatorPtr + createConfigValidator(const ProtobufWkt::Any& config, + ProtobufMessage::ValidationVisitor& validation_visitor) override; + + Envoy::ProtobufTypes::MessagePtr createEmptyConfigProto() override; + + std::string name() const override; + + std::string typeUrl() const override; +}; + +} // namespace Validators +} // namespace Config +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.cc b/source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.cc new file mode 100644 index 000000000000..3bb4ce9fcc0b --- /dev/null +++ b/source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.cc @@ -0,0 +1,85 @@ +#include "source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.h" + +#include "envoy/upstream/cluster_manager.h" + +#include "source/common/common/assert.h" + +namespace Envoy { +namespace Extensions { +namespace Config { +namespace Validators { + +void MinimumClustersValidator::validate( + const Server::Instance&, const std::vector& resources) { + absl::flat_hash_set next_cluster_names(resources.size()); + for (const auto& resource : resources) { + const envoy::config::cluster::v3::Cluster& cluster = + dynamic_cast(resource->resource()); + + // If the cluster was already added in the current update, it won't be added twice. + next_cluster_names.insert(cluster.name()); + } + + // After applying the update, the clusters names will be the same as in + // next_cluster_names. + if (next_cluster_names.size() < min_clusters_num_) { + throw EnvoyException("CDS update attempts to reduce clusters below configured minimum."); + } +} + +void MinimumClustersValidator::validate( + const Server::Instance& server, + const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources) { + const Upstream::ClusterManager& cm = server.clusterManager(); + // If the number of clusters after removing all of the clusters in the removed_resources list is + // above the threshold, then it is surely a valid config. + const Upstream::ClusterManager::ClusterInfoMaps& cur_clusters = cm.clusters(); + const uint32_t cur_clusters_num = cur_clusters.added_via_api_clusters_num_; + const uint32_t removed_resources_size = static_cast(removed_resources.size()); + if ((cur_clusters_num >= removed_resources_size) && + (cur_clusters_num - removed_resources_size >= min_clusters_num_)) { + return; + } + + // It could be that the removed clusters gets us below the threshold, simulate what happens if + // the current clusters list is updated. + uint32_t newly_added_clusters_num = 0; + absl::flat_hash_set added_cluster_names(added_resources.size()); + for (const auto& resource : added_resources) { + const envoy::config::cluster::v3::Cluster& cluster = + dynamic_cast(resource->resource()); + + // If the cluster was already added in the current update, skip this cluster. + if (!added_cluster_names.insert(cluster.name()).second) { + continue; + } + // If the cluster is new, count it. + if (!cur_clusters.hasCluster(cluster.name())) { + ++newly_added_clusters_num; + } + } + + // Count the clusters that need to be removed. + uint32_t removed_clusters_num = 0; + for (const auto& removed_cluster : removed_resources) { + Upstream::ClusterConstOptRef cluster = cur_clusters.getCluster(removed_cluster); + // Only clusters that were added via api can be removed. + if (cluster.has_value() && cluster->get().info()->addedViaApi()) { + ++removed_clusters_num; + } + } + + // Prevent integer overflow. + ASSERT(cur_clusters_num >= removed_clusters_num); + const uint64_t new_clusters_num = + static_cast(cur_clusters_num) + newly_added_clusters_num - removed_clusters_num; + if (new_clusters_num < min_clusters_num_) { + throw EnvoyException("CDS update attempts to reduce clusters below configured minimum."); + } +} + +} // namespace Validators +} // namespace Config +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.h b/source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.h new file mode 100644 index 000000000000..d36d0153cb8d --- /dev/null +++ b/source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.h @@ -0,0 +1,37 @@ +#pragma once + +#include "envoy/config/config_validator.h" +#include "envoy/extensions/config/validators/minimum_clusters/v3/minimum_clusters.pb.h" + +namespace Envoy { +namespace Extensions { +namespace Config { +namespace Validators { + +/** + * A config validator extension that validates that the number of clusters do + * not decrease below some threshold. + */ +class MinimumClustersValidator : public Envoy::Config::ConfigValidator { +public: + MinimumClustersValidator( + const envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator& + config) + : min_clusters_num_(config.min_clusters_num()) {} + + // ConfigValidator + void validate(const Server::Instance& server, + const std::vector& resources) override; + + void validate(const Server::Instance& server, + const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources) override; + +private: + const uint64_t min_clusters_num_; +}; + +} // namespace Validators +} // namespace Config +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 57d6fd5eacdd..068f98e63f27 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -29,6 +29,12 @@ EXTENSIONS = { "envoy.compression.brotli.compressor": "//source/extensions/compression/brotli/compressor:config", "envoy.compression.brotli.decompressor": "//source/extensions/compression/brotli/decompressor:config", + # + # Config validators + # + + "envoy.config.validators.minimum_clusters": "//source/extensions/config/validators/minimum_clusters:config", + # # gRPC Credentials Plugins # diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 480bae00374b..bbd0eb1d956f 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -78,6 +78,11 @@ envoy.compression.gzip.decompressor: - envoy.compression.decompressor security_posture: robust_to_untrusted_downstream status: stable +envoy.config.validators.minimum_clusters: + categories: + - envoy.config.validators + security_posture: unknown + status: stable envoy.filters.http.adaptive_concurrency: categories: - envoy.filters.http diff --git a/source/server/config_validation/cluster_manager.cc b/source/server/config_validation/cluster_manager.cc index 742fb4bb3b5e..7404adf329cb 100644 --- a/source/server/config_validation/cluster_manager.cc +++ b/source/server/config_validation/cluster_manager.cc @@ -13,7 +13,7 @@ ClusterManagerPtr ValidationClusterManagerFactory::clusterManagerFromProto( return std::make_unique( bootstrap, *this, stats_, tls_, context_.runtime(), local_info_, log_manager_, context_.mainThreadDispatcher(), admin_, validation_context_, context_.api(), http_context_, - grpc_context_, router_context_); + grpc_context_, router_context_, server_); } CdsApiPtr ValidationClusterManagerFactory::createCds( diff --git a/source/server/config_validation/cluster_manager.h b/source/server/config_validation/cluster_manager.h index 0cfc3fa31dff..b8a4ad553d86 100644 --- a/source/server/config_validation/cluster_manager.h +++ b/source/server/config_validation/cluster_manager.h @@ -28,11 +28,12 @@ class ValidationClusterManagerFactory : public ProdClusterManagerFactory { ProtobufMessage::ValidationContext& validation_context, Api::Api& api, Http::Context& http_context, Grpc::Context& grpc_context, Router::Context& router_context, AccessLog::AccessLogManager& log_manager, Singleton::Manager& singleton_manager, - const Server::Options& options, Quic::QuicStatNames& quic_stat_names) + const Server::Options& options, Quic::QuicStatNames& quic_stat_names, + Server::Instance& server) : ProdClusterManagerFactory( admin, runtime, stats, tls, dns_resolver, ssl_context_manager, main_thread_dispatcher, local_info, secret_manager, validation_context, api, http_context, grpc_context, - router_context, log_manager, singleton_manager, options, quic_stat_names), + router_context, log_manager, singleton_manager, options, quic_stat_names, server), grpc_context_(grpc_context), router_context_(router_context) {} ClusterManagerPtr diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc index 7dcf1596703c..5cb7c97b8e0f 100644 --- a/source/server/config_validation/server.cc +++ b/source/server/config_validation/server.cc @@ -108,7 +108,7 @@ void ValidationInstance::initialize(const Options& options, admin(), runtime(), stats(), threadLocal(), dnsResolver(), sslContextManager(), dispatcher(), localInfo(), *secret_manager_, messageValidationContext(), *api_, http_context_, grpc_context_, router_context_, accessLogManager(), singletonManager(), options, - quic_stat_names_); + quic_stat_names_, *this); config_.initialize(bootstrap_, *this, *cluster_manager_factory_); runtime().initialize(clusterManager()); clusterManager().setInitializedCb([this]() -> void { init_manager_.initialize(init_watcher_); }); diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index a8fa7aba5f31..1c5c4cae80b4 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -74,6 +74,9 @@ class ValidationInstance final : Logger::Loggable, Admin& admin() override { return *admin_; } Api::Api& api() override { return *api_; } Upstream::ClusterManager& clusterManager() override { return *config_.clusterManager(); } + const Upstream::ClusterManager& clusterManager() const override { + return *config_.clusterManager(); + } Ssl::ContextManager& sslContextManager() override { return *ssl_context_manager_; } Event::Dispatcher& dispatcher() override { return *dispatcher_; } Network::DnsResolverSharedPtr dnsResolver() override { diff --git a/source/server/configuration_impl.h b/source/server/configuration_impl.h index 792a106e26f5..1539639d7f81 100644 --- a/source/server/configuration_impl.h +++ b/source/server/configuration_impl.h @@ -114,6 +114,7 @@ class MainImpl : Logger::Loggable, public Main { // Server::Configuration::Main Upstream::ClusterManager* clusterManager() override { return cluster_manager_.get(); } + const Upstream::ClusterManager* clusterManager() const override { return cluster_manager_.get(); } StatsConfig& statsConfig() override { return *stats_config_; } const Watchdog& mainThreadWatchdogConfig() const override { return *main_thread_watchdog_; } const Watchdog& workerWatchdogConfig() const override { return *worker_watchdog_; } diff --git a/source/server/server.cc b/source/server/server.cc index 9871b5662e40..f95a4fd99d5c 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -167,6 +167,11 @@ Upstream::ClusterManager& InstanceImpl::clusterManager() { return *config_.clusterManager(); } +const Upstream::ClusterManager& InstanceImpl::clusterManager() const { + ASSERT(config_.clusterManager() != nullptr); + return *config_.clusterManager(); +} + void InstanceImpl::drainListeners() { ENVOY_LOG(info, "closing and draining listeners"); listener_manager_->stopListeners(ListenerManager::StopListenersType::All); @@ -655,7 +660,7 @@ void InstanceImpl::initialize(Network::Address::InstanceConstSharedPtr local_add *admin_, runtime(), stats_store_, thread_local_, dns_resolver_, *ssl_context_manager_, *dispatcher_, *local_info_, *secret_manager_, messageValidationContext(), *api_, http_context_, grpc_context_, router_context_, access_log_manager_, *singleton_manager_, - options_, quic_stat_names_); + options_, quic_stat_names_, *this); // Now the configuration gets parsed. The configuration may start setting // thread local data per above. See MainImpl::initialize() for why ConfigImpl diff --git a/source/server/server.h b/source/server/server.h index cef5cba3015b..3d93279e1ea7 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -246,6 +246,7 @@ class InstanceImpl final : Logger::Loggable, Admin& admin() override { return *admin_; } Api::Api& api() override { return *api_; } Upstream::ClusterManager& clusterManager() override; + const Upstream::ClusterManager& clusterManager() const override; Ssl::ContextManager& sslContextManager() override { return *ssl_context_manager_; } Event::Dispatcher& dispatcher() override { return *dispatcher_; } Network::DnsResolverSharedPtr dnsResolver() override { return dns_resolver_; } diff --git a/test/common/config/BUILD b/test/common/config/BUILD index ba835b57e290..976ed6ede867 100644 --- a/test/common/config/BUILD +++ b/test/common/config/BUILD @@ -161,6 +161,7 @@ envoy_cc_test( "//test/common/stats:stat_test_utility_lib", "//test/mocks:common_lib", "//test/mocks/config:config_mocks", + "//test/mocks/config:custom_config_validators_mocks", "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", @@ -187,6 +188,7 @@ envoy_cc_test( "//test/config:v2_link_hacks", "//test/mocks:common_lib", "//test/mocks/config:config_mocks", + "//test/mocks/config:custom_config_validators_mocks", "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", @@ -213,6 +215,7 @@ envoy_cc_test( "//test/config:v2_link_hacks", "//test/mocks:common_lib", "//test/mocks/config:config_mocks", + "//test/mocks/config:custom_config_validators_mocks", "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", @@ -264,6 +267,7 @@ envoy_cc_test_library( "//source/common/config:grpc_subscription_lib", "//source/common/config/xds_mux:grpc_mux_lib", "//test/mocks/config:config_mocks", + "//test/mocks/config:custom_config_validators_mocks", "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", @@ -286,6 +290,7 @@ envoy_cc_test_library( "//source/common/config/xds_mux:grpc_mux_lib", "//source/common/grpc:common_lib", "//test/mocks/config:config_mocks", + "//test/mocks/config:custom_config_validators_mocks", "//test/mocks/event:event_mocks", "//test/mocks/grpc:grpc_mocks", "//test/mocks/local_info:local_info_mocks", @@ -350,6 +355,7 @@ envoy_cc_test( "//test/mocks/filesystem:filesystem_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/protobuf:protobuf_mocks", + "//test/mocks/server:instance_mocks", "//test/mocks/stats:stats_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/test_common:environment_lib", @@ -470,6 +476,7 @@ envoy_cc_test( deps = [ "//source/common/config:watch_map_lib", "//test/mocks/config:config_mocks", + "//test/mocks/config:custom_config_validators_mocks", "//test/test_common:utility_lib", "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", @@ -558,3 +565,13 @@ envoy_cc_test( "//test/mocks/filesystem:filesystem_mocks", ], ) + +envoy_cc_test( + name = "custom_config_validators_impl_test", + srcs = ["custom_config_validators_impl_test.cc"], + deps = [ + "//test/mocks/protobuf:protobuf_mocks", + "//test/mocks/server:instance_mocks", + "//test/test_common:registry_lib", + ], +) diff --git a/test/common/config/custom_config_validators_impl_test.cc b/test/common/config/custom_config_validators_impl_test.cc new file mode 100644 index 000000000000..2a68d5ccdf94 --- /dev/null +++ b/test/common/config/custom_config_validators_impl_test.cc @@ -0,0 +1,184 @@ +#include "source/common/config/custom_config_validators_impl.h" + +#include "test/mocks/protobuf/mocks.h" +#include "test/mocks/server/instance.h" +#include "test/test_common/registry.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Config { +namespace { + +class FakeConfigValidator : public ConfigValidator { +public: + FakeConfigValidator(bool should_reject) : should_reject_(should_reject) {} + + // ConfigValidator + void validate(const Server::Instance&, + const std::vector&) override { + if (should_reject_) { + throw EnvoyException("Emulating fake action throw exception (SotW)"); + } + } + + void validate(const Server::Instance&, const std::vector&, + const Protobuf::RepeatedPtrField&) override { + if (should_reject_) { + throw EnvoyException("Emulating fake action throw exception (Delta)"); + } + } + + bool should_reject_; +}; + +class FakeConfigValidatorFactory : public ConfigValidatorFactory { +public: + FakeConfigValidatorFactory(bool should_reject) : should_reject_(should_reject) {} + + ConfigValidatorPtr createConfigValidator(const ProtobufWkt::Any&, + ProtobufMessage::ValidationVisitor&) override { + return std::make_unique(should_reject_); + } + + Envoy::ProtobufTypes::MessagePtr createEmptyConfigProto() override { + // Using Struct instead of a custom empty config proto. This is only allowed in tests. + return ProtobufTypes::MessagePtr{new Envoy::ProtobufWkt::Struct()}; + } + + std::string name() const override { + return absl::StrCat(category(), ".fake_config_validator_", + should_reject_ ? "reject" : "accept"); + } + + std::string typeUrl() const override { + return Envoy::Config::getTypeUrl(); + } + + bool should_reject_; +}; + +class CustomConfigValidatorsImplTest : public testing::Test { +public: + CustomConfigValidatorsImplTest() + : factory_accept_(false), factory_reject_(true), register_factory_accept_(factory_accept_), + register_factory_reject_(factory_reject_) {} + + static envoy::config::core::v3::TypedExtensionConfig parseConfig(const std::string& config) { + envoy::config::core::v3::TypedExtensionConfig proto; + TestUtility::loadFromYaml(config, proto); + return proto; + } + + FakeConfigValidatorFactory factory_accept_; + FakeConfigValidatorFactory factory_reject_; + Registry::InjectFactory register_factory_accept_; + Registry::InjectFactory register_factory_reject_; + testing::NiceMock validation_visitor_; + const testing::NiceMock server_; + const std::string type_url_{Envoy::Config::getTypeUrl()}; + + static constexpr char AcceptValidatorConfig[] = + "name: envoy.config.validators.fake_config_validator_accept"; + static constexpr char RejectValidatorConfig[] = + "name: envoy.config.validators.fake_config_validator_reject"; +}; + +// Validates that empty config that has no validators is always accepted. +TEST_F(CustomConfigValidatorsImplTest, EmptyConfigValidator) { + const Protobuf::RepeatedPtrField empty_list; + CustomConfigValidatorsImpl validators(validation_visitor_, server_, empty_list); + { + const std::vector resources; + validators.executeValidators(type_url_, resources); + } + { + const std::vector added_resources; + const Protobuf::RepeatedPtrField removed_resources; + validators.executeValidators(type_url_, added_resources, removed_resources); + } +} + +// Validates that a config that creates a validator that accepts does so. +TEST_F(CustomConfigValidatorsImplTest, AcceptConfigValidator) { + Protobuf::RepeatedPtrField configs_list; + auto* entry = configs_list.Add(); + *entry = parseConfig(AcceptValidatorConfig); + CustomConfigValidatorsImpl validators(validation_visitor_, server_, configs_list); + { + const std::vector resources; + validators.executeValidators(type_url_, resources); + } + { + const std::vector added_resources; + const Protobuf::RepeatedPtrField removed_resources; + validators.executeValidators(type_url_, added_resources, removed_resources); + } +} + +// Validates that a config that creates a validator that rejects throws an exception. +TEST_F(CustomConfigValidatorsImplTest, RejectConfigValidator) { + Protobuf::RepeatedPtrField configs_list; + auto* entry = configs_list.Add(); + *entry = parseConfig(RejectValidatorConfig); + CustomConfigValidatorsImpl validators(validation_visitor_, server_, configs_list); + { + const std::vector resources; + EXPECT_THROW_WITH_MESSAGE(validators.executeValidators(type_url_, resources), EnvoyException, + "Emulating fake action throw exception (SotW)"); + } + { + const std::vector added_resources; + const Protobuf::RepeatedPtrField removed_resources; + EXPECT_THROW_WITH_MESSAGE( + validators.executeValidators(type_url_, added_resources, removed_resources), EnvoyException, + "Emulating fake action throw exception (Delta)"); + } +} + +// Validates that a config that contains a validator that accepts, followed +// by a validator that rejects, throws an exception. +TEST_F(CustomConfigValidatorsImplTest, AcceptThenRejectConfigValidators) { + Protobuf::RepeatedPtrField configs_list; + auto* entry1 = configs_list.Add(); + *entry1 = parseConfig(AcceptValidatorConfig); + auto* entry2 = configs_list.Add(); + *entry2 = parseConfig(RejectValidatorConfig); + CustomConfigValidatorsImpl validators(validation_visitor_, server_, configs_list); + { + const std::vector resources; + EXPECT_THROW_WITH_MESSAGE(validators.executeValidators(type_url_, resources), EnvoyException, + "Emulating fake action throw exception (SotW)"); + } + { + const std::vector added_resources; + const Protobuf::RepeatedPtrField removed_resources; + EXPECT_THROW_WITH_MESSAGE( + validators.executeValidators(type_url_, added_resources, removed_resources), EnvoyException, + "Emulating fake action throw exception (Delta)"); + } +} + +// Validates that a config that creates a validator that rejects on +// different type_url isn't rejected. +TEST_F(CustomConfigValidatorsImplTest, ReturnFalseDifferentTypeConfigValidator) { + const std::string type_url{ + Envoy::Config::getTypeUrl()}; + Protobuf::RepeatedPtrField configs_list; + auto* entry = configs_list.Add(); + *entry = parseConfig(RejectValidatorConfig); + CustomConfigValidatorsImpl validators(validation_visitor_, server_, configs_list); + { + const std::vector resources; + validators.executeValidators(type_url, resources); + } + { + const std::vector added_resources; + const Protobuf::RepeatedPtrField removed_resources; + validators.executeValidators(type_url, added_resources, removed_resources); + } +} + +} // namespace +} // namespace Config +} // namespace Envoy diff --git a/test/common/config/delta_subscription_impl_test.cc b/test/common/config/delta_subscription_impl_test.cc index 159cef64cfae..6dcd3344f86e 100644 --- a/test/common/config/delta_subscription_impl_test.cc +++ b/test/common/config/delta_subscription_impl_test.cc @@ -150,11 +150,13 @@ TEST_P(DeltaSubscriptionNoGrpcStreamTest, NoGrpcStream) { if (GetParam() == LegacyOrUnified::Unified) { xds_context = std::make_shared( std::unique_ptr(async_client), dispatcher, *method_descriptor, - random, stats_store, rate_limit_settings, local_info, false); + random, stats_store, rate_limit_settings, local_info, false, + std::make_unique>()); } else { xds_context = std::make_shared( std::unique_ptr(async_client), dispatcher, *method_descriptor, - random, stats_store, rate_limit_settings, local_info); + random, stats_store, rate_limit_settings, local_info, + std::make_unique>()); } GrpcSubscriptionImplPtr subscription = std::make_unique( diff --git a/test/common/config/delta_subscription_test_harness.h b/test/common/config/delta_subscription_test_harness.h index 2c5d5750d1ba..dd4c861aac3e 100644 --- a/test/common/config/delta_subscription_test_harness.h +++ b/test/common/config/delta_subscription_test_harness.h @@ -14,6 +14,7 @@ #include "test/common/config/subscription_test_harness.h" #include "test/mocks/common.h" +#include "test/mocks/config/custom_config_validators.h" #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" @@ -47,11 +48,13 @@ class DeltaSubscriptionTestHarness : public SubscriptionTestHarness { if (should_use_unified_) { xds_context_ = std::make_shared( std::unique_ptr(async_client_), dispatcher_, *method_descriptor_, - random_, stats_store_, rate_limit_settings_, local_info_, false); + random_, stats_store_, rate_limit_settings_, local_info_, false, + std::make_unique>()); } else { xds_context_ = std::make_shared( std::unique_ptr(async_client_), dispatcher_, *method_descriptor_, - random_, stats_store_, rate_limit_settings_, local_info_); + random_, stats_store_, rate_limit_settings_, local_info_, + std::make_unique>()); } subscription_ = std::make_unique( xds_context_, callbacks_, resource_decoder_, stats_, diff --git a/test/common/config/grpc_mux_impl_test.cc b/test/common/config/grpc_mux_impl_test.cc index 8fbd8de8cee0..f5cfda6a4f4d 100644 --- a/test/common/config/grpc_mux_impl_test.cc +++ b/test/common/config/grpc_mux_impl_test.cc @@ -14,6 +14,7 @@ #include "test/common/stats/stat_test_utility.h" #include "test/mocks/common.h" +#include "test/mocks/config/custom_config_validators.h" #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" @@ -47,6 +48,7 @@ class GrpcMuxImplTestBase : public testing::Test { public: GrpcMuxImplTestBase() : async_client_(new Grpc::MockAsyncClient()), + config_validators_(std::make_unique>()), control_plane_connected_state_( stats_.gauge("control_plane.connected_state", Stats::Gauge::ImportMode::NeverImport)), control_plane_pending_requests_( @@ -59,7 +61,7 @@ class GrpcMuxImplTestBase : public testing::Test { local_info_, std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, rate_limit_settings_, true); + random_, stats_, rate_limit_settings_, true, std::move(config_validators_)); } void setup(const RateLimitSettings& custom_rate_limit_settings) { @@ -67,7 +69,7 @@ class GrpcMuxImplTestBase : public testing::Test { local_info_, std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, custom_rate_limit_settings, true); + random_, stats_, custom_rate_limit_settings, true, std::move(config_validators_)); } void expectSendMessage(const std::string& type_url, @@ -100,6 +102,7 @@ class GrpcMuxImplTestBase : public testing::Test { NiceMock local_info_; Grpc::MockAsyncClient* async_client_; Grpc::MockAsyncStream async_stream_; + CustomConfigValidatorsPtr config_validators_; GrpcMuxImplPtr grpc_mux_; NiceMock callbacks_; NiceMock resource_decoder_; @@ -881,7 +884,8 @@ TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyClusterName) { local_info_, std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, rate_limit_settings_, true), + random_, stats_, rate_limit_settings_, true, + std::make_unique>()), EnvoyException, "ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via " "--service-node and --service-cluster options."); @@ -894,7 +898,8 @@ TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyNodeName) { local_info_, std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, rate_limit_settings_, true), + random_, stats_, rate_limit_settings_, true, + std::make_unique>()), EnvoyException, "ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via " "--service-node and --service-cluster options."); diff --git a/test/common/config/grpc_subscription_test_harness.h b/test/common/config/grpc_subscription_test_harness.h index 85bbcb5c6c2f..1b712f236139 100644 --- a/test/common/config/grpc_subscription_test_harness.h +++ b/test/common/config/grpc_subscription_test_harness.h @@ -14,6 +14,7 @@ #include "source/common/config/xds_mux/grpc_mux_impl.h" #include "test/common/config/subscription_test_harness.h" +#include "test/mocks/config/custom_config_validators.h" #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" @@ -43,6 +44,7 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { : method_descriptor_(Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.endpoint.v3.EndpointDiscoveryService.StreamEndpoints")), async_client_(new NiceMock()), + config_validators_(std::make_unique>()), should_use_unified_(legacy_or_unified == Envoy::Config::LegacyOrUnified::Unified) { node_.set_id("fo0"); EXPECT_CALL(local_info_, node()).WillRepeatedly(testing::ReturnRef(node_)); @@ -53,11 +55,13 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { if (should_use_unified_) { mux_ = std::make_shared( std::unique_ptr(async_client_), dispatcher_, *method_descriptor_, - random_, stats_store_, rate_limit_settings_, local_info_, true); + random_, stats_store_, rate_limit_settings_, local_info_, true, + std::move(config_validators_)); } else { mux_ = std::make_shared( local_info_, std::unique_ptr(async_client_), dispatcher_, - *method_descriptor_, random_, stats_store_, rate_limit_settings_, true); + *method_descriptor_, random_, stats_store_, rate_limit_settings_, true, + std::move(config_validators_)); } subscription_ = std::make_unique( mux_, callbacks_, resource_decoder_, stats_, Config::TypeUrl::get().ClusterLoadAssignment, @@ -220,6 +224,7 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder_{"cluster_name"}; NiceMock local_info_; + CustomConfigValidatorsPtr config_validators_; NiceMock async_stream_; GrpcMuxSharedPtr mux_; GrpcSubscriptionImplPtr subscription_; diff --git a/test/common/config/new_grpc_mux_impl_test.cc b/test/common/config/new_grpc_mux_impl_test.cc index f02373044613..b1f0c789f94d 100644 --- a/test/common/config/new_grpc_mux_impl_test.cc +++ b/test/common/config/new_grpc_mux_impl_test.cc @@ -15,6 +15,7 @@ #include "test/common/stats/stat_test_utility.h" #include "test/config/v2_link_hacks.h" #include "test/mocks/common.h" +#include "test/mocks/config/custom_config_validators.h" #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" @@ -50,6 +51,7 @@ class NewGrpcMuxImplTestBase : public testing::TestWithParam { public: NewGrpcMuxImplTestBase(LegacyOrUnified legacy_or_unified) : async_client_(new Grpc::MockAsyncClient()), + config_validators_(std::make_unique>()), control_plane_stats_(Utility::generateControlPlaneStats(stats_)), control_plane_connected_state_( stats_.gauge("control_plane.connected_state", Stats::Gauge::ImportMode::NeverImport)), @@ -61,14 +63,14 @@ class NewGrpcMuxImplTestBase : public testing::TestWithParam { std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, rate_limit_settings_, local_info_, false); + random_, stats_, rate_limit_settings_, local_info_, false, std::move(config_validators_)); return; } grpc_mux_ = std::make_unique( std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, rate_limit_settings_, local_info_); + random_, stats_, rate_limit_settings_, local_info_, std::move(config_validators_)); } void expectSendMessage(const std::string& type_url, @@ -151,6 +153,7 @@ class NewGrpcMuxImplTestBase : public testing::TestWithParam { NiceMock random_; Grpc::MockAsyncClient* async_client_; NiceMock async_stream_; + CustomConfigValidatorsPtr config_validators_; NiceMock local_info_; std::unique_ptr grpc_mux_; NiceMock callbacks_; diff --git a/test/common/config/subscription_factory_impl_test.cc b/test/common/config/subscription_factory_impl_test.cc index 334b39c5805d..70585c254fbd 100644 --- a/test/common/config/subscription_factory_impl_test.cc +++ b/test/common/config/subscription_factory_impl_test.cc @@ -17,6 +17,7 @@ #include "test/mocks/filesystem/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" +#include "test/mocks/server/instance.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/upstream/cluster_manager.h" #include "test/test_common/environment.h" @@ -43,7 +44,7 @@ class SubscriptionFactoryTest : public testing::Test { SubscriptionFactoryTest() : http_request_(&cm_.thread_local_cluster_.async_client_), api_(Api::createApiForTest(stats_store_, random_)), - subscription_factory_(local_info_, dispatcher_, cm_, validation_visitor_, *api_) {} + subscription_factory_(local_info_, dispatcher_, cm_, validation_visitor_, *api_, server_) {} SubscriptionPtr subscriptionFromConfigSource(const envoy::config::core::v3::ConfigSource& config) { @@ -69,6 +70,7 @@ class SubscriptionFactoryTest : public testing::Test { Http::MockAsyncClientRequest http_request_; Stats::MockIsolatedStatsStore stats_store_; NiceMock local_info_; + NiceMock server_; NiceMock validation_visitor_; Api::ApiPtr api_; SubscriptionFactoryImpl subscription_factory_; diff --git a/test/common/config/watch_map_test.cc b/test/common/config/watch_map_test.cc index 1d8398091b05..76cfe3176836 100644 --- a/test/common/config/watch_map_test.cc +++ b/test/common/config/watch_map_test.cc @@ -8,6 +8,7 @@ #include "source/common/config/watch_map.h" +#include "test/mocks/config/custom_config_validators.h" #include "test/mocks/config/mocks.h" #include "test/test_common/utility.h" @@ -125,7 +126,8 @@ TEST(WatchMapTest, Basic) { MockSubscriptionCallbacks callbacks; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); - WatchMap watch_map(false); + NiceMock config_validators; + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); Watch* watch = watch_map.addWatch(callbacks, resource_decoder); { @@ -198,7 +200,8 @@ TEST(WatchMapTest, Overlap) { MockSubscriptionCallbacks callbacks2; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); - WatchMap watch_map(false); + NiceMock config_validators; + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); Watch* watch1 = watch_map.addWatch(callbacks1, resource_decoder); Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); @@ -261,7 +264,7 @@ TEST(WatchMapTest, Overlap) { // WatchMap defers deletes and doesn't crash. class SameWatchRemoval : public testing::Test { public: - SameWatchRemoval() : watch_map_(false) {} + SameWatchRemoval() : watch_map_(false, "ClusterLoadAssignmentType", config_validators) {} void SetUp() override { envoy::config::endpoint::v3::ClusterLoadAssignment alice; @@ -282,6 +285,7 @@ class SameWatchRemoval : public testing::Test { TestUtility::TestOpaqueResourceDecoderImpl resource_decoder_{"cluster_name"}; + NiceMock config_validators; WatchMap watch_map_; NiceMock callbacks1_; MockSubscriptionCallbacks callbacks2_; @@ -338,7 +342,8 @@ TEST(WatchMapTest, AddRemoveAdd) { MockSubscriptionCallbacks callbacks2; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); - WatchMap watch_map(false); + NiceMock config_validators; + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); Watch* watch1 = watch_map.addWatch(callbacks1, resource_decoder); Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); @@ -394,7 +399,8 @@ TEST(WatchMapTest, UninterestingUpdate) { MockSubscriptionCallbacks callbacks; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); - WatchMap watch_map(false); + NiceMock config_validators; + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); Watch* watch = watch_map.addWatch(callbacks, resource_decoder); watch_map.updateWatchInterest(watch, {"alice"}); @@ -438,7 +444,8 @@ TEST(WatchMapTest, WatchingEverything) { MockSubscriptionCallbacks callbacks2; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); - WatchMap watch_map(false); + NiceMock config_validators; + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); /*Watch* watch1 = */ watch_map.addWatch(callbacks1, resource_decoder); Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); // watch1 never specifies any names, and so is treated as interested in everything. @@ -474,7 +481,8 @@ TEST(WatchMapTest, DeltaOnConfigUpdate) { MockSubscriptionCallbacks callbacks3; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); - WatchMap watch_map(false); + NiceMock config_validators; + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); Watch* watch1 = watch_map.addWatch(callbacks1, resource_decoder); Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); Watch* watch3 = watch_map.addWatch(callbacks3, resource_decoder); @@ -507,7 +515,8 @@ TEST(WatchMapTest, DeltaOnConfigUpdate) { } TEST(WatchMapTest, OnConfigUpdateFailed) { - WatchMap watch_map(false); + NiceMock config_validators; + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); // calling on empty map doesn't break watch_map.onConfigUpdateFailed(ConfigUpdateFailureReason::UpdateRejected, nullptr); @@ -528,7 +537,8 @@ TEST(WatchMapTest, OnConfigUpdateXdsTpGlobCollections) { MockSubscriptionCallbacks callbacks; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); - WatchMap watch_map(false); + NiceMock config_validators; + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); Watch* watch = watch_map.addWatch(callbacks, resource_decoder); watch_map.updateWatchInterest(watch, {"xdstp://foo/bar/baz/*?some=thing&thing=some"}); @@ -572,7 +582,8 @@ TEST(WatchMapTest, OnConfigUpdateXdsTpSingletons) { MockSubscriptionCallbacks callbacks; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); - WatchMap watch_map(false); + NiceMock config_validators; + WatchMap watch_map(false, "ClusterLoadAssignmentType", config_validators); Watch* watch = watch_map.addWatch(callbacks, resource_decoder); watch_map.updateWatchInterest(watch, {"xdstp://foo/bar/baz?some=thing&thing=some"}); @@ -612,7 +623,8 @@ TEST(WatchMapTest, OnConfigUpdateUsingNamespaces) { MockSubscriptionCallbacks callbacks3; TestUtility::TestOpaqueResourceDecoderImpl resource_decoder("cluster_name"); - WatchMap watch_map(true); + NiceMock config_validators; + WatchMap watch_map(true, "ClusterLoadAssignmentType", config_validators); Watch* watch1 = watch_map.addWatch(callbacks1, resource_decoder); Watch* watch2 = watch_map.addWatch(callbacks2, resource_decoder); Watch* watch3 = watch_map.addWatch(callbacks3, resource_decoder); diff --git a/test/common/config/xds_grpc_mux_impl_test.cc b/test/common/config/xds_grpc_mux_impl_test.cc index e1d56a14c017..08ff8449184e 100644 --- a/test/common/config/xds_grpc_mux_impl_test.cc +++ b/test/common/config/xds_grpc_mux_impl_test.cc @@ -16,6 +16,7 @@ #include "test/common/stats/stat_test_utility.h" #include "test/config/v2_link_hacks.h" #include "test/mocks/common.h" +#include "test/mocks/config/custom_config_validators.h" #include "test/mocks/config/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/grpc/mocks.h" @@ -50,6 +51,7 @@ class GrpcMuxImplTestBase : public testing::Test { public: GrpcMuxImplTestBase() : async_client_(new Grpc::MockAsyncClient()), + config_validators_(std::make_unique>()), control_plane_stats_(Utility::generateControlPlaneStats(stats_)), control_plane_connected_state_( stats_.gauge("control_plane.connected_state", Stats::Gauge::ImportMode::NeverImport)), @@ -61,7 +63,7 @@ class GrpcMuxImplTestBase : public testing::Test { std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, rate_limit_settings_, local_info_, true); + random_, stats_, rate_limit_settings_, local_info_, true, std::move(config_validators_)); } void setup(const RateLimitSettings& custom_rate_limit_settings) { @@ -69,7 +71,8 @@ class GrpcMuxImplTestBase : public testing::Test { std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, custom_rate_limit_settings, local_info_, true); + random_, stats_, custom_rate_limit_settings, local_info_, true, + std::move(config_validators_)); } void expectSendMessage(const std::string& type_url, @@ -116,6 +119,7 @@ class GrpcMuxImplTestBase : public testing::Test { Grpc::MockAsyncClient* async_client_; Grpc::MockAsyncStream async_stream_; NiceMock local_info_; + CustomConfigValidatorsPtr config_validators_; std::unique_ptr grpc_mux_; NiceMock callbacks_; TestUtility::TestOpaqueResourceDecoderImpl @@ -888,7 +892,8 @@ TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyClusterName) { std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, rate_limit_settings_, local_info_, true), + random_, stats_, rate_limit_settings_, local_info_, true, + std::make_unique>()), EnvoyException, "ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via " "--service-node and --service-cluster options."); @@ -901,7 +906,8 @@ TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyNodeName) { std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, rate_limit_settings_, local_info_, true), + random_, stats_, rate_limit_settings_, local_info_, true, + std::make_unique>()), EnvoyException, "ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via " "--service-node and --service-cluster options."); @@ -936,7 +942,8 @@ TEST_F(GrpcMuxImplTest, AllMuxesStateTest) { std::unique_ptr(), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, stats_, rate_limit_settings_, local_info_, true); + random_, stats_, rate_limit_settings_, local_info_, true, + std::make_unique>()); Config::XdsMux::GrpcMuxSotw::shutdownAll(); diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 07337eb30dc1..cd0e4ad3b017 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -37,6 +37,7 @@ envoy_cc_test( "//source/common/protobuf:utility_lib", "//source/common/upstream:cds_api_lib", "//test/mocks/protobuf:protobuf_mocks", + "//test/mocks/server:instance_mocks", "//test/mocks/upstream:cluster_manager_mocks", "//test/mocks/upstream:cluster_priority_set_mocks", "//test/test_common:utility_lib", @@ -177,6 +178,7 @@ envoy_cc_benchmark_binary( "//source/common/upstream:eds_lib", "//source/extensions/transport_sockets/raw_buffer:config", "//source/server:transport_socket_config_lib", + "//test/mocks/config:custom_config_validators_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index bbe5a9d7fd23..f61087d92e51 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -16,6 +16,7 @@ #include "test/mocks/http/conn_pool.h" #include "test/mocks/matcher/mocks.h" #include "test/mocks/protobuf/mocks.h" +#include "test/mocks/server/instance.h" #include "test/mocks/upstream/cds_api.h" #include "test/mocks/upstream/cluster_priority_set.h" #include "test/mocks/upstream/cluster_real_priority_set.h" @@ -95,7 +96,7 @@ class ClusterManagerImplTest : public testing::Test { cluster_manager_ = std::make_unique( bootstrap, factory_, factory_.stats_, factory_.tls_, factory_.runtime_, factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, validation_context_, - *factory_.api_, http_context_, grpc_context_, router_context_); + *factory_.api_, http_context_, grpc_context_, router_context_, server_); cluster_manager_->setPrimaryClustersInitializedCb( [this, bootstrap]() { cluster_manager_->initializeSecondaryClusters(bootstrap); }); } @@ -140,7 +141,7 @@ class ClusterManagerImplTest : public testing::Test { bootstrap, factory_, factory_.stats_, factory_.tls_, factory_.runtime_, factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, validation_context_, *factory_.api_, local_cluster_update_, local_hosts_removed_, http_context_, grpc_context_, - router_context_); + router_context_, server_); } void checkStats(uint64_t added, uint64_t modified, uint64_t removed, uint64_t active, @@ -193,6 +194,7 @@ class ClusterManagerImplTest : public testing::Test { Http::ContextImpl http_context_; Grpc::ContextImpl grpc_context_; Router::ContextImpl router_context_; + NiceMock server_; NiceMock dns_resolver_factory_; Registry::InjectFactory registered_dns_factory_; }; diff --git a/test/common/upstream/eds_speed_test.cc b/test/common/upstream/eds_speed_test.cc index 64a2ebffd463..305f183ef94b 100644 --- a/test/common/upstream/eds_speed_test.cc +++ b/test/common/upstream/eds_speed_test.cc @@ -19,6 +19,7 @@ #include "test/benchmark/main.h" #include "test/common/upstream/utility.h" +#include "test/mocks/config/custom_config_validators.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" @@ -44,20 +45,21 @@ class EdsSpeedTest { : state_(state), use_unified_mux_(use_unified_mux), type_url_("type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"), subscription_stats_(Config::Utility::generateStats(stats_)), - api_(Api::createApiForTest(stats_)), async_client_(new Grpc::MockAsyncClient()) { + api_(Api::createApiForTest(stats_)), async_client_(new Grpc::MockAsyncClient()), + config_validators_(std::make_unique>()) { TestDeprecatedV2Api::allowDeprecatedV2(); if (use_unified_mux_) { grpc_mux_.reset(new Config::XdsMux::GrpcMuxSotw( std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.endpoint.v3.EndpointDiscoveryService.StreamEndpoints"), - random_, stats_, {}, local_info_, true)); + random_, stats_, {}, local_info_, true, std::move(config_validators_))); } else { grpc_mux_.reset(new Config::GrpcMuxImpl( local_info_, std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.endpoint.v3.EndpointDiscoveryService.StreamEndpoints"), - random_, stats_, {}, true)); + random_, stats_, {}, true, std::move(config_validators_))); } resetCluster(R"EOF( name: name @@ -176,6 +178,7 @@ class EdsSpeedTest { Api::ApiPtr api_; Server::MockOptions options_; Grpc::MockAsyncClient* async_client_; + Config::CustomConfigValidatorsPtr config_validators_; NiceMock async_stream_; Config::GrpcMuxSharedPtr grpc_mux_; Config::GrpcSubscriptionImplPtr subscription_; diff --git a/test/common/upstream/load_stats_reporter_test.cc b/test/common/upstream/load_stats_reporter_test.cc index 483af4ba3295..5d48cee143c5 100644 --- a/test/common/upstream/load_stats_reporter_test.cc +++ b/test/common/upstream/load_stats_reporter_test.cc @@ -125,8 +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::ClusterInfoMaps 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. @@ -277,7 +277,7 @@ TEST_F(LoadStatsReporterTest, UpstreamLocalityStats) { addStats(host2, 10.01, 0, 20.02, 30.03); cluster.info_->eds_service_name_ = "bar"; - MockClusterManager::ClusterInfoMaps cluster_info{{{"foo", cluster}}, {}}; + MockClusterManager::ClusterInfoMaps cluster_info{{{"foo", cluster}}, {}, {}}; ON_CALL(cm_, clusters()).WillByDefault(Return(cluster_info)); deliverLoadStatsResponse({"foo"}); // First stats report on timer tick. diff --git a/test/common/upstream/test_cluster_manager.h b/test/common/upstream/test_cluster_manager.h index 63cd5a122567..536417cf44b6 100644 --- a/test/common/upstream/test_cluster_manager.h +++ b/test/common/upstream/test_cluster_manager.h @@ -176,10 +176,10 @@ class TestClusterManagerImpl : public ClusterManagerImpl { Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, ProtobufMessage::ValidationContext& validation_context, Api::Api& api, Http::Context& http_context, Grpc::Context& grpc_context, - Router::Context& router_context) + Router::Context& router_context, Server::Instance& server) : ClusterManagerImpl(bootstrap, factory, stats, tls, runtime, local_info, log_manager, main_thread_dispatcher, admin, validation_context, api, http_context, - grpc_context, router_context) {} + grpc_context, router_context, server) {} std::map> activeClusters() { std::map> clusters; @@ -206,17 +206,20 @@ class TestClusterManagerImpl : public ClusterManagerImpl { // it with the right values at the right times. class MockedUpdatedClusterManagerImpl : public TestClusterManagerImpl { public: - MockedUpdatedClusterManagerImpl( - const envoy::config::bootstrap::v3::Bootstrap& bootstrap, ClusterManagerFactory& factory, - Stats::Store& stats, ThreadLocal::Instance& tls, Runtime::Loader& runtime, - const LocalInfo::LocalInfo& local_info, AccessLog::AccessLogManager& log_manager, - Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, - ProtobufMessage::ValidationContext& validation_context, Api::Api& api, - MockLocalClusterUpdate& local_cluster_update, MockLocalHostsRemoved& local_hosts_removed, - Http::Context& http_context, Grpc::Context& grpc_context, Router::Context& router_context) + MockedUpdatedClusterManagerImpl(const envoy::config::bootstrap::v3::Bootstrap& bootstrap, + ClusterManagerFactory& factory, Stats::Store& stats, + ThreadLocal::Instance& tls, Runtime::Loader& runtime, + const LocalInfo::LocalInfo& local_info, + AccessLog::AccessLogManager& log_manager, + Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, + ProtobufMessage::ValidationContext& validation_context, + Api::Api& api, MockLocalClusterUpdate& local_cluster_update, + MockLocalHostsRemoved& local_hosts_removed, + Http::Context& http_context, Grpc::Context& grpc_context, + Router::Context& router_context, Server::Instance& server) : TestClusterManagerImpl(bootstrap, factory, stats, tls, runtime, local_info, log_manager, main_thread_dispatcher, admin, validation_context, api, http_context, - grpc_context, router_context), + grpc_context, router_context, server), local_cluster_update_(local_cluster_update), local_hosts_removed_(local_hosts_removed) {} protected: diff --git a/test/config_test/config_test.cc b/test/config_test/config_test.cc index b323a1938f8c..c1a984c8c845 100644 --- a/test/config_test/config_test.cc +++ b/test/config_test/config_test.cc @@ -107,7 +107,7 @@ class ConfigTest { server_.dnsResolver(), ssl_context_manager_, server_.dispatcher(), server_.localInfo(), server_.secretManager(), server_.messageValidationContext(), *api_, server_.httpContext(), server_.grpcContext(), server_.routerContext(), server_.accessLogManager(), - server_.singletonManager(), server_.options(), server_.quic_stat_names_); + server_.singletonManager(), server_.options(), server_.quic_stat_names_, server_); ON_CALL(server_, clusterManager()).WillByDefault(Invoke([&]() -> Upstream::ClusterManager& { return *main_config.clusterManager(); diff --git a/test/extensions/clusters/aggregate/cluster_update_test.cc b/test/extensions/clusters/aggregate/cluster_update_test.cc index 90e23cc8ee68..43871d58384d 100644 --- a/test/extensions/clusters/aggregate/cluster_update_test.cc +++ b/test/extensions/clusters/aggregate/cluster_update_test.cc @@ -12,6 +12,7 @@ #include "test/common/upstream/utility.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/server/admin.h" +#include "test/mocks/server/instance.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_update_callbacks.h" #include "test/test_common/environment.h" @@ -41,7 +42,7 @@ class AggregateClusterUpdateTest : public Event::TestUsingSimulatedTime, public cluster_manager_ = std::make_unique( bootstrap, factory_, factory_.stats_, factory_.tls_, factory_.runtime_, factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, validation_context_, - *factory_.api_, http_context_, grpc_context_, router_context_); + *factory_.api_, http_context_, grpc_context_, router_context_, server_); cluster_manager_->initializeSecondaryClusters(bootstrap); EXPECT_EQ(cluster_manager_->activeClusters().size(), 1); cluster_ = cluster_manager_->getThreadLocalCluster("aggregate_cluster"); @@ -58,6 +59,7 @@ class AggregateClusterUpdateTest : public Event::TestUsingSimulatedTime, public Http::ContextImpl http_context_; Grpc::ContextImpl grpc_context_; Router::ContextImpl router_context_; + NiceMock server_; const std::string default_yaml_config_ = R"EOF( static_resources: @@ -273,7 +275,7 @@ TEST_F(AggregateClusterUpdateTest, InitializeAggregateClusterAfterOtherClusters) cluster_manager_ = std::make_unique( bootstrap, factory_, factory_.stats_, factory_.tls_, factory_.runtime_, factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, validation_context_, *factory_.api_, - http_context_, grpc_context_, router_context_); + http_context_, grpc_context_, router_context_, server_); cluster_manager_->initializeSecondaryClusters(bootstrap); EXPECT_EQ(cluster_manager_->activeClusters().size(), 2); cluster_ = cluster_manager_->getThreadLocalCluster("aggregate_cluster"); diff --git a/test/extensions/config/validators/minimum_clusters/BUILD b/test/extensions/config/validators/minimum_clusters/BUILD new file mode 100644 index 000000000000..573f12b8a045 --- /dev/null +++ b/test/extensions/config/validators/minimum_clusters/BUILD @@ -0,0 +1,58 @@ +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_names = ["envoy.config.validators.minimum_clusters"], + deps = [ + "//envoy/registry", + "//source/common/protobuf:message_validator_lib", + "//source/extensions/config/validators/minimum_clusters:config", + "@envoy_api//envoy/extensions/config/validators/minimum_clusters/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "minimum_clusters_validator_test", + srcs = ["minimum_clusters_validator_test.cc"], + extension_names = ["envoy.config.validators.minimum_clusters"], + deps = [ + "//source/common/config:decoded_resource_lib", + "//source/extensions/config/validators/minimum_clusters:minimum_clusters_validator", + "//test/mocks/server:instance_mocks", + "//test/mocks/upstream:cluster_manager_mocks", + ], +) + +envoy_extension_cc_test( + name = "minimum_clusters_validator_integration_test", + srcs = ["minimum_clusters_validator_integration_test.cc"], + extension_names = ["envoy.config.validators.minimum_clusters"], + deps = [ + "//source/common/config:protobuf_link_hacks", + "//source/common/protobuf:utility_lib", + "//source/extensions/config/validators/minimum_clusters:config", + "//source/extensions/config/validators/minimum_clusters:minimum_clusters_validator", + "//test/common/grpc:grpc_client_integration_lib", + "//test/config:v2_link_hacks", + "//test/integration:http_integration_lib", + "//test/mocks/runtime:runtime_mocks", + "//test/test_common:network_utility_lib", + "//test/test_common:resources_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/config/validators/minimum_clusters/v3:pkg_cc_proto", + "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/config/validators/minimum_clusters/config_test.cc b/test/extensions/config/validators/minimum_clusters/config_test.cc new file mode 100644 index 000000000000..adc2c2ef7f96 --- /dev/null +++ b/test/extensions/config/validators/minimum_clusters/config_test.cc @@ -0,0 +1,52 @@ +#include "envoy/extensions/config/validators/minimum_clusters/v3/minimum_clusters.pb.h" +#include "envoy/extensions/config/validators/minimum_clusters/v3/minimum_clusters.pb.validate.h" +#include "envoy/registry/registry.h" + +#include "source/common/protobuf/message_validator_impl.h" +#include "source/extensions/config/validators/minimum_clusters/config.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Config { +namespace Validators { +namespace { + +TEST(MinimumClustersValidatorFactoryTest, CreateValidator) { + auto factory = Registry::FactoryRegistry::getFactory( + "envoy.config.validators.minimum_clusters"); + EXPECT_NE(factory, nullptr); + + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + config.set_min_clusters_num(5); + ProtobufWkt::Any typed_config; + typed_config.PackFrom(config); + auto validator = + factory->createConfigValidator(typed_config, ProtobufMessage::getStrictValidationVisitor()); + EXPECT_NE(validator, nullptr); +} + +TEST(MinimumClustersValidatorFactoryTest, CreateEmptyValidator) { + auto factory = Registry::FactoryRegistry::getFactory( + "envoy.config.validators.minimum_clusters"); + EXPECT_NE(factory, nullptr); + + auto empty_proto = factory->createEmptyConfigProto(); + auto config = *dynamic_cast< + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator*>( + empty_proto.get()); + EXPECT_EQ(0, config.min_clusters_num()); + + ProtobufWkt::Any typed_config; + typed_config.PackFrom(config); + auto validator = + factory->createConfigValidator(typed_config, ProtobufMessage::getStrictValidationVisitor()); + EXPECT_NE(validator, nullptr); +} + +} // namespace +} // namespace Validators +} // namespace Config +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc b/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc new file mode 100644 index 000000000000..d79040896190 --- /dev/null +++ b/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_integration_test.cc @@ -0,0 +1,202 @@ +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/extensions/config/validators/minimum_clusters/v3/minimum_clusters.pb.h" +#include "envoy/grpc/status.h" +#include "envoy/service/discovery/v3/discovery.pb.h" +#include "envoy/stats/scope.h" + +#include "source/common/config/protobuf_link_hacks.h" +#include "source/common/protobuf/protobuf.h" +#include "source/common/protobuf/utility.h" + +#include "test/common/grpc/grpc_client_integration.h" +#include "test/config/v2_link_hacks.h" +#include "test/integration/http_integration.h" +#include "test/integration/utility.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/resources.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +using testing::AssertionResult; + +namespace Envoy { +namespace { + +const uint32_t InitialUpstreamIndex = 1; + +class MinimumClustersValidatorIntegrationTest : public Grpc::DeltaSotwIntegrationParamTest, + public HttpIntegrationTest { +public: + MinimumClustersValidatorIntegrationTest() + : HttpIntegrationTest(Http::CodecType::HTTP2, ipVersion(), + ConfigHelper::discoveredClustersBootstrap( + sotwOrDelta() == Grpc::SotwOrDelta::Sotw || + sotwOrDelta() == Grpc::SotwOrDelta::UnifiedSotw + ? "GRPC" + : "DELTA_GRPC")) { + if (sotwOrDelta() == Grpc::SotwOrDelta::UnifiedSotw || + sotwOrDelta() == Grpc::SotwOrDelta::UnifiedDelta) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.unified_mux", "true"); + } + use_lds_ = false; + sotw_or_delta_ = sotwOrDelta(); + } + + void TearDown() override { + if (!test_skipped_) { + cleanUpXdsConnection(); + } + } + + // Overridden to insert this stuff into the initialize() at the very beginning of + // HttpIntegrationTest::testRouterHeaderOnlyRequestAndResponse(). + void initializeTest(uint32_t initial_clusters_num) { + use_lds_ = false; + test_skipped_ = false; + // Controls how many addFakeUpstream() will happen in + // BaseIntegrationTest::createUpstreams() (which is part of initialize()). + // Make sure this number matches the size of the 'clusters' repeated field in the bootstrap + // config that you use! + setUpstreamCount(1); // the CDS cluster + setUpstreamProtocol(Http::CodecType::HTTP2); // CDS uses gRPC uses HTTP2. + + // HttpIntegrationTest::initialize() does many things: + // 1) It appends to fake_upstreams_ as many as you asked for via setUpstreamCount(). + // 2) It updates your bootstrap config with the ports your fake upstreams are actually listening + // on (since you're supposed to leave them as 0). + // 3) It creates and starts an IntegrationTestServer - the thing that wraps the almost-actual + // Envoy used in the tests. + // 4) Bringing up the server usually entails waiting to ensure that any listeners specified in + // the bootstrap config have come up, and registering them in a port map (see lookupPort()). + // However, this test needs to defer all of that to later. + defer_listener_finalization_ = true; + HttpIntegrationTest::initialize(); + + // Create the regular (i.e. not an xDS server) upstreams. We create them manually here after + // initialize() because finalize() expects all fake_upstreams_ to correspond to a static + // cluster in the bootstrap config - which we don't want since we're testing dynamic CDS! + for (uint32_t i = 0; i < initial_clusters_num; ++i) { + addFakeUpstream(Http::CodecType::HTTP2); + const std::string cluster_name = absl::StrCat("cluster_", i); + auto cluster = ConfigHelper::buildStaticCluster( + cluster_name, fake_upstreams_[InitialUpstreamIndex + i]->localAddress()->ip()->port(), + Network::Test::getLoopbackAddressString(ipVersion())); + clusters_.emplace_back(cluster); + } + + // Let Envoy establish its connection to the CDS server. + acceptXdsConnection(); + + // Do the initial compareDiscoveryRequest / sendDiscoveryResponse for the clusters. + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "", {}, {}, {}, true)); + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + clusters_, clusters_, {}, "7"); + + // We can continue the test once we're sure that Envoy's ClusterManager has made use of + // the DiscoveryResponse describing cluster_1 that we sent. + // 2 because the statically specified CDS server itself counts as a cluster. + test_server_->waitForGaugeGe("cluster_manager.active_clusters", initial_clusters_num + 1); + + // Wait for our statically specified listener to become ready, and register its port in the + // test framework's downstream listener port map. + test_server_->waitUntilListenersReady(); + registerTestServerPorts({"http"}); + } + + // Regression test to catch the code declaring a gRPC service method for {SotW,delta} + // when the user's bootstrap config asks for the other type. + void verifyGrpcServiceMethod() { + EXPECT_TRUE(xds_stream_->waitForHeadersComplete()); + Envoy::Http::LowerCaseString path_string(":path"); + std::string expected_method( + sotwOrDelta() == Grpc::SotwOrDelta::Sotw || sotwOrDelta() == Grpc::SotwOrDelta::UnifiedSotw + ? "/envoy.service.cluster.v3.ClusterDiscoveryService/StreamClusters" + : "/envoy.service.cluster.v3.ClusterDiscoveryService/DeltaClusters"); + EXPECT_EQ(xds_stream_->headers().get(path_string)[0]->value(), expected_method); + } + + void acceptXdsConnection() { + AssertionResult result = // xds_connection_ is filled with the new FakeHttpConnection. + fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, xds_connection_); + RELEASE_ASSERT(result, result.message()); + result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_); + RELEASE_ASSERT(result, result.message()); + xds_stream_->startGrpcStream(); + verifyGrpcServiceMethod(); + } + + void addValidator(uint32_t threshold) { + config_helper_.addConfigModifier([threshold]( + envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* config_validator_config = bootstrap.mutable_dynamic_resources() + ->mutable_cds_config() + ->mutable_api_config_source() + ->add_config_validators(); + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + config.set_min_clusters_num(threshold); + config_validator_config->mutable_typed_config()->PackFrom(config); + config_validator_config->set_name("minimum_cluster_validator"); + }); + } + + std::vector clusters_; + // True if we decided not to run the test after all. + bool test_skipped_{true}; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, MinimumClustersValidatorIntegrationTest, + DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS); + +TEST_P(MinimumClustersValidatorIntegrationTest, RemoveAllClustersThreshold0) { + addValidator(0); + initializeTest(2); + + // 3 clusters: xds + 2 upstream clusters. + EXPECT_EQ(3, test_server_->gauge("cluster_manager.active_clusters")->value()); + + // Removing the 2 clusters should be successful. + std::vector removed_clusters_names; + std::transform(clusters_.cbegin(), clusters_.cend(), std::back_inserter(removed_clusters_names), + [](const envoy::config::cluster::v3::Cluster& cluster) -> std::string { + return cluster.name(); + }); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "7", {}, {}, {})); + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, {}, {}, + {removed_clusters_names}, "8"); + + // Receive ACK. + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "8", {}, {}, {})); + EXPECT_EQ(1, test_server_->gauge("cluster_manager.active_clusters")->value()); +} + +TEST_P(MinimumClustersValidatorIntegrationTest, RemoveAllClustersThreshold1) { + addValidator(1); + initializeTest(2); + + // 3 clusters: xds + 2 upstream clusters. + EXPECT_EQ(3, test_server_->gauge("cluster_manager.active_clusters")->value()); + + // Removing the 2 clusters should be successful. + std::vector removed_clusters_names; + std::transform(clusters_.cbegin(), clusters_.cend(), std::back_inserter(removed_clusters_names), + [](const envoy::config::cluster::v3::Cluster& cluster) -> std::string { + return cluster.name(); + }); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "7", {}, {}, {})); + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, {}, {}, + {removed_clusters_names}, "8"); + + // Receive NACK. + EXPECT_TRUE( + compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "7", {}, {}, {}, false, + Grpc::Status::WellKnownGrpcStatus::Internal, + "CDS update attempts to reduce clusters below configured minimum.")); + EXPECT_EQ(3, test_server_->gauge("cluster_manager.active_clusters")->value()); +} + +} // namespace +} // namespace Envoy diff --git a/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_test.cc b/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_test.cc new file mode 100644 index 000000000000..5491b6855f00 --- /dev/null +++ b/test/extensions/config/validators/minimum_clusters/minimum_clusters_validator_test.cc @@ -0,0 +1,224 @@ +#include "source/common/config/decoded_resource_impl.h" +#include "source/extensions/config/validators/minimum_clusters/minimum_clusters_validator.h" + +#include "test/mocks/server/instance.h" +#include "test/mocks/upstream/cluster_manager.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Config { +namespace Validators { +namespace { + +class MinimumClustersValidatorTest : public testing::Test { +public: + MinimumClustersValidatorTest() { + ON_CALL(server_, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); + } + + const NiceMock cluster_manager_; + const NiceMock server_; +}; + +// Validates that an empty config accepts an update with no clusters. +TEST_F(MinimumClustersValidatorTest, NoMinimumNoClusters) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{{}, {}, 0}; + MinimumClustersValidator validator(config); + + const std::vector added_resources; + const Protobuf::RepeatedPtrField removed_resources; + + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + // No exception should occur. + validator.validate(server_, added_resources, removed_resources); +} + +// Validates that a config with threshold 1 rejects an update with no clusters. +TEST_F(MinimumClustersValidatorTest, Minimum1NoClusters) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + config.set_min_clusters_num(1); + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{{}, {}, 0}; + MinimumClustersValidator validator(config); + + const std::vector added_resources; + const Protobuf::RepeatedPtrField removed_resources; + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + EXPECT_THROW_WITH_MESSAGE(validator.validate(server_, added_resources, removed_resources), + EnvoyException, + "CDS update attempts to reduce clusters below configured minimum."); +} + +// Validates that a config with threshold 1 and a config that has a cluster +// is accepted. +TEST_F(MinimumClustersValidatorTest, Minimum1SingleCluster) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{{}, {}, 0}; + MinimumClustersValidator validator(config); + + auto cluster = std::make_unique(); + cluster->set_name("cluster1"); + std::vector added_resources; + added_resources.emplace_back( + new Envoy::Config::DecodedResourceImpl(std::move(cluster), "name", {}, "ver")); + const Protobuf::RepeatedPtrField removed_resources; + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + // No exception should occur. + validator.validate(server_, added_resources, removed_resources); +} + +// Validates that a config with threshold 2 and a config that has two clusters +// with the same name is rejected. +TEST_F(MinimumClustersValidatorTest, Minimum1TwoClustersSameName) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + config.set_min_clusters_num(2); + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{{}, {}, 0}; + MinimumClustersValidator validator(config); + + auto cluster1 = std::make_unique(); + cluster1->set_name("clusterA"); + auto cluster2 = std::make_unique(); + cluster2->set_name("clusterA"); + std::vector added_resources; + added_resources.emplace_back( + new Envoy::Config::DecodedResourceImpl(std::move(cluster1), "nameA", {}, "ver")); + added_resources.emplace_back( + new Envoy::Config::DecodedResourceImpl(std::move(cluster2), "nameB", {}, "ver")); + const Protobuf::RepeatedPtrField removed_resources; + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + EXPECT_THROW_WITH_MESSAGE(validator.validate(server_, added_resources, removed_resources), + EnvoyException, + "CDS update attempts to reduce clusters below configured minimum."); +} + +// Validates that an empty config accepts an update with a single cluster. +TEST_F(MinimumClustersValidatorTest, NoMinimumSingleCluster) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + NiceMock foo_cluster; + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{{{"foo", foo_cluster}}, {}, 1}; + MinimumClustersValidator validator(config); + + const std::vector added_resources; + const Protobuf::RepeatedPtrField removed_resources; + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + // No exception should occur. + validator.validate(server_, added_resources, removed_resources); +} + +// Validates that a config with threshold 1 and a server that already has a +// cluster, accepts an update with no clusters. +TEST_F(MinimumClustersValidatorTest, Minimum1Clusters1Empty) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + config.set_min_clusters_num(1); + NiceMock foo_cluster; + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{{{"foo", foo_cluster}}, {}, 1}; + MinimumClustersValidator validator(config); + + const std::vector added_resources; + const Protobuf::RepeatedPtrField removed_resources; + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + // No exception should occur. + validator.validate(server_, added_resources, removed_resources); +} + +// Validates that a config with threshold 1 and a server that already has 2 +// clusters, accepts an update that removes a cluster. +TEST_F(MinimumClustersValidatorTest, Minimum1Clusters2Remove1) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + config.set_min_clusters_num(1); + NiceMock foo_cluster, bar_cluster; + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{ + {{"foo", foo_cluster}, {"bar", bar_cluster}}, {}, 2}; + MinimumClustersValidator validator(config); + + const std::vector added_resources; + Protobuf::RepeatedPtrField removed_resources; + removed_resources.Add("foo"); + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + // No exception should occur. + validator.validate(server_, added_resources, removed_resources); +} + +// Validates that a config with threshold 1 and a server that already has 1 +// cluster, accepts an update that removes a non-existent cluster. +TEST_F(MinimumClustersValidatorTest, Minimum1Clusters1RemoveNonExistent) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + config.set_min_clusters_num(1); + NiceMock foo_cluster; + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{{{"foo", foo_cluster}}, {}, 1}; + MinimumClustersValidator validator(config); + + const std::vector added_resources; + Protobuf::RepeatedPtrField removed_resources; + removed_resources.Add("bar"); + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + // No exception should occur. + validator.validate(server_, added_resources, removed_resources); +} + +// Validates that a config with threshold 1 and a server that already has 1 +// cluster, rejects an update that removes the cluster. +TEST_F(MinimumClustersValidatorTest, Minimum1Clusters1Remove1) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + config.set_min_clusters_num(1); + NiceMock foo_cluster; + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{{{"foo", foo_cluster}}, {}, 1}; + MinimumClustersValidator validator(config); + + const std::vector added_resources; + Protobuf::RepeatedPtrField removed_resources; + removed_resources.Add("foo"); + // Only clusters that were added by API (CDS) can be removed. + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + EXPECT_CALL(*foo_cluster.info_, addedViaApi()).WillOnce(Return(true)); + EXPECT_THROW_WITH_MESSAGE(validator.validate(server_, added_resources, removed_resources), + EnvoyException, + "CDS update attempts to reduce clusters below configured minimum."); +} + +// Validates that a config with threshold 1 and a server that already has 1 +// cluster that wasn't updated via API, accepts an update that removes the cluster. +TEST_F(MinimumClustersValidatorTest, Minimum1Clusters1Remove1NonApi) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + config.set_min_clusters_num(1); + NiceMock foo_cluster; + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{{{"foo", foo_cluster}}, {}, 1}; + MinimumClustersValidator validator(config); + + const std::vector added_resources; + Protobuf::RepeatedPtrField removed_resources; + removed_resources.Add("foo"); + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + // No exception should occur. + validator.validate(server_, added_resources, removed_resources); +} + +// Validate that high threshold, and small update (only additions) below the +// threshold will be rejected. +TEST_F(MinimumClustersValidatorTest, Minimum5AddingOneCluster) { + envoy::extensions::config::validators::minimum_clusters::v3::MinimumClustersValidator config; + config.set_min_clusters_num(5); + Upstream::MockClusterManager::ClusterInfoMaps cluster_info{{}, {}, 0}; + MinimumClustersValidator validator(config); + + auto cluster = std::make_unique(); + cluster->set_name("cluster1"); + std::vector added_resources; + added_resources.emplace_back( + new Envoy::Config::DecodedResourceImpl(std::move(cluster), "name", {}, "ver")); + const Protobuf::RepeatedPtrField removed_resources; + + EXPECT_CALL(cluster_manager_, clusters()).WillOnce(Return(cluster_info)); + EXPECT_THROW_WITH_MESSAGE(validator.validate(server_, added_resources, removed_resources), + EnvoyException, + "CDS update attempts to reduce clusters below configured minimum."); +} + +} // namespace +} // namespace Validators +} // namespace Config +} // namespace Extensions +} // namespace Envoy diff --git a/test/mocks/config/BUILD b/test/mocks/config/BUILD index 198c6c06d293..dd41f9d8b9bd 100644 --- a/test/mocks/config/BUILD +++ b/test/mocks/config/BUILD @@ -26,3 +26,12 @@ envoy_cc_mock( "@envoy_api//envoy/service/discovery/v3:pkg_cc_proto", ], ) + +envoy_cc_mock( + name = "custom_config_validators_mocks", + srcs = ["custom_config_validators.cc"], + hdrs = ["custom_config_validators.h"], + deps = [ + "//source/common/config:custom_config_validators_interface", + ], +) diff --git a/test/mocks/config/custom_config_validators.cc b/test/mocks/config/custom_config_validators.cc new file mode 100644 index 000000000000..e93ee8d3324f --- /dev/null +++ b/test/mocks/config/custom_config_validators.cc @@ -0,0 +1,13 @@ +#include "test/mocks/config/custom_config_validators.h" + +namespace Envoy { +namespace Config { + +MockConfigValidator::MockConfigValidator() = default; +MockConfigValidator::~MockConfigValidator() = default; + +MockCustomConfigValidators::MockCustomConfigValidators() = default; +MockCustomConfigValidators::~MockCustomConfigValidators() = default; + +} // namespace Config +} // namespace Envoy diff --git a/test/mocks/config/custom_config_validators.h b/test/mocks/config/custom_config_validators.h new file mode 100644 index 000000000000..0d4842e8d230 --- /dev/null +++ b/test/mocks/config/custom_config_validators.h @@ -0,0 +1,38 @@ +#pragma once + +#include "source/common/config/custom_config_validators.h" + +#include "gmock/gmock.h" + +namespace Envoy { +namespace Config { + +class MockConfigValidator : public ConfigValidator { +public: + MockConfigValidator(); + ~MockConfigValidator() override; + + MOCK_METHOD(void, validate, + (const Server::Instance& server, const std::vector& resources)); + + MOCK_METHOD(void, validate, + (const Server::Instance& server, + const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources)); +}; + +class MockCustomConfigValidators : public CustomConfigValidators { +public: + MockCustomConfigValidators(); + ~MockCustomConfigValidators() override; + + MOCK_METHOD(void, executeValidators, + (absl::string_view type_url, const std::vector& resources)); + + MOCK_METHOD(void, executeValidators, + (absl::string_view type_url, const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources)); +}; + +} // namespace Config +} // namespace Envoy diff --git a/test/mocks/server/instance.h b/test/mocks/server/instance.h index b9967e58c3e1..73f341178e96 100644 --- a/test/mocks/server/instance.h +++ b/test/mocks/server/instance.h @@ -51,6 +51,7 @@ class MockInstance : public Instance { MOCK_METHOD(Admin&, admin, ()); MOCK_METHOD(Api::Api&, api, ()); MOCK_METHOD(Upstream::ClusterManager&, clusterManager, ()); + MOCK_METHOD(const Upstream::ClusterManager&, clusterManager, (), (const)); MOCK_METHOD(Ssl::ContextManager&, sslContextManager, ()); MOCK_METHOD(Event::Dispatcher&, dispatcher, ()); MOCK_METHOD(Network::DnsResolverSharedPtr, dnsResolver, ()); diff --git a/test/mocks/server/main.h b/test/mocks/server/main.h index 427a8689c8f4..04a1136a0536 100644 --- a/test/mocks/server/main.h +++ b/test/mocks/server/main.h @@ -19,6 +19,7 @@ class MockMain : public Main { ~MockMain() override = default; MOCK_METHOD(Upstream::ClusterManager*, clusterManager, ()); + MOCK_METHOD(const Upstream::ClusterManager*, clusterManager, (), (const)); MOCK_METHOD(StatsConfig&, statsConfig, (), ()); MOCK_METHOD(const Watchdog&, mainThreadWatchdogConfig, (), (const)); MOCK_METHOD(const Watchdog&, workerWatchdogConfig, (), (const)); diff --git a/test/mocks/upstream/cluster_manager.h b/test/mocks/upstream/cluster_manager.h index 69f75b443804..fb38f12bc13b 100644 --- a/test/mocks/upstream/cluster_manager.h +++ b/test/mocks/upstream/cluster_manager.h @@ -42,7 +42,7 @@ class MockClusterManager : public ClusterManager { MOCK_METHOD(void, setInitializedCb, (InitializationCompleteCallback)); MOCK_METHOD(void, initializeSecondaryClusters, (const envoy::config::bootstrap::v3::Bootstrap& bootstrap)); - MOCK_METHOD(ClusterInfoMaps, clusters, ()); + MOCK_METHOD(ClusterInfoMaps, clusters, (), (const)); MOCK_METHOD(const ClusterSet&, primaryClusters, ()); MOCK_METHOD(ThreadLocalCluster*, getThreadLocalCluster, (absl::string_view cluster)); diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index 626b0b9381f3..08473a7a3d87 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -27,6 +27,7 @@ envoy_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/secret:secret_mocks", "//test/mocks/server:admin_mocks", + "//test/mocks/server:instance_mocks", "//test/mocks/server:options_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/test_common:simulated_time_system_lib", diff --git a/test/server/config_validation/cluster_manager_test.cc b/test/server/config_validation/cluster_manager_test.cc index 526585025970..174421e2e847 100644 --- a/test/server/config_validation/cluster_manager_test.cc +++ b/test/server/config_validation/cluster_manager_test.cc @@ -18,6 +18,7 @@ #include "test/mocks/runtime/mocks.h" #include "test/mocks/secret/mocks.h" #include "test/mocks/server/admin.h" +#include "test/mocks/server/instance.h" #include "test/mocks/server/options.h" #include "test/mocks/thread_local/mocks.h" #include "test/test_common/simulated_time_system.h" @@ -48,11 +49,12 @@ TEST(ValidationClusterManagerTest, MockedMethods) { Quic::QuicStatNames quic_stat_names(stats_store.symbolTable()); AccessLog::MockAccessLogManager log_manager; Singleton::ManagerImpl singleton_manager{Thread::threadFactoryForTest()}; + NiceMock server; ValidationClusterManagerFactory factory( admin, runtime, stats_store, tls, dns_resolver, ssl_context_manager, dispatcher, local_info, secret_manager, validation_context, *api, http_context, grpc_context, router_context, - log_manager, singleton_manager, options, quic_stat_names); + log_manager, singleton_manager, options, quic_stat_names, server); const envoy::config::bootstrap::v3::Bootstrap bootstrap; ClusterManagerPtr cluster_manager = factory.clusterManagerFromProto(bootstrap); diff --git a/test/server/config_validation/server_test.cc b/test/server/config_validation/server_test.cc index d65e7354fa65..f23063f52d16 100644 --- a/test/server/config_validation/server_test.cc +++ b/test/server/config_validation/server_test.cc @@ -134,6 +134,36 @@ TEST_P(ValidationServerTest, NoopLifecycleNotifier) { server.shutdown(); } +// A test to increase coverage of dummy methods (naively implemented methods +// needed for interface implementation). +TEST_P(ValidationServerTest, DummyMethodsTest) { + // Setup the server instance. + Thread::MutexBasicLockable access_log_lock; + Stats::IsolatedStoreImpl stats_store; + DangerousDeprecatedTestTime time_system; + ValidationInstance server(options_, time_system.timeSystem(), + Network::Address::InstanceConstSharedPtr(), stats_store, + access_log_lock, component_factory_, Thread::threadFactoryForTest(), + Filesystem::fileSystemForTest()); + + // Execute dummy methods. + server.drainListeners(); + server.failHealthcheck(true); + server.lifecycleNotifier(); + server.secretManager(); + EXPECT_FALSE(server.isShutdown()); + EXPECT_FALSE(server.healthCheckFailed()); + server.grpcContext(); + EXPECT_FALSE(server.processContext().has_value()); + server.timeSource(); + server.mutexTracer(); + server.flushStats(); + server.statsConfig(); + server.transportSocketFactoryContext(); + server.shutdownAdmin(); + server.shutdown(); +} + // TODO(rlazarus): We'd like use this setup to replace //test/config_test (that is, run it against // all the example configs) but can't until light validation is implemented, mocking out access to // the filesystem for TLS certs, etc. In the meantime, these are the example configs that work diff --git a/test/server/configuration_impl_test.cc b/test/server/configuration_impl_test.cc index ee7dbc3e8365..c2ee9097440e 100644 --- a/test/server/configuration_impl_test.cc +++ b/test/server/configuration_impl_test.cc @@ -66,7 +66,7 @@ class ConfigurationImplTest : public testing::Test { server_.localInfo(), server_.secretManager(), server_.messageValidationContext(), *api_, server_.httpContext(), server_.grpcContext(), server_.routerContext(), server_.accessLogManager(), server_.singletonManager(), server_.options(), - server_.quic_stat_names_) {} + server_.quic_stat_names_, server_) {} void addStatsdFakeClusterConfig(envoy::config::metrics::v3::StatsSink& sink) { envoy::config::metrics::v3::StatsdSink statsd_sink; diff --git a/tools/extensions/extensions_check.py b/tools/extensions/extensions_check.py index b664e3bfb814..5cf004103eb9 100644 --- a/tools/extensions/extensions_check.py +++ b/tools/extensions/extensions_check.py @@ -41,19 +41,19 @@ # Extension categories as defined by factories EXTENSION_CATEGORIES = ( "envoy.access_loggers", "envoy.bootstrap", "envoy.clusters", "envoy.compression.compressor", - "envoy.compression.decompressor", "envoy.filters.http", "envoy.filters.http.cache", - "envoy.filters.listener", "envoy.filters.network", "envoy.filters.udp_listener", - "envoy.formatter", "envoy.grpc_credentials", "envoy.guarddog_actions", "envoy.health_checkers", - "envoy.http.stateful_header_formatters", "envoy.internal_redirect_predicates", - "envoy.io_socket", "envoy.http.original_ip_detection", "envoy.matching.common_inputs", - "envoy.matching.input_matchers", "envoy.tls.key_providers", "envoy.quic.proof_source", - "envoy.quic.server.crypto_stream", "envoy.rate_limit_descriptors", "envoy.request_id", - "envoy.resource_monitors", "envoy.retry_host_predicates", "envoy.retry_priorities", - "envoy.stats_sinks", "envoy.thrift_proxy.filters", "envoy.tracers", "envoy.sip_proxy.filters", - "envoy.transport_sockets.downstream", "envoy.transport_sockets.upstream", - "envoy.tls.cert_validator", "envoy.upstreams", "envoy.wasm.runtime", "envoy.common.key_value", - "envoy.network.dns_resolver", "envoy.rbac.matchers", "envoy.access_loggers.extension_filters", - "envoy.http.stateful_session") + "envoy.compression.decompressor", "envoy.config.validators", "envoy.filters.http", + "envoy.filters.http.cache", "envoy.filters.listener", "envoy.filters.network", + "envoy.filters.udp_listener", "envoy.formatter", "envoy.grpc_credentials", + "envoy.guarddog_actions", "envoy.health_checkers", "envoy.http.stateful_header_formatters", + "envoy.internal_redirect_predicates", "envoy.io_socket", "envoy.http.original_ip_detection", + "envoy.matching.common_inputs", "envoy.matching.input_matchers", "envoy.tls.key_providers", + "envoy.quic.proof_source", "envoy.quic.server.crypto_stream", "envoy.rate_limit_descriptors", + "envoy.request_id", "envoy.resource_monitors", "envoy.retry_host_predicates", + "envoy.retry_priorities", "envoy.stats_sinks", "envoy.thrift_proxy.filters", "envoy.tracers", + "envoy.sip_proxy.filters", "envoy.transport_sockets.downstream", + "envoy.transport_sockets.upstream", "envoy.tls.cert_validator", "envoy.upstreams", + "envoy.wasm.runtime", "envoy.common.key_value", "envoy.network.dns_resolver", + "envoy.rbac.matchers", "envoy.access_loggers.extension_filters", "envoy.http.stateful_session") EXTENSION_STATUS_VALUES = ( # This extension is stable and is expected to be production usable. From 71ff5c94143c43056958ca48d1bad5bd3fe8b715 Mon Sep 17 00:00:00 2001 From: Jojy George Varghese Date: Wed, 9 Mar 2022 16:02:04 -0800 Subject: [PATCH 30/68] network: allow transport socket to intercept `connect` (#20213) Currently a transport socket does not have the ability to intercept a `connect` call made in the context of a request. In use cases which needs cluster context to make a connection, (for example authenticating a connection's address tuple along with the backend service's FQN) we would need the cluster context (for example service FQN) along with the connection. This can be done if we allow the cluster's transport socket to intercept `connect`. Signed-off-by: Jojy George Varghese Co-authored-by: Greg Greenway --- envoy/network/BUILD | 1 + envoy/network/transport_socket.h | 10 ++++++ source/common/network/connection_impl.cc | 3 +- source/common/network/raw_buffer_socket.h | 5 ++- test/common/network/connection_impl_test.cc | 40 +++++++++++++++++++-- 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/envoy/network/BUILD b/envoy/network/BUILD index 6fc727974cf2..d3f63009b978 100644 --- a/envoy/network/BUILD +++ b/envoy/network/BUILD @@ -156,6 +156,7 @@ envoy_cc_library( ":post_io_action_interface", ":proxy_protocol_options_lib", "//envoy/buffer:buffer_interface", + "//envoy/network:listen_socket_interface", "//envoy/ssl:connection_interface", "//envoy/stream_info:filter_state_interface", ], diff --git a/envoy/network/transport_socket.h b/envoy/network/transport_socket.h index f4b495f3f8ed..cad17810fbf9 100644 --- a/envoy/network/transport_socket.h +++ b/envoy/network/transport_socket.h @@ -5,6 +5,7 @@ #include "envoy/buffer/buffer.h" #include "envoy/common/pure.h" #include "envoy/network/io_handle.h" +#include "envoy/network/listen_socket.h" #include "envoy/network/post_io_action.h" #include "envoy/network/proxy_protocol.h" #include "envoy/ssl/connection.h" @@ -118,6 +119,15 @@ class TransportSocket { */ virtual bool canFlushClose() PURE; + /** + * Connect the underlying transport. + * @param socket provides the socket to connect. + * @return int the result from connect. + */ + virtual Api::SysCallIntResult connect(Network::ConnectionSocket& socket) { + return socket.connect(socket.connectionInfoProvider().remoteAddress()); + } + /** * Closes the transport socket. * @param event supplies the connection event that is closing the socket. diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index a8c54da16736..9b66476c69a6 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -905,8 +905,7 @@ ClientConnectionImpl::ClientConnectionImpl( void ClientConnectionImpl::connect() { ENVOY_CONN_LOG_EVENT(debug, "client_connection", "connecting to {}", *this, socket_->connectionInfoProvider().remoteAddress()->asString()); - const Api::SysCallIntResult result = - socket_->connect(socket_->connectionInfoProvider().remoteAddress()); + const Api::SysCallIntResult result = transport_socket_->connect(*socket_); stream_info_.upstreamInfo()->upstreamTiming().onUpstreamConnectStart(dispatcher_.timeSource()); if (result.return_value_ == 0) { // write will become ready. diff --git a/source/common/network/raw_buffer_socket.h b/source/common/network/raw_buffer_socket.h index 46ecccc5c4c6..33228296257f 100644 --- a/source/common/network/raw_buffer_socket.h +++ b/source/common/network/raw_buffer_socket.h @@ -25,9 +25,12 @@ class RawBufferSocket : public TransportSocket, protected Logger::Loggable { } } + virtual TransportSocketPtr createTransportSocket() { + return Network::Test::createRawBufferSocket(); + } + void setUpBasicConnection() { if (dispatcher_ == nullptr) { dispatcher_ = api_->allocateDispatcher("test_thread"); @@ -156,7 +161,7 @@ class ConnectionImplTest : public testing::TestWithParam { listener_ = dispatcher_->createListener(socket_, listener_callbacks_, runtime_, true, false); client_connection_ = std::make_unique( *dispatcher_, socket_->connectionInfoProvider().localAddress(), source_address_, - Network::Test::createRawBufferSocket(), socket_options_); + createTransportSocket(), socket_options_); client_connection_->addConnectionCallbacks(client_callbacks_); EXPECT_EQ(nullptr, client_connection_->ssl()); const Network::ClientConnection& const_connection = *client_connection_; @@ -265,7 +270,7 @@ class ConnectionImplTest : public testing::TestWithParam { return ConnectionMocks{std::move(dispatcher), timer_, std::move(transport_socket), file_event, &file_ready_cb_}; } - Network::TestClientConnectionImpl* testClientConnection() { + Network::TestClientConnectionImpl* testClientConnection() const { return dynamic_cast(client_connection_.get()); } @@ -3047,6 +3052,37 @@ TEST_F(InternalClientConnectionImplTest, "panic: not implemented"); } +class ClientConnectionWithCustomRawBufferSocketTest : public ConnectionImplTest { +protected: + class TestRawBufferSocket : public RawBufferSocket { + public: + bool compareCallbacks(TransportSocketCallbacks* callback) const { + return this->transportSocketCallbacks() == callback; + } + }; + + TransportSocketPtr createTransportSocket() override { + return std::make_unique(); + } + + TestRawBufferSocket* getTransportSocket() const { + Network::TestClientConnectionImpl* client_conn_impl = testClientConnection(); + return dynamic_cast(client_conn_impl->transportSocket().get()); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, ClientConnectionWithCustomRawBufferSocketTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(ClientConnectionWithCustomRawBufferSocketTest, TransportSocketCallbacks) { + setUpBasicConnection(); + + EXPECT_TRUE(getTransportSocket()->compareCallbacks(testClientConnection())); + + disconnect(false); +} + } // namespace } // namespace Network } // namespace Envoy From cc6b501e7495c801d978ea026c72b8d1428d70b3 Mon Sep 17 00:00:00 2001 From: Bryan Wu Date: Thu, 10 Mar 2022 09:26:54 +0800 Subject: [PATCH 31/68] update tracing error tag for grpc status codes (#20090) Below is the proposed change for tracing error tag behaviour. A brief description about whether each response code should be indicated as an error has also been listed. ``` OK | 0 | error=false CANCELLED | 1 | error=false Not a server error. The operation is cancelled by the client. UNKNOWN | 2 | error=true Server error. Status value received from unknown address space. INVALID_ARGUMENT | 3 | error=false Not a server error. The client enters an invalid argument. DEADLINE_EXCEEDED | 4 | error=true Server error. Response from the server has been delayed long, even if it may be successful. NOT_FOUND | 5 | error=false Not a server error. The requested file or directory is not found. ALREADY_EXISTS | 6 | error=false Not a server error. The file or directory the client attempts to create already exists. PERMISSION_DENIED | 7 | error=false Not a server error. The client doesn't have permission to execute the operation. RESOURCE_EXHAUSTED | 8 | error=false Not a server error. Some resource has been exhausted like the file system is out of space. FAILED_PRECONDITION | 9 | error=false Not a server error. The requested operation is rejected because it doesn't meet preconditions. ABORTED | 10 | error=false Not a server error. The operation is aborted, typically due to concurrency issues. OUT_OF_RANGE | 11 | error=false Not a server error. The operation was attempted to read past the valid range. UNIMPLEMENTED | 12 | error=true Server error. The service is not implemented or not enabled. INTERNAL | 13 | error=true Server error. The system is broken, typically reserved for serious cases. UNAVAILABLE | 14 | error=true Server error. The service is currently not available. DATA_LOSS | 15 | error=true Server error. Unrecoverable data loss or corruption. UNAUTHENTICATED | 16 | error=false Not a server error. The request does not have valid credentials for the operation. ``` Risk Level: Low Testing: Docs Changes: Release Notes: Platform Specific Features: Fixes #18877 Signed-off-by: bryanwux --- .../arch_overview/observability/tracing.rst | 3 +- docs/root/version_history/current.rst | 1 + source/common/runtime/runtime_features.cc | 2 + source/common/tracing/http_tracer_impl.cc | 41 +++++++++++++- test/common/tracing/BUILD | 36 ++++++++++++ test/common/tracing/http_tracer_impl_test.cc | 55 +++++++++++++------ 6 files changed, 118 insertions(+), 20 deletions(-) diff --git a/docs/root/intro/arch_overview/observability/tracing.rst b/docs/root/intro/arch_overview/observability/tracing.rst index ece8d4e8b807..db4c9c92ed6d 100644 --- a/docs/root/intro/arch_overview/observability/tracing.rst +++ b/docs/root/intro/arch_overview/observability/tracing.rst @@ -134,7 +134,8 @@ associated with it. Each span generated by Envoy contains the following data: * Upstream cluster name, observability name, and address. * HTTP response status code. * GRPC response status and message (if available). -* An error tag when HTTP status is 5xx or GRPC status is not "OK". +* An error tag when HTTP status is 5xx or GRPC status is not "OK" and represents a server side error. + See `GRPC's documentation `_ for more information about GRPC status code. * Tracing system-specific metadata. The span also includes a name (or operation) which by default is defined as the host of the invoked diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index e7c890f9160d..462a066aa28c 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -26,6 +26,7 @@ Minor Behavior Changes * router: record upstream request timeouts for all the cases and not just for those requests which are awaiting headers. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.do_not_await_headers_on_upstream_timeout_to_emit_stats`` to false. * sip-proxy: add customized affinity support by adding :ref:`tra_service_config ` and :ref:`customized_affinity `. * sip-proxy: add support for the ``503`` response code. When there is something wrong occurred, send ``503 Service Unavailable`` back to downstream. +* tracing: set tracing error tag for grpc non-ok response code only when it is a upstream error. Client error will not be tagged as a grpc error. This fix is guarded by ``envoy.reloadable_features.update_grpc_response_error_tag``. Bug Fixes --------- diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 221d9f840eff..654fde0777cd 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -53,6 +53,7 @@ RUNTIME_GUARD(envoy_reloadable_features_support_locality_update_on_eds_cluster_e RUNTIME_GUARD(envoy_reloadable_features_test_feature_true); RUNTIME_GUARD(envoy_reloadable_features_udp_listener_updates_filter_chain_in_place); RUNTIME_GUARD(envoy_reloadable_features_update_expected_rq_timeout_on_retry); +RUNTIME_GUARD(envoy_reloadable_features_update_grpc_response_error_tag); RUNTIME_GUARD(envoy_reloadable_features_use_dns_ttl); RUNTIME_GUARD(envoy_reloadable_features_validate_connect); RUNTIME_GUARD(envoy_restart_features_explicit_wildcard_resource); @@ -169,6 +170,7 @@ constexpr absl::Flag* runtime_features[] = { &FLAGS_envoy_reloadable_features_udp_listener_updates_filter_chain_in_place, &FLAGS_envoy_reloadable_features_unified_mux, &FLAGS_envoy_reloadable_features_update_expected_rq_timeout_on_retry, + &FLAGS_envoy_reloadable_features_update_grpc_response_error_tag, &FLAGS_envoy_reloadable_features_use_dns_ttl, &FLAGS_envoy_reloadable_features_validate_connect, &FLAGS_envoy_restart_features_explicit_wildcard_resource, diff --git a/source/common/tracing/http_tracer_impl.cc b/source/common/tracing/http_tracer_impl.cc index d72f0275c03a..f14fe8f832c8 100644 --- a/source/common/tracing/http_tracer_impl.cc +++ b/source/common/tracing/http_tracer_impl.cc @@ -84,10 +84,45 @@ static void addGrpcRequestTags(Span& span, const Http::RequestHeaderMap& headers template static void addGrpcResponseTags(Span& span, const T& headers) { addTagIfNotNull(span, Tracing::Tags::get().GrpcStatusCode, headers.GrpcStatus()); addTagIfNotNull(span, Tracing::Tags::get().GrpcMessage, headers.GrpcMessage()); - // Set error tag when status is not OK. + // Set error tag when Grpc status code represents an upstream error. See + // https://github.com/envoyproxy/envoy/issues/18877. absl::optional grpc_status_code = Grpc::Common::getGrpcStatus(headers); - if (grpc_status_code && grpc_status_code.value() != Grpc::Status::WellKnownGrpcStatus::Ok) { - span.setTag(Tracing::Tags::get().Error, Tracing::Tags::get().True); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.update_grpc_response_error_tag")) { + if (grpc_status_code.has_value()) { + const auto& status = grpc_status_code.value(); + if (status != Grpc::Status::WellKnownGrpcStatus::InvalidCode) { + switch (status) { + // Each case below is considered to be a client side error, therefore should not be + // tagged as an upstream error. See https://grpc.github.io/grpc/core/md_doc_statuscodes.html + // for more details about how each Grpc status code is defined and whether it is an + // upstream error or a client error. + case Grpc::Status::WellKnownGrpcStatus::Ok: + case Grpc::Status::WellKnownGrpcStatus::Canceled: + case Grpc::Status::WellKnownGrpcStatus::InvalidArgument: + case Grpc::Status::WellKnownGrpcStatus::NotFound: + case Grpc::Status::WellKnownGrpcStatus::AlreadyExists: + case Grpc::Status::WellKnownGrpcStatus::PermissionDenied: + case Grpc::Status::WellKnownGrpcStatus::FailedPrecondition: + case Grpc::Status::WellKnownGrpcStatus::Aborted: + case Grpc::Status::WellKnownGrpcStatus::OutOfRange: + case Grpc::Status::WellKnownGrpcStatus::Unauthenticated: + break; + case Grpc::Status::WellKnownGrpcStatus::Unknown: + case Grpc::Status::WellKnownGrpcStatus::DeadlineExceeded: + case Grpc::Status::WellKnownGrpcStatus::Unimplemented: + case Grpc::Status::WellKnownGrpcStatus::ResourceExhausted: + case Grpc::Status::WellKnownGrpcStatus::Internal: + case Grpc::Status::WellKnownGrpcStatus::Unavailable: + case Grpc::Status::WellKnownGrpcStatus::DataLoss: + span.setTag(Tracing::Tags::get().Error, Tracing::Tags::get().True); + break; + } + } + } + } else { + if (grpc_status_code && grpc_status_code.value() != Grpc::Status::WellKnownGrpcStatus::Ok) { + span.setTag(Tracing::Tags::get().Error, Tracing::Tags::get().True); + } } } diff --git a/test/common/tracing/BUILD b/test/common/tracing/BUILD index 2a6621554ba1..449fb54efa33 100644 --- a/test/common/tracing/BUILD +++ b/test/common/tracing/BUILD @@ -8,11 +8,47 @@ licenses(["notice"]) # Apache 2 envoy_package() +envoy_cc_test( + name = "http_tracer_impl_legacy_test", + srcs = [ + "http_tracer_impl_test.cc", + ], + args = [ + "--runtime-feature-disable-for-tests=envoy.reloadable_features.update_grpc_response_error_tag", + ], + coverage = True, + deps = [ + "//source/common/common:base64_lib", + "//source/common/http:header_map_lib", + "//source/common/http:headers_lib", + "//source/common/http:message_lib", + "//source/common/network:address_lib", + "//source/common/runtime:runtime_lib", + "//source/common/tracing:custom_tag_lib", + "//source/common/tracing:http_tracer_lib", + "//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", + "//test/mocks/thread_local:thread_local_mocks", + "//test/mocks/tracing:tracing_mocks", + "//test/test_common:environment_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/type/tracing/v3:pkg_cc_proto", + ], +) + envoy_cc_test( name = "http_tracer_impl_test", srcs = [ "http_tracer_impl_test.cc", ], + args = [ + "--runtime-feature-override-for-tests=envoy.reloadable_features.update_grpc_response_error_tag", + ], + coverage = True, deps = [ "//source/common/common:base64_lib", "//source/common/http:header_map_lib", diff --git a/test/common/tracing/http_tracer_impl_test.cc b/test/common/tracing/http_tracer_impl_test.cc index 35d35edae87a..5dcd1932f009 100644 --- a/test/common/tracing/http_tracer_impl_test.cc +++ b/test/common/tracing/http_tracer_impl_test.cc @@ -622,8 +622,15 @@ TEST_F(HttpConnManFinalizerImplTest, GrpcErrorTag) { Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, {"content-type", "application/grpc"}}; - Http::TestResponseTrailerMapImpl response_trailers{{"grpc-status", "7"}, - {"grpc-message", "permission denied"}}; + + Http::TestResponseTrailerMapImpl response_trailers; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.update_grpc_response_error_tag")) { + response_trailers.setGrpcStatus("14"); + response_trailers.setGrpcMessage("unavailable"); + } else { + response_trailers.setGrpcStatus("7"); + response_trailers.setGrpcMessage("permission denied"); + } absl::optional protocol = Http::Protocol::Http2; absl::optional response_code(200); @@ -634,7 +641,6 @@ TEST_F(HttpConnManFinalizerImplTest, GrpcErrorTag) { stream_info.downstream_connection_info_provider_->setDirectRemoteAddressForTest(remote_address); EXPECT_CALL(span, setTag(_, _)).Times(testing::AnyNumber()); - EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpMethod), Eq("POST"))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpProtocol), Eq("HTTP/2"))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpStatusCode), Eq("200"))); @@ -642,8 +648,14 @@ TEST_F(HttpConnManFinalizerImplTest, GrpcErrorTag) { EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcAuthority), Eq("example.com:80"))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcContentType), Eq("application/grpc"))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcTimeout), Eq("10s"))); - EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("7"))); - EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("permission denied"))); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.update_grpc_response_error_tag")) { + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("14"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("unavailable"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); + } else { + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("7"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("permission denied"))); + } EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().PeerAddress), Eq(expected_ip))); HttpTracerUtility::finalizeDownstreamSpan(span, &request_headers, &response_headers, @@ -665,9 +677,16 @@ TEST_F(HttpConnManFinalizerImplTest, GrpcTrailersOnly) { {"te", "trailers"}}; Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}, - {"content-type", "application/grpc"}, - {"grpc-status", "7"}, - {"grpc-message", "permission denied"}}; + {"content-type", "application/grpc"}}; + + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.update_grpc_response_error_tag")) { + response_headers.setGrpcStatus("14"); + response_headers.setGrpcMessage("unavailable"); + } else { + response_headers.setGrpcStatus("7"); + response_headers.setGrpcMessage("permission denied"); + } + Http::TestResponseTrailerMapImpl response_trailers; absl::optional protocol = Http::Protocol::Http2; @@ -679,15 +698,20 @@ TEST_F(HttpConnManFinalizerImplTest, GrpcTrailersOnly) { stream_info.downstream_connection_info_provider_->setDirectRemoteAddressForTest(remote_address); EXPECT_CALL(span, setTag(_, _)).Times(testing::AnyNumber()); - EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpMethod), Eq("POST"))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpProtocol), Eq("HTTP/2"))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpStatusCode), Eq("200"))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcPath), Eq("/pb.Foo/Bar"))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcAuthority), Eq("example.com:80"))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcContentType), Eq("application/grpc"))); - EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("7"))); - EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("permission denied"))); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.update_grpc_response_error_tag")) { + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("14"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("unavailable"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); + } else { + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("7"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("permission denied"))); + } EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().PeerAddress), Eq(expected_ip))); HttpTracerUtility::finalizeDownstreamSpan(span, &request_headers, &response_headers, @@ -740,13 +764,12 @@ class HttpTracerImplTest : public testing::Test { Upstream::HostDescriptionConstSharedPtr shared_host(host_); stream_info_.upstreamInfo()->setUpstreamHost(shared_host); } - Http::TestRequestHeaderMapImpl request_headers_{ {":path", "/"}, {":method", "GET"}, {"x-request-id", "foo"}, {":authority", "test"}}; Http::TestResponseHeaderMapImpl response_headers_{{":status", "200"}, {"content-type", "application/grpc"}, - {"grpc-status", "7"}, - {"grpc-message", "permission denied"}}; + {"grpc-status", "14"}, + {"grpc-message", "unavailable"}}; Http::TestResponseTrailerMapImpl response_trailers_; NiceMock stream_info_; NiceMock local_info_; @@ -823,8 +846,8 @@ TEST_F(HttpTracerImplTest, ChildUpstreamSpanTest) { EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().UpstreamClusterName), Eq("ob fake cluster"))); EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().HttpStatusCode), Eq("200"))); - EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("7"))); - EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("permission denied"))); + EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("14"))); + EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("unavailable"))); EXPECT_CALL(*second_span, setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); HttpTracerUtility::finalizeUpstreamSpan(*child_span, &response_headers_, &response_trailers_, From 1575185d14abc3bc508ce5a6ba45422393322551 Mon Sep 17 00:00:00 2001 From: code Date: Thu, 10 Mar 2022 10:02:06 +0800 Subject: [PATCH 32/68] support override host status restriction for stateful session (#19665) Stateful session will try to parse upstream address from downstream request directly and override the result of load balancing algorithm by the LoadBalancerContext::overrideHostToSelect API. To avoid the load balancer selecting hosts that in unexpected statuses, specifying some expected statuses are necessary. In the previous design, we will provide expected statuses of override host by the LoadBalancerContext::overrideHostToSelect API. And in the PR #18207, after some discussion with @htuch, we found may be cluster-level config may be more reasonable design and implementation. Ref some more details: #18207 (comment) So this PR try to close previous discussion in the #18207: Refactoring LoadBalancerContext::overrideHostToSelect API to remove expected statuses for the return value. Add new common lb config override_host_status and related implementation. Risk Level: Mid. Testing: N/A. Docs Changes: N/A. Release Notes: N/A. Platform Specific Features: N/A. @wbpcode Signed-off-by: wbpcode --- api/envoy/config/cluster/v3/cluster.proto | 10 +- api/envoy/config/core/v3/health_check.proto | 6 + .../load_balancing/load_balancing.rst | 1 + .../upstream/load_balancing/override_host.rst | 19 +++ docs/root/version_history/current.rst | 1 + envoy/upstream/load_balancer.h | 17 +-- source/common/router/router.h | 8 +- source/common/upstream/load_balancer_impl.cc | 69 ++++++----- source/common/upstream/load_balancer_impl.h | 23 +++- source/common/upstream/subset_lb.cc | 7 +- source/common/upstream/subset_lb.h | 4 + .../common/upstream/thread_aware_lb_impl.cc | 6 +- source/common/upstream/thread_aware_lb_impl.h | 11 +- test/common/router/router_test.cc | 3 +- .../upstream/load_balancer_impl_test.cc | 113 ++++++++++++++---- test/common/upstream/maglev_lb_test.cc | 4 +- test/common/upstream/ring_hash_lb_test.cc | 4 +- test/common/upstream/subset_lb_test.cc | 4 +- 18 files changed, 214 insertions(+), 96 deletions(-) create mode 100644 docs/root/intro/arch_overview/upstream/load_balancing/override_host.rst diff --git a/api/envoy/config/cluster/v3/cluster.proto b/api/envoy/config/cluster/v3/cluster.proto index e98414f8f2a4..89b1b5f25631 100644 --- a/api/envoy/config/cluster/v3/cluster.proto +++ b/api/envoy/config/cluster/v3/cluster.proto @@ -486,7 +486,7 @@ message Cluster { } // Common configuration for all load balancer implementations. - // [#next-free-field: 8] + // [#next-free-field: 9] message CommonLbConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Cluster.CommonLbConfig"; @@ -595,6 +595,14 @@ message Cluster { // Common Configuration for all consistent hashing load balancers (MaglevLb, RingHashLb, etc.) ConsistentHashingLbConfig consistent_hashing_lb_config = 7; + + // This controls what hosts are considered valid when using + // :ref:`host overrides `, which is used by some + // filters to modify the load balancing decision. + // + // If this is unset then [UNKNOWN, HEALTHY, DEGRADED] will be applied by default. If this is + // set with an empty set of statuses then host overrides will be ignored by the load balancing. + core.v3.HealthStatusSet override_host_status = 8; } message RefreshRate { diff --git a/api/envoy/config/core/v3/health_check.proto b/api/envoy/config/core/v3/health_check.proto index 99934bb45208..83cce7ccdb57 100644 --- a/api/envoy/config/core/v3/health_check.proto +++ b/api/envoy/config/core/v3/health_check.proto @@ -54,6 +54,12 @@ enum HealthStatus { DEGRADED = 5; } +message HealthStatusSet { + // An order-independent set of health status. + repeated HealthStatus statuses = 1 + [(validate.rules).repeated = {items {enum {defined_only: true}}}]; +} + // [#next-free-field: 25] message HealthCheck { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.HealthCheck"; diff --git a/docs/root/intro/arch_overview/upstream/load_balancing/load_balancing.rst b/docs/root/intro/arch_overview/upstream/load_balancing/load_balancing.rst index de648a4b8c73..be284b6e99ea 100644 --- a/docs/root/intro/arch_overview/upstream/load_balancing/load_balancing.rst +++ b/docs/root/intro/arch_overview/upstream/load_balancing/load_balancing.rst @@ -16,3 +16,4 @@ Load Balancing zone_aware subsets slow_start + override_host diff --git a/docs/root/intro/arch_overview/upstream/load_balancing/override_host.rst b/docs/root/intro/arch_overview/upstream/load_balancing/override_host.rst new file mode 100644 index 000000000000..8c6d708d80b3 --- /dev/null +++ b/docs/root/intro/arch_overview/upstream/load_balancing/override_host.rst @@ -0,0 +1,19 @@ +.. _arch_overview_load_balancing_override_host: + +Override host +============= + +Load balancing algorithms (round robin, random, etc.) are used to select upstream hosts by default. +Also, Envoy supports overriding the results of the load balancing algorithms by specifying a valid +override host address. If a valid override host address is specified and the corresponding upstream +host has the +:ref:`expected health status `, +that upstream host will be selected preferentially. + +For example, :ref:`stateful session filter ` will specify +override host address directly based on the downstream request attributes. Then the results of load +balancing algorithms will be ignored. By this way, stateful session stickiness can be achieved. + +In summary, override host provides a mechanism by which L4/L7 extensions can influence the final +results of upstream load balancing. This mechanism can be used by different extensions in different +scenarios. diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 462a066aa28c..dfa58a5ccda7 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -68,6 +68,7 @@ New Features ------------ * access_log: make consistent access_log format fields ``%(DOWN|DIRECT_DOWN|UP)STREAM_(LOCAL|REMOTE)_*%`` to provide all combinations of local & remote addresses for upstream & downstream connections. * admin: :http:post:`/logging` now accepts ``/logging?paths=name1:level1,name2:level2,...`` to change multiple log levels at once. +* cluster: support :ref:`override host status restriction `. * config: added new file based xDS configuration via :ref:`path_config_source `. :ref:`watched_directory ` can be used to setup an independent watch for when to reload the file path, for example when using diff --git a/envoy/upstream/load_balancer.h b/envoy/upstream/load_balancer.h index 75252fae82d6..288aba5fb61d 100644 --- a/envoy/upstream/load_balancer.h +++ b/envoy/upstream/load_balancer.h @@ -89,22 +89,11 @@ class LoadBalancerContext { */ virtual Network::TransportSocketOptionsConstSharedPtr upstreamTransportSocketOptions() const PURE; - // Using uint32_t to express expected status of override host. Every bit in the OverrideHostStatus - // represent an enum value of Host::Health. The specific correspondence is shown below: - // - // * 0b001: Host::Health::Unhealthy - // * 0b010: Host::Health::Degraded - // * 0b100: Host::Health::Healthy - // - // If multiple bit fields are set, it is acceptable as long as the status of override host is in - // any of these statuses. - using OverrideHostStatus = uint32_t; - using OverrideHost = std::pair; - + using OverrideHost = absl::string_view; /** * Returns the host the load balancer should select directly. If the expected host exists and - * the health status of the host matches the expectation, the load balancer can bypass the load - * balancing algorithm and return the corresponding host directly. + * the host can be selected directly, the load balancer can bypass the load balancing algorithm + * and return the corresponding host directly. */ virtual absl::optional overrideHostToSelect() const PURE; }; diff --git a/source/common/router/router.h b/source/common/router/router.h index f731c14158f9..346c7f7891f2 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -431,13 +431,7 @@ class Filter : Logger::Loggable, return {}; } - auto override_host = callbacks_->upstreamOverrideHost(); - if (override_host.has_value()) { - // TODO(wbpcode): Currently we need to provide additional expected host status to the load - // balancer. This should be resolved after the `overrideHostToSelect()` refactoring. - return std::make_pair(std::string(override_host.value()), ~static_cast(0)); - } - return {}; + return callbacks_->upstreamOverrideHost(); } /** diff --git a/source/common/upstream/load_balancer_impl.cc b/source/common/upstream/load_balancer_impl.cc index 36c446bcc430..ff3218b3d3b7 100644 --- a/source/common/upstream/load_balancer_impl.cc +++ b/source/common/upstream/load_balancer_impl.cc @@ -1,6 +1,7 @@ #include "source/common/upstream/load_balancer_impl.h" #include +#include #include #include #include @@ -24,10 +25,6 @@ static const std::string RuntimeZoneEnabled = "upstream.zone_routing.enabled"; static const std::string RuntimeMinClusterSize = "upstream.zone_routing.min_cluster_size"; static const std::string RuntimePanicThreshold = "upstream.healthy_panic_threshold"; -static constexpr uint32_t UnhealthyStatus = 1u << static_cast(Host::Health::Unhealthy); -static constexpr uint32_t DegradedStatus = 1u << static_cast(Host::Health::Degraded); -static constexpr uint32_t HealthyStatus = 1u << static_cast(Host::Health::Healthy); - bool tooManyPreconnects(size_t num_preconnect_picks, uint32_t healthy_hosts) { // Currently we only allow the number of preconnected connections to equal the // number of healthy hosts. @@ -118,7 +115,8 @@ LoadBalancerBase::LoadBalancerBase( : stats_(stats), runtime_(runtime), random_(random), default_healthy_panic_percent_(PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( common_config, healthy_panic_threshold, 100, 50)), - priority_set_(priority_set) { + priority_set_(priority_set), + override_host_status_(LoadBalancerContextBase::createOverrideHostStatus(common_config)) { for (auto& host_set : priority_set_.hostSetsPerPriority()) { recalculatePerPriorityState(host_set->priority(), priority_set_, per_priority_load_, per_priority_health_, per_priority_degraded_, total_healthy_hosts_); @@ -517,20 +515,39 @@ bool ZoneAwareLoadBalancerBase::earlyExitNonLocalityRouting() { return false; } -bool LoadBalancerContextBase::validateOverrideHostStatus(Host::Health health, - OverrideHostStatus status) { - switch (health) { - case Host::Health::Unhealthy: - return status & UnhealthyStatus; - case Host::Health::Degraded: - return status & DegradedStatus; - case Host::Health::Healthy: - return status & HealthyStatus; +HostStatusSet LoadBalancerContextBase::createOverrideHostStatus( + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) { + HostStatusSet override_host_status; + + if (!common_config.has_override_host_status()) { + // No override host status and 'Healthy' and 'Degraded' will be applied by default. + override_host_status.set(static_cast(Host::Health::Healthy)); + override_host_status.set(static_cast(Host::Health::Degraded)); + return override_host_status; + } + + for (auto single_status : common_config.override_host_status().statuses()) { + switch (static_cast(single_status)) { + PANIC_ON_PROTO_ENUM_SENTINEL_VALUES; + case envoy::config::core::v3::HealthStatus::UNKNOWN: + case envoy::config::core::v3::HealthStatus::HEALTHY: + override_host_status.set(static_cast(Host::Health::Healthy)); + break; + case envoy::config::core::v3::HealthStatus::UNHEALTHY: + case envoy::config::core::v3::HealthStatus::DRAINING: + case envoy::config::core::v3::HealthStatus::TIMEOUT: + override_host_status.set(static_cast(Host::Health::Unhealthy)); + break; + case envoy::config::core::v3::HealthStatus::DEGRADED: + override_host_status.set(static_cast(Host::Health::Degraded)); + break; + } } - return false; + return override_host_status; } HostConstSharedPtr LoadBalancerContextBase::selectOverrideHost(const HostMap* host_map, + HostStatusSet status, LoadBalancerContext* context) { if (context == nullptr) { return nullptr; @@ -545,7 +562,7 @@ HostConstSharedPtr LoadBalancerContextBase::selectOverrideHost(const HostMap* ho return nullptr; } - auto host_iter = host_map->find(override_host.value().first); + auto host_iter = host_map->find(override_host.value()); // The override host cannot be found in the host map. if (host_iter == host_map->end()) { @@ -555,17 +572,15 @@ HostConstSharedPtr LoadBalancerContextBase::selectOverrideHost(const HostMap* ho HostConstSharedPtr host = host_iter->second; ASSERT(host != nullptr); - // Verify the host status. - if (LoadBalancerContextBase::validateOverrideHostStatus(host->health(), - override_host.value().second)) { + if (status[static_cast(host->health())]) { return host; } return nullptr; } HostConstSharedPtr ZoneAwareLoadBalancerBase::chooseHost(LoadBalancerContext* context) { - HostConstSharedPtr host = - LoadBalancerContextBase::selectOverrideHost(cross_priority_host_map_.get(), context); + HostConstSharedPtr host = LoadBalancerContextBase::selectOverrideHost( + cross_priority_host_map_.get(), override_host_status_, context); if (host != nullptr) { return host; } @@ -652,8 +667,8 @@ uint32_t ZoneAwareLoadBalancerBase::tryChooseLocalLocalityHosts(const HostSet& h return random_.random() % number_of_localities; } - // Random sampling to select specific locality for cross locality traffic based on the additional - // capacity in localities. + // Random sampling to select specific locality for cross locality traffic based on the + // additional capacity in localities. uint64_t threshold = random_.random() % state.residual_capacity_[number_of_localities - 1]; // This potentially can be optimized to be O(log(N)) where N is the number of localities. @@ -923,8 +938,8 @@ HostConstSharedPtr EdfLoadBalancerBase::peekAnotherHost(LoadBalancerContext* con // As has been commented in both EdfLoadBalancerBase::refresh and // BaseDynamicClusterImpl::updateDynamicHostList, we must do a runtime pivot here to determine - // whether to use EDF or do unweighted (fast) selection. EDF is non-null iff the original weights - // of 2 or more hosts differ. + // whether to use EDF or do unweighted (fast) selection. EDF is non-null iff the original + // weights of 2 or more hosts differ. if (scheduler.edf_ != nullptr) { return scheduler.edf_->peekAgain([this](const Host& host) { return hostWeight(host); }); } else { @@ -949,8 +964,8 @@ HostConstSharedPtr EdfLoadBalancerBase::chooseHostOnce(LoadBalancerContext* cont // As has been commented in both EdfLoadBalancerBase::refresh and // BaseDynamicClusterImpl::updateDynamicHostList, we must do a runtime pivot here to determine - // whether to use EDF or do unweighted (fast) selection. EDF is non-null iff the original weights - // of 2 or more hosts differ. + // whether to use EDF or do unweighted (fast) selection. EDF is non-null iff the original + // weights of 2 or more hosts differ. if (scheduler.edf_ != nullptr) { auto host = scheduler.edf_->pickAndAdd([this](const Host& host) { return hostWeight(host); }); return host; diff --git a/source/common/upstream/load_balancer_impl.h b/source/common/upstream/load_balancer_impl.h index 78f2c200cc87..8d81f9dc267b 100644 --- a/source/common/upstream/load_balancer_impl.h +++ b/source/common/upstream/load_balancer_impl.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -24,6 +25,8 @@ namespace Upstream { // Priority levels and localities are considered overprovisioned with this factor. static constexpr uint32_t kDefaultOverProvisioningFactor = 140; +using HostStatusSet = std::bitset<32>; + /** * Base class for all LB implementations. */ @@ -155,17 +158,31 @@ class LoadBalancerBase : public LoadBalancer { // The total count of healthy hosts across all priority levels. uint32_t total_healthy_hosts_; + // Expected override host statues. Every bit in the OverrideHostStatus represent an enum value of + // Host::Health. The specific correspondence is shown below: + // + // * 0b001: Host::Health::Unhealthy + // * 0b010: Host::Health::Degraded + // * 0b100: Host::Health::Healthy + // + // If multiple bit fields are set, it is acceptable as long as the status of override host is in + // any of these statuses. + const HostStatusSet override_host_status_{}; + private: Common::CallbackHandlePtr priority_update_cb_; }; class LoadBalancerContextBase : public LoadBalancerContext { public: - static bool validateOverrideHostStatus(Host::Health health, OverrideHostStatus status); - - static HostConstSharedPtr selectOverrideHost(const HostMap* host_map, + // A utility function to select override host from host map according to load balancer context. + static HostConstSharedPtr selectOverrideHost(const HostMap* host_map, HostStatusSet status, LoadBalancerContext* context); + // A utility function to create override host status from lb config. + static HostStatusSet createOverrideHostStatus( + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config); + absl::optional computeHashKey() override { return {}; } const Network::Connection* downstreamConnection() const override { return nullptr; } diff --git a/source/common/upstream/subset_lb.cc b/source/common/upstream/subset_lb.cc index c080c643d3fa..70fd4c5acae6 100644 --- a/source/common/upstream/subset_lb.cc +++ b/source/common/upstream/subset_lb.cc @@ -42,7 +42,8 @@ SubsetLoadBalancer::SubsetLoadBalancer( original_local_priority_set_(local_priority_set), locality_weight_aware_(subsets.localityWeightAware()), scale_locality_weight_(subsets.scaleLocalityWeight()), list_as_any_(subsets.listAsAny()), - time_source_(time_source) { + time_source_(time_source), + override_host_status_(LoadBalancerContextBase::createOverrideHostStatus(common_config)) { ASSERT(subsets.isEnabled()); if (fallback_policy_ != envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK) { @@ -283,8 +284,8 @@ void SubsetLoadBalancer::initSelectorFallbackSubset( } HostConstSharedPtr SubsetLoadBalancer::chooseHost(LoadBalancerContext* context) { - HostConstSharedPtr override_host = - LoadBalancerContextBase::selectOverrideHost(cross_priority_host_map_.get(), context); + HostConstSharedPtr override_host = LoadBalancerContextBase::selectOverrideHost( + cross_priority_host_map_.get(), override_host_status_, context); if (override_host != nullptr) { return override_host; } diff --git a/source/common/upstream/subset_lb.h b/source/common/upstream/subset_lb.h index 977285287948..82950e1ba88d 100644 --- a/source/common/upstream/subset_lb.h +++ b/source/common/upstream/subset_lb.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -13,6 +14,7 @@ #include "source/common/common/macros.h" #include "source/common/protobuf/protobuf.h" #include "source/common/protobuf/utility.h" +#include "source/common/upstream/load_balancer_impl.h" #include "source/common/upstream/upstream_impl.h" #include "absl/container/node_hash_map.h" @@ -297,6 +299,8 @@ class SubsetLoadBalancer : public LoadBalancer, Logger::Loggabledegraded_per_priority_load_ = degraded_per_priority_load_; lb->per_priority_state_ = per_priority_state_; lb->cross_priority_host_map_ = cross_priority_host_map_; - + lb->override_host_status_ = override_host_status_; return lb; } diff --git a/source/common/upstream/thread_aware_lb_impl.h b/source/common/upstream/thread_aware_lb_impl.h index a29e4a2c3959..f16f634f522c 100644 --- a/source/common/upstream/thread_aware_lb_impl.h +++ b/source/common/upstream/thread_aware_lb_impl.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "envoy/common/callback.h" #include "envoy/config/cluster/v3/cluster.pb.h" @@ -109,7 +111,7 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL Random::RandomGenerator& random, const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) : LoadBalancerBase(priority_set, stats, runtime, random, common_config), - factory_(new LoadBalancerFactoryImpl(stats, random)) {} + factory_(new LoadBalancerFactoryImpl(stats, random, override_host_status_)) {} private: struct PerPriorityState { @@ -138,6 +140,7 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL ClusterStats& stats_; Random::RandomGenerator& random_; + HostStatusSet override_host_status_{}; std::shared_ptr> per_priority_state_; std::shared_ptr healthy_per_priority_load_; std::shared_ptr degraded_per_priority_load_; @@ -147,14 +150,16 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL }; struct LoadBalancerFactoryImpl : public LoadBalancerFactory { - LoadBalancerFactoryImpl(ClusterStats& stats, Random::RandomGenerator& random) - : stats_(stats), random_(random) {} + LoadBalancerFactoryImpl(ClusterStats& stats, Random::RandomGenerator& random, + HostStatusSet status) + : stats_(stats), random_(random), override_host_status_(status) {} // Upstream::LoadBalancerFactory LoadBalancerPtr create() override; ClusterStats& stats_; Random::RandomGenerator& random_; + HostStatusSet override_host_status_{}; absl::Mutex mutex_; std::shared_ptr> per_priority_state_ ABSL_GUARDED_BY(mutex_); // This is split out of PerPriorityState so LoadBalancerBase::ChoosePriority can be reused. diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 4a8f08bf4864..ff6a434ad500 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -6170,8 +6170,7 @@ TEST_F(RouterTest, RequestWithUpstreamOverrideHost) { .WillOnce(Return(absl::make_optional("1.2.3.4"))); auto override_host = router_.overrideHostToSelect(); - EXPECT_EQ("1.2.3.4", override_host->first); - EXPECT_EQ(~static_cast(0), override_host->second); + EXPECT_EQ("1.2.3.4", override_host.value()); Http::TestRequestHeaderMapImpl headers{{"x-envoy-retry-on", "5xx"}, {"x-envoy-internal", "true"}}; HttpTestUtility::addDefaultHeaders(headers); diff --git a/test/common/upstream/load_balancer_impl_test.cc b/test/common/upstream/load_balancer_impl_test.cc index c2420c9215f2..d38e58f09f48 100644 --- a/test/common/upstream/load_balancer_impl_test.cc +++ b/test/common/upstream/load_balancer_impl_test.cc @@ -1,3 +1,5 @@ +#include +#include #include #include #include @@ -63,9 +65,9 @@ class TestZoneAwareLoadBalancer : public ZoneAwareLoadBalancerBase { namespace { -static constexpr uint32_t UnhealthyStatus = 1u << static_cast(Host::Health::Unhealthy); -static constexpr uint32_t DegradedStatus = 1u << static_cast(Host::Health::Degraded); -static constexpr uint32_t HealthyStatus = 1u << static_cast(Host::Health::Healthy); +static constexpr HostStatusSet UnhealthyStatus = 1u << static_cast(Host::Health::Unhealthy); +static constexpr HostStatusSet DegradedStatus = 1u << static_cast(Host::Health::Degraded); +static constexpr HostStatusSet HealthyStatus = 1u << static_cast(Host::Health::Healthy); class LoadBalancerTestBase : public Event::TestUsingSimulatedTime, public testing::TestWithParam { @@ -635,7 +637,7 @@ TEST_F(ZoneAwareLoadBalancerBaseTest, SelectOverrideHostTestInLb) { NiceMock context; { - LoadBalancerContext::OverrideHost override_host{"1.2.3.4", HealthyStatus | DegradedStatus}; + LoadBalancerContext::OverrideHost override_host{"1.2.3.4"}; EXPECT_CALL(context, overrideHostToSelect()) .WillOnce(Return(absl::make_optional(override_host))); @@ -652,7 +654,7 @@ TEST_F(ZoneAwareLoadBalancerBaseTest, SelectOverrideHostTestInLb) { auto mock_host = std::make_shared>(); EXPECT_CALL(*mock_host, health()).WillOnce(Return(Host::Health::Unhealthy)); - LoadBalancerContext::OverrideHost override_host{"1.2.3.4", HealthyStatus | DegradedStatus}; + LoadBalancerContext::OverrideHost override_host{"1.2.3.4"}; EXPECT_CALL(context, overrideHostToSelect()) .WillOnce(Return(absl::make_optional(override_host))); @@ -662,8 +664,8 @@ TEST_F(ZoneAwareLoadBalancerBaseTest, SelectOverrideHostTestInLb) { priority_set_.cross_priority_host_map_ = host_map; host_set_.runCallbacks({}, {}); - // Host status does not match the expected host status, therefore `chooseHostOnce` will be - // called. + // Host status does not match the expected host status (Healthy & Degraded by default), + // therefore `chooseHostOnce` will be called. EXPECT_EQ(lb_.choose_host_once_host_, lb_.chooseHost(&context)); } @@ -671,7 +673,7 @@ TEST_F(ZoneAwareLoadBalancerBaseTest, SelectOverrideHostTestInLb) { auto mock_host = std::make_shared>(); EXPECT_CALL(*mock_host, health()).WillOnce(Return(Host::Health::Degraded)); - LoadBalancerContext::OverrideHost override_host{"1.2.3.4", HealthyStatus | DegradedStatus}; + LoadBalancerContext::OverrideHost override_host{"1.2.3.4"}; EXPECT_CALL(context, overrideHostToSelect()) .WillOnce(Return(absl::make_optional(override_host))); @@ -2557,65 +2559,128 @@ TEST(LoadBalancerContextBaseTest, LoadBalancerContextBaseTest) { EXPECT_EQ(absl::nullopt, context.overrideHostToSelect()); } - EXPECT_TRUE(LoadBalancerContextBase::validateOverrideHostStatus(Host::Health::Unhealthy, - UnhealthyStatus)); - EXPECT_TRUE( - LoadBalancerContextBase::validateOverrideHostStatus(Host::Health::Healthy, HealthyStatus)); - EXPECT_FALSE( - LoadBalancerContextBase::validateOverrideHostStatus(Host::Health::Healthy, UnhealthyStatus)); + { + envoy::config::cluster::v3::Cluster::CommonLbConfig lb_config; + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::UNKNOWN); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::HEALTHY); + EXPECT_EQ(LoadBalancerContextBase::createOverrideHostStatus(lb_config), HealthyStatus); + } + { + envoy::config::cluster::v3::Cluster::CommonLbConfig lb_config; + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::UNHEALTHY); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::DRAINING); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::TIMEOUT); + + EXPECT_EQ(LoadBalancerContextBase::createOverrideHostStatus(lb_config), UnhealthyStatus); + } + { + envoy::config::cluster::v3::Cluster::CommonLbConfig lb_config; + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::DEGRADED); + EXPECT_EQ(LoadBalancerContextBase::createOverrideHostStatus(lb_config), DegradedStatus); + } + { + envoy::config::cluster::v3::Cluster::CommonLbConfig lb_config; + EXPECT_EQ(LoadBalancerContextBase::createOverrideHostStatus(lb_config), 0b110u); + } + { + envoy::config::cluster::v3::Cluster::CommonLbConfig lb_config; + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::UNHEALTHY); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::DRAINING); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::TIMEOUT); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::UNKNOWN); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::HEALTHY); + + EXPECT_EQ(LoadBalancerContextBase::createOverrideHostStatus(lb_config), 0b101u); + } + + { + envoy::config::cluster::v3::Cluster::CommonLbConfig lb_config; + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::UNHEALTHY); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::DRAINING); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::TIMEOUT); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::UNKNOWN); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::HEALTHY); + lb_config.mutable_override_host_status()->add_statuses( + ::envoy::config::core::v3::HealthStatus::DEGRADED); + EXPECT_EQ(LoadBalancerContextBase::createOverrideHostStatus(lb_config), 0b111u); + } } -TEST(LoadBalancerContextBaseTest, selectOverrideHostTest) { +TEST(LoadBalancerContextBaseTest, SelectOverrideHostTest) { NiceMock context; + const HostStatusSet all_health_statuses = UnhealthyStatus | DegradedStatus | HealthyStatus; + { // No valid host map. - EXPECT_EQ(nullptr, LoadBalancerContextBase::selectOverrideHost(nullptr, &context)); + EXPECT_EQ(nullptr, + LoadBalancerContextBase::selectOverrideHost(nullptr, all_health_statuses, &context)); } { // No valid load balancer context. auto host_map = std::make_shared(); - EXPECT_EQ(nullptr, LoadBalancerContextBase::selectOverrideHost(host_map.get(), nullptr)); + EXPECT_EQ(nullptr, LoadBalancerContextBase::selectOverrideHost(host_map.get(), + all_health_statuses, nullptr)); } { // No valid expected host. EXPECT_CALL(context, overrideHostToSelect()).WillOnce(Return(absl::nullopt)); auto host_map = std::make_shared(); - EXPECT_EQ(nullptr, LoadBalancerContextBase::selectOverrideHost(host_map.get(), &context)); + EXPECT_EQ(nullptr, LoadBalancerContextBase::selectOverrideHost(host_map.get(), + all_health_statuses, &context)); } { // The host map does not contain the expected host. - LoadBalancerContext::OverrideHost override_host{"1.2.3.4", HealthyStatus}; + LoadBalancerContext::OverrideHost override_host{"1.2.3.4"}; EXPECT_CALL(context, overrideHostToSelect()) .WillOnce(Return(absl::make_optional(override_host))); auto host_map = std::make_shared(); - EXPECT_EQ(nullptr, LoadBalancerContextBase::selectOverrideHost(host_map.get(), &context)); + EXPECT_EQ(nullptr, + LoadBalancerContextBase::selectOverrideHost(host_map.get(), HealthyStatus, &context)); } { // The status of host is not as expected. auto mock_host = std::make_shared>(); EXPECT_CALL(*mock_host, health()).WillOnce(Return(Host::Health::Unhealthy)); - LoadBalancerContext::OverrideHost override_host{"1.2.3.4", HealthyStatus}; + LoadBalancerContext::OverrideHost override_host{"1.2.3.4"}; EXPECT_CALL(context, overrideHostToSelect()) .WillOnce(Return(absl::make_optional(override_host))); auto host_map = std::make_shared(); host_map->insert({"1.2.3.4", mock_host}); - EXPECT_EQ(nullptr, LoadBalancerContextBase::selectOverrideHost(host_map.get(), &context)); + EXPECT_EQ(nullptr, + LoadBalancerContextBase::selectOverrideHost(host_map.get(), HealthyStatus, &context)); } { // Get expected host. auto mock_host = std::make_shared>(); EXPECT_CALL(*mock_host, health()).WillOnce(Return(Host::Health::Degraded)); - LoadBalancerContext::OverrideHost override_host{"1.2.3.4", HealthyStatus | DegradedStatus}; + LoadBalancerContext::OverrideHost override_host{"1.2.3.4"}; EXPECT_CALL(context, overrideHostToSelect()) .WillOnce(Return(absl::make_optional(override_host))); auto host_map = std::make_shared(); host_map->insert({"1.2.3.4", mock_host}); - EXPECT_EQ(mock_host, LoadBalancerContextBase::selectOverrideHost(host_map.get(), &context)); + EXPECT_EQ(mock_host, LoadBalancerContextBase::selectOverrideHost( + host_map.get(), HealthyStatus | DegradedStatus, &context)); } } diff --git a/test/common/upstream/maglev_lb_test.cc b/test/common/upstream/maglev_lb_test.cc index d8472c5a5aef..934d5ec6ba18 100644 --- a/test/common/upstream/maglev_lb_test.cc +++ b/test/common/upstream/maglev_lb_test.cc @@ -88,9 +88,7 @@ TEST_F(MaglevLoadBalancerTest, SelectOverrideHost) { auto mock_host = std::make_shared>(); EXPECT_CALL(*mock_host, health()).WillOnce(testing::Return(Host::Health::Degraded)); - LoadBalancerContext::OverrideHost expected_host{ - "1.2.3.4", 1u << static_cast(Host::Health::Healthy) | - 1u << static_cast(Host::Health::Degraded)}; + LoadBalancerContext::OverrideHost expected_host{"1.2.3.4"}; EXPECT_CALL(context, overrideHostToSelect()) .WillOnce(testing::Return(absl::make_optional(expected_host))); diff --git a/test/common/upstream/ring_hash_lb_test.cc b/test/common/upstream/ring_hash_lb_test.cc index 99b9d402e6b4..c83fddedf0a2 100644 --- a/test/common/upstream/ring_hash_lb_test.cc +++ b/test/common/upstream/ring_hash_lb_test.cc @@ -124,9 +124,7 @@ TEST_P(RingHashLoadBalancerTest, SelectOverrideHost) { auto mock_host = std::make_shared>(); EXPECT_CALL(*mock_host, health()).WillOnce(Return(Host::Health::Degraded)); - LoadBalancerContext::OverrideHost expected_host{ - "1.2.3.4", 1u << static_cast(Host::Health::Healthy) | - 1u << static_cast(Host::Health::Degraded)}; + LoadBalancerContext::OverrideHost expected_host{"1.2.3.4"}; EXPECT_CALL(context, overrideHostToSelect()).WillOnce(Return(absl::make_optional(expected_host))); // Mock membership update and update host map shared pointer in the lb. diff --git a/test/common/upstream/subset_lb_test.cc b/test/common/upstream/subset_lb_test.cc index 133d15cacba9..801f74440ef7 100644 --- a/test/common/upstream/subset_lb_test.cc +++ b/test/common/upstream/subset_lb_test.cc @@ -515,9 +515,7 @@ TEST_F(SubsetLoadBalancerTest, SelectOverrideHost) { auto mock_host = std::make_shared>(); EXPECT_CALL(*mock_host, health()).WillOnce(Return(Host::Health::Degraded)); - LoadBalancerContext::OverrideHost expected_host{ - "1.2.3.4", 1u << static_cast(Host::Health::Healthy) | - 1u << static_cast(Host::Health::Degraded)}; + LoadBalancerContext::OverrideHost expected_host{"1.2.3.4"}; EXPECT_CALL(context, overrideHostToSelect()).WillOnce(Return(absl::make_optional(expected_host))); // Mock membership update and update host map shared pointer in the lb. From 8fea09354096e0d6024c338967e555da44de6447 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 10 Mar 2022 07:24:09 +0000 Subject: [PATCH 33/68] bazel: Small cleanup to README (#20253) Signed-off-by: Ryan Northey --- bazel/README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/bazel/README.md b/bazel/README.md index 750456c0c450..b9e0e21301a5 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -916,29 +916,41 @@ Edit the paths shown here to reflect the installation locations on your system: export CLANG_FORMAT="$HOME/ext/clang+llvm-12.0.1-x86_64-linux-gnu-ubuntu-16.04/bin/clang-format" export BUILDIFIER_BIN="/usr/bin/buildifier" ``` -The easiest way to use the correct `clang-format` in your host system is to copy the `clang-format` from the ci docker image. + +A relatively easy way to use the correct `clang-format` in your host system is to copy the `clang-format` from the ci docker image. + * Run the ci docker image + ```shell ci/run_envoy_docker.sh bash ``` + * Get the docker container ID + ```shell dockerContainerID=$(docker ps | grep envoy-build-ubuntu | awk '{print $1}') ``` + * Copy the `clang-format` to host machine + ```shell -docker copy $dockerContainerID:/opt/llvm/bin/clang-format clang-format-ci +docker cp $dockerContainerID:/opt/llvm/bin/clang-format clang-format-ci ``` -* Replace the host `clang-format` with the new one. Ensure that the copied `clang-format` is the default one. You can do this by ensuring it is in `$PATH`: + +* Ensure that the copied `clang-format` is the default one, by ensuring it is in `$PATH`: + ```shell cp clang-format-ci /usr/local/bin/clang-format ``` -If you are a non-root user, alternatively you can use a bin dir and add that to `$PATH` + +Alternatively, if you are a non-root user, you can use a bin dir and add that to `$PATH` + ```shell mkdir bin mv clang-format-ci bin/clang-format export PATH=$PATH:$PWD/bin/ ``` + Once this is set up, you can run clang-format without docker: ```shell From f19807e436d08825cec68cf298c4942783f8bb46 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 10 Mar 2022 08:01:04 +0000 Subject: [PATCH 34/68] bazel: Add rules for debian packaging (#20235) Signed-off-by: Ryan Northey --- distribution/BUILD | 47 ++++++++ distribution/debian/BUILD | 11 ++ distribution/debian/copyright | 8 ++ distribution/debian/packages.bzl | 102 ++++++++++++++++++ distribution/debian/postinst | 42 ++++++++ distribution/debian/preinst | 47 ++++++++ distribution/distros.yaml | 20 ++++ .../distrotest.sh | 5 +- distribution/packages.bzl | 76 +++++++++++++ tools/distribution/BUILD | 4 - 10 files changed, 356 insertions(+), 6 deletions(-) create mode 100644 distribution/BUILD create mode 100644 distribution/debian/BUILD create mode 100644 distribution/debian/copyright create mode 100644 distribution/debian/packages.bzl create mode 100644 distribution/debian/postinst create mode 100755 distribution/debian/preinst create mode 100644 distribution/distros.yaml rename {tools/distribution => distribution}/distrotest.sh (93%) create mode 100644 distribution/packages.bzl diff --git a/distribution/BUILD b/distribution/BUILD new file mode 100644 index 000000000000..0001d7e1cb23 --- /dev/null +++ b/distribution/BUILD @@ -0,0 +1,47 @@ +load("//bazel:envoy_build_system.bzl", "envoy_package") +load(":packages.bzl", "envoy_pkg_distros") +load("@envoy_repo//:version.bzl", "VERSION") + +licenses(["notice"]) # Apache 2 + +envoy_package() + +MAINTAINER = "Envoy maintainers " + +genrule( + name = "envoy-bin", + srcs = ["//source/exe:envoy-static.stripped"], + outs = ["envoy"], + cmd = "cp -L $< $@", +) + +envoy_pkg_distros( + name = "packages", + maintainer = MAINTAINER, + version = VERSION, +) + +genrule( + name = "verification", + outs = ["verification.sh"], + cmd = """ + echo 'exec $${@}' > $@ \ + && chmod +x $@ + """, +) + +sh_binary( + name = "verify_packages", + srcs = [":verification.sh"], + args = [ + "$(location //tools/distribution:verify)", + "$(location :distrotest.sh)", + VERSION, + "$(location :distros.yaml)", + ], + data = [ + ":distros.yaml", + ":distrotest.sh", + "//tools/distribution:verify", + ], +) diff --git a/distribution/debian/BUILD b/distribution/debian/BUILD new file mode 100644 index 000000000000..7aa9b46b8434 --- /dev/null +++ b/distribution/debian/BUILD @@ -0,0 +1,11 @@ +load("//bazel:envoy_build_system.bzl", "envoy_package") + +licenses(["notice"]) # Apache 2 + +envoy_package() + +exports_files([ + "copyright", + "preinst", + "postinst", +]) diff --git a/distribution/debian/copyright b/distribution/debian/copyright new file mode 100644 index 000000000000..daed6c35093b --- /dev/null +++ b/distribution/debian/copyright @@ -0,0 +1,8 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: Envoy +Source: + +Files: * +Copyright: Copyright 2016-2022 Envoy Project Authors +License: Apache +/usr/share/common-licenses/Apache-2.0 diff --git a/distribution/debian/packages.bzl b/distribution/debian/packages.bzl new file mode 100644 index 000000000000..24d9281acad6 --- /dev/null +++ b/distribution/debian/packages.bzl @@ -0,0 +1,102 @@ +load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") +load("@rules_pkg//pkg:deb.bzl", "pkg_deb") + +GLIBC_MIN_VERSION = "2.27" + +def envoy_pkg_deb( + name, + data, + homepage = "https://www.envoyproxy.io/", + description = "Envoy built for Debian/Ubuntu", + preinst = "//distribution/debian:preinst", + postinst = "//distribution/debian:postinst", + supported_distributions = "buster bullseye bionic focal impish", + architecture = select({ + "//bazel:x86": "amd64", + "//conditions:default": "arm64", + }), + depends = [ + "libc6 (>= %s)" % GLIBC_MIN_VERSION, + ], + version = None, + maintainer = None, + **kwargs): + """Wrapper for `pkg_deb` with Envoy defaults""" + pkg_deb( + name = "%s-deb" % name, + architecture = architecture, + data = data, + depends = depends, + description = description, + distribution = supported_distributions, + homepage = homepage, + maintainer = maintainer, + package = name, + version = version, + preinst = preinst, + postinst = postinst, + **kwargs + ) + + native.filegroup( + name = "%s.changes" % name, + srcs = ["%s-deb" % name], + output_group = "changes", + ) + native.filegroup( + name = "%s.deb" % name, + srcs = ["%s-deb" % name], + output_group = "deb", + ) + +def envoy_pkg_debs(name, version, release_version, maintainer, bin_files = ":envoy-bin-files", config = ":envoy-config"): + """Package the Envoy .debs with their .changes files. + + Packages are created for the version *and* the release version, eg + + - envoy_1.21.0_amd64.deb + - envoy-1.21_1.21.0_amd64.deb + + This way packages are available for both "envoy" and "envoy-1.21" in package managers. + """ + + # generate deb data for all packages + pkg_tar( + name = "deb-data", + srcs = [ + "//distribution/debian:copyright", + config, + bin_files, + ], + remap_paths = {"/copyright": "/usr/share/doc/envoy/copyright"}, + ) + + # generate package for this patch version + envoy_pkg_deb( + name = "envoy", + data = ":deb-data", + version = version, + maintainer = maintainer, + ) + + # generate package for this minor version + envoy_pkg_deb( + name = "envoy-%s" % release_version, + data = ":deb-data", + version = version, + conflicts = ["envoy"], + provides = ["envoy"], + maintainer = maintainer, + ) + + pkg_tar( + name = name, + srcs = [ + "envoy.changes", + "envoy.deb", + "envoy-%s.changes" % release_version, + "envoy-%s.deb" % release_version, + ], + extension = "tar", + package_dir = "deb", + ) diff --git a/distribution/debian/postinst b/distribution/debian/postinst new file mode 100644 index 000000000000..d56003e6a9cd --- /dev/null +++ b/distribution/debian/postinst @@ -0,0 +1,42 @@ +#!/bin/sh + +# postinst script for envoy + +set -e + +case "$1" in + + configure) + cat <&2 + exit 1 + ;; + +esac + +#DEBHELPER# + +exit 0 diff --git a/distribution/debian/preinst b/distribution/debian/preinst new file mode 100755 index 000000000000..71de976079df --- /dev/null +++ b/distribution/debian/preinst @@ -0,0 +1,47 @@ +#! /bin/sh + +# preinst script for envoy + +set -e + +addenvoyuser() { + if ! getent group envoy >/dev/null; then + addgroup --system envoy >/dev/null + fi + + if ! getent passwd envoy >/dev/null; then + adduser \ + --system \ + --disabled-login \ + --ingroup envoy \ + --no-create-home \ + --home /nonexistent \ + --gecos "envoy user" \ + --shell /bin/false \ + envoy >/dev/null + + fi +} + +case "$1" in + + install) + addenvoyuser + ;; + + upgrade) + addenvoyuser + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 0 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/distribution/distros.yaml b/distribution/distros.yaml new file mode 100644 index 000000000000..549d4249dc65 --- /dev/null +++ b/distribution/distros.yaml @@ -0,0 +1,20 @@ + +debian_buster: + image: debian:buster-slim + ext: buster.changes + +debian_bullseye: + image: debian:bullseye-slim + ext: bullseye.changes + +ubuntu_bionic: + image: ubuntu:18.04 + ext: bionic.changes + +ubuntu_focal: + image: ubuntu:20.04 + ext: focal.changes + +ubuntu_impish: + image: ubuntu:21.10 + ext: impish.changes diff --git a/tools/distribution/distrotest.sh b/distribution/distrotest.sh similarity index 93% rename from tools/distribution/distrotest.sh rename to distribution/distrotest.sh index d19c6d087a04..741d1e792fd4 100755 --- a/tools/distribution/distrotest.sh +++ b/distribution/distrotest.sh @@ -84,7 +84,8 @@ test "$(stat -L -c "%a %G %U" /usr/bin/envoy)" == "$BINARY_PERMISSIONS" && echo run_log config-permissions "Check ownership/permissions of envoy config" test "$(stat -L -c "%a %G %U" /etc/envoy/envoy.yaml)" == "$CONFIG_PERMISSIONS" && echo "Correct permissions: ${CONFIG_PERMISSIONS}" -run_log envoy-version "Envoy version" +run_log envoy-version "Envoy version: ${ENVOY_VERSION}" +envoy --version envoy --version | grep "$ENVOY_VERSION" run_log start-envoy "Start Envoy" @@ -99,7 +100,7 @@ pgrep envoy run_log proxy-responds "Check proxy responds" RESPONSE=$(curl -s http://localhost:10000/) -echo "$RESPONSE" | grep "Welcome to Envoy" +echo "$RESPONSE" | grep "Envoy is an open source edge and service proxy, designed for cloud-native applications" run_log stop-envoy "Stop envoy" sudo -u envoy pkill envoy && echo "Envoy stopped" diff --git a/distribution/packages.bzl b/distribution/packages.bzl new file mode 100644 index 000000000000..187585709c82 --- /dev/null +++ b/distribution/packages.bzl @@ -0,0 +1,76 @@ +load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") +load("@rules_pkg//pkg:mappings.bzl", "pkg_attributes", "pkg_files") +load("//distribution/debian:packages.bzl", "envoy_pkg_debs") + +def _release_version_for(version): + if "-" in version: + version, version_suffix = version.split("-") + + major, minor, patch = version.split(".") + return ".".join((major, minor)) + +def envoy_pkg_distros( + name, + envoy_bin = ":envoy-bin", + version = None, + maintainer = None, + config = "//configs:envoyproxy_io_proxy.yaml"): + # data common to all packages + pkg_files( + name = "envoy-config", + srcs = [config], + renames = { + config: "/etc/envoy/envoy.yaml", + }, + ) + + pkg_files( + name = "envoy-bin-files", + srcs = [envoy_bin], + attributes = pkg_attributes(mode = "0755"), + renames = {envoy_bin: "/usr/bin/envoy"}, + ) + + # build debs + envoy_pkg_debs( + name = "debs", + version = version, + release_version = _release_version_for(version), + maintainer = maintainer, + ) + + # bundle distro packages into a tarball + pkg_tar( + name = "distro_packages", + extension = "tar", + deps = [ + ":debs", + ], + ) + + # sign the packages + native.genrule( + name = name, + cmd = """ + SIGNING_ARGS=() \ + && if [[ -n $${PACKAGES_GEN_KEY+x} ]]; then \ + SIGNING_ARGS+=("--gen-key"); \ + fi \ + && if [[ -n $${PACKAGES_MAINTAINER_NAME+x} ]]; then \ + SIGNING_ARGS+=("--maintainer-name" "$${PACKAGES_MAINTAINER_NAME}"); \ + fi \ + && if [[ -n $${PACKAGES_MAINTAINER_EMAIL+x} ]]; then \ + SIGNING_ARGS+=("--maintainer-email" "$${PACKAGES_MAINTAINER_EMAIL}"); \ + fi \ + && $(location //tools/distribution:sign) \ + --extract \ + --tar $@ \ + "$${SIGNING_ARGS[@]}" \ + $(location :distro_packages) + """, + outs = ["%s.tar.gz" % name], + srcs = [":distro_packages"], + tools = [ + "//tools/distribution:sign", + ], + ) diff --git a/tools/distribution/BUILD b/tools/distribution/BUILD index f4bea79a2132..c1214cd0842d 100644 --- a/tools/distribution/BUILD +++ b/tools/distribution/BUILD @@ -5,10 +5,6 @@ licenses(["notice"]) # Apache 2 envoy_package() -exports_files([ - "distrotest.sh", -]) - envoy_entry_point( name = "release", pkg = "envoy.distribution.release", From 63505caf5117528480954c20f80a7a5e63d43522 Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Thu, 10 Mar 2022 06:16:03 -0800 Subject: [PATCH 35/68] Remove dead code. (#20257) Signed-off-by: Kevin Baichoo --- test/integration/http_integration.h | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index 0b777b0731d3..a8f701b6cc2c 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -222,7 +222,6 @@ class HttpIntegrationTest : public BaseIntegrationTest { int upstream_index = 0, const std::string& path = "/test/long/url", const std::string& authority = "host"); - void testRequestAndResponseShutdownWithActiveConnection(); // Disconnect tests void testRouterUpstreamDisconnectBeforeRequestComplete(); From 5c56e45a041a4446411a8dccb5262b12323ff9be Mon Sep 17 00:00:00 2001 From: Xie Zhihao Date: Thu, 10 Mar 2022 22:19:48 +0800 Subject: [PATCH 36/68] ratelimit: add support for x-ratelimit-* headers in local rate limiting (#18157) ratelimit: add support for x-ratelimit-* headers in local rate limiting Signed-off-by: Xie Zhihao --- .../common/ratelimit/v3/ratelimit.proto | 17 +++ .../local_ratelimit/v3/local_rate_limit.proto | 8 +- .../http/ratelimit/v3/rate_limit.proto | 4 + docs/root/version_history/current.rst | 1 + .../local_ratelimit/local_ratelimit_impl.cc | 61 ++++++++- .../local_ratelimit/local_ratelimit_impl.h | 8 +- source/extensions/filters/http/common/BUILD | 8 ++ .../filters/http/common/ratelimit_headers.h | 30 +++++ .../filters/http/local_ratelimit/BUILD | 2 + .../http/local_ratelimit/local_ratelimit.cc | 67 ++++++++- .../http/local_ratelimit/local_ratelimit.h | 16 +++ .../extensions/filters/http/ratelimit/BUILD | 1 + .../http/ratelimit/ratelimit_headers.cc | 27 ++-- .../http/ratelimit/ratelimit_headers.h | 17 --- .../local_ratelimit/local_ratelimit_test.cc | 127 ++++++++++++++++++ .../http/local_ratelimit/filter_test.cc | 80 ++++++++--- .../ratelimit/ratelimit_integration_test.cc | 16 ++- .../local_ratelimit_fuzz_test.cc | 5 + .../local_ratelimit/local_ratelimit_test.cc | 2 +- 19 files changed, 437 insertions(+), 60 deletions(-) create mode 100644 source/extensions/filters/http/common/ratelimit_headers.h diff --git a/api/envoy/extensions/common/ratelimit/v3/ratelimit.proto b/api/envoy/extensions/common/ratelimit/v3/ratelimit.proto index 9f8666b6a4ad..594aec339a5c 100644 --- a/api/envoy/extensions/common/ratelimit/v3/ratelimit.proto +++ b/api/envoy/extensions/common/ratelimit/v3/ratelimit.proto @@ -17,6 +17,23 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#protodoc-title: Common rate limit components] +// Defines the version of the standard to use for X-RateLimit headers. +enum XRateLimitHeadersRFCVersion { + // X-RateLimit headers disabled. + OFF = 0; + + // Use `draft RFC Version 03 `_ where 3 headers will be added: + // + // * ``X-RateLimit-Limit`` - indicates the request-quota associated to the + // client in the current time-window followed by the description of the + // quota policy. The value is returned by the maximum tokens of the token bucket. + // * ``X-RateLimit-Remaining`` - indicates the remaining requests in the + // current time-window. The value is returned by the remaining tokens in the token bucket. + // * ``X-RateLimit-Reset`` - indicates the number of seconds until reset of + // the current time-window. The value is returned by the remaining fill interval of the token bucket. + DRAFT_VERSION_03 = 1; +} + // A RateLimitDescriptor is a list of hierarchical entries that are used by the service to // determine the final rate limit key and overall allowed limit. Here are some examples of how // they might be used for the domain "envoy". diff --git a/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto b/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto index 9d8252d9bc98..ad98da0a50f0 100644 --- a/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto +++ b/api/envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.proto @@ -20,7 +20,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Local Rate limit :ref:`configuration overview `. // [#extension: envoy.filters.http.local_ratelimit] -// [#next-free-field: 12] +// [#next-free-field: 13] message LocalRateLimit { // The human readable prefix to use when emitting stats. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; @@ -107,4 +107,10 @@ message LocalRateLimit { // one to rate limit requests on a per connection basis. // If unspecified, the default value is false. bool local_rate_limit_per_downstream_connection = 11; + + // Defines the standard version to use for X-RateLimit headers emitted by the filter. + // + // Disabled by default. + common.ratelimit.v3.XRateLimitHeadersRFCVersion enable_x_ratelimit_headers = 12 + [(validate.rules).enum = {defined_only: true}]; } diff --git a/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto b/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto index b76cbf4a4a9e..a0b30661db45 100644 --- a/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto +++ b/api/envoy/extensions/filters/http/ratelimit/v3/rate_limit.proto @@ -29,6 +29,8 @@ message RateLimit { "envoy.config.filter.http.rate_limit.v2.RateLimit"; // Defines the version of the standard to use for X-RateLimit headers. + // + // [#next-major-version: unify with local ratelimit, should use common.ratelimit.v3.XRateLimitHeadersRFCVersion instead.] enum XRateLimitHeadersRFCVersion { // X-RateLimit headers disabled. OFF = 0; @@ -100,6 +102,8 @@ message RateLimit { // the `draft RFC `_. // // Disabled by default. + // + // [#next-major-version: unify with local ratelimit, should use common.ratelimit.v3.XRateLimitHeadersRFCVersion instead.] XRateLimitHeadersRFCVersion enable_x_ratelimit_headers = 8 [(validate.rules).enum = {defined_only: true}]; diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index dfa58a5ccda7..53eef9cbea7f 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -81,6 +81,7 @@ New Features * http2: re-enabled the HTTP/2 wrapper API. This should be a transparent change that does not affect functionality. Any behavior changes can be reverted by setting the ``envoy.reloadable_features.http2_new_codec_wrapper`` runtime feature to false. * http3: downstream HTTP/3 support is now GA! Upstream HTTP/3 also GA for specific deployments. See :ref:`here ` for details. * http3: supports upstream HTTP/3 retries. Automatically retry `0-RTT safe requests `_ if they are rejected because they are sent `too early `_. And automatically retry 0-RTT safe requests if connect attempt fails later on and the cluster is configured with TCP fallback. And add retry on ``http3-post-connect-failure`` policy which allows retry of failed HTTP/3 requests with TCP fallback even after handshake if the cluster is configured with TCP fallback. This feature is guarded by ``envoy.reloadable_features.conn_pool_new_stream_with_early_data_and_http3``. +* local_ratelimit: added support for X-RateLimit-* headers as defined in `draft RFC `_. * matching: the matching API can now express a match tree that will always match by omitting a matcher at the top level. * outlier_detection: :ref:`max_ejection_time_jitter` configuration added to allow adding a random value to the ejection time to prevent 'thundering herd' scenarios. Defaults to 0 so as to not break or change the behavior of existing deployments. * redis: support for hostnames returned in `cluster slots` response is now available. diff --git a/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.cc b/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.cc index 1acd3049456c..d52f710eb93d 100644 --- a/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.cc +++ b/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.cc @@ -1,5 +1,7 @@ #include "source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h" +#include + #include "source/common/protobuf/utility.h" namespace Envoy { @@ -25,6 +27,7 @@ LocalRateLimiterImpl::LocalRateLimiterImpl( token_bucket_.tokens_per_fill_ = tokens_per_fill; token_bucket_.fill_interval_ = absl::FromChrono(fill_interval); tokens_.tokens_ = max_tokens; + tokens_.fill_time_ = time_source_.monotonicTime(); if (fill_timer_) { fill_timer_->enableTimer(fill_interval); @@ -72,7 +75,7 @@ void LocalRateLimiterImpl::onFillTimer() { fill_timer_->enableTimer(absl::ToChronoMilliseconds(token_bucket_.fill_interval_)); } -void LocalRateLimiterImpl::onFillTimerHelper(const TokenState& tokens, +void LocalRateLimiterImpl::onFillTimerHelper(TokenState& tokens, const RateLimit::TokenBucket& bucket) { // Relaxed consistency is used for all operations because we don't care about ordering, just the // final atomic correctness. @@ -88,6 +91,9 @@ void LocalRateLimiterImpl::onFillTimerHelper(const TokenState& tokens, // Loop while the weak CAS fails trying to update the tokens value. } while (!tokens.tokens_.compare_exchange_weak(expected_tokens, new_tokens_value, std::memory_order_relaxed)); + + // Update fill time at last. + tokens.fill_time_ = time_source_.monotonicTime(); } void LocalRateLimiterImpl::onFillTimerDescriptorHelper() { @@ -97,7 +103,6 @@ void LocalRateLimiterImpl::onFillTimerDescriptorHelper() { current_time - descriptor.token_state_->fill_time_) >= absl::ToChronoMilliseconds(descriptor.token_bucket_.fill_interval_)) { onFillTimerHelper(*descriptor.token_state_, descriptor.token_bucket_); - descriptor.token_state_->fill_time_ = current_time; } } } @@ -123,17 +128,63 @@ bool LocalRateLimiterImpl::requestAllowedHelper(const TokenState& tokens) const return true; } -bool LocalRateLimiterImpl::requestAllowed( +OptRef LocalRateLimiterImpl::descriptorHelper( absl::Span request_descriptors) const { if (!descriptors_.empty() && !request_descriptors.empty()) { + // The override rate limit descriptor is selected by the first full match from the request + // descriptors. for (const auto& request_descriptor : request_descriptors) { auto it = descriptors_.find(request_descriptor); if (it != descriptors_.end()) { - return requestAllowedHelper(*it->token_state_); + return *it; } } } - return requestAllowedHelper(tokens_); + return {}; +} + +bool LocalRateLimiterImpl::requestAllowed( + absl::Span request_descriptors) const { + auto descriptor = descriptorHelper(request_descriptors); + + return descriptor.has_value() ? requestAllowedHelper(*descriptor.value().get().token_state_) + : requestAllowedHelper(tokens_); +} + +uint32_t LocalRateLimiterImpl::maxTokens( + absl::Span request_descriptors) const { + auto descriptor = descriptorHelper(request_descriptors); + + return descriptor.has_value() ? descriptor.value().get().token_bucket_.max_tokens_ + : token_bucket_.max_tokens_; +} + +uint32_t LocalRateLimiterImpl::remainingTokens( + absl::Span request_descriptors) const { + auto descriptor = descriptorHelper(request_descriptors); + + return descriptor.has_value() + ? descriptor.value().get().token_state_->tokens_.load(std::memory_order_relaxed) + : tokens_.tokens_.load(std::memory_order_relaxed); +} + +int64_t LocalRateLimiterImpl::remainingFillInterval( + absl::Span request_descriptors) const { + using namespace std::literals; + + auto current_time = time_source_.monotonicTime(); + auto descriptor = descriptorHelper(request_descriptors); + // Remaining time to next fill = fill interval - (current time - last fill time). + if (descriptor.has_value()) { + ASSERT(std::chrono::duration_cast( + current_time - descriptor.value().get().token_state_->fill_time_) <= + absl::ToChronoMilliseconds(descriptor.value().get().token_bucket_.fill_interval_)); + return absl::ToInt64Seconds( + descriptor.value().get().token_bucket_.fill_interval_ - + absl::Seconds((current_time - descriptor.value().get().token_state_->fill_time_) / 1s)); + } + return absl::ToInt64Seconds(token_bucket_.fill_interval_ - + absl::Seconds((current_time - tokens_.fill_time_) / 1s)); } } // namespace LocalRateLimit diff --git a/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h b/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h index ab8f4e3a5ea5..452b85747f70 100644 --- a/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h +++ b/source/extensions/filters/common/local_ratelimit/local_ratelimit_impl.h @@ -26,6 +26,10 @@ class LocalRateLimiterImpl { ~LocalRateLimiterImpl(); bool requestAllowed(absl::Span request_descriptors) const; + uint32_t maxTokens(absl::Span request_descriptors) const; + uint32_t remainingTokens(absl::Span request_descriptors) const; + int64_t + remainingFillInterval(absl::Span request_descriptors) const; private: struct TokenState { @@ -59,8 +63,10 @@ class LocalRateLimiterImpl { }; void onFillTimer(); - void onFillTimerHelper(const TokenState& state, const RateLimit::TokenBucket& bucket); + void onFillTimerHelper(TokenState& state, const RateLimit::TokenBucket& bucket); void onFillTimerDescriptorHelper(); + OptRef + descriptorHelper(absl::Span request_descriptors) const; bool requestAllowedHelper(const TokenState& tokens) const; RateLimit::TokenBucket token_bucket_; diff --git a/source/extensions/filters/http/common/BUILD b/source/extensions/filters/http/common/BUILD index 140a0787a749..7bc4eea7f456 100644 --- a/source/extensions/filters/http/common/BUILD +++ b/source/extensions/filters/http/common/BUILD @@ -56,3 +56,11 @@ envoy_cc_library( "//source/common/common:token_bucket_impl_lib", ], ) + +envoy_cc_library( + name = "ratelimit_headers_lib", + hdrs = ["ratelimit_headers.h"], + deps = [ + "//source/common/http:header_map_lib", + ], +) diff --git a/source/extensions/filters/http/common/ratelimit_headers.h b/source/extensions/filters/http/common/ratelimit_headers.h new file mode 100644 index 000000000000..f8c9372c9d32 --- /dev/null +++ b/source/extensions/filters/http/common/ratelimit_headers.h @@ -0,0 +1,30 @@ +#pragma once + +#include "envoy/http/header_map.h" + +#include "source/common/singleton/const_singleton.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace Common { +namespace RateLimit { + +class XRateLimitHeaderValues { +public: + const Http::LowerCaseString XRateLimitLimit{"x-ratelimit-limit"}; + const Http::LowerCaseString XRateLimitRemaining{"x-ratelimit-remaining"}; + const Http::LowerCaseString XRateLimitReset{"x-ratelimit-reset"}; + + struct { + const std::string Window{"w"}; + const std::string Name{"name"}; + } QuotaPolicyKeys; +}; + +using XRateLimitHeaders = ConstSingleton; +} // namespace RateLimit +} // namespace Common +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/local_ratelimit/BUILD b/source/extensions/filters/http/local_ratelimit/BUILD index 0f3949915f59..88d4042e9809 100644 --- a/source/extensions/filters/http/local_ratelimit/BUILD +++ b/source/extensions/filters/http/local_ratelimit/BUILD @@ -28,6 +28,8 @@ envoy_cc_library( "//source/extensions/filters/common/local_ratelimit:local_ratelimit_lib", "//source/extensions/filters/common/ratelimit:ratelimit_lib", "//source/extensions/filters/http/common:pass_through_filter_lib", + "//source/extensions/filters/http/common:ratelimit_headers_lib", + "@envoy_api//envoy/extensions/common/ratelimit/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/local_ratelimit/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc index 5dc4a1c3286a..a4a5ee44fe06 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc @@ -4,10 +4,13 @@ #include #include +#include "envoy/extensions/common/ratelimit/v3/ratelimit.pb.h" +#include "envoy/extensions/filters/http/local_ratelimit/v3/local_rate_limit.pb.h" #include "envoy/http/codes.h" #include "source/common/http/utility.h" #include "source/common/router/config_impl.h" +#include "source/extensions/filters/http/common/ratelimit_headers.h" namespace Envoy { namespace Extensions { @@ -48,7 +51,9 @@ FilterConfig::FilterConfig( request_headers_parser_(Envoy::Router::HeaderParser::configure( config.request_headers_to_add_when_not_enforced())), stage_(static_cast(config.stage())), - has_descriptors_(!config.descriptors().empty()) { + has_descriptors_(!config.descriptors().empty()), + enable_x_rate_limit_headers_(config.enable_x_ratelimit_headers() == + envoy::extensions::common::ratelimit::v3::DRAFT_VERSION_03) { // Note: no token bucket is fine for the global config, which would be the case for enabling // the filter globally but disabled and then applying limits at the virtual host or // route level. At the virtual or route level, it makes no sense to have an no token @@ -64,6 +69,21 @@ bool FilterConfig::requestAllowed( return rate_limiter_.requestAllowed(request_descriptors); } +uint32_t +FilterConfig::maxTokens(absl::Span request_descriptors) const { + return rate_limiter_.maxTokens(request_descriptors); +} + +uint32_t FilterConfig::remainingTokens( + absl::Span request_descriptors) const { + return rate_limiter_.remainingTokens(request_descriptors); +} + +int64_t FilterConfig::remainingFillInterval( + absl::Span request_descriptors) const { + return rate_limiter_.remainingFillInterval(request_descriptors); +} + LocalRateLimitStats FilterConfig::generateStats(const std::string& prefix, Stats::Scope& scope) { const std::string final_prefix = prefix + ".http_local_rate_limit"; return {ALL_LOCAL_RATE_LIMIT_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))}; @@ -91,6 +111,9 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, populateDescriptors(descriptors, headers); } + // Store descriptors which is used to generate x-ratelimit-* headers in encoding response headers. + stored_descriptors_ = descriptors; + if (requestAllowed(descriptors)) { config->stats().ok_.inc(); return Http::FilterHeadersStatus::Continue; @@ -116,6 +139,26 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, return Http::FilterHeadersStatus::StopIteration; } +Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool) { + const auto* config = getConfig(); + + if (config->enabled() && config->enableXRateLimitHeaders()) { + ASSERT(stored_descriptors_.has_value()); + auto limit = maxTokens(stored_descriptors_.value()); + auto remaining = remainingTokens(stored_descriptors_.value()); + auto reset = remainingFillInterval(stored_descriptors_.value()); + + headers.addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitLimit, limit); + headers.addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitRemaining, remaining); + headers.addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitReset, reset); + } + + return Http::FilterHeadersStatus::Continue; +} + bool Filter::requestAllowed(absl::Span request_descriptors) { const auto* config = getConfig(); return config->rateLimitPerConnection() @@ -123,6 +166,28 @@ bool Filter::requestAllowed(absl::Span request : config->requestAllowed(request_descriptors); } +uint32_t Filter::maxTokens(absl::Span request_descriptors) { + const auto* config = getConfig(); + return config->rateLimitPerConnection() + ? getPerConnectionRateLimiter().maxTokens(request_descriptors) + : config->maxTokens(request_descriptors); +} + +uint32_t Filter::remainingTokens(absl::Span request_descriptors) { + const auto* config = getConfig(); + return config->rateLimitPerConnection() + ? getPerConnectionRateLimiter().remainingTokens(request_descriptors) + : config->remainingTokens(request_descriptors); +} + +int64_t +Filter::remainingFillInterval(absl::Span request_descriptors) { + const auto* config = getConfig(); + return config->rateLimitPerConnection() + ? getPerConnectionRateLimiter().remainingFillInterval(request_descriptors) + : config->remainingFillInterval(request_descriptors); +} + const Filters::Common::LocalRateLimit::LocalRateLimiterImpl& Filter::getPerConnectionRateLimiter() { const auto* config = getConfig(); ASSERT(config->rateLimitPerConnection()); diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h index 5b485e6b999f..bbe8238a8db9 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -70,6 +71,10 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { const LocalInfo::LocalInfo& localInfo() const { return local_info_; } Runtime::Loader& runtime() { return runtime_; } bool requestAllowed(absl::Span request_descriptors) const; + uint32_t maxTokens(absl::Span request_descriptors) const; + uint32_t remainingTokens(absl::Span request_descriptors) const; + int64_t + remainingFillInterval(absl::Span request_descriptors) const; bool enabled() const; bool enforced() const; LocalRateLimitStats& stats() const { return stats_; } @@ -87,6 +92,7 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { return descriptors_; } bool rateLimitPerConnection() const { return rate_limit_per_connection_; } + bool enableXRateLimitHeaders() const { return enable_x_rate_limit_headers_; } private: friend class FilterTest; @@ -119,6 +125,7 @@ class FilterConfig : public Router::RouteSpecificFilterConfig { Router::HeaderParserPtr request_headers_parser_; const uint64_t stage_; const bool has_descriptors_; + const bool enable_x_rate_limit_headers_; }; using FilterConfigSharedPtr = std::shared_ptr; @@ -135,6 +142,10 @@ class Filter : public Http::PassThroughFilter { Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool end_stream) override; + // Http::StreamEncoderFilter + Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap& headers, + bool end_stream) override; + private: friend class FilterTest; @@ -142,9 +153,14 @@ class Filter : public Http::PassThroughFilter { Http::RequestHeaderMap& headers); const Filters::Common::LocalRateLimit::LocalRateLimiterImpl& getPerConnectionRateLimiter(); bool requestAllowed(absl::Span request_descriptors); + uint32_t maxTokens(absl::Span request_descriptors); + uint32_t remainingTokens(absl::Span request_descriptors); + int64_t remainingFillInterval(absl::Span request_descriptors); const FilterConfig* getConfig() const; FilterConfigSharedPtr config_; + + absl::optional> stored_descriptors_; }; } // namespace LocalRateLimitFilter diff --git a/source/extensions/filters/http/ratelimit/BUILD b/source/extensions/filters/http/ratelimit/BUILD index 4b2890af5b49..fd4c15c81ace 100644 --- a/source/extensions/filters/http/ratelimit/BUILD +++ b/source/extensions/filters/http/ratelimit/BUILD @@ -38,6 +38,7 @@ envoy_cc_library( deps = [ "//source/common/http:header_map_lib", "//source/extensions/filters/common/ratelimit:ratelimit_client_interface", + "//source/extensions/filters/http/common:ratelimit_headers_lib", ], ) diff --git a/source/extensions/filters/http/ratelimit/ratelimit_headers.cc b/source/extensions/filters/http/ratelimit/ratelimit_headers.cc index 76644be416df..2bffd908f4cb 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit_headers.cc +++ b/source/extensions/filters/http/ratelimit/ratelimit_headers.cc @@ -1,6 +1,7 @@ #include "source/extensions/filters/http/ratelimit/ratelimit_headers.h" #include "source/common/http/header_map_impl.h" +#include "source/extensions/filters/http/common/ratelimit_headers.h" #include "absl/strings/substitute.h" @@ -34,14 +35,15 @@ Http::ResponseHeaderMapPtr XRateLimitHeaderUtils::create( // Example of the result: `, 10;w=1;name="per-ip", 1000;w=3600` if (window) { // For each descriptor status append `;w=` - absl::SubstituteAndAppend("a_policy, ", $0;$1=$2", - status.current_limit().requests_per_unit(), - XRateLimitHeaders::get().QuotaPolicyKeys.Window, window); + absl::SubstituteAndAppend( + "a_policy, ", $0;$1=$2", status.current_limit().requests_per_unit(), + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().QuotaPolicyKeys.Window, window); if (!status.current_limit().name().empty()) { // If the descriptor has a name, append `;name=""` - absl::SubstituteAndAppend("a_policy, ";$0=\"$1\"", - XRateLimitHeaders::get().QuotaPolicyKeys.Name, - status.current_limit().name()); + absl::SubstituteAndAppend( + "a_policy, ";$0=\"$1\"", + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().QuotaPolicyKeys.Name, + status.current_limit().name()); } } } @@ -49,11 +51,14 @@ Http::ResponseHeaderMapPtr XRateLimitHeaderUtils::create( if (min_remaining_limit_status) { const std::string rate_limit_limit = absl::StrCat( min_remaining_limit_status.value().current_limit().requests_per_unit(), quota_policy); - result->addReferenceKey(XRateLimitHeaders::get().XRateLimitLimit, rate_limit_limit); - result->addReferenceKey(XRateLimitHeaders::get().XRateLimitRemaining, - min_remaining_limit_status.value().limit_remaining()); - result->addReferenceKey(XRateLimitHeaders::get().XRateLimitReset, - min_remaining_limit_status.value().duration_until_reset().seconds()); + result->addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitLimit, rate_limit_limit); + result->addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitRemaining, + min_remaining_limit_status.value().limit_remaining()); + result->addReferenceKey( + HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitReset, + min_remaining_limit_status.value().duration_until_reset().seconds()); } descriptor_statuses = nullptr; return result; diff --git a/source/extensions/filters/http/ratelimit/ratelimit_headers.h b/source/extensions/filters/http/ratelimit/ratelimit_headers.h index 671794142656..14c83391e293 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit_headers.h +++ b/source/extensions/filters/http/ratelimit/ratelimit_headers.h @@ -1,28 +1,11 @@ #pragma once -#include "envoy/http/header_map.h" - -#include "source/common/singleton/const_singleton.h" #include "source/extensions/filters/common/ratelimit/ratelimit.h" namespace Envoy { namespace Extensions { namespace HttpFilters { namespace RateLimitFilter { - -class XRateLimitHeaderValues { -public: - const Http::LowerCaseString XRateLimitLimit{"x-ratelimit-limit"}; - const Http::LowerCaseString XRateLimitRemaining{"x-ratelimit-remaining"}; - const Http::LowerCaseString XRateLimitReset{"x-ratelimit-reset"}; - - struct { - const std::string Window{"w"}; - const std::string Name{"name"}; - } QuotaPolicyKeys; -}; -using XRateLimitHeaders = ConstSingleton; - class XRateLimitHeaderUtils { public: static Http::ResponseHeaderMapPtr diff --git a/test/extensions/filters/common/local_ratelimit/local_ratelimit_test.cc b/test/extensions/filters/common/local_ratelimit/local_ratelimit_test.cc index 3965930cfb33..87b05a3d97aa 100644 --- a/test/extensions/filters/common/local_ratelimit/local_ratelimit_test.cc +++ b/test/extensions/filters/common/local_ratelimit/local_ratelimit_test.cc @@ -172,6 +172,42 @@ TEST_F(LocalRateLimiterImplTest, TokenBucketMaxTokensGreaterThanTokensPerFill) { EXPECT_FALSE(rate_limiter_->requestAllowed(route_descriptors_)); } +// Verify token bucket status of max tokens, remaining tokens and remaining fill interval. +TEST_F(LocalRateLimiterImplTest, TokenBucketStatus) { + initialize(std::chrono::milliseconds(3000), 2, 2); + + // 2 -> 1 tokens + EXPECT_CALL(*fill_timer_, enableTimer(std::chrono::milliseconds(3000), nullptr)); + EXPECT_TRUE(rate_limiter_->requestAllowed(route_descriptors_)); + EXPECT_EQ(rate_limiter_->maxTokens(route_descriptors_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(route_descriptors_), 1); + EXPECT_EQ(rate_limiter_->remainingFillInterval(route_descriptors_), 3); + + // 1 -> 0 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_TRUE(rate_limiter_->requestAllowed(route_descriptors_)); + EXPECT_EQ(rate_limiter_->maxTokens(route_descriptors_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(route_descriptors_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(route_descriptors_), 2); + + // 0 -> 0 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_FALSE(rate_limiter_->requestAllowed(route_descriptors_)); + EXPECT_EQ(rate_limiter_->maxTokens(route_descriptors_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(route_descriptors_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(route_descriptors_), 1); + + // 0 -> 2 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + fill_timer_->invokeCallback(); + EXPECT_EQ(rate_limiter_->maxTokens(route_descriptors_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(route_descriptors_), 2); + EXPECT_EQ(rate_limiter_->remainingFillInterval(route_descriptors_), 3); +} + class LocalRateLimiterDescriptorImplTest : public LocalRateLimiterImplTest { public: void initializeWithDescriptor(const std::chrono::milliseconds fill_interval, @@ -394,6 +430,97 @@ TEST_F(LocalRateLimiterDescriptorImplTest, TokenBucketDifferentDescriptorDiffere EXPECT_FALSE(rate_limiter_->requestAllowed(descriptor_)); } +// Verify token bucket status of max tokens, remaining tokens and remaining fill interval. +TEST_F(LocalRateLimiterDescriptorImplTest, TokenBucketDescriptorStatus) { + TestUtility::loadFromYaml(fmt::format(single_descriptor_config_yaml, 2, 2, "3s"), + *descriptors_.Add()); + initializeWithDescriptor(std::chrono::milliseconds(3000), 2, 2); + + // 2 -> 1 tokens + EXPECT_CALL(*fill_timer_, enableTimer(std::chrono::milliseconds(3000), nullptr)); + EXPECT_TRUE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 1); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); + + // 1 -> 0 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_TRUE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 2); + + // 0 -> 0 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_FALSE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 1); + + // 0 -> 2 tokens + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(1000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + fill_timer_->invokeCallback(); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); +} + +// Verify token bucket status of max tokens, remaining tokens and remaining fill interval with +// multiple descriptors. +TEST_F(LocalRateLimiterDescriptorImplTest, TokenBucketDifferentDescriptorStatus) { + TestUtility::loadFromYaml(multiple_descriptor_config_yaml, *descriptors_.Add()); + TestUtility::loadFromYaml(fmt::format(single_descriptor_config_yaml, 2, 2, "3s"), + *descriptors_.Add()); + initializeWithDescriptor(std::chrono::milliseconds(50), 2, 1); + + // 2 -> 1 tokens for descriptor_ + EXPECT_TRUE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 1); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); + + // 1 -> 0 tokens for descriptor_ and descriptor2_ + EXPECT_TRUE(rate_limiter_->requestAllowed(descriptor2_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor2_), 1); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor2_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor2_), 0); + EXPECT_TRUE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); + + // 0 -> 0 tokens for descriptor_ and descriptor2_ + EXPECT_FALSE(rate_limiter_->requestAllowed(descriptor2_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor2_), 1); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor2_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor2_), 0); + EXPECT_FALSE(rate_limiter_->requestAllowed(descriptor_)); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 0); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); + + // 0 -> 1 tokens for descriptor2_ + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(50), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_CALL(*fill_timer_, enableTimer(std::chrono::milliseconds(50), nullptr)); + fill_timer_->invokeCallback(); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor2_), 1); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor2_), 1); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor2_), 0); + + // 0 -> 2 tokens for descriptor_ + dispatcher_.globalTimeSystem().advanceTimeAndRun(std::chrono::milliseconds(3000), dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + EXPECT_CALL(*fill_timer_, enableTimer(std::chrono::milliseconds(50), nullptr)); + fill_timer_->invokeCallback(); + EXPECT_EQ(rate_limiter_->maxTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingTokens(descriptor_), 2); + EXPECT_EQ(rate_limiter_->remainingFillInterval(descriptor_), 3); +} + } // Namespace LocalRateLimit } // namespace Common } // namespace Filters diff --git a/test/extensions/filters/http/local_ratelimit/filter_test.cc b/test/extensions/filters/http/local_ratelimit/filter_test.cc index 939cebf025ba..16942d533873 100644 --- a/test/extensions/filters/http/local_ratelimit/filter_test.cc +++ b/test/extensions/filters/http/local_ratelimit/filter_test.cc @@ -40,6 +40,7 @@ stat_prefix: test key: x-local-ratelimited value: 'true' local_rate_limit_per_downstream_connection: {} +enable_x_ratelimit_headers: {} )"; // '{}' used in the yaml config above are position dependent placeholders used for substitutions. // Different test cases toggle functionality based on these positional placeholder variables @@ -99,17 +100,17 @@ class FilterTest : public testing::Test { }; TEST_F(FilterTest, Runtime) { - setup(fmt::format(config_yaml, "1", "false"), false, false); + setup(fmt::format(config_yaml, "1", "false", "\"OFF\""), false, false); EXPECT_EQ(&runtime_, &(config_->runtime())); } TEST_F(FilterTest, ToErrorCode) { - setup(fmt::format(config_yaml, "1", "false"), false, false); + setup(fmt::format(config_yaml, "1", "false", "\"OFF\""), false, false); EXPECT_EQ(Http::Code::BadRequest, toErrorCode(400)); } TEST_F(FilterTest, Disabled) { - setup(fmt::format(config_yaml, "1", "false"), false, false); + setup(fmt::format(config_yaml, "1", "false", "\"OFF\""), false, false); auto headers = Http::TestRequestHeaderMapImpl(); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); EXPECT_EQ(0U, findCounter("test.http_local_rate_limit.enabled")); @@ -117,7 +118,7 @@ TEST_F(FilterTest, Disabled) { } TEST_F(FilterTest, RequestOk) { - setup(fmt::format(config_yaml, "1", "false")); + setup(fmt::format(config_yaml, "1", "false", "\"OFF\"")); auto headers = Http::TestRequestHeaderMapImpl(); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_2_->decodeHeaders(headers, false)); @@ -128,7 +129,7 @@ TEST_F(FilterTest, RequestOk) { } TEST_F(FilterTest, RequestOkPerConnection) { - setup(fmt::format(config_yaml, "1", "true")); + setup(fmt::format(config_yaml, "1", "true", "\"OFF\"")); auto headers = Http::TestRequestHeaderMapImpl(); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_2_->decodeHeaders(headers, false)); @@ -139,7 +140,7 @@ TEST_F(FilterTest, RequestOkPerConnection) { } TEST_F(FilterTest, RequestRateLimited) { - setup(fmt::format(config_yaml, "1", "false")); + setup(fmt::format(config_yaml, "1", "false", "\"OFF\"")); EXPECT_CALL(decoder_callbacks_2_, sendLocalReply(Http::Code::TooManyRequests, _, _, _, _)) .WillOnce(Invoke([](Http::Code code, absl::string_view body, @@ -179,7 +180,7 @@ connection rate limiting and even though 'max_token' is set to 1, it allows 2 re allowed (across the process) for the same configuration. */ TEST_F(FilterTest, RequestRateLimitedPerConnection) { - setup(fmt::format(config_yaml, "1", "true")); + setup(fmt::format(config_yaml, "1", "true", "\"OFF\"")); EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::TooManyRequests, _, _, _, _)) .WillOnce(Invoke([](Http::Code code, absl::string_view body, @@ -216,7 +217,7 @@ TEST_F(FilterTest, RequestRateLimitedPerConnection) { } TEST_F(FilterTest, RequestRateLimitedButNotEnforced) { - setup(fmt::format(config_yaml, "0", "false"), true, false); + setup(fmt::format(config_yaml, "0", "false", "\"OFF\""), true, false); EXPECT_CALL(decoder_callbacks_, sendLocalReply(Http::Code::TooManyRequests, _, _, _, _)).Times(0); @@ -231,6 +232,29 @@ TEST_F(FilterTest, RequestRateLimitedButNotEnforced) { EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited")); } +TEST_F(FilterTest, RequestRateLimitedXRateLimitHeaders) { + setup(fmt::format(config_yaml, "1", "false", "DRAFT_VERSION_03")); + + auto request_headers = Http::TestRequestHeaderMapImpl(); + auto response_headers = Http::TestResponseHeaderMapImpl(); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); + EXPECT_EQ("1", response_headers.get_("x-ratelimit-limit")); + EXPECT_EQ("0", response_headers.get_("x-ratelimit-remaining")); + EXPECT_EQ("1000", response_headers.get_("x-ratelimit-reset")); + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_2_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_2_->encodeHeaders(response_headers, false)); + EXPECT_EQ("1", response_headers.get_("x-ratelimit-limit")); + EXPECT_EQ("0", response_headers.get_("x-ratelimit-remaining")); + EXPECT_EQ("1000", response_headers.get_("x-ratelimit-reset")); + EXPECT_EQ(2U, findCounter("test.http_local_rate_limit.enabled")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enforced")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.ok")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited")); +} + static const std::string descriptor_config_yaml = R"( stat_prefix: test token_bucket: @@ -253,6 +277,7 @@ stat_prefix: test key: x-test-rate-limit value: 'true' local_rate_limit_per_downstream_connection: true +enable_x_ratelimit_headers: {} descriptors: - entries: - key: hello @@ -297,7 +322,7 @@ class DescriptorFilterTest : public FilterTest { }; TEST_F(DescriptorFilterTest, NoRouteEntry) { - setupPerRoute(fmt::format(descriptor_config_yaml, "1", "1", "0"), true, true, true); + setupPerRoute(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "0"), true, true, true); auto headers = Http::TestRequestHeaderMapImpl(); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false)); @@ -307,7 +332,7 @@ TEST_F(DescriptorFilterTest, NoRouteEntry) { } TEST_F(DescriptorFilterTest, NoCluster) { - setUpTest(fmt::format(descriptor_config_yaml, "1", "1", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "0")); EXPECT_CALL(decoder_callbacks_, clusterInfo()).WillRepeatedly(testing::Return(nullptr)); @@ -319,7 +344,7 @@ TEST_F(DescriptorFilterTest, NoCluster) { } TEST_F(DescriptorFilterTest, DisabledInRoute) { - setUpTest(fmt::format(descriptor_config_yaml, "1", "1", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "0")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -334,7 +359,7 @@ TEST_F(DescriptorFilterTest, DisabledInRoute) { } TEST_F(DescriptorFilterTest, RouteDescriptorRequestOk) { - setUpTest(fmt::format(descriptor_config_yaml, "1", "1", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "0")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -350,7 +375,7 @@ TEST_F(DescriptorFilterTest, RouteDescriptorRequestOk) { } TEST_F(DescriptorFilterTest, RouteDescriptorRequestRatelimited) { - setUpTest(fmt::format(descriptor_config_yaml, "0", "0", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "0", "\"OFF\"", "0", "0")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -366,7 +391,7 @@ TEST_F(DescriptorFilterTest, RouteDescriptorRequestRatelimited) { } TEST_F(DescriptorFilterTest, RouteDescriptorNotFound) { - setUpTest(fmt::format(descriptor_config_yaml, "1", "1", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "0")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -383,7 +408,7 @@ TEST_F(DescriptorFilterTest, RouteDescriptorNotFound) { TEST_F(DescriptorFilterTest, RouteDescriptorFirstMatch) { // Request should not be rate limited as it should match first descriptor with 10 req/min - setUpTest(fmt::format(descriptor_config_yaml, "0", "0", "0")); + setUpTest(fmt::format(descriptor_config_yaml, "0", "\"OFF\"", "0", "0")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(0)); @@ -399,7 +424,7 @@ TEST_F(DescriptorFilterTest, RouteDescriptorFirstMatch) { } TEST_F(DescriptorFilterTest, RouteDescriptorWithStageConfig) { - setUpTest(fmt::format(descriptor_config_yaml, "1", "1", "1")); + setUpTest(fmt::format(descriptor_config_yaml, "1", "\"OFF\"", "1", "1")); EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, getApplicableRateLimit(1)); @@ -414,6 +439,29 @@ TEST_F(DescriptorFilterTest, RouteDescriptorWithStageConfig) { EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.ok")); } +TEST_F(DescriptorFilterTest, RouteDescriptorRequestRatelimitedXRateLimitHeaders) { + setUpTest(fmt::format(descriptor_config_yaml, "0", "DRAFT_VERSION_03", "0", "0")); + + EXPECT_CALL(decoder_callbacks_.route_->route_entry_.rate_limit_policy_, + getApplicableRateLimit(0)); + + EXPECT_CALL(route_rate_limit_, populateLocalDescriptors(_, _, _, _)) + .WillOnce(testing::SetArgReferee<0>(descriptor_)); + + auto request_headers = Http::TestRequestHeaderMapImpl(); + auto response_headers = Http::TestResponseHeaderMapImpl(); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers, false)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); + EXPECT_EQ("0", response_headers.get_("x-ratelimit-limit")); + EXPECT_EQ("0", response_headers.get_("x-ratelimit-remaining")); + EXPECT_EQ("60", response_headers.get_("x-ratelimit-reset")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enabled")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.enforced")); + EXPECT_EQ(1U, findCounter("test.http_local_rate_limit.rate_limited")); +} + } // namespace LocalRateLimitFilter } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc index f125f8f722ef..5ed1299c2ff6 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_integration_test.cc @@ -8,8 +8,8 @@ #include "source/common/buffer/zero_copy_input_stream_impl.h" #include "source/common/grpc/codec.h" #include "source/common/grpc/common.h" +#include "source/extensions/filters/http/common/ratelimit_headers.h" #include "source/extensions/filters/http/ratelimit/config.h" -#include "source/extensions/filters/http/ratelimit/ratelimit_headers.h" #include "test/common/grpc/grpc_client_integration.h" #include "test/extensions/filters/common/ratelimit/utils.h" @@ -406,17 +406,18 @@ TEST_P(RatelimitFilterHeadersEnabledIntegrationTest, OkWithFilterHeaders) { EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitLimit, + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitLimit, "1, 1;w=60;name=\"first\", 4;w=3600;name=\"second\"")); EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitRemaining, + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitRemaining, "2")); EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitReset, "3")); + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitReset, + "3")); cleanup(); @@ -442,17 +443,18 @@ TEST_P(RatelimitFilterHeadersEnabledIntegrationTest, OverLimitWithFilterHeaders) EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitLimit, + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitLimit, "1, 1;w=60;name=\"first\", 4;w=3600;name=\"second\"")); EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitRemaining, + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitRemaining, "2")); EXPECT_THAT( responses_[0].get()->headers(), Http::HeaderValueOf( - Extensions::HttpFilters::RateLimitFilter::XRateLimitHeaders::get().XRateLimitReset, "3")); + Extensions::HttpFilters::Common::RateLimit::XRateLimitHeaders::get().XRateLimitReset, + "3")); cleanup(); diff --git a/test/extensions/filters/network/local_ratelimit/local_ratelimit_fuzz_test.cc b/test/extensions/filters/network/local_ratelimit/local_ratelimit_fuzz_test.cc index c13c03b5f876..d2cf7c8290ca 100644 --- a/test/extensions/filters/network/local_ratelimit/local_ratelimit_fuzz_test.cc +++ b/test/extensions/filters/network/local_ratelimit/local_ratelimit_fuzz_test.cc @@ -50,6 +50,11 @@ DEFINE_PROTO_FUZZER( return; } static NiceMock dispatcher; + // TODO(zhxie): The GlobalTimeSystem in MockDispatcher will initialize itself into a + // TestRealTimeSystem by default which is incompatible with the SimulatedTimeSystem in + // MockReadFilterCallbacks. We will not need to change the time system after the switching of + // default time system in GlobalTimeSystem. + dispatcher.time_system_ = std::make_unique(); Stats::IsolatedStoreImpl stats_store; static NiceMock runtime; Event::MockTimer* fill_timer = new Event::MockTimer(&dispatcher); diff --git a/test/extensions/filters/network/local_ratelimit/local_ratelimit_test.cc b/test/extensions/filters/network/local_ratelimit/local_ratelimit_test.cc index 50568d622f69..222c9a5105a5 100644 --- a/test/extensions/filters/network/local_ratelimit/local_ratelimit_test.cc +++ b/test/extensions/filters/network/local_ratelimit/local_ratelimit_test.cc @@ -21,7 +21,7 @@ namespace Extensions { namespace NetworkFilters { namespace LocalRateLimitFilter { -class LocalRateLimitTestBase : public testing::Test { +class LocalRateLimitTestBase : public testing::Test, public Event::TestUsingSimulatedTime { public: void initialize(const std::string& filter_yaml, bool expect_timer_create = true) { envoy::extensions::filters::network::local_ratelimit::v3::LocalRateLimit proto_config; From b435d3a3baa39f1a15cd68625da085cfa16ae957 Mon Sep 17 00:00:00 2001 From: code Date: Thu, 10 Mar 2022 23:27:54 +0800 Subject: [PATCH 37/68] lazy read disable for the http1 codec (#20148) Signed-off-by: wbpcode --- docs/root/version_history/current.rst | 1 + source/common/http/http1/codec_impl.cc | 32 ++++- source/common/http/http1/codec_impl.h | 5 +- source/common/runtime/runtime_features.cc | 2 + test/common/http/http1/codec_impl_test.cc | 153 ++++++++++++++++++++++ test/integration/fake_upstream.cc | 20 --- 6 files changed, 189 insertions(+), 24 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 53eef9cbea7f..865e768fc3fa 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -19,6 +19,7 @@ Minor Behavior Changes * grpc: flip runtime guard ``envoy.reloadable_features.enable_grpc_async_client_cache`` to be default enabled. async grpc client created through getOrCreateRawAsyncClient will be cached by default. * health_checker: exposing `initial_metadata` to GrpcHealthCheck in a way similar to `request_headers_to_add` of HttpHealthCheck. * http: avoiding delay-close for HTTP/1.0 responses framed by connection: close as well as HTTP/1.1 if the request is fully read. This means for responses to such requests, the FIN will be sent immediately after the response. This behavior can be temporarily reverted by setting ``envoy.reloadable_features.skip_delay_close`` to false. If clients are are seen to be receiving sporadic partial responses and flipping this flag fixes it, please notify the project immediately. +* http: lazy disable downstream connection reading in the HTTP/1 codec to reduce unnecessary system calls. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.http1_lazy_read_disable`` to false. * http: now the max concurrent streams of http2 connection can not only be adjusted down according to the SETTINGS frame but also can be adjusted up, of course, it can not exceed the configured upper bounds. This fix is guarded by ``envoy.reloadable_features.http2_allow_capacity_increase_by_settings``. * http: when writing custom filters, `injectEncodedDataToFilterChain` and `injectDecodedDataToFilterChain` now trigger sending of headers if they were not yet sent due to `StopIteration`. Previously, calling one of the inject functions in that state would trigger an assertion. See issue #19891 for more details. * listener: the :ref:`ipv4_compat ` flag can only be set on Ipv6 address and Ipv4-mapped Ipv6 address. A runtime guard is added ``envoy.reloadable_features.strict_check_on_ipv4_compat`` and the default is true. diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index 7cad7ec80a9e..92e828f1a36f 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -964,7 +964,9 @@ ServerConnectionImpl::ServerConnectionImpl( response_buffer_releasor_([this](const Buffer::OwnedBufferFragmentImpl* fragment) { releaseOutboundResponse(fragment); }), - headers_with_underscores_action_(headers_with_underscores_action) {} + headers_with_underscores_action_(headers_with_underscores_action), + runtime_lazy_read_disable_( + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http1_lazy_read_disable")) {} uint32_t ServerConnectionImpl::getHeadersSize() { // Add in the size of the request URL if processing request headers. @@ -1143,13 +1145,39 @@ void ServerConnectionImpl::onBody(Buffer::Instance& data) { } } +Http::Status ServerConnectionImpl::dispatch(Buffer::Instance& data) { + if (runtime_lazy_read_disable_ && active_request_ != nullptr && + active_request_->remote_complete_) { + // Eagerly read disable the connection if the downstream is sending pipelined requests as we + // serially process them. Reading from the connection will be re-enabled after the active + // request is completed. + active_request_->response_encoder_.readDisable(true); + return okStatus(); + } + + Http::Status status = ConnectionImpl::dispatch(data); + + if (runtime_lazy_read_disable_ && active_request_ != nullptr && + active_request_->remote_complete_) { + // Read disable the connection if the downstream is sending additional data while we are working + // on an existing request. Reading from the connection will be re-enabled after the active + // request is completed. + if (data.length() > 0) { + active_request_->response_encoder_.readDisable(true); + } + } + return status; +} + ParserStatus ServerConnectionImpl::onMessageCompleteBase() { ASSERT(!handling_upgrade_); if (active_request_) { // The request_decoder should be non-null after we've called the newStream on callbacks. ASSERT(active_request_->request_decoder_); - active_request_->response_encoder_.readDisable(true); + if (!runtime_lazy_read_disable_) { + active_request_->response_encoder_.readDisable(true); + } active_request_->remote_complete_ = true; if (deferred_end_stream_headers_) { diff --git a/source/common/http/http1/codec_impl.h b/source/common/http/http1/codec_impl.h index 161ef63b1164..9d1c6731e486 100644 --- a/source/common/http/http1/codec_impl.h +++ b/source/common/http/http1/codec_impl.h @@ -81,8 +81,6 @@ class StreamEncoderImpl : public virtual StreamEncoder, void setIsResponseToConnectRequest(bool value) { is_response_to_connect_request_ = value; } void setDetails(absl::string_view details) { details_ = details; } - void clearReadDisableCallsForTests() { read_disable_calls_ = 0; } - const StreamInfo::BytesMeterSharedPtr& bytesMeter() override { return bytes_meter_; } protected: @@ -477,6 +475,7 @@ class ServerConnectionImpl : public ServerConnection, public ConnectionImpl { Status onUrl(const char* data, size_t length) override; Status onStatus(const char*, size_t) override { return okStatus(); } // ConnectionImpl + Http::Status dispatch(Buffer::Instance& data) override; void onEncodeComplete() override; StreamInfo::BytesMeter& getBytesMeter() override { if (active_request_) { @@ -540,6 +539,8 @@ class ServerConnectionImpl : public ServerConnection, public ConnectionImpl { // The action to take when a request header name contains underscore characters. const envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action_; + + const bool runtime_lazy_read_disable_{}; }; /** diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 654fde0777cd..e1eeb39df46f 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -33,6 +33,7 @@ RUNTIME_GUARD(envoy_reloadable_features_do_not_await_headers_on_upstream_timeout RUNTIME_GUARD(envoy_reloadable_features_enable_grpc_async_client_cache); RUNTIME_GUARD(envoy_reloadable_features_fix_added_trailers); RUNTIME_GUARD(envoy_reloadable_features_handle_stream_reset_during_hcm_encoding); +RUNTIME_GUARD(envoy_reloadable_features_http1_lazy_read_disable); RUNTIME_GUARD(envoy_reloadable_features_http2_allow_capacity_increase_by_settings); RUNTIME_GUARD(envoy_reloadable_features_http2_new_codec_wrapper); RUNTIME_GUARD(envoy_reloadable_features_http_ext_authz_do_not_skip_direct_response_and_redirect); @@ -151,6 +152,7 @@ constexpr absl::Flag* runtime_features[] = { &FLAGS_envoy_reloadable_features_enable_grpc_async_client_cache, &FLAGS_envoy_reloadable_features_fix_added_trailers, &FLAGS_envoy_reloadable_features_handle_stream_reset_during_hcm_encoding, + &FLAGS_envoy_reloadable_features_http1_lazy_read_disable, &FLAGS_envoy_reloadable_features_http2_allow_capacity_increase_by_settings, &FLAGS_envoy_reloadable_features_http2_new_codec_wrapper, &FLAGS_envoy_reloadable_features_http_ext_authz_do_not_skip_direct_response_and_redirect, diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 36726e28f325..06d90dffa418 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -3000,6 +3000,159 @@ TEST_F(Http1ServerConnectionImplTest, ManyLargeRequestHeadersAccepted) { testRequestHeadersAccepted(createLargeHeaderFragment(64)); } +TEST_F(Http1ServerConnectionImplTest, RuntimeLazyReadDisableTest) { + TestScopedRuntime scoped_runtime; + Runtime::RuntimeFeaturesDefaults::get().restoreDefaults(); + + // No readDisable for normal non-piped HTTP request. + { + initialize(); + + NiceMock decoder; + Http::ResponseEncoder* response_encoder = nullptr; + EXPECT_CALL(callbacks_, newStream(_, _)) + .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { + response_encoder = &encoder; + return decoder; + })); + + EXPECT_CALL(decoder, decodeHeaders_(_, true)); + EXPECT_CALL(decoder, decodeData(_, _)).Times(0); + + EXPECT_CALL(connection_, readDisable(true)).Times(0); + + Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\nhost: a.com\r\n\r\n"); + auto status = codec_->dispatch(buffer); + EXPECT_TRUE(status.ok()); + + std::string output; + ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); + TestResponseHeaderMapImpl headers{{":status", "200"}}; + response_encoder->encodeHeaders(headers, true); + EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n", output); + + EXPECT_CALL(connection_, readDisable(false)).Times(0); + // Delete active request. + connection_.dispatcher_.clearDeferredDeleteList(); + } + + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.http1_lazy_read_disable", "false"}}); + + // Always call readDisable if lazy read disable flag is set to false. + { + initialize(); + + NiceMock decoder; + Http::ResponseEncoder* response_encoder = nullptr; + EXPECT_CALL(callbacks_, newStream(_, _)) + .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { + response_encoder = &encoder; + return decoder; + })); + + EXPECT_CALL(decoder, decodeHeaders_(_, true)); + EXPECT_CALL(decoder, decodeData(_, _)).Times(0); + + EXPECT_CALL(connection_, readDisable(true)); + + Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\nhost: a.com\r\n\r\n"); + + auto status = codec_->dispatch(buffer); + EXPECT_TRUE(status.ok()); + + std::string output; + ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); + TestResponseHeaderMapImpl headers{{":status", "200"}}; + response_encoder->encodeHeaders(headers, true); + EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n", output); + + EXPECT_CALL(connection_, readDisable(false)); + // Delete active request. + connection_.dispatcher_.clearDeferredDeleteList(); + } +} + +// Tests the scenario where the client sends pipelined requests and the requests reach Envoy at the +// same time. +TEST_F(Http1ServerConnectionImplTest, PipedRequestWithSingleEvent) { + TestScopedRuntime scoped_runtime; + Runtime::RuntimeFeaturesDefaults::get().restoreDefaults(); + + initialize(); + + NiceMock decoder; + Http::ResponseEncoder* response_encoder = nullptr; + EXPECT_CALL(callbacks_, newStream(_, _)) + .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { + response_encoder = &encoder; + return decoder; + })); + + EXPECT_CALL(decoder, decodeHeaders_(_, true)); + EXPECT_CALL(decoder, decodeData(_, _)).Times(0); + + EXPECT_CALL(connection_, readDisable(true)); + + Buffer::OwnedImpl buffer( + "GET / HTTP/1.1\r\nhost: a.com\r\n\r\nGET / HTTP/1.1\r\nhost: b.com\r\n\r\n"); + auto status = codec_->dispatch(buffer); + EXPECT_TRUE(status.ok()); + + std::string output; + ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); + TestResponseHeaderMapImpl headers{{":status", "200"}}; + response_encoder->encodeHeaders(headers, true); + EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n", output); + + EXPECT_CALL(connection_, readDisable(false)); + // Delete active request to re-enable connection reading. + connection_.dispatcher_.clearDeferredDeleteList(); +} + +// Tests the scenario where the client sends pipelined requests. The second request reaches Envoy +// before the end of the first request. +TEST_F(Http1ServerConnectionImplTest, PipedRequestWithMutipleEvent) { + TestScopedRuntime scoped_runtime; + Runtime::RuntimeFeaturesDefaults::get().restoreDefaults(); + + initialize(); + + NiceMock decoder; + Http::ResponseEncoder* response_encoder = nullptr; + EXPECT_CALL(callbacks_, newStream(_, _)) + .WillOnce(Invoke([&](ResponseEncoder& encoder, bool) -> RequestDecoder& { + response_encoder = &encoder; + return decoder; + })); + + EXPECT_CALL(decoder, decodeHeaders_(_, true)); + EXPECT_CALL(decoder, decodeData(_, _)).Times(0); + + Buffer::OwnedImpl buffer("GET / HTTP/1.1\r\nhost: a.com\r\n\r\n"); + auto status = codec_->dispatch(buffer); + EXPECT_TRUE(status.ok()); + + Buffer::OwnedImpl second_buffer("GET / HTTP/1.1\r\nhost: a.com\r\n\r\n"); + + // Second request before first request complete will disable downstream connection reading. + EXPECT_CALL(connection_, readDisable(true)); + auto second_status = codec_->dispatch(second_buffer); + EXPECT_TRUE(second_status.ok()); + // The second request will no be consumed. + EXPECT_TRUE(second_buffer.length() != 0); + + std::string output; + ON_CALL(connection_, write(_, _)).WillByDefault(AddBufferToString(&output)); + TestResponseHeaderMapImpl headers{{":status", "200"}}; + response_encoder->encodeHeaders(headers, true); + EXPECT_EQ("HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n", output); + + EXPECT_CALL(connection_, readDisable(false)); + // Delete active request to re-enable connection reading. + connection_.dispatcher_.clearDeferredDeleteList(); +} + // Tests that incomplete response headers of 80 kB header value fails. TEST_F(Http1ClientConnectionImplTest, ResponseHeadersWithLargeValueRejected) { initialize(); diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index 0c7eb169adb6..9675e7b63e01 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -298,29 +298,9 @@ void FakeStream::finishGrpcStream(Grpc::Status::GrpcStatus status) { {"grpc-status", std::to_string(static_cast(status))}}); } -// The TestHttp1ServerConnectionImpl outlives its underlying Network::Connection -// so must not access the Connection on teardown. To achieve this, clear the -// read disable calls to avoid checking / editing the Connection blocked state. class TestHttp1ServerConnectionImpl : public Http::Http1::ServerConnectionImpl { public: using Http::Http1::ServerConnectionImpl::ServerConnectionImpl; - - Http::Http1::ParserStatus onMessageCompleteBase() override { - auto rc = ServerConnectionImpl::onMessageCompleteBase(); - - if (activeRequest() && activeRequest()->request_decoder_) { - // Undo the read disable from the base class - we have many tests which - // waitForDisconnect after a full request has been read which will not - // receive the disconnect if reading is disabled. - activeRequest()->response_encoder_.readDisable(false); - } - return rc; - } - ~TestHttp1ServerConnectionImpl() override { - if (activeRequest()) { - activeRequest()->response_encoder_.clearReadDisableCallsForTests(); - } - } }; class TestHttp2ServerConnectionImpl : public Http::Http2::ServerConnectionImpl { From a9c3a9663539097d7e1ec67036e2306cb553bdc5 Mon Sep 17 00:00:00 2001 From: Timon Wong Date: Thu, 10 Mar 2022 23:33:37 +0800 Subject: [PATCH 38/68] config: track message ancestors for unknown fields (#20055) Signed-off-by: Tianpeng Wang --- docs/root/version_history/current.rst | 1 + source/common/protobuf/utility.cc | 19 ++++++-- source/common/protobuf/visitor.cc | 39 ++++++++++++--- source/common/protobuf/visitor.h | 7 ++- test/common/protobuf/BUILD | 1 + test/common/protobuf/utility_test.cc | 70 +++++++++++++++++++++------ test/test_common/utility.h | 5 +- 7 files changed, 111 insertions(+), 31 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 865e768fc3fa..a55c4e1119b4 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -13,6 +13,7 @@ Minor Behavior Changes *Changes that may cause incompatibilities for some users, but should not for most* * access_log: log all header values in the grpc access log. +* config: warning messages for protobuf unknown fields now contain ancestors for easier troubleshooting. * dynamic_forward_proxy: if a DNS resolution fails, failing immediately with a specific resolution error, rather than finishing up all local filters and failing to select an upstream host. * ext_authz: added requested server name in ext_authz network filter for auth review. * file: changed disk based files to truncate files which are not being appended to. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.append_or_truncate`` to false. diff --git a/source/common/protobuf/utility.cc b/source/common/protobuf/utility.cc index cb2dd853f92b..b33d3ec1c5c7 100644 --- a/source/common/protobuf/utility.cc +++ b/source/common/protobuf/utility.cc @@ -408,7 +408,8 @@ class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { } } - void onMessage(const Protobuf::Message& message, bool) override { + void onMessage(const Protobuf::Message& message, + absl::Span parents, bool) override { if (message.GetDescriptor() ->options() .GetExtension(xds::annotations::v3::message_status) @@ -432,11 +433,18 @@ class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { if (!unknown_fields.empty()) { std::string error_msg; for (int n = 0; n < unknown_fields.field_count(); ++n) { - error_msg += absl::StrCat(n > 0 ? ", " : "", unknown_fields.field(n).number()); + absl::StrAppend(&error_msg, n > 0 ? ", " : "", unknown_fields.field(n).number()); } if (!error_msg.empty()) { - validation_visitor_.onUnknownField("type " + message.GetTypeName() + - " with unknown field set {" + error_msg + "}"); + validation_visitor_.onUnknownField( + fmt::format("type {}({}) with unknown field set {{{}}}", message.GetTypeName(), + !parents.empty() + ? absl::StrJoin(parents, "::", + [](std::string* out, const Protobuf::Message* const m) { + absl::StrAppend(out, m->GetTypeName()); + }) + : "root", + error_msg)); } } } @@ -462,7 +470,8 @@ namespace { class PgvCheckVisitor : public ProtobufMessage::ConstProtoVisitor { public: - void onMessage(const Protobuf::Message& message, bool was_any_or_top_level) override { + void onMessage(const Protobuf::Message& message, absl::Span, + bool was_any_or_top_level) override { std::string err; // PGV verification is itself recursive up to the point at which it hits an Any message. As // such, to avoid N^2 checking of the tree, we only perform an additional check at the point diff --git a/source/common/protobuf/visitor.cc b/source/common/protobuf/visitor.cc index a233ac3b2efc..433fa0aebb16 100644 --- a/source/common/protobuf/visitor.cc +++ b/source/common/protobuf/visitor.cc @@ -1,5 +1,7 @@ #include "source/common/protobuf/visitor.h" +#include + #include "source/common/protobuf/message_validator_impl.h" #include "source/common/protobuf/utility.h" @@ -37,9 +39,26 @@ convertTypedStruct(const Protobuf::Message& message) { return {std::move(inner_message), target_type_url}; } +/** + * RAII wrapper that push message to parents on construction and pop it on destruction. + */ +struct ScopedMessageParents { + ScopedMessageParents(std::vector& parents, + const Protobuf::Message& message) + : parents_(parents) { + parents_.push_back(&message); + } + + ~ScopedMessageParents() { parents_.pop_back(); } + +private: + std::vector& parents_; +}; + void traverseMessageWorker(ConstProtoVisitor& visitor, const Protobuf::Message& message, + std::vector& parents, bool was_any_or_top_level, bool recurse_into_any) { - visitor.onMessage(message, was_any_or_top_level); + visitor.onMessage(message, parents, was_any_or_top_level); // If told to recurse into Any messages, do that here and skip the rest of the function. if (recurse_into_any) { @@ -62,7 +81,9 @@ void traverseMessageWorker(ConstProtoVisitor& visitor, const Protobuf::Message& } if (inner_message != nullptr) { - traverseMessageWorker(visitor, *inner_message, true, recurse_into_any); + // Push the Any message as a wrapper. + ScopedMessageParents scoped_parents(parents, message); + traverseMessageWorker(visitor, *inner_message, parents, true, recurse_into_any); return; } else if (!target_type_url.empty()) { throw EnvoyException(fmt::format("Invalid type_url '{}' during traversal", target_type_url)); @@ -74,16 +95,19 @@ void traverseMessageWorker(ConstProtoVisitor& visitor, const Protobuf::Message& for (int i = 0; i < descriptor->field_count(); ++i) { const Protobuf::FieldDescriptor* field = descriptor->field(i); visitor.onField(message, *field); - // If this is a message, recurse to scrub deprecated fields in the sub-message. + + // If this is a message, recurse in to the sub-message. if (field->cpp_type() == Protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + ScopedMessageParents scoped_parents(parents, message); + if (field->is_repeated()) { const int size = reflection->FieldSize(message, field); for (int j = 0; j < size; ++j) { - traverseMessageWorker(visitor, reflection->GetRepeatedMessage(message, field, j), false, - recurse_into_any); + traverseMessageWorker(visitor, reflection->GetRepeatedMessage(message, field, j), parents, + false, recurse_into_any); } } else if (reflection->HasField(message, field)) { - traverseMessageWorker(visitor, reflection->GetMessage(message, field), false, + traverseMessageWorker(visitor, reflection->GetMessage(message, field), parents, false, recurse_into_any); } } @@ -94,7 +118,8 @@ void traverseMessageWorker(ConstProtoVisitor& visitor, const Protobuf::Message& void traverseMessage(ConstProtoVisitor& visitor, const Protobuf::Message& message, bool recurse_into_any) { - traverseMessageWorker(visitor, message, true, recurse_into_any); + std::vector parents; + traverseMessageWorker(visitor, message, parents, true, recurse_into_any); } } // namespace ProtobufMessage diff --git a/source/common/protobuf/visitor.h b/source/common/protobuf/visitor.h index cf306acaaf40..0043364b15d8 100644 --- a/source/common/protobuf/visitor.h +++ b/source/common/protobuf/visitor.h @@ -4,6 +4,8 @@ #include "source/common/protobuf/protobuf.h" +#include "absl/types/span.h" + namespace Envoy { namespace ProtobufMessage { @@ -14,11 +16,12 @@ class ConstProtoVisitor { // Invoked when a field is visited, with the message, and field descriptor. virtual void onField(const Protobuf::Message&, const Protobuf::FieldDescriptor&) PURE; - // Invoked when a message is visited, with the message. + // Invoked when a message is visited, with the message and visited parents. // @param was_any_or_top_level supplies whether the message was either the top level message or an // Any before being unpacked for further recursion. The latter can // only be achieved by using recurse_into_any. - virtual void onMessage(const Protobuf::Message&, bool was_any_or_top_level) PURE; + virtual void onMessage(const Protobuf::Message&, absl::Span, + bool was_any_or_top_level) PURE; }; void traverseMessage(ConstProtoVisitor& visitor, const Protobuf::Message& message, diff --git a/test/common/protobuf/BUILD b/test/common/protobuf/BUILD index 1f61758acb1b..46dee93037cd 100644 --- a/test/common/protobuf/BUILD +++ b/test/common/protobuf/BUILD @@ -59,6 +59,7 @@ envoy_cc_test( "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/health_checker/redis/v2:pkg_cc_proto", + "@envoy_api//envoy/extensions/clusters/dynamic_forward_proxy/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/health_checkers/redis/v3:pkg_cc_proto", "@envoy_api//envoy/type/v3:pkg_cc_proto", ], diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index 7d259f621c86..f3d63cabb95a 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -10,6 +10,8 @@ #include "envoy/config/core/v3/base.pb.h" #include "envoy/config/health_checker/redis/v2/redis.pb.h" #include "envoy/config/health_checker/redis/v2/redis.pb.validate.h" +#include "envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.pb.h" +#include "envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.pb.validate.h" #include "envoy/extensions/health_checkers/redis/v3/redis.pb.h" #include "envoy/extensions/health_checkers/redis/v3/redis.pb.validate.h" #include "envoy/type/v3/percent.pb.h" @@ -215,16 +217,25 @@ TEST_F(ProtobufUtilityTest, DowncastAndValidateFailedValidation) { ProtoValidationException); } +namespace { +inline std::string unknownFieldsMessage(absl::string_view type_name, + const std::vector& parent_paths, + const std::vector& field_numbers) { + return fmt::format( + "Protobuf message (type {}({}) with unknown field set {{{}}}) has unknown fields", type_name, + !parent_paths.empty() ? absl::StrJoin(parent_paths, "::") : "root", + absl::StrJoin(field_numbers, ", ")); +} +} // namespace + // Validated exception thrown when downcastAndValidate observes a unknown field. TEST_F(ProtobufUtilityTest, DowncastAndValidateUnknownFields) { envoy::config::bootstrap::v3::Bootstrap bootstrap; bootstrap.GetReflection()->MutableUnknownFields(&bootstrap)->AddVarint(1, 0); EXPECT_THROW_WITH_MESSAGE(TestUtility::validate(bootstrap), EnvoyException, - "Protobuf message (type envoy.config.bootstrap.v3.Bootstrap with " - "unknown field set {1}) has unknown fields"); + unknownFieldsMessage("envoy.config.bootstrap.v3.Bootstrap", {}, {1})); EXPECT_THROW_WITH_MESSAGE(TestUtility::validate(bootstrap), EnvoyException, - "Protobuf message (type envoy.config.bootstrap.v3.Bootstrap with " - "unknown field set {1}) has unknown fields"); + unknownFieldsMessage("envoy.config.bootstrap.v3.Bootstrap", {}, {1})); } // Validated exception thrown when downcastAndValidate observes a nested unknown field. @@ -233,11 +244,42 @@ TEST_F(ProtobufUtilityTest, DowncastAndValidateUnknownFieldsNested) { auto* cluster = bootstrap.mutable_static_resources()->add_clusters(); cluster->GetReflection()->MutableUnknownFields(cluster)->AddVarint(1, 0); EXPECT_THROW_WITH_MESSAGE(TestUtility::validate(*cluster), EnvoyException, - "Protobuf message (type envoy.config.cluster.v3.Cluster with " - "unknown field set {1}) has unknown fields"); - EXPECT_THROW_WITH_MESSAGE(TestUtility::validate(bootstrap), EnvoyException, - "Protobuf message (type envoy.config.cluster.v3.Cluster with " - "unknown field set {1}) has unknown fields"); + unknownFieldsMessage("envoy.config.cluster.v3.Cluster", {}, {1})); + EXPECT_THROW_WITH_MESSAGE( + TestUtility::validate(bootstrap), EnvoyException, + unknownFieldsMessage("envoy.config.cluster.v3.Cluster", + {"envoy.config.bootstrap.v3.Bootstrap", + "envoy.config.bootstrap.v3.Bootstrap.StaticResources"}, + {1})); +} + +// Validated exception thrown when observed nested unknown field with any. +TEST_F(ProtobufUtilityTest, ValidateUnknownFieldsNestedAny) { + // Constructs a nested message with unknown field + envoy::extensions::clusters::dynamic_forward_proxy::v3::ClusterConfig cluster_config; + auto* dns_cache_config = cluster_config.mutable_dns_cache_config(); + dns_cache_config->set_name("dynamic_forward_proxy_cache_config"); + dns_cache_config->GetReflection()->MutableUnknownFields(dns_cache_config)->AddVarint(999, 0); + + // Constructs ancestors of the nested any message with unknown field. + envoy::config::bootstrap::v3::Bootstrap bootstrap; + auto* cluster = bootstrap.mutable_static_resources()->add_clusters(); + auto* cluster_type = cluster->mutable_cluster_type(); + cluster_type->set_name("envoy.clusters.dynamic_forward_proxy"); + cluster_type->mutable_typed_config()->PackFrom(cluster_config); + + EXPECT_THROW_WITH_MESSAGE( + TestUtility::validate(bootstrap, /*recurse_into_any*/ true), EnvoyException, + unknownFieldsMessage("envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig", + { + "envoy.config.bootstrap.v3.Bootstrap", + "envoy.config.bootstrap.v3.Bootstrap.StaticResources", + "envoy.config.cluster.v3.Cluster", + "envoy.config.cluster.v3.Cluster.CustomClusterType", + "google.protobuf.Any", + "envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig", + }, + {999})); } TEST_F(ProtobufUtilityTest, JsonConvertAnyUnknownMessageType) { @@ -366,8 +408,7 @@ TEST_F(ProtobufUtilityTest, LoadBinaryProtoUnknownFieldFromFile) { envoy::config::bootstrap::v3::Bootstrap proto_from_file; EXPECT_THROW_WITH_MESSAGE(TestUtility::loadFromFile(filename, proto_from_file, *api_), EnvoyException, - "Protobuf message (type envoy.config.bootstrap.v3.Bootstrap with " - "unknown field set {1}) has unknown fields"); + unknownFieldsMessage("envoy.config.bootstrap.v3.Bootstrap", {}, {1})); } // Multiple unknown fields (or with wrong type) in a message are rejected. @@ -378,10 +419,9 @@ TEST_F(ProtobufUtilityTest, LoadBinaryProtoUnknownMultipleFieldsFromFile) { const std::string filename = TestEnvironment::writeStringToFileForTest("proto.pb", source_duration.SerializeAsString()); envoy::config::bootstrap::v3::Bootstrap proto_from_file; - EXPECT_THROW_WITH_MESSAGE(TestUtility::loadFromFile(filename, proto_from_file, *api_), - EnvoyException, - "Protobuf message (type envoy.config.bootstrap.v3.Bootstrap with " - "unknown field set {1, 2}) has unknown fields"); + EXPECT_THROW_WITH_MESSAGE( + TestUtility::loadFromFile(filename, proto_from_file, *api_), EnvoyException, + unknownFieldsMessage("envoy.config.bootstrap.v3.Bootstrap", {}, {1, 2})); } TEST_F(ProtobufUtilityTest, LoadTextProtoFromFile) { diff --git a/test/test_common/utility.h b/test/test_common/utility.h index 04b79443e959..4ee338703bae 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -647,8 +647,9 @@ class TestUtility { ProtobufMessage::getStrictValidationVisitor()); } - template static void validate(const MessageType& message) { - MessageUtil::validate(message, ProtobufMessage::getStrictValidationVisitor()); + template + static void validate(const MessageType& message, bool recurse_into_any = false) { + MessageUtil::validate(message, ProtobufMessage::getStrictValidationVisitor(), recurse_into_any); } template From e89110b0ff9a4e722af442da930e0cf4f7c5330b Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 10 Mar 2022 11:03:35 -0500 Subject: [PATCH 39/68] undeprecating used re2 knobs (#20274) Part of https://github.com/envoyproxy/envoy/issues/20254 Signed-off-by: Alyssa Wilk --- docs/root/version_history/current.rst | 1 - source/common/runtime/runtime_features.cc | 19 +++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index a55c4e1119b4..908db0c1b90b 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -113,4 +113,3 @@ Deprecated * http: deprecated ``envoy.http.headermap.lazy_map_min_size``. If you are using this config knob you can revert this temporarily by setting ``envoy.reloadable_features.deprecate_global_ints`` to true but you MUST file an upstream issue to ensure this feature remains available. * http: removing support for long-deprecated old style filter names, e.g. envoy.router, envoy.lua. * re2: removed undocumented histograms ``re2.program_size`` and ``re2.exceeded_warn_level``. -* re2: deprecated ``re2.max_program_size.error_level`` and ``re2.max_program_size.warn_level``. If you are using these config knobs you can revert this temporarily by setting ``envoy.reloadable_features.deprecate_global_ints`` to true but you MUST file an upstream issue to ensure this feature remains available. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index e1eeb39df46f..d4f94574a72f 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -97,7 +97,7 @@ bool runtimeFeatureEnabled(absl::string_view feature) { uint64_t getInteger(absl::string_view feature, uint64_t default_value) { if (absl::StartsWith(feature, "envoy.")) { - // DO NOT ADD MORE FLAGS HERE. This function deprecated and being removed. + // DO NOT ADD MORE FLAGS HERE. This function deprecated. if (feature == "envoy.http.headermap.lazy_map_min_size") { return absl::GetFlag(FLAGS_envoy_headermap_lazy_map_min_size); } @@ -190,30 +190,25 @@ void maybeSetRuntimeGuard(absl::string_view name, bool value) { absl::SetFlag(flag, value); } -// TODO(alyssawilk) deprecate use of this void maybeSetDeprecatedInts(absl::string_view name, uint32_t value) { if (!absl::StartsWith(name, "envoy.") && !absl::StartsWith(name, "re2.")) { return; } - bool set = false; // DO NOT ADD MORE FLAGS HERE. This function deprecated and being removed. if (name == "envoy.http.headermap.lazy_map_min_size") { - set = true; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.deprecate_global_ints")) { + IS_ENVOY_BUG(absl::StrCat( + "The Envoy community is attempting to remove global integers. Given you use ", name, + " please immediately file an upstream issue to retain the functionality as it will " + "otherwise be removed following the usual deprecation cycle.")); + } absl::SetFlag(&FLAGS_envoy_headermap_lazy_map_min_size, value); } else if (name == "re2.max_program_size.error_level") { - set = true; absl::SetFlag(&FLAGS_re2_max_program_size_error_level, value); } else if (name == "re2.max_program_size.warn_level") { - set = true; absl::SetFlag(&FLAGS_re2_max_program_size_warn_level, value); } - if (set && Runtime::runtimeFeatureEnabled("envoy.reloadable_features.deprecate_global_ints")) { - IS_ENVOY_BUG(absl::StrCat( - "The Envoy community is attempting to remove global integers. Given you use ", name, - " please immediately file an upstream issue to retain the functionality as it will " - "otherwise be removed following the usual deprecation cycle.")); - } } std::string swapPrefix(std::string name) { From d596cb5f3917bad15ca5f92fa8e9e4590c404df2 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Thu, 10 Mar 2022 12:09:57 -0500 Subject: [PATCH 40/68] json: remove ``envoy.reloadable_features.remove_legacy_json`` and legacy code paths (#20282) Signed-off-by: Yan Avlasov --- bazel/external/rapidjson.BUILD | 4 +- bazel/repositories.bzl | 4 - bazel/repository_locations.bzl | 6 +- docs/root/version_history/current.rst | 1 + source/common/json/BUILD | 17 - source/common/json/json_internal_legacy.cc | 698 --------------------- source/common/json/json_internal_legacy.h | 22 - source/common/json/json_loader.cc | 6 +- source/common/runtime/runtime_features.cc | 2 - test/common/json/BUILD | 14 +- test/common/json/json_loader_test.cc | 59 +- 11 files changed, 25 insertions(+), 808 deletions(-) delete mode 100644 source/common/json/json_internal_legacy.cc delete mode 100644 source/common/json/json_internal_legacy.h diff --git a/bazel/external/rapidjson.BUILD b/bazel/external/rapidjson.BUILD index a74a0fe55d37..ef3db0ef55ac 100644 --- a/bazel/external/rapidjson.BUILD +++ b/bazel/external/rapidjson.BUILD @@ -7,5 +7,7 @@ cc_library( hdrs = glob(["include/rapidjson/**/*.h"]), defines = ["RAPIDJSON_HAS_STDSTRING=1"], includes = ["include"], - visibility = ["//visibility:public"], + # rapidjson is only needed to build external dependency of the Zipkin tracer. + # For Envoy source code plese use source/common/json/json_loader.h + visibility = ["@io_opencensus_cpp//opencensus/exporters/trace/zipkin:__pkg__"], ) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index b7abfd8154ef..16c001b66016 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -522,10 +522,6 @@ def _com_github_tencent_rapidjson(): name = "com_github_tencent_rapidjson", build_file = "@envoy//bazel/external:rapidjson.BUILD", ) - native.bind( - name = "rapidjson", - actual = "@com_github_tencent_rapidjson//:rapidjson", - ) def _com_github_nlohmann_json(): external_http_archive( diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 6de829cd9f88..19396c952864 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -537,9 +537,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( sha256 = "a2faafbc402394df0fa94602df4b5e4befd734aad6bb55dfef46f62fcaf1090b", strip_prefix = "rapidjson-{version}", urls = ["https://github.com/Tencent/rapidjson/archive/{version}.tar.gz"], - # We're mostly using com_google_protobuf for JSON, but there are some extensions and hard to - # disentangle uses on the dataplane, e.g. header_formatter, Squash filter. - use_category = ["controlplane", "dataplane_core"], + use_category = ["observability_ext"], + # Rapidjson is used in the external dependency of zipkin tracer. + extensions = ["envoy.tracers.zipkin", "envoy.tracers.opencensus"], release_date = "2019-12-03", cpe = "cpe:2.3:a:tencent:rapidjson:*", ), diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 908db0c1b90b..da33d397ba83 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -60,6 +60,7 @@ Removed Config or Runtime * http: removed ``envoy.reloadable_features.allow_response_for_timeout`` and legacy code paths. * http: removed ``envoy.reloadable_features.http2_consume_stream_refused_errors`` and legacy code paths. * http: removed ``envoy.reloadable_features.internal_redirects_with_body`` and legacy code paths. +* json: removed ``envoy.reloadable_features.remove_legacy_json`` and legacy code paths. * listener: removed ``envoy.reloadable_features.listener_reuse_port_default_enabled`` and legacy code paths. * udp: removed ``envoy.reloadable_features.udp_per_event_loop_read_limit`` and legacy code paths. * upstream: removed ``envoy.reloadable_features.health_check.graceful_goaway_handling`` and legacy code paths. diff --git a/source/common/json/BUILD b/source/common/json/BUILD index 23060918cb52..1a42cd000438 100644 --- a/source/common/json/BUILD +++ b/source/common/json/BUILD @@ -8,22 +8,6 @@ licenses(["notice"]) # Apache 2 envoy_package() -envoy_cc_library( - name = "json_internal_legacy_lib", - srcs = ["json_internal_legacy.cc"], - hdrs = ["json_internal_legacy.h"], - external_deps = [ - "rapidjson", - ], - deps = [ - "//envoy/json:json_object_interface", - "//source/common/common:assert_lib", - "//source/common/common:hash_lib", - "//source/common/common:utility_lib", - "//source/common/protobuf:utility_lib", - ], -) - envoy_cc_library( name = "json_internal_lib", srcs = ["json_internal.cc"], @@ -45,7 +29,6 @@ envoy_cc_library( srcs = ["json_loader.cc"], hdrs = ["json_loader.h"], deps = [ - ":json_internal_legacy_lib", ":json_internal_lib", "//envoy/json:json_object_interface", "//source/common/runtime:runtime_features_lib", diff --git a/source/common/json/json_internal_legacy.cc b/source/common/json/json_internal_legacy.cc deleted file mode 100644 index 0b4cecb5b4fe..000000000000 --- a/source/common/json/json_internal_legacy.cc +++ /dev/null @@ -1,698 +0,0 @@ -#include "source/common/json/json_internal_legacy.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "source/common/common/assert.h" -#include "source/common/common/fmt.h" -#include "source/common/common/hash.h" -#include "source/common/common/utility.h" -#include "source/common/protobuf/utility.h" - -// Do not let RapidJson leak outside of this file. -#include "rapidjson/document.h" -#include "rapidjson/error/en.h" -#include "rapidjson/reader.h" -#include "rapidjson/schema.h" -#include "rapidjson/stream.h" -#include "rapidjson/stringbuffer.h" -#include "rapidjson/writer.h" - -#include "absl/strings/match.h" - -namespace Envoy { -namespace Json { -namespace RapidJson { - -namespace { -/** - * Internal representation of Object. - */ -class Field; -using FieldSharedPtr = std::shared_ptr; - -class Field : public Object { -public: - void setLineNumberStart(uint64_t line_number) { line_number_start_ = line_number; } - void setLineNumberEnd(uint64_t line_number) { line_number_end_ = line_number; } - - // Container factories for handler. - static FieldSharedPtr createObject() { return FieldSharedPtr{new Field(Type::Object)}; } - static FieldSharedPtr createArray() { return FieldSharedPtr{new Field(Type::Array)}; } - static FieldSharedPtr createNull() { return FieldSharedPtr{new Field(Type::Null)}; } - - bool isArray() const override { return type_ == Type::Array; } - bool isObject() const override { return type_ == Type::Object; } - - // Value factory. - template static FieldSharedPtr createValue(T value) { - return FieldSharedPtr{new Field(value)}; // NOLINT(modernize-make-shared) - } - - void append(FieldSharedPtr field_ptr) { - checkType(Type::Array); - value_.array_value_.push_back(field_ptr); - } - void insert(const std::string& key, FieldSharedPtr field_ptr) { - checkType(Type::Object); - value_.object_value_[key] = field_ptr; - } - - uint64_t hash() const override; - - bool getBoolean(const std::string& name) const override; - bool getBoolean(const std::string& name, bool default_value) const override; - double getDouble(const std::string& name) const override; - double getDouble(const std::string& name, double default_value) const override; - int64_t getInteger(const std::string& name) const override; - int64_t getInteger(const std::string& name, int64_t default_value) const override; - ObjectSharedPtr getObject(const std::string& name, bool allow_empty) const override; - std::vector getObjectArray(const std::string& name, - bool allow_empty) const override; - std::string getString(const std::string& name) const override; - std::string getString(const std::string& name, const std::string& default_value) const override; - std::vector getStringArray(const std::string& name, bool allow_empty) const override; - std::vector asObjectArray() const override; - std::string asString() const override { return stringValue(); } - std::string asJsonString() const override; - - bool empty() const override; - bool hasObject(const std::string& name) const override; - void iterate(const ObjectCallback& callback) const override; - void validateSchema(const std::string& schema) const override; - -private: - enum class Type { - Array, - Boolean, - Double, - Integer, - Null, - Object, - String, - }; - static const char* typeAsString(Type t) { - switch (t) { - case Type::Array: - return "Array"; - case Type::Boolean: - return "Boolean"; - case Type::Double: - return "Double"; - case Type::Integer: - return "Integer"; - case Type::Null: - return "Null"; - case Type::Object: - return "Object"; - case Type::String: - return "String"; - } - - return ""; - } - - struct Value { - std::vector array_value_; - bool boolean_value_; - double double_value_; - int64_t integer_value_; - std::map object_value_; - std::string string_value_; - }; - - explicit Field(Type type) : type_(type) {} - explicit Field(const std::string& value) : type_(Type::String) { value_.string_value_ = value; } - explicit Field(int64_t value) : type_(Type::Integer) { value_.integer_value_ = value; } - explicit Field(double value) : type_(Type::Double) { value_.double_value_ = value; } - explicit Field(bool value) : type_(Type::Boolean) { value_.boolean_value_ = value; } - - bool isType(Type type) const { return type == type_; } - void checkType(Type type) const { - if (!isType(type)) { - throw Exception(fmt::format( - "JSON field from line {} accessed with type '{}' does not match actual type '{}'.", - line_number_start_, typeAsString(type), typeAsString(type_))); - } - } - - // Value return type functions. - std::string stringValue() const { - checkType(Type::String); - return value_.string_value_; - } - std::vector arrayValue() const { - checkType(Type::Array); - return value_.array_value_; - } - bool booleanValue() const { - checkType(Type::Boolean); - return value_.boolean_value_; - } - double doubleValue() const { - checkType(Type::Double); - return value_.double_value_; - } - int64_t integerValue() const { - checkType(Type::Integer); - return value_.integer_value_; - } - - rapidjson::Document asRapidJsonDocument() const; - static void buildRapidJsonDocument(const Field& field, rapidjson::Value& value, - rapidjson::Document::AllocatorType& allocator); - - uint64_t line_number_start_ = 0; - uint64_t line_number_end_ = 0; - const Type type_; - Value value_; -}; - -/** - * Custom stream to allow access to the line number for each object. - */ -class LineCountingStringStream : public rapidjson::StringStream { - // Ch is typedef in parent class to handle character encoding. -public: - LineCountingStringStream(const Ch* src) : rapidjson::StringStream(src), line_number_(1) {} - Ch Take() { - Ch ret = rapidjson::StringStream::Take(); - if (ret == '\n') { - line_number_++; - } - return ret; - } - uint64_t getLineNumber() const { return line_number_; } - -private: - uint64_t line_number_; -}; - -/** - * Consume events from SAX callbacks to build JSON Field. - */ -class ObjectHandler : public rapidjson::BaseReaderHandler, ObjectHandler> { -public: - ObjectHandler(LineCountingStringStream& stream) : state_(State::ExpectRoot), stream_(stream){}; - - bool StartObject(); - bool EndObject(rapidjson::SizeType); - bool Key(const char* value, rapidjson::SizeType size, bool); - bool StartArray(); - bool EndArray(rapidjson::SizeType); - bool Bool(bool value); - bool Double(double value); - bool Int(int value); - bool Uint(unsigned value); - bool Int64(int64_t value); - bool Uint64(uint64_t value); - bool Null(); - bool String(const char* value, rapidjson::SizeType size, bool); - bool RawNumber(const char*, rapidjson::SizeType, bool); - - ObjectSharedPtr getRoot() { return root_; } - -private: - bool handleValueEvent(FieldSharedPtr ptr); - - enum class State { - ExpectRoot, - ExpectKeyOrEndObject, - ExpectValueOrStartObjectArray, - ExpectArrayValueOrEndArray, - ExpectFinished, - }; - State state_; - LineCountingStringStream& stream_; - - std::stack stack_; - std::string key_; - - FieldSharedPtr root_; -}; - -void Field::buildRapidJsonDocument(const Field& field, rapidjson::Value& value, - rapidjson::Document::AllocatorType& allocator) { - - switch (field.type_) { - case Type::Array: { - value.SetArray(); - value.Reserve(field.value_.array_value_.size(), allocator); - for (const auto& element : field.value_.array_value_) { - switch (element->type_) { - case Type::Array: - case Type::Object: { - rapidjson::Value nested_value; - buildRapidJsonDocument(*element, nested_value, allocator); - value.PushBack(nested_value, allocator); - break; - } - case Type::Boolean: - value.PushBack(element->value_.boolean_value_, allocator); - break; - case Type::Double: - value.PushBack(element->value_.double_value_, allocator); - break; - case Type::Integer: - value.PushBack(element->value_.integer_value_, allocator); - break; - case Type::Null: - value.PushBack(rapidjson::Value(), allocator); - break; - case Type::String: - value.PushBack(rapidjson::StringRef(element->value_.string_value_.c_str()), allocator); - } - } - break; - } - case Type::Object: { - value.SetObject(); - for (const auto& item : field.value_.object_value_) { - auto name = rapidjson::StringRef(item.first.c_str()); - - switch (item.second->type_) { - case Type::Array: - case Type::Object: { - rapidjson::Value nested_value; - buildRapidJsonDocument(*item.second, nested_value, allocator); - value.AddMember(name, nested_value, allocator); - break; - } - case Type::Boolean: - value.AddMember(name, item.second->value_.boolean_value_, allocator); - break; - case Type::Double: - value.AddMember(name, item.second->value_.double_value_, allocator); - break; - case Type::Integer: - value.AddMember(name, item.second->value_.integer_value_, allocator); - break; - case Type::Null: - value.AddMember(name, rapidjson::Value(), allocator); - break; - case Type::String: - value.AddMember(name, rapidjson::StringRef(item.second->value_.string_value_.c_str()), - allocator); - break; - } - } - break; - } - case Type::Null: { - value.SetNull(); - break; - } - case Type::Boolean: - FALLTHRU; - case Type::Double: - FALLTHRU; - case Type::Integer: - FALLTHRU; - case Type::String: - PANIC("not implemented"); - } -} - -rapidjson::Document Field::asRapidJsonDocument() const { - rapidjson::Document document; - rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); - buildRapidJsonDocument(*this, document, allocator); - return document; -} - -uint64_t Field::hash() const { return HashUtil::xxHash64(asJsonString()); } - -bool Field::getBoolean(const std::string& name) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - if (value_itr == value_.object_value_.end() || !value_itr->second->isType(Type::Boolean)) { - throw Exception(fmt::format("key '{}' missing or not a boolean from lines {}-{}", name, - line_number_start_, line_number_end_)); - } - return value_itr->second->booleanValue(); -} - -bool Field::getBoolean(const std::string& name, bool default_value) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - if (value_itr != value_.object_value_.end()) { - return getBoolean(name); - } - return default_value; -} - -double Field::getDouble(const std::string& name) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - if (value_itr == value_.object_value_.end() || !value_itr->second->isType(Type::Double)) { - throw Exception(fmt::format("key '{}' missing or not a double from lines {}-{}", name, - line_number_start_, line_number_end_)); - } - return value_itr->second->doubleValue(); -} - -double Field::getDouble(const std::string& name, double default_value) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - if (value_itr != value_.object_value_.end()) { - return getDouble(name); - } - return default_value; -} - -int64_t Field::getInteger(const std::string& name) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - if (value_itr == value_.object_value_.end() || !value_itr->second->isType(Type::Integer)) { - throw Exception(fmt::format("key '{}' missing or not an integer from lines {}-{}", name, - line_number_start_, line_number_end_)); - } - return value_itr->second->integerValue(); -} - -int64_t Field::getInteger(const std::string& name, int64_t default_value) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - if (value_itr != value_.object_value_.end()) { - return getInteger(name); - } - return default_value; -} - -ObjectSharedPtr Field::getObject(const std::string& name, bool allow_empty) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - if (value_itr == value_.object_value_.end()) { - if (allow_empty) { - return createObject(); - } else { - throw Exception(fmt::format("key '{}' missing from lines {}-{}", name, line_number_start_, - line_number_end_)); - } - } else if (!value_itr->second->isType(Type::Object)) { - throw Exception(fmt::format("key '{}' not an object from line {}", name, - value_itr->second->line_number_start_)); - } else { - return value_itr->second; - } -} - -std::vector Field::getObjectArray(const std::string& name, - bool allow_empty) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - if (value_itr == value_.object_value_.end() || !value_itr->second->isType(Type::Array)) { - if (allow_empty && value_itr == value_.object_value_.end()) { - return std::vector(); - } - throw Exception(fmt::format("key '{}' missing or not an array from lines {}-{}", name, - line_number_start_, line_number_end_)); - } - - std::vector array_value = value_itr->second->arrayValue(); - return {array_value.begin(), array_value.end()}; -} - -std::string Field::getString(const std::string& name) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - if (value_itr == value_.object_value_.end() || !value_itr->second->isType(Type::String)) { - throw Exception(fmt::format("key '{}' missing or not a string from lines {}-{}", name, - line_number_start_, line_number_end_)); - } - return value_itr->second->stringValue(); -} - -std::string Field::getString(const std::string& name, const std::string& default_value) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - if (value_itr != value_.object_value_.end()) { - return getString(name); - } - return default_value; -} - -std::vector Field::getStringArray(const std::string& name, bool allow_empty) const { - checkType(Type::Object); - std::vector string_array; - auto value_itr = value_.object_value_.find(name); - if (value_itr == value_.object_value_.end() || !value_itr->second->isType(Type::Array)) { - if (allow_empty && value_itr == value_.object_value_.end()) { - return string_array; - } - throw Exception(fmt::format("key '{}' missing or not an array from lines {}-{}", name, - line_number_start_, line_number_end_)); - } - - std::vector array = value_itr->second->arrayValue(); - string_array.reserve(array.size()); - for (const auto& element : array) { - if (!element->isType(Type::String)) { - throw Exception(fmt::format("JSON array '{}' from line {} does not contain all strings", name, - line_number_start_)); - } - string_array.push_back(element->stringValue()); - } - - return string_array; -} - -std::vector Field::asObjectArray() const { - checkType(Type::Array); - return {value_.array_value_.begin(), value_.array_value_.end()}; -} - -std::string Field::asJsonString() const { - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - rapidjson::Document document = asRapidJsonDocument(); - document.Accept(writer); - return buffer.GetString(); -} - -bool Field::empty() const { - if (isType(Type::Object)) { - return value_.object_value_.empty(); - } else if (isType(Type::Array)) { - return value_.array_value_.empty(); - } else { - throw Exception( - fmt::format("Json does not support empty() on types other than array and object")); - } -} - -bool Field::hasObject(const std::string& name) const { - checkType(Type::Object); - auto value_itr = value_.object_value_.find(name); - return value_itr != value_.object_value_.end(); -} - -void Field::iterate(const ObjectCallback& callback) const { - checkType(Type::Object); - for (const auto& item : value_.object_value_) { - bool stop_iteration = !callback(item.first, *item.second); - if (stop_iteration) { - break; - } - } -} - -void Field::validateSchema(const std::string& schema) const { - rapidjson::Document schema_document; - if (schema_document.Parse<0>(schema.c_str()).HasParseError()) { - throw std::invalid_argument(fmt::format( - "Schema supplied to validateSchema is not valid JSON\n Error(offset {}) : {}\n", - schema_document.GetErrorOffset(), GetParseError_En(schema_document.GetParseError()))); - } - - rapidjson::SchemaDocument schema_document_for_validator(schema_document); - rapidjson::GenericSchemaValidator - schema_validator(schema_document_for_validator); - - if (!asRapidJsonDocument().Accept(schema_validator)) { - rapidjson::StringBuffer schema_string_buffer; - rapidjson::StringBuffer document_string_buffer; - - schema_validator.GetInvalidSchemaPointer().StringifyUriFragment(schema_string_buffer); - schema_validator.GetInvalidDocumentPointer().StringifyUriFragment(document_string_buffer); - - throw Exception(fmt::format( - "JSON at lines {}-{} does not conform to schema.\n Invalid schema: {}\n" - " Schema violation: {}\n" - " Offending document key: {}", - line_number_start_, line_number_end_, schema_string_buffer.GetString(), - schema_validator.GetInvalidSchemaKeyword(), document_string_buffer.GetString())); - } -} - -bool ObjectHandler::StartObject() { - FieldSharedPtr object = Field::createObject(); - object->setLineNumberStart(stream_.getLineNumber()); - - switch (state_) { - case State::ExpectValueOrStartObjectArray: - stack_.top()->insert(key_, object); - stack_.push(object); - state_ = State::ExpectKeyOrEndObject; - return true; - case State::ExpectArrayValueOrEndArray: - stack_.top()->append(object); - stack_.push(object); - state_ = State::ExpectKeyOrEndObject; - return true; - case State::ExpectRoot: - root_ = object; - stack_.push(object); - state_ = State::ExpectKeyOrEndObject; - return true; - default: - PANIC("not implemented"); - } -} - -bool ObjectHandler::EndObject(rapidjson::SizeType) { - switch (state_) { - case State::ExpectKeyOrEndObject: - stack_.top()->setLineNumberEnd(stream_.getLineNumber()); - stack_.pop(); - - if (stack_.empty()) { - state_ = State::ExpectFinished; - } else if (stack_.top()->isObject()) { - state_ = State::ExpectKeyOrEndObject; - } else if (stack_.top()->isArray()) { - state_ = State::ExpectArrayValueOrEndArray; - } - return true; - default: - PANIC("not implemented"); - } -} - -bool ObjectHandler::Key(const char* value, rapidjson::SizeType size, bool) { - switch (state_) { - case State::ExpectKeyOrEndObject: - key_ = std::string(value, size); - state_ = State::ExpectValueOrStartObjectArray; - return true; - default: - PANIC("not implemented"); - } -} - -bool ObjectHandler::StartArray() { - FieldSharedPtr array = Field::createArray(); - array->setLineNumberStart(stream_.getLineNumber()); - - switch (state_) { - case State::ExpectValueOrStartObjectArray: - stack_.top()->insert(key_, array); - stack_.push(array); - state_ = State::ExpectArrayValueOrEndArray; - return true; - case State::ExpectArrayValueOrEndArray: - stack_.top()->append(array); - stack_.push(array); - return true; - case State::ExpectRoot: - root_ = array; - stack_.push(array); - state_ = State::ExpectArrayValueOrEndArray; - return true; - default: - PANIC("not implemented"); - } -} - -bool ObjectHandler::EndArray(rapidjson::SizeType) { - switch (state_) { - case State::ExpectArrayValueOrEndArray: - stack_.top()->setLineNumberEnd(stream_.getLineNumber()); - stack_.pop(); - - if (stack_.empty()) { - state_ = State::ExpectFinished; - } else if (stack_.top()->isObject()) { - state_ = State::ExpectKeyOrEndObject; - } else if (stack_.top()->isArray()) { - state_ = State::ExpectArrayValueOrEndArray; - } - - return true; - default: - PANIC("not implemented"); - } -} - -// Value handlers -bool ObjectHandler::Bool(bool value) { return handleValueEvent(Field::createValue(value)); } -bool ObjectHandler::Double(double value) { return handleValueEvent(Field::createValue(value)); } -bool ObjectHandler::Int(int value) { - return handleValueEvent(Field::createValue(static_cast(value))); -} -bool ObjectHandler::Uint(unsigned value) { - return handleValueEvent(Field::createValue(static_cast(value))); -} -bool ObjectHandler::Int64(int64_t value) { return handleValueEvent(Field::createValue(value)); } -bool ObjectHandler::Uint64(uint64_t value) { - if (value > static_cast(std::numeric_limits::max())) { - throw Exception(fmt::format("JSON value from line {} is larger than int64_t (not supported)", - stream_.getLineNumber())); - } - return handleValueEvent(Field::createValue(static_cast(value))); -} - -bool ObjectHandler::Null() { return handleValueEvent(Field::createNull()); } - -bool ObjectHandler::String(const char* value, rapidjson::SizeType size, bool) { - return handleValueEvent(Field::createValue(std::string(value, size))); -} - -bool ObjectHandler::RawNumber(const char*, rapidjson::SizeType, bool) { - // Only called if kParseNumbersAsStrings is set as a parse flag, which it is not. - PANIC("not implemented"); -} - -bool ObjectHandler::handleValueEvent(FieldSharedPtr ptr) { - ptr->setLineNumberStart(stream_.getLineNumber()); - - switch (state_) { - case State::ExpectValueOrStartObjectArray: - state_ = State::ExpectKeyOrEndObject; - stack_.top()->insert(key_, ptr); - return true; - case State::ExpectArrayValueOrEndArray: - stack_.top()->append(ptr); - return true; - default: - return false; - } -} - -} // namespace - -ObjectSharedPtr Factory::loadFromString(const std::string& json) { - LineCountingStringStream json_stream(json.c_str()); - - ObjectHandler handler(json_stream); - rapidjson::Reader reader; - reader.Parse(json_stream, handler); - - if (reader.HasParseError()) { - throw Exception(fmt::format("JSON supplied is not valid. Error(offset {}, line {}): {}\n", - reader.GetErrorOffset(), json_stream.getLineNumber(), - GetParseError_En(reader.GetParseErrorCode()))); - } - - return handler.getRoot(); -} - -} // namespace RapidJson -} // namespace Json -} // namespace Envoy diff --git a/source/common/json/json_internal_legacy.h b/source/common/json/json_internal_legacy.h deleted file mode 100644 index 6b50ccd2b626..000000000000 --- a/source/common/json/json_internal_legacy.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include -#include - -#include "envoy/json/json_object.h" - -namespace Envoy { -namespace Json { -namespace RapidJson { - -class Factory { -public: - /** - * Constructs a Json Object from a string. - */ - static ObjectSharedPtr loadFromString(const std::string& json); -}; - -} // namespace RapidJson -} // namespace Json -} // namespace Envoy diff --git a/source/common/json/json_loader.cc b/source/common/json/json_loader.cc index 2bef4c314be2..15dccdfa4e09 100644 --- a/source/common/json/json_loader.cc +++ b/source/common/json/json_loader.cc @@ -1,17 +1,13 @@ #include "source/common/json/json_loader.h" #include "source/common/json/json_internal.h" -#include "source/common/json/json_internal_legacy.h" #include "source/common/runtime/runtime_features.h" namespace Envoy { namespace Json { ObjectSharedPtr Factory::loadFromString(const std::string& json) { - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.remove_legacy_json")) { - return Nlohmann::Factory::loadFromString(json); - } - return RapidJson::Factory::loadFromString(json); + return Nlohmann::Factory::loadFromString(json); } } // namespace Json diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index d4f94574a72f..5cc63b3ec950 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -45,7 +45,6 @@ RUNTIME_GUARD(envoy_reloadable_features_listener_wildcard_match_ip_family); RUNTIME_GUARD(envoy_reloadable_features_new_tcp_connection_pool); RUNTIME_GUARD(envoy_reloadable_features_postpone_h3_client_connect_to_next_loop); RUNTIME_GUARD(envoy_reloadable_features_proxy_102_103); -RUNTIME_GUARD(envoy_reloadable_features_remove_legacy_json); RUNTIME_GUARD(envoy_reloadable_features_sanitize_http_header_referer); RUNTIME_GUARD(envoy_reloadable_features_skip_delay_close); RUNTIME_GUARD(envoy_reloadable_features_skip_dispatching_frames_for_closed_connection); @@ -163,7 +162,6 @@ constexpr absl::Flag* runtime_features[] = { &FLAGS_envoy_reloadable_features_new_tcp_connection_pool, &FLAGS_envoy_reloadable_features_postpone_h3_client_connect_to_next_loop, &FLAGS_envoy_reloadable_features_proxy_102_103, - &FLAGS_envoy_reloadable_features_remove_legacy_json, &FLAGS_envoy_reloadable_features_sanitize_http_header_referer, &FLAGS_envoy_reloadable_features_skip_delay_close, &FLAGS_envoy_reloadable_features_skip_dispatching_frames_for_closed_connection, diff --git a/test/common/json/BUILD b/test/common/json/BUILD index e68b7f30bad6..ebb6d6aeb63a 100644 --- a/test/common/json/BUILD +++ b/test/common/json/BUILD @@ -30,13 +30,9 @@ JSON_TEST_DEPS = [ envoy_cc_test( name = "json_loader_test", srcs = ["json_loader_test.cc"], - args = ["--runtime-feature-override-for-tests=envoy.reloadable_features.remove_legacy_json"], - deps = JSON_TEST_DEPS, -) - -envoy_cc_test( - name = "json_loader_legacy_test", - srcs = ["json_loader_test.cc"], - args = ["--runtime-feature-disable-for-tests=envoy.reloadable_features.remove_legacy_json"], - deps = JSON_TEST_DEPS, + deps = [ + "//source/common/json:json_loader_lib", + "//source/common/stats:isolated_store_lib", + "//test/test_common:utility_lib", + ], ) diff --git a/test/common/json/json_loader_test.cc b/test/common/json/json_loader_test.cc index 92fbfda59c21..0befc9d19959 100644 --- a/test/common/json/json_loader_test.cc +++ b/test/common/json/json_loader_test.cc @@ -71,16 +71,10 @@ TEST_F(JsonLoaderTest, Basic) { } { - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.remove_legacy_json")) { - EXPECT_THROW_WITH_MESSAGE(Factory::loadFromString("{\"hello\": \n\n\"world\""), Exception, - "JSON supplied is not valid. Error(line 3, column 8, token " - "\"world\"): syntax error while " - "parsing object - unexpected end of input; expected '}'\n"); - } else { - EXPECT_THROW_WITH_MESSAGE(Factory::loadFromString("{\"hello\": \n\n\"world\""), Exception, - "JSON supplied is not valid. Error(offset 19, line 3): Missing a " - "comma or '}' after an object member.\n"); - } + EXPECT_THROW_WITH_MESSAGE(Factory::loadFromString("{\"hello\": \n\n\"world\""), Exception, + "JSON supplied is not valid. Error(line 3, column 8, token " + "\"world\"): syntax error while " + "parsing object - unexpected end of input; expected '}'\n"); } { @@ -219,11 +213,7 @@ TEST_F(JsonLoaderTest, Basic) { { ObjectSharedPtr json1 = Factory::loadFromString("[ [ ] , { } ]"); - if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.remove_legacy_json")) { - EXPECT_EQ(json1->asJsonString(), "[[],{}]"); - } else { - EXPECT_EQ(json1->asJsonString(), "[null,null]"); - } + EXPECT_EQ(json1->asJsonString(), "[null,null]"); } { @@ -238,20 +228,12 @@ TEST_F(JsonLoaderTest, Basic) { { ObjectSharedPtr json = Factory::loadFromString("{\"hello\": {}}"); - if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.remove_legacy_json")) { - EXPECT_EQ(json->getObject("hello")->asJsonString(), "{}"); - } else { - EXPECT_EQ(json->getObject("hello")->asJsonString(), "null"); - } + EXPECT_EQ(json->getObject("hello")->asJsonString(), "null"); } { ObjectSharedPtr json = Factory::loadFromString("{\"hello\": [] }"); - if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.remove_legacy_json")) { - EXPECT_EQ(json->asJsonString(), "{\"hello\":[]}"); - } else { - EXPECT_EQ(json->asJsonString(), "{\"hello\":null}"); - } + EXPECT_EQ(json->asJsonString(), "{\"hello\":null}"); } } @@ -264,10 +246,6 @@ TEST_F(JsonLoaderTest, Integer) { } { EXPECT_THROW(Factory::loadFromString("{\"val\":9223372036854775808}"), EnvoyException); - - // I believe this is a bug with rapidjson. - // It silently eats numbers below min int64_t with no exception. - // Fail when reading key instead of on parse. ObjectSharedPtr json = Factory::loadFromString("{\"val\":-9223372036854775809}"); EXPECT_THROW(json->getInteger("val"), EnvoyException); } @@ -335,14 +313,7 @@ TEST_F(JsonLoaderTest, Schema) { )EOF"; ObjectSharedPtr json = Factory::loadFromString(json_string); - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.remove_legacy_json")) { - EXPECT_THROW_WITH_MESSAGE(json->validateSchema(invalid_schema), Exception, "not implemented"); - } else { - EXPECT_THROW_WITH_MESSAGE( - json->validateSchema(invalid_schema), Exception, - "JSON at lines 2-5 does not conform to schema.\n Invalid schema: #/properties/value1\n " - "Schema violation: type\n Offending document key: #/value1"); - } + EXPECT_THROW_WITH_MESSAGE(json->validateSchema(invalid_schema), Exception, "not implemented"); } TEST_F(JsonLoaderTest, MissingEnclosingDocument) { @@ -355,16 +326,10 @@ TEST_F(JsonLoaderTest, MissingEnclosingDocument) { } ] )EOF"; - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.remove_legacy_json")) { - EXPECT_THROW_WITH_MESSAGE( - Factory::loadFromString(json_string), Exception, - "JSON supplied is not valid. Error(line 2, column 15, token \"listeners\" :): syntax error " - "while parsing value - unexpected ':'; expected end of input\n"); - } else { - EXPECT_THROW_WITH_MESSAGE(Factory::loadFromString(json_string), Exception, - "JSON supplied is not valid. Error(offset 14, line 2): Terminate " - "parsing due to Handler error.\n"); - } + EXPECT_THROW_WITH_MESSAGE( + Factory::loadFromString(json_string), Exception, + "JSON supplied is not valid. Error(line 2, column 15, token \"listeners\" :): syntax error " + "while parsing value - unexpected ':'; expected end of input\n"); } TEST_F(JsonLoaderTest, AsString) { From 9d3d5885e5b12d87f8e280a9aef4e1d94d7c1fea Mon Sep 17 00:00:00 2001 From: Yuchen Dai Date: Thu, 10 Mar 2022 11:19:10 -0800 Subject: [PATCH 41/68] log: do not print http filters config in multiline (#20258) The multi line json config is annoying because it doesn't contain timestamp and occupy a large screen space. Note that this log behavior in HCM aligns was done in network filter at listener and cluster. Also degrade some obvious non-error logs. Risk Level: LOW Testing: n/a Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Yuchen Dai --- source/extensions/filters/network/dubbo_proxy/config.cc | 2 +- .../filters/network/http_connection_manager/config.cc | 2 +- test/common/upstream/subset_lb_test.cc | 2 +- .../filters/listener/tls_inspector/tls_inspector_test.cc | 2 +- test/integration/extension_discovery_integration_test.cc | 2 +- test/integration/listener_lds_integration_test.cc | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/source/extensions/filters/network/dubbo_proxy/config.cc b/source/extensions/filters/network/dubbo_proxy/config.cc index ad379489f362..8a1df8f13711 100644 --- a/source/extensions/filters/network/dubbo_proxy/config.cc +++ b/source/extensions/filters/network/dubbo_proxy/config.cc @@ -138,7 +138,7 @@ void ConfigImpl::registerFilter(const DubboFilterConfig& proto_config) { ENVOY_LOG(debug, " dubbo filter #{}", filter_factories_.size()); ENVOY_LOG(debug, " name: {}", string_name); ENVOY_LOG(debug, " config: {}", - MessageUtil::getJsonStringFromMessageOrError(proto_config.config(), true)); + MessageUtil::getJsonStringFromMessageOrError(proto_config.config())); auto& factory = Envoy::Config::Utility::getAndCheckFactoryByName( diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 275997caee78..2faa92b8b2a4 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -657,7 +657,7 @@ void HttpConnectionManagerConfig::processFilter( ENVOY_LOG(debug, " name: {}", filter_config_provider->name()); ENVOY_LOG(debug, " config: {}", MessageUtil::getJsonStringFromMessageOrError( - static_cast(proto_config.typed_config()), true)); + static_cast(proto_config.typed_config()))); filter_factories.push_back(std::move(filter_config_provider)); } diff --git a/test/common/upstream/subset_lb_test.cc b/test/common/upstream/subset_lb_test.cc index 801f74440ef7..e7d2d873f8db 100644 --- a/test/common/upstream/subset_lb_test.cc +++ b/test/common/upstream/subset_lb_test.cc @@ -2456,7 +2456,7 @@ TEST_F(SubsetLoadBalancerSingleHostPerSubsetTest, DuplicateMetadataStat) { // The first 'a' is the original, the next 2 instances of 'a' are duplicates (counted // in stat), and 'b' is another non-duplicate. for (auto& gauge : stats_store_.gauges()) { - ENVOY_LOG_MISC(error, "name {} value {}", gauge->name(), gauge->value()); + ENVOY_LOG_MISC(debug, "name {} value {}", gauge->name(), gauge->value()); } EXPECT_EQ(2, TestUtility::findGauge(stats_store_, "testprefix.lb_subsets_single_host_per_subset_duplicate") diff --git a/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc b/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc index 33ef557dbc63..06d49dae2e21 100644 --- a/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc +++ b/test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc @@ -50,7 +50,7 @@ class TlsInspectorTest : public testing::TestWithParam Api::SysCallSizeResult { - ENVOY_LOG_MISC(error, "In mock syscall recv {} {} {} {}", fd, buffer, length, flag); + ENVOY_LOG_MISC(debug, "In mock syscall recv {} {} {} {}", fd, buffer, length, flag); return Api::SysCallSizeResult{ssize_t(-1), SOCKET_ERROR_AGAIN}; })); EXPECT_CALL(dispatcher_, createFileEvent_(_, _, Event::PlatformDefaultTriggerType, diff --git a/test/integration/extension_discovery_integration_test.cc b/test/integration/extension_discovery_integration_test.cc index 870852c8cb8f..b0def4f684b6 100644 --- a/test/integration/extension_discovery_integration_test.cc +++ b/test/integration/extension_discovery_integration_test.cc @@ -159,7 +159,7 @@ class ExtensionDiscoveryIntegrationTest : public Grpc::GrpcClientIntegrationPara config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { listener_config_.Swap(bootstrap.mutable_static_resources()->mutable_listeners(0)); listener_config_.set_name(listener_name_); - ENVOY_LOG_MISC(error, "listener config: {}", listener_config_.DebugString()); + ENVOY_LOG_MISC(debug, "listener config: {}", listener_config_.DebugString()); bootstrap.mutable_static_resources()->mutable_listeners()->Clear(); auto* lds_config_source = bootstrap.mutable_dynamic_resources()->mutable_lds_config(); lds_config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); diff --git a/test/integration/listener_lds_integration_test.cc b/test/integration/listener_lds_integration_test.cc index 06eacbaa0758..70d9d988f9f6 100644 --- a/test/integration/listener_lds_integration_test.cc +++ b/test/integration/listener_lds_integration_test.cc @@ -82,7 +82,7 @@ class ListenerIntegrationTest : public HttpIntegrationTest, config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { listener_config_.Swap(bootstrap.mutable_static_resources()->mutable_listeners(0)); listener_config_.set_name(listener_name_); - ENVOY_LOG_MISC(error, "listener config: {}", listener_config_.DebugString()); + ENVOY_LOG_MISC(debug, "listener config: {}", listener_config_.DebugString()); bootstrap.mutable_static_resources()->mutable_listeners()->Clear(); auto* lds_config_source = bootstrap.mutable_dynamic_resources()->mutable_lds_config(); lds_config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); From a7375fa951325dd3d65efb7ce7be711577bafef5 Mon Sep 17 00:00:00 2001 From: code Date: Fri, 11 Mar 2022 03:21:58 +0800 Subject: [PATCH 42/68] stateful_session: only enable cookie based session state when request path matches (#19662) Risk Level: low Testing: unit test added Docs Changes: N/A Release Notes: add Platform Specific Features: N/A Fixes #19655 Signed-off-by: wbpcode --- docs/root/version_history/current.rst | 1 + .../http/stateful_session/stateful_session.cc | 3 + .../http/stateful_session/cookie/cookie.cc | 36 +++++++ .../http/stateful_session/cookie/cookie.h | 12 +++ .../stateful_session_integration_test.cc | 100 +++++++++++++++--- .../stateful_session/stateful_session_test.cc | 15 +++ .../stateful_session/cookie/cookie_test.cc | 87 ++++++++++++++- 7 files changed, 236 insertions(+), 18 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index da33d397ba83..9dd7bacecd6d 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -28,6 +28,7 @@ Minor Behavior Changes * router: record upstream request timeouts for all the cases and not just for those requests which are awaiting headers. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.do_not_await_headers_on_upstream_timeout_to_emit_stats`` to false. * sip-proxy: add customized affinity support by adding :ref:`tra_service_config ` and :ref:`customized_affinity `. * sip-proxy: add support for the ``503`` response code. When there is something wrong occurred, send ``503 Service Unavailable`` back to downstream. +* stateful session http filter: only enable cookie based session state when request path matches the configured cookie path. * tracing: set tracing error tag for grpc non-ok response code only when it is a upstream error. Client error will not be tagged as a grpc error. This fix is guarded by ``envoy.reloadable_features.update_grpc_response_error_tag``. Bug Fixes diff --git a/source/extensions/filters/http/stateful_session/stateful_session.cc b/source/extensions/filters/http/stateful_session/stateful_session.cc index 4e99d7fa3700..57b857d71cc6 100644 --- a/source/extensions/filters/http/stateful_session/stateful_session.cc +++ b/source/extensions/filters/http/stateful_session/stateful_session.cc @@ -45,6 +45,9 @@ Http::FilterHeadersStatus StatefulSession::decodeHeaders(Http::RequestHeaderMap& config = route_config->statefuleSessionConfig(); } session_state_ = config->createSessionState(headers); + if (session_state_ == nullptr) { + return Http::FilterHeadersStatus::Continue; + } if (auto upstream_address = session_state_->upstreamAddress(); upstream_address.has_value()) { decoder_callbacks_->setUpstreamOverrideHost(upstream_address.value()); diff --git a/source/extensions/http/stateful_session/cookie/cookie.cc b/source/extensions/http/stateful_session/cookie/cookie.cc index 4fb6737bd431..ade6c4bebcf0 100644 --- a/source/extensions/http/stateful_session/cookie/cookie.cc +++ b/source/extensions/http/stateful_session/cookie/cookie.cc @@ -26,6 +26,42 @@ CookieBasedSessionStateFactory::CookieBasedSessionStateFactory( if (name_.empty()) { throw EnvoyException("Cookie key cannot be empty for cookie based stateful sessions"); } + + // If no cookie path is specified or root cookie path is specified then this session state will + // be enabled for any request. + if (path_.empty() || path_ == "/") { + path_matcher_ = [](absl::string_view) { return true; }; + return; + } + + // If specified cookie path is ends with '/' then this session state will be enabled for the + // requests with a path that starts with the cookie path. + // For example the cookie path '/foo/' will matches request paths '/foo/bar' or '/foo/dir', but + // will not match request path '/foo'. + if (absl::EndsWith(path_, "/")) { + path_matcher_ = [path = path_](absl::string_view request_path) { + return absl::StartsWith(request_path, path); + }; + return; + } + + path_matcher_ = [path = path_](absl::string_view request_path) { + if (absl::StartsWith(request_path, path)) { + // Request path is same with cookie path. + if (request_path.size() == path.size()) { + return true; + } + + // The next character of the matching part should be the slash ('/'), question mark ('?') or + // number sign ('#'). + ASSERT(request_path.size() > path.size()); + const char next_char = request_path[path.size()]; + if (next_char == '/' || next_char == '?' || next_char == '#') { + return true; + } + } + return false; + }; } } // namespace Cookie diff --git a/source/extensions/http/stateful_session/cookie/cookie.h b/source/extensions/http/stateful_session/cookie/cookie.h index 31fabcdc034a..ecc883f37638 100644 --- a/source/extensions/http/stateful_session/cookie/cookie.h +++ b/source/extensions/http/stateful_session/cookie/cookie.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "envoy/extensions/http/stateful_session/cookie/v3/cookie.pb.h" @@ -38,9 +39,18 @@ class CookieBasedSessionStateFactory : public Envoy::Http::SessionStateFactory { CookieBasedSessionStateFactory(const CookieBasedSessionStateProto& config); Envoy::Http::SessionStatePtr create(const Envoy::Http::RequestHeaderMap& headers) const override { + if (!requestPathMatch(headers.getPathValue())) { + return nullptr; + } + return std::make_unique(parseAddress(headers), *this); } + bool requestPathMatch(absl::string_view request_path) const { + ASSERT(path_matcher_ != nullptr); + return path_matcher_(request_path); + } + private: absl::optional parseAddress(const Envoy::Http::RequestHeaderMap& headers) const { const std::string cookie_value = Envoy::Http::Utility::parseCookieValue(headers, name_); @@ -56,6 +66,8 @@ class CookieBasedSessionStateFactory : public Envoy::Http::SessionStateFactory { const std::string name_; const std::chrono::seconds ttl_; const std::string path_; + + std::function path_matcher_; }; } // namespace Cookie diff --git a/test/extensions/filters/http/stateful_session/stateful_session_integration_test.cc b/test/extensions/filters/http/stateful_session/stateful_session_integration_test.cc index 82507ebf75e9..fc684dc1962b 100644 --- a/test/extensions/filters/http/stateful_session/stateful_session_integration_test.cc +++ b/test/extensions/filters/http/stateful_session/stateful_session_integration_test.cc @@ -95,7 +95,7 @@ name: envoy.filters.http.stateful_session "@type": type.googleapis.com/envoy.extensions.http.stateful_session.cookie.v3.CookieBasedSessionState cookie: name: global-session-cookie - path: /path + path: /test ttl: 120s )EOF"; @@ -115,7 +115,7 @@ static const std::string OVERRIDE_STATEFUL_SESSION = "@type": type.googleapis.com/envoy.extensions.http.stateful_session.cookie.v3.CookieBasedSessionState cookie: name: route-session-cookie - path: /path + path: /test ttl: 120s )EOF"; @@ -150,7 +150,7 @@ TEST_F(StatefulSessionIntegrationTest, NormalStatefulSession) { // The selected upstream server address would be selected to the response headers. EXPECT_EQ( - Envoy::Http::Utility::makeSetCookieValue("global-session-cookie", encoded_address, "/path", + Envoy::Http::Utility::makeSetCookieValue("global-session-cookie", encoded_address, "/test", std::chrono::seconds(120), true), response->headers().get(Http::LowerCaseString("set-cookie"))[0]->value().getStringView()); @@ -263,7 +263,7 @@ TEST_F(StatefulSessionIntegrationTest, DownstreamRequestWithStatefulSessionCooki // The selected upstream server address would be selected to the response headers. EXPECT_EQ( - Envoy::Http::Utility::makeSetCookieValue("global-session-cookie", encoded_address, "/path", + Envoy::Http::Utility::makeSetCookieValue("global-session-cookie", encoded_address, "/test", std::chrono::seconds(120), true), response->headers().get(Http::LowerCaseString("set-cookie"))[0]->value().getStringView()); @@ -284,14 +284,15 @@ TEST_F(StatefulSessionIntegrationTest, StatefulSessionDisabledByRoute) { const std::string encoded_address = Envoy::Base64::encode(address_string.data(), address_string.size()); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, + {":path", "/test"}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}, + {"cookie", fmt::format("global-session-cookie=\"{}\"", encoded_address)}}; + { codec_client_ = makeHttpConnection(lookupPort("http")); - Http::TestRequestHeaderMapImpl request_headers{ - {":method", "GET"}, - {":path", "/test"}, - {":scheme", "http"}, - {":authority", "stateful.session.com"}, - {"cookie", fmt::format("global-session-cookie=\"{}\"", encoded_address)}}; auto response = codec_client_->makeRequestWithBody(request_headers, 0); @@ -314,12 +315,6 @@ TEST_F(StatefulSessionIntegrationTest, StatefulSessionDisabledByRoute) { { codec_client_ = makeHttpConnection(lookupPort("http")); - Http::TestRequestHeaderMapImpl request_headers{ - {":method", "GET"}, - {":path", "/test"}, - {":scheme", "http"}, - {":authority", "stateful.session.com"}, - {"cookie", fmt::format("global-session-cookie=\"{}\"", encoded_address)}}; auto response = codec_client_->makeRequestWithBody(request_headers, 0); @@ -384,7 +379,7 @@ TEST_F(StatefulSessionIntegrationTest, StatefulSessionOverriddenByRoute) { EXPECT_EQ( Envoy::Http::Utility::makeSetCookieValue("route-session-cookie", route_encoded_address, - "/path", std::chrono::seconds(120), true), + "/test", std::chrono::seconds(120), true), response->headers().get(Http::LowerCaseString("set-cookie"))[0]->value().getStringView()); cleanupUpstreamAndDownstream(); @@ -426,6 +421,77 @@ TEST_F(StatefulSessionIntegrationTest, StatefulSessionOverriddenByRoute) { } } +TEST_F(StatefulSessionIntegrationTest, CookieBasedStatefulSessionDisabledByRequestPath) { + initializeFilterAndRoute(STATEFUL_SESSION_FILTER, ""); + + uint64_t first_index = 0; + uint64_t second_index = 0; + + envoy::config::endpoint::v3::LbEndpoint endpoint; + setUpstreamAddress(1, endpoint); + const std::string address_string = + fmt::format("127.0.0.1:{}", endpoint.endpoint().address().socket_address().port_value()); + const std::string encoded_address = + Envoy::Base64::encode(address_string.data(), address_string.size()); + + // Request path is not start with cookie path which means that the stateful session cookie in the + // request my not generated by current filter. The stateful session will skip processing this + // request. + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, + {":path", "/path_not_match"}, + {":scheme", "http"}, + {":authority", "stateful.session.com"}, + {"cookie", fmt::format("global-session-cookie=\"{}\"", encoded_address)}}; + + { + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = codec_client_->makeRequestWithBody(request_headers, 0); + + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2, 3}); + ASSERT(upstream_index.has_value()); + first_index = upstream_index.value(); + + upstream_request_->encodeHeaders(default_response_headers_, true); + + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + + // No response header to be added. + EXPECT_TRUE(response->headers().get(Http::LowerCaseString("set-cookie")).empty()); + + cleanupUpstreamAndDownstream(); + } + + { + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = codec_client_->makeRequestWithBody(request_headers, 0); + + auto upstream_index = waitForNextUpstreamRequest({0, 1, 2, 3}); + ASSERT(upstream_index.has_value()); + second_index = upstream_index.value(); + + upstream_request_->encodeHeaders(default_response_headers_, true); + + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + + // No response header to be added. + EXPECT_TRUE(response->headers().get(Http::LowerCaseString("set-cookie")).empty()); + + cleanupUpstreamAndDownstream(); + } + + // Choose different upstream servers by default. + EXPECT_NE(first_index, second_index); +} + } // namespace } // namespace StatefulSession } // namespace HttpFilters diff --git a/test/extensions/filters/http/stateful_session/stateful_session_test.cc b/test/extensions/filters/http/stateful_session/stateful_session_test.cc index 47bf867c4d40..fe3e686aad6c 100644 --- a/test/extensions/filters/http/stateful_session/stateful_session_test.cc +++ b/test/extensions/filters/http/stateful_session/stateful_session_test.cc @@ -191,6 +191,21 @@ TEST_F(StatefulSessionTest, NoUpstreamHost) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, true)); } +// Test the case that no valid session state. +TEST_F(StatefulSessionTest, NullSessionState) { + initialize(ConfigYaml); + Http::TestRequestHeaderMapImpl request_headers{ + {":path", "/"}, {":method", "GET"}, {":authority", "test.com"}}; + Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + + EXPECT_CALL(*factory_, create(_)).WillOnce(Return(testing::ByMove(nullptr))); + EXPECT_CALL(decoder_callbacks_, setUpstreamOverrideHost(_)).Times(0); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, true)); +} + } // namespace } // namespace StatefulSession } // namespace HttpFilters diff --git a/test/extensions/http/stateful_session/cookie/cookie_test.cc b/test/extensions/http/stateful_session/cookie/cookie_test.cc index c4963b239e8f..81d91bfdb532 100644 --- a/test/extensions/http/stateful_session/cookie/cookie_test.cc +++ b/test/extensions/http/stateful_session/cookie/cookie_test.cc @@ -59,7 +59,7 @@ TEST(CookieBasedSessionStateFactoryTest, SessionStateTest) { // Get upstream address from request headers. Envoy::Http::TestRequestHeaderMapImpl request_headers = { - {"cookie", "override_host=" + Envoy::Base64::encode("1.2.3.4:80", 10)}}; + {":path", "/path"}, {"cookie", "override_host=" + Envoy::Base64::encode("1.2.3.4:80", 10)}}; auto session_state = factory.create(request_headers); EXPECT_EQ("1.2.3.4:80", session_state->upstreamAddress().value()); @@ -83,6 +83,91 @@ TEST(CookieBasedSessionStateFactoryTest, SessionStateTest) { Envoy::Base64::encode("2.3.4.5:80", 10), "/path", std::chrono::seconds(5), true)); } + + { + CookieBasedSessionStateProto config; + config.mutable_cookie()->set_name("override_host"); + config.mutable_cookie()->set_path("/path"); + config.mutable_cookie()->mutable_ttl()->set_seconds(5); + CookieBasedSessionStateFactory factory(config); + + // Get upstream address from request headers. + Envoy::Http::TestRequestHeaderMapImpl request_headers = { + {":path", "/not_match_path"}, + {"cookie", "override_host=" + Envoy::Base64::encode("1.2.3.4:80", 10)}}; + auto session_state = factory.create(request_headers); + EXPECT_EQ(nullptr, session_state); + } +} + +TEST(CookieBasedSessionStateFactoryTest, SessionStatePathMatchTest) { + { + // Any request path will be accepted for empty cookie path. + CookieBasedSessionStateProto config; + config.mutable_cookie()->set_name("override_host"); + config.mutable_cookie()->mutable_ttl()->set_seconds(5); + CookieBasedSessionStateFactory factory(config); + + EXPECT_TRUE(factory.requestPathMatch("/")); + EXPECT_TRUE(factory.requestPathMatch("/foo")); + EXPECT_TRUE(factory.requestPathMatch("/bar")); + EXPECT_TRUE(factory.requestPathMatch("/foo/bar")); + EXPECT_TRUE(factory.requestPathMatch("/foo#bar")); + EXPECT_TRUE(factory.requestPathMatch("/foo?bar")); + EXPECT_TRUE(factory.requestPathMatch("/foobar")); + } + + { + // Any request path will be accepted for root cookie path. + CookieBasedSessionStateProto config; + config.mutable_cookie()->set_name("override_host"); + config.mutable_cookie()->set_path("/"); + config.mutable_cookie()->mutable_ttl()->set_seconds(5); + CookieBasedSessionStateFactory factory(config); + + EXPECT_TRUE(factory.requestPathMatch("/")); + EXPECT_TRUE(factory.requestPathMatch("/foo")); + EXPECT_TRUE(factory.requestPathMatch("/bar")); + EXPECT_TRUE(factory.requestPathMatch("/foo/bar")); + EXPECT_TRUE(factory.requestPathMatch("/foo#bar")); + EXPECT_TRUE(factory.requestPathMatch("/foo?bar")); + EXPECT_TRUE(factory.requestPathMatch("/foobar")); + } + + { + // Request paths that start with the cookie path will be accepted for cookie path ends with '/'. + CookieBasedSessionStateProto config; + config.mutable_cookie()->set_name("override_host"); + config.mutable_cookie()->set_path("/foo/"); + config.mutable_cookie()->mutable_ttl()->set_seconds(5); + CookieBasedSessionStateFactory factory(config); + + EXPECT_FALSE(factory.requestPathMatch("/")); + EXPECT_FALSE(factory.requestPathMatch("/foo")); + EXPECT_FALSE(factory.requestPathMatch("/bar")); + EXPECT_TRUE(factory.requestPathMatch("/foo/")); + EXPECT_TRUE(factory.requestPathMatch("/foo/bar")); + EXPECT_FALSE(factory.requestPathMatch("/foo#bar")); + EXPECT_FALSE(factory.requestPathMatch("/foo?bar")); + EXPECT_FALSE(factory.requestPathMatch("/foobar")); + } + + { + CookieBasedSessionStateProto config; + config.mutable_cookie()->set_name("override_host"); + config.mutable_cookie()->set_path("/foo"); + config.mutable_cookie()->mutable_ttl()->set_seconds(5); + CookieBasedSessionStateFactory factory(config); + + EXPECT_FALSE(factory.requestPathMatch("/")); + EXPECT_TRUE(factory.requestPathMatch("/foo")); + EXPECT_FALSE(factory.requestPathMatch("/bar")); + EXPECT_TRUE(factory.requestPathMatch("/foo/")); + EXPECT_TRUE(factory.requestPathMatch("/foo/bar")); + EXPECT_TRUE(factory.requestPathMatch("/foo#bar")); + EXPECT_TRUE(factory.requestPathMatch("/foo?bar")); + EXPECT_FALSE(factory.requestPathMatch("/foobar")); + } } } // namespace From e3fede5b9374bca134ec546934335ab5cdeb7aef Mon Sep 17 00:00:00 2001 From: jiangshantao <634142246@qq.com> Date: Fri, 11 Mar 2022 04:47:58 +0800 Subject: [PATCH 43/68] fix: slow start config add min_weight_percent field to avoid too big edf deadline (#19712) * fix: slow start config add min_weight_percent field to avoid too big edf deadline Signed-off-by: jiangshantao --- api/envoy/config/cluster/v3/cluster.proto | 7 +- .../upstream/load_balancing/slow_start.rst | 4 +- docs/root/version_history/current.rst | 1 + source/common/upstream/load_balancer_impl.cc | 10 +- source/common/upstream/load_balancer_impl.h | 1 + .../upstream/load_balancer_impl_test.cc | 200 +++++++++++++++--- 6 files changed, 187 insertions(+), 36 deletions(-) diff --git a/api/envoy/config/cluster/v3/cluster.proto b/api/envoy/config/cluster/v3/cluster.proto index 89b1b5f25631..8d901de58589 100644 --- a/api/envoy/config/cluster/v3/cluster.proto +++ b/api/envoy/config/cluster/v3/cluster.proto @@ -363,12 +363,17 @@ message Cluster { // By tuning the parameter, is possible to achieve polynomial or exponential shape of ramp-up curve. // // During slow start window, effective weight of an endpoint would be scaled with time factor and aggression: - // `new_weight = weight * time_factor ^ (1 / aggression)`, + // `new_weight = weight * max(min_weight_percent, time_factor ^ (1 / aggression))`, // where `time_factor=(time_since_start_seconds / slow_start_time_seconds)`. // // As time progresses, more and more traffic would be sent to endpoint, which is in slow start window. // Once host exits slow start, time_factor and aggression no longer affect its weight. core.v3.RuntimeDouble aggression = 2; + + // Configures the minimum percentage of origin weight that avoids too small new weight, + // which may cause endpoints in slow start mode receive no traffic in slow start window. + // If not specified, the default is 10%. + type.v3.Percent min_weight_percent = 3; } // Specific configuration for the RoundRobin load balancing policy. diff --git a/docs/root/intro/arch_overview/upstream/load_balancing/slow_start.rst b/docs/root/intro/arch_overview/upstream/load_balancing/slow_start.rst index e510f6698255..391160d3276b 100644 --- a/docs/root/intro/arch_overview/upstream/load_balancing/slow_start.rst +++ b/docs/root/intro/arch_overview/upstream/load_balancing/slow_start.rst @@ -15,7 +15,7 @@ During slow start window, load balancing weight of a particular endpoint will be .. math:: - NewWeight = {Weight*TimeFactor}^\frac{1}{Aggression} + NewWeight = {Weight}*{max(MinWeightPercent,{TimeFactor}^\frac{1}{Aggression})} where, @@ -25,6 +25,8 @@ where, As time progresses, more and more traffic would be sent to endpoint within slow start window. +:ref:`MinWeightPercent parameter` specifies the minimum percent of origin weight to make sure the EDF scheduler has a reasonable deadline, default is 10%. + :ref:`Aggression parameter` non-linearly affects endpoint weight and represents the speed of ramp-up. By tuning aggression parameter, one could achieve polynomial or exponential speed for traffic increase. Below simulation demonstrates how various values for aggression affect traffic ramp-up: diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 9dd7bacecd6d..9ecb3f63ebde 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -42,6 +42,7 @@ Bug Fixes * jwt_authn: fixed the crash when a CONNECT request is sent to JWT filter configured with regex match on the Host header. * tcp_proxy: fix a crash that occurs when configured for :ref:`upstream tunneling ` and the downstream connection disconnects while the the upstream connection or http/2 stream is still being established. * tls: fix a bug while matching a certificate SAN with an exact value in ``match_typed_subject_alt_names`` of a listener where wildcard ``*`` character is not the only character of the dns label. Example, ``baz*.example.net`` and ``*baz.example.net`` and ``b*z.example.net`` will match ``baz1.example.net`` and ``foobaz.example.net`` and ``buzz.example.net``, respectively. +* upstream: cluster slow start config add ``min_weight_percent`` field to avoid too big EDF deadline which cause slow start endpoints receiving no traffic, default 10%. This fix is releted to `issue#19526 `_. * upstream: fix stack overflow when a cluster with large number of idle connections is removed. * xray: fix the AWS X-Ray tracer extension to not sample the trace if ``sampled=`` keyword is not present in the header ``x-amzn-trace-id``. diff --git a/source/common/upstream/load_balancer_impl.cc b/source/common/upstream/load_balancer_impl.cc index ff3218b3d3b7..8493185e94ce 100644 --- a/source/common/upstream/load_balancer_impl.cc +++ b/source/common/upstream/load_balancer_impl.cc @@ -804,7 +804,12 @@ EdfLoadBalancerBase::EdfLoadBalancerBase( slow_start_config.has_value() && slow_start_config.value().has_aggression() ? absl::optional({slow_start_config.value().aggression(), runtime}) : absl::nullopt), - time_source_(time_source), latest_host_added_time_(time_source_.monotonicTime()) { + time_source_(time_source), latest_host_added_time_(time_source_.monotonicTime()), + slow_start_min_weight_percent_(slow_start_config.has_value() + ? PROTOBUF_PERCENT_TO_DOUBLE_OR_DEFAULT( + slow_start_config.value(), min_weight_percent, 10) / + 100.0 + : 0.1) { // We fully recompute the schedulers for a given host set here on membership change, which is // consistent with what other LB implementations do (e.g. thread aware). // The downside of a full recompute is that time complexity is O(n * log n), @@ -1002,7 +1007,8 @@ double EdfLoadBalancerBase::applySlowStartFactor(double host_weight, const Host& auto time_factor = static_cast(std::max(std::chrono::milliseconds(1).count(), host_create_duration.count())) / slow_start_window_.count(); - return host_weight * applyAggressionFactor(time_factor); + return host_weight * + std::max(applyAggressionFactor(time_factor), slow_start_min_weight_percent_); } else { return host_weight; } diff --git a/source/common/upstream/load_balancer_impl.h b/source/common/upstream/load_balancer_impl.h index 8d81f9dc267b..a98bb72c988c 100644 --- a/source/common/upstream/load_balancer_impl.h +++ b/source/common/upstream/load_balancer_impl.h @@ -484,6 +484,7 @@ class EdfLoadBalancerBase : public ZoneAwareLoadBalancerBase, const absl::optional aggression_runtime_; TimeSource& time_source_; MonotonicTime latest_host_added_time_; + const double slow_start_min_weight_percent_; }; /** diff --git a/test/common/upstream/load_balancer_impl_test.cc b/test/common/upstream/load_balancer_impl_test.cc index d38e58f09f48..504eba157074 100644 --- a/test/common/upstream/load_balancer_impl_test.cc +++ b/test/common/upstream/load_balancer_impl_test.cc @@ -46,6 +46,9 @@ class EdfLoadBalancerBasePeer { return std::chrono::time_point_cast(edf_lb.latest_host_added_time_) .time_since_epoch(); } + static double slowStartMinWeightPercent(const EdfLoadBalancerBase& edf_lb) { + return edf_lb.slow_start_min_weight_percent_; + } }; class TestZoneAwareLoadBalancer : public ZoneAwareLoadBalancerBase { @@ -1616,6 +1619,26 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartWithDefaultParams) { const auto latest_host_added_time = EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(*lb_)); EXPECT_EQ(std::chrono::milliseconds(0), latest_host_added_time); + const auto slow_start_min_weight_percent = + EdfLoadBalancerBasePeer::slowStartMinWeightPercent(static_cast(*lb_)); + EXPECT_DOUBLE_EQ(slow_start_min_weight_percent, 0.1); +} + +TEST_P(RoundRobinLoadBalancerTest, SlowStartWithMinWeightPercent) { + round_robin_lb_config_.mutable_slow_start_config()->mutable_min_weight_percent()->set_value(30); + init(false); + const auto slow_start_window = + EdfLoadBalancerBasePeer::slowStartWindow(static_cast(*lb_)); + EXPECT_EQ(std::chrono::milliseconds(0), slow_start_window); + const auto aggression = + EdfLoadBalancerBasePeer::aggression(static_cast(*lb_)); + EXPECT_EQ(1.0, aggression); + const auto latest_host_added_time = + EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(*lb_)); + EXPECT_EQ(std::chrono::milliseconds(0), latest_host_added_time); + const auto slow_start_min_weight_percent = + EdfLoadBalancerBasePeer::slowStartMinWeightPercent(static_cast(*lb_)); + EXPECT_DOUBLE_EQ(slow_start_min_weight_percent, 0.3); } TEST_P(RoundRobinLoadBalancerTest, SlowStartNoWait) { @@ -1652,7 +1675,7 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartNoWait) { EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(*lb_)); EXPECT_EQ(std::chrono::milliseconds(62000), latest_host_added_time_ms); - // host2 is 12 secs in slow start, the weight is scaled with time factor 12 / 60 == 0.2. + // host2 is 12 secs in slow start, the weight is scaled with time factor max(12 / 60, 0.1) = 0.2. simTime().advanceTimeWait(std::chrono::seconds(12)); // Recalculate weights. @@ -1739,7 +1762,7 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartWaitForPassingHC) { hostSet().runCallbacks({}, {}); // We expect 3:1 ratio, as host2 is in slow start mode, its weight is scaled with time factor - // 5 / 10 == 0.5. + // max(6/10, 0.1) = 0.6. EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); @@ -1783,7 +1806,7 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartWithRuntimeAggression) { EXPECT_EQ(std::chrono::milliseconds(1000), latest_host_added_time_ms); // We should see 2:1:1 ratio, as hosts 2 and 3 are in slow start, their weights are scaled with - // 0.5 factor. + // max(0.5,0.1)=0.5 factor. EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[2], lb_->chooseHost(nullptr)); @@ -1803,8 +1826,8 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartWithRuntimeAggression) { EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(*lb_)); EXPECT_EQ(std::chrono::milliseconds(10000), latest_host_added_time_ms); - // We should see 1:1:1:0 ratio, as host 2 and 3 weight is scaled with (9/10)^(1/1.5)=0.93 factor, - // host4 weight is 0.002. + // We should see 1:1:1:0 ratio, as host 2 and 3 weight is scaled with max((9/10)^(1/1.5),0.1)=0.93 + // factor, host4 weight is 1*max(0.002,0.1)=0.1. EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[2], lb_->chooseHost(nullptr)); @@ -1812,7 +1835,8 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartWithRuntimeAggression) { EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[2], lb_->chooseHost(nullptr)); - // host4 is 9 seconds in slow start, it's weight is scaled with (9/10)^(1/1.5)=0.93 factor. + // host4 is 9 seconds in slow start, it's weight is scaled with max((9/10)^(1/1.5), 0.1)=0.93 + // factor. simTime().advanceTimeWait(std::chrono::seconds(9)); hostSet().runCallbacks({}, {}); @@ -1837,7 +1861,7 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartNoWaitNonLinearAggression) { hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}; hostSet().hosts_ = hostSet().healthy_hosts_; simTime().advanceTimeWait(std::chrono::seconds(5)); - // Host1 is 5 secs in slow start, its weight is scaled with (0.5/60)^(1/2)=0.28 factor. + // Host1 is 5 secs in slow start, its weight is scaled with max((5/60)^(1/2), 0.1)=0.28 factor. hostSet().runCallbacks({}, {}); // Advance time, so that host1 is no longer in slow start. @@ -1850,7 +1874,7 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartNoWaitNonLinearAggression) { hostSet().healthy_hosts_.push_back(host2); hostSet().hosts_ = hostSet().healthy_hosts_; - // host2 weight is scaled with 0.004 factor. + // host2 weight is scaled with max((0.001/60)^(1/2), 0.1)=max(0.004, 0.1)=0.1 factor. hostSet().runCallbacks(hosts_added, {}); // host2 is 6 secs in slow start. @@ -1860,7 +1884,7 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartNoWaitNonLinearAggression) { hostSet().runCallbacks({}, {}); // We expect 3:1 ratio, as host2 is 6 secs in slow start mode and it's weight is scaled with - // pow(0.1, 0.5)==0.31 factor. + // max(pow(0.1, 0.5), 0.1)=0.31 factor. EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); @@ -1873,7 +1897,7 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartNoWaitNonLinearAggression) { hostSet().runCallbacks({}, {}); // We still expect 5:3 ratio, as host2 is in slow start mode and it's weight is scaled with - // pow(0.43, 0.5)==0.65 factor. + // max(pow(0.43, 0.5), 0.1)=0.65 factor. EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); @@ -1896,6 +1920,102 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartNoWaitNonLinearAggression) { EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); } +TEST_P(RoundRobinLoadBalancerTest, SlowStartNoWaitMinWeightPercent35) { + round_robin_lb_config_.mutable_slow_start_config()->mutable_slow_start_window()->set_seconds(60); + round_robin_lb_config_.mutable_slow_start_config()->mutable_min_weight_percent()->set_value(35); + simTime().advanceTimeWait(std::chrono::seconds(1)); + auto host1 = makeTestHost(info_, "tcp://127.0.0.1:80", simTime()); + host_set_.hosts_ = {host1}; + + init(true); + + // As no healthcheck is configured, hosts would enter slow start immediately. + HostVector empty; + HostVector hosts_added; + hosts_added.push_back(host1); + simTime().advanceTimeWait(std::chrono::seconds(5)); + hostSet().runCallbacks(hosts_added, empty); + auto latest_host_added_time_ms = + EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(*lb_)); + EXPECT_EQ(std::chrono::milliseconds(1000), latest_host_added_time_ms); + + // Advance time, so that host is no longer in slow start. + simTime().advanceTimeWait(std::chrono::seconds(56)); + + hosts_added.clear(); + auto host2 = makeTestHost(info_, "tcp://127.0.0.1:90", simTime()); + + hosts_added.push_back(host2); + + hostSet().healthy_hosts_ = {host1, host2}; + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks(hosts_added, empty); + + latest_host_added_time_ms = + EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(*lb_)); + EXPECT_EQ(std::chrono::milliseconds(62000), latest_host_added_time_ms); + + // host2 is 12 secs in slow start, the weight is scaled with time factor max(12 / 60, 0.35) = + // 0.35. + simTime().advanceTimeWait(std::chrono::seconds(12)); + + // Recalculate weights. + hostSet().runCallbacks(empty, empty); + + // We expect 5:2 ratio, as host2 is in slow start mode and it's weight is scaled with + // 0.35 factor. + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[1,20/7] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[2,20/7] + EXPECT_EQ(hostSet().healthy_hosts_[1], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[3,20/7] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[3,40/7] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[4,40/7] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[5,40/7] + EXPECT_EQ(hostSet().healthy_hosts_[1], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[6,40/7] + + // host2 is 30 secs in slow start, the weight is scaled with time factor max(30 / 60, 0.35) == + // 0.5. + simTime().advanceTimeWait(std::chrono::seconds(18)); + + // Recalculate weights. + hostSet().runCallbacks(empty, empty); + + // We expect 2:1 ratio, as host2 is in slow start mode and it's weight is scaled with + // 0.5 factor. + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[1,2] + EXPECT_EQ(hostSet().healthy_hosts_[1], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[2,2] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[2,4] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[3,4] + EXPECT_EQ(hostSet().healthy_hosts_[1], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[4,4] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[4,6] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_->chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[5,6] + + // Advance time, so that there are no hosts in slow start. + simTime().advanceTimeWait(std::chrono::seconds(45)); + + // Recalculate weights. + hostSet().runCallbacks(empty, empty); + + // Now expect 1:1 ratio. + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); +} + class LeastRequestLoadBalancerTest : public LoadBalancerTestBase { public: LeastRequestLoadBalancer lb_{ @@ -2170,6 +2290,9 @@ TEST_P(LeastRequestLoadBalancerTest, SlowStartWithDefaultParams) { const auto latest_host_added_time = EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); EXPECT_EQ(std::chrono::milliseconds(0), latest_host_added_time); + const auto slow_start_min_weight_percent = + EdfLoadBalancerBasePeer::slowStartMinWeightPercent(static_cast(lb_2)); + EXPECT_DOUBLE_EQ(slow_start_min_weight_percent, 0.1); } TEST_P(LeastRequestLoadBalancerTest, SlowStartNoWait) { @@ -2185,7 +2308,7 @@ TEST_P(LeastRequestLoadBalancerTest, SlowStartNoWait) { hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}; hostSet().hosts_ = hostSet().healthy_hosts_; simTime().advanceTimeWait(std::chrono::seconds(5)); - // Host1 is 5 secs in slow start, its weight is scaled with (5/60)^1=0.08 factor. + // Host1 is 5 secs in slow start, its weight is scaled with max((5/60)^1, 0.1)=0.1 factor. hostSet().runCallbacks({}, {}); auto latest_host_added_time = @@ -2207,7 +2330,7 @@ TEST_P(LeastRequestLoadBalancerTest, SlowStartNoWait) { EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); EXPECT_EQ(std::chrono::milliseconds(62000), latest_host_added_time); - // host2 is 20 secs in slow start, the weight is scaled with time factor 20 / 60 == 0.16. + // host2 is 20 secs in slow start, the weight is scaled with time factor max(20/60, 0.1) = 0.16. simTime().advanceTimeWait(std::chrono::seconds(10)); // Recalculate weights. @@ -2223,7 +2346,7 @@ TEST_P(LeastRequestLoadBalancerTest, SlowStartNoWait) { EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - // host2 is 50 secs in slow start, the weight is scaled with time factor 40 / 60 == 0.66. + // host2 is 50 secs in slow start, the weight is scaled with time factor max(40/60, 0.1) = 0.66. simTime().advanceTimeWait(std::chrono::seconds(30)); // Recalculate weights. @@ -2294,22 +2417,35 @@ TEST_P(LeastRequestLoadBalancerTest, SlowStartWaitForPassingHC) { hostSet().runCallbacks({}, {}); // We expect 11:2 ratio, as host2 is in slow start mode, its weight is scaled with factor - // pow(0.1, 1.11)=0.07. Host1 is 7 seconds in slow start and its weight is scaled with active - // request and time bias 0.53 * pow(0.7, 1.11) = 0.36. - - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + // max(pow(0.1, 1.11), 0.1)=0.1. Host1 is 7 seconds in slow start and its weight is scaled with + // active request and time bias 0.53 * max(pow(0.7, 1.11), 0.1) = 0.36. + + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[25/9, 10] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[50/9, 10] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[75/9, 10] + EXPECT_EQ(hostSet().healthy_hosts_[1], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[100/9, 10] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[100/9, 20] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[125/9, 20] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[150/9, 20] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[175/9, 20] + EXPECT_EQ(hostSet().healthy_hosts_[1], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[200/9, 20] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[200/9, 30] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[225/9, 30] + EXPECT_EQ(hostSet().healthy_hosts_[0], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[250/9, 30] + EXPECT_EQ(hostSet().healthy_hosts_[1], + lb_2.chooseHost(nullptr)); // before choose: edf.deadline[host1,host2]=[275/9, 30] simTime().advanceTimeWait(std::chrono::seconds(3)); host1->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC); @@ -2317,7 +2453,7 @@ TEST_P(LeastRequestLoadBalancerTest, SlowStartWaitForPassingHC) { hostSet().runCallbacks({}, {}); // We expect 3:5 ratio, as host2 is 4 seconds in slow start, its weight is scaled with factor - // pow(0.4, 1.11)=0.36. Host1 is not in slow start and its weight is scaled with active + // max(pow(0.4, 1.11), 0.1)=0.36. Host1 is not in slow start and its weight is scaled with active // request bias = 0.53. EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); @@ -2328,13 +2464,13 @@ TEST_P(LeastRequestLoadBalancerTest, SlowStartWaitForPassingHC) { EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - // Host2 is 7 seconds in slow start, the weight is scaled with time factor 7 / 10 == 0.6. + // Host2 is 7 seconds in slow start, the weight is scaled with time factor 7 / 10 == 0.7. simTime().advanceTimeWait(std::chrono::seconds(3)); hostSet().runCallbacks({}, {}); // We expect 6:5 ratio, as host2 is in slow start mode, its weight is scaled with time factor - // pow(0.7, 1.11)=0.67. Host1 weight is scaled with active request bias = 0.53. + // max(pow(0.7, 1.11), 0.1)=0.67. Host1 weight is scaled with active request bias = 0.53. EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); From 3c190fefeb83dfe198c42f4a2857ee2244d88dc3 Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Thu, 10 Mar 2022 14:36:37 -0800 Subject: [PATCH 44/68] bazel: Patch m1 support for v8 (#20216) Submitted this upstream Partially fixes: https://github.com/envoyproxy/envoy/issues/19916 Signed-off-by: Keith Smiley --- bazel/v8.patch | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/bazel/v8.patch b/bazel/v8.patch index 306530b1126b..a55ec77a3c93 100644 --- a/bazel/v8.patch +++ b/bazel/v8.patch @@ -2,9 +2,10 @@ # 2. Fix the include path for //external:zlib. # 3. Add support for --define=no_debug_info=1. # 4. Disable pointer compression (https://crbug.com/v8/12592). +# 5. Add M1 CPU support https://chromium-review.googlesource.com/c/v8/v8/+/3502848 diff --git a/BUILD.bazel b/BUILD.bazel -index 1cc0121e60..dedc78fbb0 100644 +index 13f2a5bebf..2197568c48 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -4,7 +4,7 @@ @@ -16,7 +17,7 @@ index 1cc0121e60..dedc78fbb0 100644 load( "@v8//:bazel/defs.bzl", "v8_binary", -@@ -161,7 +161,7 @@ v8_int( +@@ -157,7 +157,7 @@ v8_int( # If no explicit value for v8_enable_pointer_compression, we set it to 'none'. v8_string( name = "v8_enable_pointer_compression", @@ -26,7 +27,7 @@ index 1cc0121e60..dedc78fbb0 100644 # Default setting for v8_enable_pointer_compression. diff --git a/bazel/defs.bzl b/bazel/defs.bzl -index 0e9559a65f..fa0c460c48 100644 +index dee5e69cc4..d0b5a3c49a 100644 --- a/bazel/defs.bzl +++ b/bazel/defs.bzl @@ -151,6 +151,11 @@ def _default_args(): @@ -41,6 +42,14 @@ index 0e9559a65f..fa0c460c48 100644 }), includes = ["include"], linkopts = select({ +@@ -383,6 +388,7 @@ def _v8_target_cpu_transition_impl(settings, attr): + "k8": "x64", + "x86_64": "x64", + "darwin": "x64", ++ "darwin_arm64": "arm64", + "darwin_x86_64": "x64", + "x64_windows": "x64", + "x86": "ia32", diff --git a/src/snapshot/snapshot-utils.cc b/src/snapshot/snapshot-utils.cc index 6db6698d7e..b56d31085f 100644 --- a/src/snapshot/snapshot-utils.cc From 517fd57648fd9b6913c93a16091891d4a1a913bc Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Thu, 10 Mar 2022 20:04:05 -0500 Subject: [PATCH 45/68] deps: Update benchamark to 1.6.1 (#20294) Signed-off-by: Yan Avlasov --- bazel/repository_locations.bzl | 6 +++--- test/common/common/logger_speed_test.cc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 19396c952864..cd6773efd4a2 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -401,12 +401,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Benchmark", project_desc = "Library to benchmark code snippets", project_url = "https://github.com/google/benchmark", - version = "1.5.1", - sha256 = "23082937d1663a53b90cb5b61df4bcc312f6dee7018da78ba00dd6bd669dfef2", + version = "1.6.1", + sha256 = "6132883bc8c9b0df5375b16ab520fac1a85dc9e4cf5be59480448ece74b278d4", strip_prefix = "benchmark-{version}", urls = ["https://github.com/google/benchmark/archive/v{version}.tar.gz"], use_category = ["test_only"], - release_date = "2020-06-09", + release_date = "2022-01-10", ), com_github_libevent_libevent = dict( project_name = "libevent", diff --git a/test/common/common/logger_speed_test.cc b/test/common/common/logger_speed_test.cc index 880eb450a9f7..ac5f0a9fe9c4 100644 --- a/test/common/common/logger_speed_test.cc +++ b/test/common/common/logger_speed_test.cc @@ -17,7 +17,7 @@ static void fancySlowPath(benchmark::State& state) { for (auto _ : state) { UNREFERENCED_PARAMETER(_); for (int i = 0; i < state.range(0); i++) { - std::string key = "k" + std::to_string(i + (state.thread_index << 8)); + std::string key = "k" + std::to_string(i + (state.thread_index() << 8)); getFancyContext().initFancyLogger(key, logger); } } From 851c56cf88010d56723d301018beb9a690bcd2ec Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Fri, 11 Mar 2022 09:07:14 -0500 Subject: [PATCH 46/68] Use regex to match exception message from absl::Status (#20296) Use regex to match exception message from absl::Status (#20296) Signed-off-by: Yan Avlasov --- test/common/router/config_impl_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index f46605ddc03a..a6bbb2de1576 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -1352,7 +1352,7 @@ TEST_F(RouteMatcherTest, TestMatchInvalidInput) { NiceMock stream_info; factory_context_.cluster_manager_.initializeClusters( {"www2", "root_www2", "www2_staging", "instant-server"}, {}); - EXPECT_THROW_WITH_MESSAGE( + EXPECT_THROW_WITH_REGEX( TestConfigImpl(parseRouteConfigurationFromYaml(yaml), factory_context_, true), EnvoyException, "requirement violation while creating route match tree: INVALID_ARGUMENT: Route table can " "only match on request headers, saw " From 824032e93ef295e22086f465d4c60a3aeea4ae13 Mon Sep 17 00:00:00 2001 From: Yuchen Dai Date: Fri, 11 Mar 2022 07:09:42 -0800 Subject: [PATCH 47/68] api: tcp_proxy with on demand cds (#20092) TcpProxy ondemand vs http on demand Why not a dedicated on demand filter prior to tcp_proxy? A filter prior to tcp proxy cannot figure out the dest cluster before tcp_proxy establish the upstream connection. In http stack, a route entry which contains the upstream cluster is offered to the potential on-demand filter before the router filter. Why no per-route config in tcp_proxy? TcpProxy provides single upstream cluster. An alternative weighted_clusters can be used as tcp_proxy upstream cluster. However, it's probably too early to distinguish the per cluster ondemand requirement within the weighted_cluster. We can always add in the future. Risk Level: LOW Signed-off-by: Yuchen Dai --- .../network/tcp_proxy/v3/tcp_proxy.proto | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto index ff37308b0c8d..99c448219216 100644 --- a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto +++ b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto @@ -4,6 +4,7 @@ package envoy.extensions.filters.network.tcp_proxy.v3; import "envoy/config/accesslog/v3/accesslog.proto"; import "envoy/config/core/v3/base.proto"; +import "envoy/config/core/v3/config_source.proto"; import "envoy/type/v3/hash_policy.proto"; import "google/protobuf/duration.proto"; @@ -23,7 +24,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // TCP Proxy :ref:`configuration overview `. // [#extension: envoy.filters.network.tcp_proxy] -// [#next-free-field: 14] +// [#next-free-field: 15] message TcpProxy { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.tcp_proxy.v2.TcpProxy"; @@ -84,6 +85,25 @@ message TcpProxy { [(validate.rules).repeated = {max_items: 1000}]; } + message OnDemand { + // An optional configuration for on-demand cluster discovery + // service. If not specified, the on-demand cluster discovery will + // be disabled. When it's specified, the filter will pause a request + // to an unknown cluster and will begin a cluster discovery + // process. When the discovery is finished (successfully or not), + // the request will be resumed. + config.core.v3.ConfigSource odcds_config = 1; + + // xdstp:// resource locator for on-demand cluster collection. + // [#not-implemented-hide:] + string resources_locator = 2; + + // The timeout for on demand cluster lookup. If the CDS cannot return the required cluster, + // the downstream request will be closed with the error code detail NO_CLUSTER_FOUND. + // [#not-implemented-hide:] + google.protobuf.Duration timeout = 3; + } + reserved 6; reserved "deprecated_v1"; @@ -104,6 +124,14 @@ message TcpProxy { WeightedCluster weighted_clusters = 10; } + // The on demand policy for the upstream cluster. + // It applies to both + // :ref:`TcpProxy.cluster Date: Sat, 12 Mar 2022 00:43:36 +0800 Subject: [PATCH 48/68] add ratelimit populate descriptors log (#20265) Signed-off-by: hejianpeng --- .../filters/http/local_ratelimit/local_ratelimit.cc | 8 ++++++++ .../filters/http/local_ratelimit/local_ratelimit.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc index a4a5ee44fe06..67b75c8b0be8 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.cc @@ -114,6 +114,14 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, // Store descriptors which is used to generate x-ratelimit-* headers in encoding response headers. stored_descriptors_ = descriptors; + if (ENVOY_LOG_CHECK_LEVEL(debug)) { + for (const auto& request_descriptor : descriptors) { + for (const Envoy::RateLimit::DescriptorEntry& entry : request_descriptor.entries_) { + ENVOY_LOG(debug, "populate descriptors: key={} value={}", entry.key_, entry.value_); + } + } + } + if (requestAllowed(descriptors)) { config->stats().ok_.inc(); return Http::FilterHeadersStatus::Continue; diff --git a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h index bbe8238a8db9..9fb9fef64fed 100644 --- a/source/extensions/filters/http/local_ratelimit/local_ratelimit.h +++ b/source/extensions/filters/http/local_ratelimit/local_ratelimit.h @@ -134,7 +134,7 @@ using FilterConfigSharedPtr = std::shared_ptr; * HTTP local rate limit filter. Depending on the route configuration, this filter calls consults * with local token bucket before allowing further filter iteration. */ -class Filter : public Http::PassThroughFilter { +class Filter : public Http::PassThroughFilter, Logger::Loggable { public: Filter(FilterConfigSharedPtr config) : config_(config) {} From 57a85315f4ff34a2931b2eeea275578bccf23dd3 Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Fri, 11 Mar 2022 11:43:59 -0500 Subject: [PATCH 49/68] event: add non-Api ctor to DispatcherImpl (#20297) Signed-off-by: Snow Pettersen --- source/common/event/dispatcher_impl.cc | 30 ++++++++++++------- source/common/event/dispatcher_impl.h | 14 +++++++-- .../common/filesystem/inotify/watcher_impl.cc | 6 ++-- .../common/filesystem/inotify/watcher_impl.h | 4 +-- .../common/filesystem/kqueue/watcher_impl.cc | 24 +++++++-------- .../common/filesystem/kqueue/watcher_impl.h | 4 +-- .../common/filesystem/win32/watcher_impl.cc | 6 ++-- source/common/filesystem/win32/watcher_impl.h | 4 +-- 8 files changed, 55 insertions(+), 37 deletions(-) diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index 428700382db9..e5f3b9ee4ea2 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -54,11 +54,21 @@ DispatcherImpl::DispatcherImpl(const std::string& name, Api::Api& api, Event::TimeSystem& time_system, const ScaledRangeTimerManagerFactory& scaled_timer_factory, const Buffer::WatermarkFactorySharedPtr& watermark_factory) - : name_(name), api_(api), - buffer_factory_(watermark_factory != nullptr - ? watermark_factory - : std::make_shared( - api.bootstrap().overload_manager().buffer_factory_config())), + : DispatcherImpl(name, api.threadFactory(), api.timeSource(), api.randomGenerator(), + api.fileSystem(), time_system, scaled_timer_factory, + watermark_factory != nullptr + ? watermark_factory + : std::make_shared( + api.bootstrap().overload_manager().buffer_factory_config())) {} + +DispatcherImpl::DispatcherImpl(const std::string& name, Thread::ThreadFactory& thread_factory, + TimeSource& time_source, Random::RandomGenerator& random_generator, + Filesystem::Instance& file_system, Event::TimeSystem& time_system, + const ScaledRangeTimerManagerFactory& scaled_timer_factory, + const Buffer::WatermarkFactorySharedPtr& watermark_factory) + : name_(name), thread_factory_(thread_factory), time_source_(time_source), + random_generator_(random_generator), file_system_(file_system), + buffer_factory_(watermark_factory), scheduler_(time_system.createScheduler(base_scheduler_, base_scheduler_)), thread_local_delete_cb_( base_scheduler_.createSchedulableCallback([this]() -> void { runThreadLocalDelete(); })), @@ -166,7 +176,7 @@ FileEventPtr DispatcherImpl::createFileEvent(os_fd_t fd, FileReadyCb cb, FileTri Filesystem::WatcherPtr DispatcherImpl::createFilesystemWatcher() { ASSERT(isThreadSafe()); - return Filesystem::WatcherPtr{new Filesystem::WatcherImpl(*this, api_)}; + return Filesystem::WatcherPtr{new Filesystem::WatcherImpl(*this, file_system_)}; } Network::ListenerPtr DispatcherImpl::createListener(Network::SocketSharedPtr&& socket, @@ -174,7 +184,7 @@ Network::ListenerPtr DispatcherImpl::createListener(Network::SocketSharedPtr&& s Runtime::Loader& runtime, bool bind_to_port, bool ignore_global_conn_limit) { ASSERT(isThreadSafe()); - return std::make_unique(*this, api_.randomGenerator(), runtime, + return std::make_unique(*this, random_generator_, runtime, std::move(socket), cb, bind_to_port, ignore_global_conn_limit); } @@ -268,7 +278,7 @@ void DispatcherImpl::deleteInDispatcherThread(DispatcherThreadDeletableConstPtr } void DispatcherImpl::run(RunType type) { - run_tid_ = api_.threadFactory().currentThreadId(); + run_tid_ = thread_factory_.currentThreadId(); // Flush all post callbacks before we run the event loop. We do this because there are post // callbacks that have to get run before the initial event loop starts running. libevent does // not guarantee that events are run in any particular order. So even if we post() and call @@ -313,7 +323,7 @@ void DispatcherImpl::shutdown() { void DispatcherImpl::updateApproximateMonotonicTime() { updateApproximateMonotonicTimeInternal(); } void DispatcherImpl::updateApproximateMonotonicTimeInternal() { - approximate_monotonic_time_ = api_.timeSource().monotonicTime(); + approximate_monotonic_time_ = time_source_.monotonicTime(); } void DispatcherImpl::runThreadLocalDelete() { @@ -375,7 +385,7 @@ void DispatcherImpl::runFatalActionsOnTrackedObject( const FatalAction::FatalActionPtrList& actions) const { // Only run if this is the dispatcher of the current thread and // DispatcherImpl::Run has been called. - if (run_tid_.isEmpty() || (run_tid_ != api_.threadFactory().currentThreadId())) { + if (run_tid_.isEmpty() || (run_tid_ != thread_factory_.currentThreadId())) { return; } diff --git a/source/common/event/dispatcher_impl.h b/source/common/event/dispatcher_impl.h index a0c3f98d35a8..b98ecaa400a5 100644 --- a/source/common/event/dispatcher_impl.h +++ b/source/common/event/dispatcher_impl.h @@ -43,6 +43,11 @@ class DispatcherImpl : Logger::Loggable, DispatcherImpl(const std::string& name, Api::Api& api, Event::TimeSystem& time_system, const ScaledRangeTimerManagerFactory& scaled_timer_factory, const Buffer::WatermarkFactorySharedPtr& watermark_factory); + DispatcherImpl(const std::string& name, Thread::ThreadFactory& thread_factory, + TimeSource& time_source, Random::RandomGenerator& random_generator, + Filesystem::Instance& file_system, Event::TimeSystem& time_system, + const ScaledRangeTimerManagerFactory& scaled_timer_factory, + const Buffer::WatermarkFactorySharedPtr& watermark_factory); ~DispatcherImpl() override; /** @@ -54,7 +59,7 @@ class DispatcherImpl : Logger::Loggable, 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(); } + TimeSource& timeSource() override { return time_source_; } void initializeStats(Stats::Scope& scope, const absl::optional& prefix) override; void clearDeferredDeleteList() override; Network::ServerConnectionPtr @@ -137,11 +142,14 @@ class DispatcherImpl : Logger::Loggable, // dispatcher run loop is executing on. We allow run_tid_ to be empty for tests where we don't // invoke run(). bool isThreadSafe() const override { - return run_tid_.isEmpty() || run_tid_ == api_.threadFactory().currentThreadId(); + return run_tid_.isEmpty() || run_tid_ == thread_factory_.currentThreadId(); } const std::string name_; - Api::Api& api_; + Thread::ThreadFactory& thread_factory_; + TimeSource& time_source_; + Random::RandomGenerator& random_generator_; + Filesystem::Instance& file_system_; std::string stats_prefix_; DispatcherStatsPtr stats_; Thread::ThreadId run_tid_; diff --git a/source/common/filesystem/inotify/watcher_impl.cc b/source/common/filesystem/inotify/watcher_impl.cc index a30d6ef48e56..7935d550b4b7 100644 --- a/source/common/filesystem/inotify/watcher_impl.cc +++ b/source/common/filesystem/inotify/watcher_impl.cc @@ -16,8 +16,8 @@ namespace Envoy { namespace Filesystem { -WatcherImpl::WatcherImpl(Event::Dispatcher& dispatcher, Api::Api& api) - : api_(api), inotify_fd_(inotify_init1(IN_NONBLOCK)), +WatcherImpl::WatcherImpl(Event::Dispatcher& dispatcher, Filesystem::Instance& file_system) + : file_system_(file_system), inotify_fd_(inotify_init1(IN_NONBLOCK)), inotify_event_(dispatcher.createFileEvent( inotify_fd_, [this](uint32_t events) -> void { @@ -34,7 +34,7 @@ WatcherImpl::~WatcherImpl() { close(inotify_fd_); } void WatcherImpl::addWatch(absl::string_view path, uint32_t events, OnChangedCb callback) { // Because of general inotify pain, we always watch the directory that the file lives in, // and then synthetically raise per file events. - const PathSplitResult result = api_.fileSystem().splitPathFromFilename(path); + const PathSplitResult result = file_system_.splitPathFromFilename(path); const uint32_t watch_mask = IN_MODIFY | IN_MOVED_TO; int watch_fd = inotify_add_watch(inotify_fd_, std::string(result.directory_).c_str(), watch_mask); diff --git a/source/common/filesystem/inotify/watcher_impl.h b/source/common/filesystem/inotify/watcher_impl.h index 73c4439fc53a..f2474b3c1d73 100644 --- a/source/common/filesystem/inotify/watcher_impl.h +++ b/source/common/filesystem/inotify/watcher_impl.h @@ -22,7 +22,7 @@ namespace Filesystem { */ class WatcherImpl : public Watcher, Logger::Loggable { public: - WatcherImpl(Event::Dispatcher& dispatcher, Api::Api& api); + WatcherImpl(Event::Dispatcher& dispatcher, Filesystem::Instance& file_system); ~WatcherImpl() override; // Filesystem::Watcher @@ -41,7 +41,7 @@ class WatcherImpl : public Watcher, Logger::Loggable { void onInotifyEvent(); - Api::Api& api_; + Filesystem::Instance& file_system_; int inotify_fd_; Event::FileEventPtr inotify_event_; absl::node_hash_map callback_map_; diff --git a/source/common/filesystem/kqueue/watcher_impl.cc b/source/common/filesystem/kqueue/watcher_impl.cc index 47edf3a2c291..1cf9e0865bf5 100644 --- a/source/common/filesystem/kqueue/watcher_impl.cc +++ b/source/common/filesystem/kqueue/watcher_impl.cc @@ -16,15 +16,16 @@ namespace Envoy { namespace Filesystem { -WatcherImpl::WatcherImpl(Event::Dispatcher& dispatcher, Api::Api& api) - : api_(api), queue_(kqueue()), kqueue_event_(dispatcher.createFileEvent( - queue_, - [this](uint32_t events) -> void { - if (events & Event::FileReadyType::Read) { - onKqueueEvent(); - } - }, - Event::FileTriggerType::Edge, Event::FileReadyType::Read)) {} +WatcherImpl::WatcherImpl(Event::Dispatcher& dispatcher, Filesystem::Instance& file_system) + : file_system_(file_system), queue_(kqueue()), + kqueue_event_(dispatcher.createFileEvent( + queue_, + [this](uint32_t events) -> void { + if (events & Event::FileReadyType::Read) { + onKqueueEvent(); + } + }, + Event::FileTriggerType::Edge, Event::FileReadyType::Read)) {} WatcherImpl::~WatcherImpl() { close(queue_); @@ -48,8 +49,7 @@ WatcherImpl::FileWatchPtr WatcherImpl::addWatch(absl::string_view path, uint32_t return nullptr; } - watch_fd = - open(std::string(api_.fileSystem().splitPathFromFilename(path).directory_).c_str(), 0); + watch_fd = open(std::string(file_system_.splitPathFromFilename(path).directory_).c_str(), 0); if (watch_fd == -1) { return nullptr; } @@ -106,7 +106,7 @@ void WatcherImpl::onKqueueEvent() { ASSERT(file != nullptr); ASSERT(watch_fd == file->fd_); - auto pathname = api_.fileSystem().splitPathFromFilename(file->file_); + auto pathname = file_system_.splitPathFromFilename(file->file_); if (file->watching_dir_) { if (event.fflags & NOTE_DELETE) { diff --git a/source/common/filesystem/kqueue/watcher_impl.h b/source/common/filesystem/kqueue/watcher_impl.h index 4a86f8be4ca5..ba5d908a05a0 100644 --- a/source/common/filesystem/kqueue/watcher_impl.h +++ b/source/common/filesystem/kqueue/watcher_impl.h @@ -23,7 +23,7 @@ namespace Filesystem { */ class WatcherImpl : public Watcher, Logger::Loggable { public: - WatcherImpl(Event::Dispatcher& dispatcher, Api::Api& api); + WatcherImpl(Event::Dispatcher& dispatcher, Filesystem::Instance& file_system); ~WatcherImpl(); // Filesystem::Watcher @@ -47,7 +47,7 @@ class WatcherImpl : public Watcher, Logger::Loggable { bool pathMustExist); void removeWatch(FileWatchPtr& watch); - Api::Api& api_; + Filesystem::Instance& file_system_; int queue_; absl::node_hash_map watches_; Event::FileEventPtr kqueue_event_; diff --git a/source/common/filesystem/win32/watcher_impl.cc b/source/common/filesystem/win32/watcher_impl.cc index ed89d98ceba9..ff723e9db608 100644 --- a/source/common/filesystem/win32/watcher_impl.cc +++ b/source/common/filesystem/win32/watcher_impl.cc @@ -7,8 +7,8 @@ namespace Envoy { namespace Filesystem { -WatcherImpl::WatcherImpl(Event::Dispatcher& dispatcher, Api::Api& api) - : api_(api), os_sys_calls_(Api::OsSysCallsSingleton::get()) { +WatcherImpl::WatcherImpl(Event::Dispatcher& dispatcher, Filesystem::Instance& file_system) + : file_system_(file_system), os_sys_calls_(Api::OsSysCallsSingleton::get()) { os_fd_t socks[2]; Api::SysCallIntResult result = os_sys_calls_.socketpair(AF_INET, SOCK_STREAM, IPPROTO_TCP, socks); ASSERT(result.return_value_ == 0); @@ -55,7 +55,7 @@ void WatcherImpl::addWatch(absl::string_view path, uint32_t events, OnChangedCb return; } - const PathSplitResult result = api_.fileSystem().splitPathFromFilename(path); + const PathSplitResult result = file_system_.splitPathFromFilename(path); // ReadDirectoryChangesW only has a Unicode version, so we need // to use wide strings here const std::wstring directory = wstring_converter_.from_bytes(std::string(result.directory_)); diff --git a/source/common/filesystem/win32/watcher_impl.h b/source/common/filesystem/win32/watcher_impl.h index 2742293db648..ce6acd518b49 100644 --- a/source/common/filesystem/win32/watcher_impl.h +++ b/source/common/filesystem/win32/watcher_impl.h @@ -27,7 +27,7 @@ namespace Filesystem { class WatcherImpl : public Watcher, Logger::Loggable { public: - WatcherImpl(Event::Dispatcher& dispatcher, Api::Api& api); + WatcherImpl(Event::Dispatcher& dispatcher, Filesystem::Instance& file_system); ~WatcherImpl(); // Filesystem::Watcher @@ -59,7 +59,7 @@ class WatcherImpl : public Watcher, Logger::Loggable { typedef std::unique_ptr DirectoryWatchPtr; - Api::Api& api_; + Filesystem::Instance& file_system_; absl::node_hash_map callback_map_; Network::IoHandlePtr read_handle_; Network::IoHandlePtr write_handle_; From 65bbd712fb5e199845ea0e07f5344ec2c4116a41 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Fri, 11 Mar 2022 14:50:53 -0500 Subject: [PATCH 50/68] Fix deprecated macro and update doc (#20298) Signed-off-by: Yan Avlasov --- STYLE.md | 2 +- source/common/common/thread.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/STYLE.md b/STYLE.md index bc45c15a9d92..3c0a83c7531e 100644 --- a/STYLE.md +++ b/STYLE.md @@ -90,7 +90,7 @@ NiceMock for mocks whose behavior is not the focus of a test. * [Thread annotations](https://github.com/abseil/abseil-cpp/blob/master/absl/base/thread_annotations.h), - such as `GUARDED_BY`, should be used for shared state guarded by + such as `ABSL_GUARDED_BY`, should be used for shared state guarded by locks/mutexes. * Functions intended to be local to a cc file should be declared in an anonymous namespace, rather than using the 'static' keyword. Note that the diff --git a/source/common/common/thread.cc b/source/common/common/thread.cc index 44b6f6c64ca1..dd32ca985bc2 100644 --- a/source/common/common/thread.cc +++ b/source/common/common/thread.cc @@ -74,7 +74,7 @@ struct ThreadIds { // thread. std::atomic main_thread_id_; - int32_t main_thread_use_count_ GUARDED_BY(mutex_) = 0; + int32_t main_thread_use_count_ ABSL_GUARDED_BY(mutex_) = 0; mutable absl::Mutex mutex_; std::atomic skip_asserts_{}; From d4ca42905d52574f6976e51d772ee7814709c85d Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 11 Mar 2022 22:18:26 +0000 Subject: [PATCH 51/68] build: Update build image and toolchains (#20241) Signed-off-by: Ryan Northey --- .bazelrc | 2 +- .devcontainer/Dockerfile | 2 +- bazel/repository_locations.bzl | 6 +++--- examples/wasm-cc/docker-compose-wasm.yaml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.bazelrc b/.bazelrc index fbfcbcf5625f..80eef8d7e09f 100644 --- a/.bazelrc +++ b/.bazelrc @@ -277,7 +277,7 @@ build:remote-clang-cl --config=rbe-toolchain-clang-cl # Docker sandbox # NOTE: Update this from https://github.com/envoyproxy/envoy-build-tools/blob/main/toolchains/rbe_toolchains_config.bzl#L8 -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:514e2f7bc36c1f0495a523b16aab9168a4aa13b6 +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build-ubuntu:d859a503314ae611bb7ca4a7b4b4a19194e199f0 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 aba483ec2836..a8e8f3a49cbe 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:514e2f7bc36c1f0495a523b16aab9168a4aa13b6 +FROM gcr.io/envoy-ci/envoy-build:d859a503314ae611bb7ca4a7b4b4a19194e199f0 ARG USERNAME=vscode ARG USER_UID=501 diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index cd6773efd4a2..8392dfffdaa5 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -64,11 +64,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "envoy-build-tools", project_desc = "Common build tools shared by the Envoy/UDPA ecosystem", project_url = "https://github.com/envoyproxy/envoy-build-tools", - version = "1162be3669036d2c09359a95d39ff65fc6608f39", - sha256 = "8f5ac011a443649a27a7b82bc447de4f564f9cb5b6812d87c3bc1b1e74d2055f", + version = "f3a70cf47bd91d8f6ab080e4da361cc7f8e6f24d", + sha256 = "f18224874887fae1f897fa582fb7aad34fc7604cadb1d3fe92527eabdb8af513", strip_prefix = "envoy-build-tools-{version}", urls = ["https://github.com/envoyproxy/envoy-build-tools/archive/{version}.tar.gz"], - release_date = "2022-01-28", + release_date = "2022-03-11", use_category = ["build"], ), boringssl = dict( diff --git a/examples/wasm-cc/docker-compose-wasm.yaml b/examples/wasm-cc/docker-compose-wasm.yaml index 95b70ed28dd1..f89002cf7d14 100644 --- a/examples/wasm-cc/docker-compose-wasm.yaml +++ b/examples/wasm-cc/docker-compose-wasm.yaml @@ -2,7 +2,7 @@ version: "3.7" services: wasm_compile_update: - image: envoyproxy/envoy-build-ubuntu:514e2f7bc36c1f0495a523b16aab9168a4aa13b6 + image: envoyproxy/envoy-build-ubuntu:d859a503314ae611bb7ca4a7b4b4a19194e199f0 command: | bash -c "bazel build //examples/wasm-cc:envoy_filter_http_wasm_updated_example.wasm && cp -a bazel-bin/examples/wasm-cc/* /build" working_dir: /source @@ -11,7 +11,7 @@ services: - ./lib:/build wasm_compile: - image: envoyproxy/envoy-build-ubuntu:514e2f7bc36c1f0495a523b16aab9168a4aa13b6 + image: envoyproxy/envoy-build-ubuntu:d859a503314ae611bb7ca4a7b4b4a19194e199f0 command: | bash -c "bazel build //examples/wasm-cc:envoy_filter_http_wasm_example.wasm && cp -a bazel-bin/examples/wasm-cc/* /build" working_dir: /source From 7ecd39b747349e35fdf7150300f543f3caf9f224 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Fri, 11 Mar 2022 20:58:34 -0500 Subject: [PATCH 52/68] Fix variable declaration (#20316) Signed-off-by: Yan Avlasov --- source/common/common/key_value_store_base.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/common/common/key_value_store_base.cc b/source/common/common/key_value_store_base.cc index ac7787474a60..e2725a725855 100644 --- a/source/common/common/key_value_store_base.cc +++ b/source/common/common/key_value_store_base.cc @@ -90,7 +90,7 @@ void KeyValueStoreBase::iterate(ConstIterateCb cb) const { #ifndef NDEBUG // When running in debug mode, verify we don't modify the underlying store // while iterating. - absl::flat_hash_map store_before_iteration = store_; + absl::flat_hash_map store_before_iteration = store_; absl::Cleanup verify_store_is_not_modified = [this, &store_before_iteration] { ASSERT(store_ == store_before_iteration, "Expected iterate to not modify the underlying store."); From 3c6b2fd2d21974b39715b2fcd5c871133962e681 Mon Sep 17 00:00:00 2001 From: code Date: Mon, 14 Mar 2022 10:02:25 +0800 Subject: [PATCH 53/68] stats: remove never used allocator instance (#20307) Signed-off-by: wbpcode Commit Message: remove never used allocator instance in the thread local store Additional Description: simple cleanup. Risk Level: Low. Testing: N/A. Docs Changes: N/A. Release Notes: N/A. Platform Specific Features: N/A. --- source/common/stats/thread_local_store.cc | 5 ++--- source/common/stats/thread_local_store.h | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/source/common/stats/thread_local_store.cc b/source/common/stats/thread_local_store.cc index 60a56be2db65..35fabc8f63b0 100644 --- a/source/common/stats/thread_local_store.cc +++ b/source/common/stats/thread_local_store.cc @@ -28,9 +28,8 @@ ThreadLocalStoreImpl::ThreadLocalStoreImpl(Allocator& alloc) : alloc_(alloc), tag_producer_(std::make_unique()), stats_matcher_(std::make_unique()), histogram_settings_(std::make_unique()), - heap_allocator_(alloc.symbolTable()), null_counter_(alloc.symbolTable()), - null_gauge_(alloc.symbolTable()), null_histogram_(alloc.symbolTable()), - null_text_readout_(alloc.symbolTable()), + null_counter_(alloc.symbolTable()), null_gauge_(alloc.symbolTable()), + null_histogram_(alloc.symbolTable()), null_text_readout_(alloc.symbolTable()), well_known_tags_(alloc.symbolTable().makeSet("well_known_tags")) { for (const auto& desc : Config::TagNames::get().descriptorVec()) { well_known_tags_->rememberBuiltin(desc.name_); diff --git a/source/common/stats/thread_local_store.h b/source/common/stats/thread_local_store.h index 1fdae4ad18a7..74f625eaa2b2 100644 --- a/source/common/stats/thread_local_store.h +++ b/source/common/stats/thread_local_store.h @@ -528,7 +528,6 @@ class ThreadLocalStoreImpl : Logger::Loggable, public StoreRo std::atomic threading_ever_initialized_{}; std::atomic shutting_down_{}; std::atomic merge_in_progress_{}; - AllocatorImpl heap_allocator_; OptRef tls_; NullCounterImpl null_counter_; From 18d00abd3670acb0a74050e5c262d984342bd463 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 12:12:17 +0000 Subject: [PATCH 54/68] build(deps): bump github/codeql-action from 1.1.3 to 1.1.4 (#20255) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1.1.3 to 1.1.4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/75f07e7ab2ee63cba88752d8c696324e4df67466...f5d822707ee6e8fb81b04a5c0040b736da22e587) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql-daily.yml | 4 ++-- .github/workflows/codeql-push.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 6179fa81ca7a..10b556ad5492 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -26,7 +26,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@75f07e7ab2ee63cba88752d8c696324e4df67466 + uses: github/codeql-action/init@f5d822707ee6e8fb81b04a5c0040b736da22e587 # Override language selection by uncommenting this and choosing your languages with: languages: cpp @@ -53,4 +53,4 @@ jobs: git clean -xdf - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@75f07e7ab2ee63cba88752d8c696324e4df67466 + uses: github/codeql-action/analyze@f5d822707ee6e8fb81b04a5c0040b736da22e587 diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 583b16867802..32ea678544de 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -37,7 +37,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@75f07e7ab2ee63cba88752d8c696324e4df67466 + uses: github/codeql-action/init@f5d822707ee6e8fb81b04a5c0040b736da22e587 # Override language selection by uncommenting this and choosing your languages with: languages: cpp @@ -67,4 +67,4 @@ jobs: - name: Perform CodeQL Analysis if: env.BUILD_TARGETS != '' - uses: github/codeql-action/analyze@75f07e7ab2ee63cba88752d8c696324e4df67466 + uses: github/codeql-action/analyze@f5d822707ee6e8fb81b04a5c0040b736da22e587 From 9de998f0822b8b691d6241d31efee8b3b019e37a Mon Sep 17 00:00:00 2001 From: RenjieTang Date: Mon, 14 Mar 2022 06:05:41 -0700 Subject: [PATCH 55/68] QUIC: Default add RVCM to Envoy QUIC client sessions. (#20259) Commit Message: Default add connection option RVCM to client QUIC connections. Additional Description: This will truly enable active port migration implemented at #18103 Risk Level: Low Testing: Unit tests Docs Changes: n/a. Release Notes: n/a. Platform Specific Features: n/a. Fixes #16311 Signed-off-by: Renjie Tang --- source/common/quic/client_connection_factory_impl.cc | 7 +++++++ test/common/quic/client_connection_factory_impl_test.cc | 4 +++- test/integration/http_integration.cc | 3 ++- test/integration/quic_http_integration_test.cc | 4 +--- test/mocks/upstream/cluster_info.cc | 2 +- tools/spelling/spelling_dictionary.txt | 1 + 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/source/common/quic/client_connection_factory_impl.cc b/source/common/quic/client_connection_factory_impl.cc index aab72a68533b..94a2e70db808 100644 --- a/source/common/quic/client_connection_factory_impl.cc +++ b/source/common/quic/client_connection_factory_impl.cc @@ -22,6 +22,13 @@ createPersistentQuicInfoForCluster(Event::Dispatcher& dispatcher, quic::QuicTime::Delta crypto_timeout = quic::QuicTime::Delta::FromMilliseconds(cluster.connectTimeout().count()); quic_info->quic_config_.set_max_time_before_crypto_handshake(crypto_timeout); + // Default enable RVCM connection option so that port migration is enabled. + quic::QuicTagVector connection_options; + if (quic_info->quic_config_.HasSendConnectionOptions()) { + connection_options = quic_info->quic_config_.SendConnectionOptions(); + } + connection_options.push_back(quic::kRVCM); + quic_info->quic_config_.SetConnectionOptionsToSend(connection_options); return quic_info; } diff --git a/test/common/quic/client_connection_factory_impl_test.cc b/test/common/quic/client_connection_factory_impl_test.cc index ef10af1ea228..a647b2750e03 100644 --- a/test/common/quic/client_connection_factory_impl_test.cc +++ b/test/common/quic/client_connection_factory_impl_test.cc @@ -40,6 +40,9 @@ class QuicNetworkConnectionTest : public Event::TestUsingSimulatedTime, protocol_options->max_concurrent_streams().value()); EXPECT_EQ(quic_info_->quic_config_.GetInitialMaxStreamDataBytesIncomingBidirectionalToSend(), protocol_options->initial_stream_window_size().value()); + ASSERT_TRUE(quic_info_->quic_config_.HasSendConnectionOptions()); + EXPECT_TRUE( + quic::ContainsQuicTag(quic_info_->quic_config_.SendConnectionOptions(), quic::kRVCM)); test_address_ = Network::Utility::resolveUrl( absl::StrCat("tcp://", Network::Test::getLoopbackAddressUrlString(GetParam()), ":30")); @@ -95,7 +98,6 @@ TEST_P(QuicNetworkConnectionTest, Srtt) { initialize(); Http::MockAlternateProtocolsCache rtt_cache; - quic::QuicConfig config; PersistentQuicInfoImpl info{dispatcher_, 45}; EXPECT_CALL(rtt_cache, getSrtt).WillOnce(Return(std::chrono::microseconds(5))); diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 7c3fc675fd7f..e38e45eda1cc 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -360,8 +360,9 @@ void HttpIntegrationTest::initialize() { registerTestServerPorts({"http"}, test_server_); // Needs to outlive all QUIC connections. + auto cluster = std::make_shared>(); auto quic_connection_persistent_info = - std::make_unique(*dispatcher_, 0); + Quic::createPersistentQuicInfoForCluster(*dispatcher_, *cluster); // Config IETF QUIC flow control window. quic_connection_persistent_info->quic_config_ .SetInitialMaxStreamDataBytesIncomingBidirectionalToSend( diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 08e7b6599480..0ac27e43672c 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -146,9 +146,7 @@ class QuicHttpIntegrationTest : public HttpIntegrationTest, : HttpIntegrationTest(Http::CodecType::HTTP3, GetParam(), ConfigHelper::quicHttpProxyConfig()), supported_versions_(quic::CurrentSupportedHttp3Versions()), conn_helper_(*dispatcher_), - alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) { - SetQuicReloadableFlag(quic_remove_connection_migration_connection_option, true); - } + alarm_factory_(*dispatcher_, *conn_helper_.GetClock()) {} ~QuicHttpIntegrationTest() override { cleanupUpstreamAndDownstream(); diff --git a/test/mocks/upstream/cluster_info.cc b/test/mocks/upstream/cluster_info.cc index 75b0f629e68f..495e6f7ed3ef 100644 --- a/test/mocks/upstream/cluster_info.cc +++ b/test/mocks/upstream/cluster_info.cc @@ -61,7 +61,7 @@ MockClusterInfo::MockClusterInfo() runtime_, "fake_key", 1, 1024, 1024, 1, std::numeric_limits::max(), circuit_breakers_stats_, absl::nullopt, absl::nullopt)), stats_scope_(stats_store_.createScope("test_scope")) { - ON_CALL(*this, connectTimeout()).WillByDefault(Return(std::chrono::milliseconds(1))); + ON_CALL(*this, connectTimeout()).WillByDefault(Return(std::chrono::milliseconds(5001))); ON_CALL(*this, idleTimeout()).WillByDefault(Return(absl::optional())); ON_CALL(*this, perUpstreamPreconnectRatio()).WillByDefault(Return(1.0)); ON_CALL(*this, name()).WillByDefault(ReturnRef(name_)); diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 4eb45381497c..673742784a6b 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -300,6 +300,7 @@ RTDS RTOS RTTI RUNDIR +RVCM RW RX RXQ From 3aba9a71dd269851ca394c70ede456519d5eb1f2 Mon Sep 17 00:00:00 2001 From: Yuchen Dai Date: Mon, 14 Mar 2022 06:32:36 -0700 Subject: [PATCH 56/68] remove unused code in config_test.cc (#20300) remove unused code in config_test.cc. Signed-off-by: Yuchen Dai --- test/config_test/config_test.cc | 24 ------------------------ test/config_test/config_test.h | 14 -------------- 2 files changed, 38 deletions(-) diff --git a/test/config_test/config_test.cc b/test/config_test/config_test.cc index c1a984c8c845..13092ff3fbc9 100644 --- a/test/config_test/config_test.cc +++ b/test/config_test/config_test.cc @@ -228,29 +228,5 @@ uint32_t run(const std::string& directory) { } return num_tested; } - -void loadVersionedBootstrapFile(const std::string& filename, - envoy::config::bootstrap::v3::Bootstrap& bootstrap_message) { - Api::ApiPtr api = Api::createApiForTest(); - OptionsImpl options( - Envoy::Server::createTestOptionsImpl(filename, "", Network::Address::IpVersion::v6)); - // Avoid contention issues with other tests over the hot restart domain socket. - options.setHotRestartDisabled(true); - Server::InstanceUtil::loadBootstrapConfig(bootstrap_message, options, - ProtobufMessage::getStrictValidationVisitor(), *api); -} - -void loadBootstrapConfigProto(const envoy::config::bootstrap::v3::Bootstrap& in_proto, - envoy::config::bootstrap::v3::Bootstrap& bootstrap_message) { - Api::ApiPtr api = Api::createApiForTest(); - OptionsImpl options( - Envoy::Server::createTestOptionsImpl("", "", Network::Address::IpVersion::v6)); - options.setConfigProto(in_proto); - // Avoid contention issues with other tests over the hot restart domain socket. - options.setHotRestartDisabled(true); - Server::InstanceUtil::loadBootstrapConfig(bootstrap_message, options, - ProtobufMessage::getStrictValidationVisitor(), *api); -} - } // namespace ConfigTest } // namespace Envoy diff --git a/test/config_test/config_test.h b/test/config_test/config_test.h index e4ee26501fcc..5d23bcd212cc 100644 --- a/test/config_test/config_test.h +++ b/test/config_test/config_test.h @@ -22,19 +22,5 @@ uint32_t run(const std::string& path); */ void testMerge(); -/** - * Loads the given bootstrap file with an optional bootstrap_version into the - * given bootstrap protobuf message using the server's loadBootstrapConfig. - */ -void loadVersionedBootstrapFile(const std::string& filename, - envoy::config::bootstrap::v3::Bootstrap& bootstrap_message); - -/** - * Loads the given bootstrap proto into the given bootstrap protobuf message - * using the server's loadBootstrapConfig. - */ -void loadBootstrapConfigProto(const envoy::config::bootstrap::v3::Bootstrap& in_proto, - envoy::config::bootstrap::v3::Bootstrap& bootstrap_message); - } // namespace ConfigTest } // namespace Envoy From 009072a69743e91f38318efe2e060380c5263eaf Mon Sep 17 00:00:00 2001 From: YuanYingdong <1975643103@qq.com> Date: Mon, 14 Mar 2022 22:10:36 +0800 Subject: [PATCH 57/68] doc: fix wrong emphasize line in cds demo config (#20328) Signed-off-by: YuanYingdong <1975643103@qq.com> Co-authored-by: yingdongyuan --- docs/root/start/sandboxes/dynamic-configuration-filesystem.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst b/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst index eb52a8f9cf2d..b10016616f08 100644 --- a/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst +++ b/docs/root/start/sandboxes/dynamic-configuration-filesystem.rst @@ -92,7 +92,7 @@ from ``service1`` to ``service2``: :linenos: :lines: 6-13 :lineno-start: 6 - :emphasize-lines: 8 + :emphasize-lines: 7 You can do this using ``sed`` inside the container: From 4346c10bff83c5ed6bc4d2ce82b5859e5b8a82c2 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 14 Mar 2022 10:11:59 -0400 Subject: [PATCH 58/68] Fix IgnoreUnknownOptionalHttpFilter test to work in IPv6 only environment (#20308) Signed-off-by: Yan Avlasov --- test/integration/listener_lds_integration_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/listener_lds_integration_test.cc b/test/integration/listener_lds_integration_test.cc index 70d9d988f9f6..ac8126610ab7 100644 --- a/test/integration/listener_lds_integration_test.cc +++ b/test/integration/listener_lds_integration_test.cc @@ -314,7 +314,7 @@ TEST_P(ListenerIntegrationTest, IgnoreUnknownOptionalHttpFilter) { name: fake_listener address: socket_address: - address: 127.0.0.1 + address: "::" port_value: 0 filter_chains: - filters: From 3cf75517d5501cb695967a460420b5330366d5eb Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 14 Mar 2022 10:18:50 -0400 Subject: [PATCH 59/68] Fix clang-tidy complaint about std::string::find (#20306) Signed-off-by: Yan Avlasov --- source/common/network/utility.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/common/network/utility.cc b/source/common/network/utility.cc index 2d662df1b57c..9db87aac2913 100644 --- a/source/common/network/utility.cc +++ b/source/common/network/utility.cc @@ -422,7 +422,7 @@ void Utility::parsePortRangeList(absl::string_view string, std::list& uint32_t min = 0; uint32_t max = 0; - if (s.find('-') != std::string::npos) { + if (absl::StrContains(s, '-')) { char dash = 0; ss >> min; ss >> dash; From c55b207b97e0d08358f7603e54cceaa9c5f158b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 14:32:34 +0000 Subject: [PATCH 60/68] build(deps): bump wrapt from 1.13.3 to 1.14.0 in /tools/dependency (#20291) Bumps [wrapt](https://github.com/GrahamDumpleton/wrapt) from 1.13.3 to 1.14.0. - [Release notes](https://github.com/GrahamDumpleton/wrapt/releases) - [Changelog](https://github.com/GrahamDumpleton/wrapt/blob/develop/docs/changes.rst) - [Commits](https://github.com/GrahamDumpleton/wrapt/compare/1.13.3...1.14.0) --- updated-dependencies: - dependency-name: wrapt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/dependency/requirements.txt | 117 +++++++++++++++++------------- 1 file changed, 65 insertions(+), 52 deletions(-) diff --git a/tools/dependency/requirements.txt b/tools/dependency/requirements.txt index dd94db4ea0cc..17598f24f6dd 100644 --- a/tools/dependency/requirements.txt +++ b/tools/dependency/requirements.txt @@ -126,56 +126,69 @@ urllib3==1.26.8 \ --hash=sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed \ --hash=sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c # via requests -wrapt==1.13.3 \ - --hash=sha256:086218a72ec7d986a3eddb7707c8c4526d677c7b35e355875a0fe2918b059179 \ - --hash=sha256:0877fe981fd76b183711d767500e6b3111378ed2043c145e21816ee589d91096 \ - --hash=sha256:0a017a667d1f7411816e4bf214646d0ad5b1da2c1ea13dec6c162736ff25a374 \ - --hash=sha256:0cb23d36ed03bf46b894cfec777eec754146d68429c30431c99ef28482b5c1df \ - --hash=sha256:1fea9cd438686e6682271d36f3481a9f3636195578bab9ca3382e2f5f01fc185 \ - --hash=sha256:220a869982ea9023e163ba915077816ca439489de6d2c09089b219f4e11b6785 \ - --hash=sha256:25b1b1d5df495d82be1c9d2fad408f7ce5ca8a38085e2da41bb63c914baadff7 \ - --hash=sha256:2dded5496e8f1592ec27079b28b6ad2a1ef0b9296d270f77b8e4a3a796cf6909 \ - --hash=sha256:2ebdde19cd3c8cdf8df3fc165bc7827334bc4e353465048b36f7deeae8ee0918 \ - --hash=sha256:43e69ffe47e3609a6aec0fe723001c60c65305784d964f5007d5b4fb1bc6bf33 \ - --hash=sha256:46f7f3af321a573fc0c3586612db4decb7eb37172af1bc6173d81f5b66c2e068 \ - --hash=sha256:47f0a183743e7f71f29e4e21574ad3fa95676136f45b91afcf83f6a050914829 \ - --hash=sha256:498e6217523111d07cd67e87a791f5e9ee769f9241fcf8a379696e25806965af \ - --hash=sha256:4b9c458732450ec42578b5642ac53e312092acf8c0bfce140ada5ca1ac556f79 \ - --hash=sha256:51799ca950cfee9396a87f4a1240622ac38973b6df5ef7a41e7f0b98797099ce \ - --hash=sha256:5601f44a0f38fed36cc07db004f0eedeaadbdcec90e4e90509480e7e6060a5bc \ - --hash=sha256:5f223101f21cfd41deec8ce3889dc59f88a59b409db028c469c9b20cfeefbe36 \ - --hash=sha256:610f5f83dd1e0ad40254c306f4764fcdc846641f120c3cf424ff57a19d5f7ade \ - --hash=sha256:6a03d9917aee887690aa3f1747ce634e610f6db6f6b332b35c2dd89412912bca \ - --hash=sha256:705e2af1f7be4707e49ced9153f8d72131090e52be9278b5dbb1498c749a1e32 \ - --hash=sha256:766b32c762e07e26f50d8a3468e3b4228b3736c805018e4b0ec8cc01ecd88125 \ - --hash=sha256:77416e6b17926d953b5c666a3cb718d5945df63ecf922af0ee576206d7033b5e \ - --hash=sha256:778fd096ee96890c10ce96187c76b3e99b2da44e08c9e24d5652f356873f6709 \ - --hash=sha256:78dea98c81915bbf510eb6a3c9c24915e4660302937b9ae05a0947164248020f \ - --hash=sha256:7dd215e4e8514004c8d810a73e342c536547038fb130205ec4bba9f5de35d45b \ - --hash=sha256:7dde79d007cd6dfa65afe404766057c2409316135cb892be4b1c768e3f3a11cb \ - --hash=sha256:81bd7c90d28a4b2e1df135bfbd7c23aee3050078ca6441bead44c42483f9ebfb \ - --hash=sha256:85148f4225287b6a0665eef08a178c15097366d46b210574a658c1ff5b377489 \ - --hash=sha256:865c0b50003616f05858b22174c40ffc27a38e67359fa1495605f96125f76640 \ - --hash=sha256:87883690cae293541e08ba2da22cacaae0a092e0ed56bbba8d018cc486fbafbb \ - --hash=sha256:8aab36778fa9bba1a8f06a4919556f9f8c7b33102bd71b3ab307bb3fecb21851 \ - --hash=sha256:8c73c1a2ec7c98d7eaded149f6d225a692caa1bd7b2401a14125446e9e90410d \ - --hash=sha256:936503cb0a6ed28dbfa87e8fcd0a56458822144e9d11a49ccee6d9a8adb2ac44 \ - --hash=sha256:944b180f61f5e36c0634d3202ba8509b986b5fbaf57db3e94df11abee244ba13 \ - --hash=sha256:96b81ae75591a795d8c90edc0bfaab44d3d41ffc1aae4d994c5aa21d9b8e19a2 \ - --hash=sha256:981da26722bebb9247a0601e2922cedf8bb7a600e89c852d063313102de6f2cb \ - --hash=sha256:ae9de71eb60940e58207f8e71fe113c639da42adb02fb2bcbcaccc1ccecd092b \ - --hash=sha256:b73d4b78807bd299b38e4598b8e7bd34ed55d480160d2e7fdaabd9931afa65f9 \ - --hash=sha256:d4a5f6146cfa5c7ba0134249665acd322a70d1ea61732723c7d3e8cc0fa80755 \ - --hash=sha256:dd91006848eb55af2159375134d724032a2d1d13bcc6f81cd8d3ed9f2b8e846c \ - --hash=sha256:e05e60ff3b2b0342153be4d1b597bbcfd8330890056b9619f4ad6b8d5c96a81a \ - --hash=sha256:e6906d6f48437dfd80464f7d7af1740eadc572b9f7a4301e7dd3d65db285cacf \ - --hash=sha256:e92d0d4fa68ea0c02d39f1e2f9cb5bc4b4a71e8c442207433d8db47ee79d7aa3 \ - --hash=sha256:e94b7d9deaa4cc7bac9198a58a7240aaf87fe56c6277ee25fa5b3aa1edebd229 \ - --hash=sha256:ea3e746e29d4000cd98d572f3ee2a6050a4f784bb536f4ac1f035987fc1ed83e \ - --hash=sha256:ec7e20258ecc5174029a0f391e1b948bf2906cd64c198a9b8b281b811cbc04de \ - --hash=sha256:ec9465dd69d5657b5d2fa6133b3e1e989ae27d29471a672416fd729b429eb554 \ - --hash=sha256:f122ccd12fdc69628786d0c947bdd9cb2733be8f800d88b5a37c57f1f1d73c10 \ - --hash=sha256:f99c0489258086308aad4ae57da9e8ecf9e1f3f30fa35d5e170b4d4896554d80 \ - --hash=sha256:f9c51d9af9abb899bd34ace878fbec8bf357b3194a10c4e8e0a25512826ef056 \ - --hash=sha256:fd76c47f20984b43d93de9a82011bb6e5f8325df6c9ed4d8310029a55fa361ea +wrapt==1.14.0 \ + --hash=sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b \ + --hash=sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0 \ + --hash=sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330 \ + --hash=sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3 \ + --hash=sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68 \ + --hash=sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa \ + --hash=sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe \ + --hash=sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd \ + --hash=sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b \ + --hash=sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80 \ + --hash=sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38 \ + --hash=sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f \ + --hash=sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350 \ + --hash=sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd \ + --hash=sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb \ + --hash=sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3 \ + --hash=sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0 \ + --hash=sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff \ + --hash=sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c \ + --hash=sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758 \ + --hash=sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036 \ + --hash=sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb \ + --hash=sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763 \ + --hash=sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9 \ + --hash=sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7 \ + --hash=sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1 \ + --hash=sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7 \ + --hash=sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0 \ + --hash=sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5 \ + --hash=sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce \ + --hash=sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8 \ + --hash=sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279 \ + --hash=sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0 \ + --hash=sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06 \ + --hash=sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561 \ + --hash=sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a \ + --hash=sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311 \ + --hash=sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131 \ + --hash=sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4 \ + --hash=sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291 \ + --hash=sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4 \ + --hash=sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8 \ + --hash=sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8 \ + --hash=sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d \ + --hash=sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c \ + --hash=sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd \ + --hash=sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d \ + --hash=sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6 \ + --hash=sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775 \ + --hash=sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e \ + --hash=sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627 \ + --hash=sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e \ + --hash=sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8 \ + --hash=sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1 \ + --hash=sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48 \ + --hash=sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc \ + --hash=sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3 \ + --hash=sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6 \ + --hash=sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425 \ + --hash=sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d \ + --hash=sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23 \ + --hash=sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c \ + --hash=sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33 \ + --hash=sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653 # via deprecated From 17ec14b35c4f310c0b1e2d7a6d7b4b1c19a736f2 Mon Sep 17 00:00:00 2001 From: Ismo Puustinen Date: Mon, 14 Mar 2022 18:09:18 +0200 Subject: [PATCH 61/68] decompressor: do not add accepted content encoding twice. (#19394) Signed-off-by: Ismo Puustinen --- docs/root/version_history/current.rst | 1 + source/common/http/header_utility.cc | 41 +++++++++++ source/common/http/header_utility.h | 18 +++++ source/common/runtime/runtime_features.cc | 2 + .../filters/http/cache/cache_headers_utils.cc | 9 +-- .../http/decompressor/decompressor_filter.cc | 12 +++- .../http/decompressor/decompressor_filter.h | 1 + test/common/http/header_utility_test.cc | 71 +++++++++++++++++++ .../filters/http/decompressor/BUILD | 1 + .../decompressor/decompressor_filter_test.cc | 50 +++++++++++++ 10 files changed, 199 insertions(+), 7 deletions(-) diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 9ecb3f63ebde..8e269af2cdff 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -14,6 +14,7 @@ Minor Behavior Changes * access_log: log all header values in the grpc access log. * config: warning messages for protobuf unknown fields now contain ancestors for easier troubleshooting. +* decompressor: decompressor does not duplicate `accept-encoding` header values anymore. This behavioral change can be reverted by setting runtime guard ``envoy.reloadable_features.append_to_accept_content_encoding_only_once`` to false. * dynamic_forward_proxy: if a DNS resolution fails, failing immediately with a specific resolution error, rather than finishing up all local filters and failing to select an upstream host. * ext_authz: added requested server name in ext_authz network filter for auth review. * file: changed disk based files to truncate files which are not being appended to. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.append_or_truncate`` to false. diff --git a/source/common/http/header_utility.cc b/source/common/http/header_utility.cc index fcce30f090ce..f85f62e7e7be 100644 --- a/source/common/http/header_utility.cc +++ b/source/common/http/header_utility.cc @@ -434,5 +434,46 @@ HeaderUtility::validateContentLength(absl::string_view header_value, return HeaderValidationResult::ACCEPT; } +std::vector +HeaderUtility::parseCommaDelimitedHeader(absl::string_view header_value) { + std::vector values; + for (absl::string_view s : absl::StrSplit(header_value, ',')) { + absl::string_view token = absl::StripAsciiWhitespace(s); + if (token.empty()) { + continue; + } + values.emplace_back(token); + } + return values; +} + +absl::string_view HeaderUtility::getSemicolonDelimitedAttribute(absl::string_view value) { + return absl::StripAsciiWhitespace(StringUtil::cropRight(value, ";")); +} + +std::string HeaderUtility::addEncodingToAcceptEncoding(absl::string_view accept_encoding_header, + absl::string_view encoding) { + // Append the content encoding only if it isn't already present in the + // accept_encoding header. If it is present with a q-value ("gzip;q=0.3"), + // remove the q-value to indicate that the content encoding setting that we + // add has max priority (i.e. q-value 1.0). + std::vector newContentEncodings; + std::vector contentEncodings = + Http::HeaderUtility::parseCommaDelimitedHeader(accept_encoding_header); + for (absl::string_view contentEncoding : contentEncodings) { + absl::string_view strippedEncoding = + Http::HeaderUtility::getSemicolonDelimitedAttribute(contentEncoding); + if (strippedEncoding != encoding) { + // Add back all content encodings back except for the content encoding that we want to + // add. For example, if content encoding is "gzip", this filters out encodings "gzip" and + // "gzip;q=0.6". + newContentEncodings.push_back(contentEncoding); + } + } + // Finally add a single instance of our content encoding. + newContentEncodings.push_back(encoding); + return absl::StrJoin(newContentEncodings, ","); +} + } // namespace Http } // namespace Envoy diff --git a/source/common/http/header_utility.h b/source/common/http/header_utility.h index 40a42c7da604..6b55d514a5f3 100644 --- a/source/common/http/header_utility.h +++ b/source/common/http/header_utility.h @@ -271,6 +271,24 @@ class HeaderUtility { validateContentLength(absl::string_view header_value, bool override_stream_error_on_invalid_http_message, bool& should_close_connection, size_t& content_length_output); + + /** + * Parse a comma-separated header string to the individual tokens. Discard empty tokens + * and whitespace. Return a vector of the comma-separated tokens. + */ + static std::vector parseCommaDelimitedHeader(absl::string_view header_value); + + /** + * Return the part of attribute before first ';'-sign. For example, + * "foo;bar=1" would return "foo". + */ + static absl::string_view getSemicolonDelimitedAttribute(absl::string_view value); + + /** + * Return a new AcceptEncoding header string vector. + */ + static std::string addEncodingToAcceptEncoding(absl::string_view accept_encoding_header, + absl::string_view encoding); }; } // namespace Http diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 5cc63b3ec950..3ebedf93a72c 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -23,6 +23,7 @@ RUNTIME_GUARD(envoy_reloadable_features_allow_upstream_inline_write); RUNTIME_GUARD(envoy_reloadable_features_append_or_truncate); +RUNTIME_GUARD(envoy_reloadable_features_append_to_accept_content_encoding_only_once); RUNTIME_GUARD(envoy_reloadable_features_conn_pool_delete_when_idle); RUNTIME_GUARD(envoy_reloadable_features_conn_pool_new_stream_with_early_data_and_http3); RUNTIME_GUARD(envoy_reloadable_features_correct_scheme_and_xfp); @@ -140,6 +141,7 @@ constexpr absl::Flag* runtime_features[] = { &FLAGS_envoy_reloadable_features_allow_multiple_dns_addresses, &FLAGS_envoy_reloadable_features_allow_upstream_inline_write, &FLAGS_envoy_reloadable_features_append_or_truncate, + &FLAGS_envoy_reloadable_features_append_to_accept_content_encoding_only_once, &FLAGS_envoy_reloadable_features_conn_pool_delete_when_idle, &FLAGS_envoy_reloadable_features_conn_pool_new_stream_with_early_data_and_http3, &FLAGS_envoy_reloadable_features_correct_scheme_and_xfp, diff --git a/source/extensions/filters/http/cache/cache_headers_utils.cc b/source/extensions/filters/http/cache/cache_headers_utils.cc index 608990f038f6..1e9b3b8b588b 100644 --- a/source/extensions/filters/http/cache/cache_headers_utils.cc +++ b/source/extensions/filters/http/cache/cache_headers_utils.cc @@ -218,12 +218,9 @@ std::vector CacheHeadersUtils::parseCommaDelimitedHeader(const Http::HeaderMap::GetResult& entry) { std::vector values; for (size_t i = 0; i < entry.size(); ++i) { - for (absl::string_view s : absl::StrSplit(entry[i]->value().getStringView(), ',')) { - if (s.empty()) { - continue; - } - values.emplace_back(absl::StripAsciiWhitespace(s)); - } + std::vector tokens = + Http::HeaderUtility::parseCommaDelimitedHeader(entry[i]->value().getStringView()); + values.insert(values.end(), tokens.begin(), tokens.end()); } return values; } diff --git a/source/extensions/filters/http/decompressor/decompressor_filter.cc b/source/extensions/filters/http/decompressor/decompressor_filter.cc index 03dc4d3e7f60..f160c88fa7cd 100644 --- a/source/extensions/filters/http/decompressor/decompressor_filter.cc +++ b/source/extensions/filters/http/decompressor/decompressor_filter.cc @@ -3,6 +3,7 @@ #include "source/common/buffer/buffer_impl.h" #include "source/common/common/empty_string.h" #include "source/common/common/macros.h" +#include "source/common/runtime/runtime_features.h" namespace Envoy { namespace Extensions { @@ -72,7 +73,16 @@ Http::FilterHeadersStatus DecompressorFilter::decodeHeaders(Http::RequestHeaderM // the upstream that this hop is able to decompress responses via the Accept-Encoding header. if (config_->responseDirectionConfig().decompressionEnabled() && config_->requestDirectionConfig().advertiseAcceptEncoding()) { - headers.appendInline(accept_encoding_handle.handle(), config_->contentEncoding(), ","); + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.append_to_accept_content_encoding_only_once")) { + const std::string new_accept_encoding_header = + Http::HeaderUtility::addEncodingToAcceptEncoding( + headers.getInlineValue(accept_encoding_handle.handle()), config_->contentEncoding()); + headers.setInline(accept_encoding_handle.handle(), new_accept_encoding_header); + } else { + headers.appendInline(accept_encoding_handle.handle(), config_->contentEncoding(), ","); + } + ENVOY_STREAM_LOG(debug, "DecompressorFilter::decodeHeaders advertise Accept-Encoding with value '{}'", *decoder_callbacks_, headers.getInlineValue(accept_encoding_handle.handle())); diff --git a/source/extensions/filters/http/decompressor/decompressor_filter.h b/source/extensions/filters/http/decompressor/decompressor_filter.h index ebc704081978..d67a5ea7bf8e 100644 --- a/source/extensions/filters/http/decompressor/decompressor_filter.h +++ b/source/extensions/filters/http/decompressor/decompressor_filter.h @@ -6,6 +6,7 @@ #include "envoy/http/filter.h" #include "source/common/common/macros.h" +#include "source/common/http/header_utility.h" #include "source/common/http/headers.h" #include "source/common/runtime/runtime_protos.h" #include "source/extensions/filters/http/common/pass_through_filter.h" diff --git a/test/common/http/header_utility_test.cc b/test/common/http/header_utility_test.cc index 7e8fdba22208..963b8b9c15d6 100644 --- a/test/common/http/header_utility_test.cc +++ b/test/common/http/header_utility_test.cc @@ -13,6 +13,8 @@ #include "gtest/gtest.h" +using testing::ElementsAre; + namespace Envoy { namespace Http { @@ -924,5 +926,74 @@ TEST(ValidateHeaders, ContentLength) { EXPECT_TRUE(should_close_connection); } +TEST(ValidateHeaders, ParseCommaDelimitedHeader) { + // Basic case + EXPECT_THAT(HeaderUtility::parseCommaDelimitedHeader("one,two,three"), + ElementsAre("one", "two", "three")); + + // Whitespace at the end or beginning of tokens + EXPECT_THAT(HeaderUtility::parseCommaDelimitedHeader("one ,two,three"), + ElementsAre("one", "two", "three")); + + // Empty tokens are removed (from beginning, middle, and end of the string) + EXPECT_THAT(HeaderUtility::parseCommaDelimitedHeader(", one,, two, three,,,"), + ElementsAre("one", "two", "three")); + + // Whitespace is not removed from the middle of the tokens + EXPECT_THAT(HeaderUtility::parseCommaDelimitedHeader("one, two, t hree"), + ElementsAre("one", "two", "t hree")); + + // Semicolons are kept as part of the tokens + EXPECT_THAT(HeaderUtility::parseCommaDelimitedHeader("one, two;foo, three"), + ElementsAre("one", "two;foo", "three")); + + // Check that a single token is parsed regardless of commas + EXPECT_THAT(HeaderUtility::parseCommaDelimitedHeader("foo"), ElementsAre("foo")); + EXPECT_THAT(HeaderUtility::parseCommaDelimitedHeader(",foo,"), ElementsAre("foo")); + + // Empty string is handled + EXPECT_TRUE(HeaderUtility::parseCommaDelimitedHeader("").empty()); + + // Empty string is handled (whitespace) + EXPECT_TRUE(HeaderUtility::parseCommaDelimitedHeader(" ").empty()); + + // Empty string is handled (commas) + EXPECT_TRUE(HeaderUtility::parseCommaDelimitedHeader(",,,").empty()); +} + +TEST(ValidateHeaders, GetSemicolonDelimitedAttribute) { + // Basic case + EXPECT_EQ(HeaderUtility::getSemicolonDelimitedAttribute("foo;bar=1"), "foo"); + + // Only attribute without semicolon + EXPECT_EQ(HeaderUtility::getSemicolonDelimitedAttribute("foo"), "foo"); + + // Only attribute with semicolon + EXPECT_EQ(HeaderUtility::getSemicolonDelimitedAttribute("foo;"), "foo"); + + // Two semicolons, case 1 + EXPECT_EQ(HeaderUtility::getSemicolonDelimitedAttribute("foo;;"), "foo"); + + // Two semicolons, case 2 + EXPECT_EQ(HeaderUtility::getSemicolonDelimitedAttribute(";foo;"), ""); +} + +TEST(ValidateHeaders, ModifyAcceptEncodingHeader) { + // Add a new encoding + EXPECT_EQ(HeaderUtility::addEncodingToAcceptEncoding("one,two", "three"), "one,two,three"); + + // Add an already existing encoding + EXPECT_EQ(HeaderUtility::addEncodingToAcceptEncoding("one,two", "one"), "two,one"); + + // Preserve q-values for other tokens than the added encoding + EXPECT_EQ(HeaderUtility::addEncodingToAcceptEncoding("one;q=0.3,two", "two"), "one;q=0.3,two"); + + // Remove q-values for the current encoding + EXPECT_EQ(HeaderUtility::addEncodingToAcceptEncoding("one;q=0.3,two", "one"), "two,one"); + + // Add encoding to an empty header + EXPECT_EQ(HeaderUtility::addEncodingToAcceptEncoding("", "one"), "one"); +} + } // namespace Http } // namespace Envoy diff --git a/test/extensions/filters/http/decompressor/BUILD b/test/extensions/filters/http/decompressor/BUILD index 6ab72ce93c91..4168466a7ddc 100644 --- a/test/extensions/filters/http/decompressor/BUILD +++ b/test/extensions/filters/http/decompressor/BUILD @@ -24,6 +24,7 @@ envoy_extension_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/filters/http/decompressor/v3:pkg_cc_proto", ], diff --git a/test/extensions/filters/http/decompressor/decompressor_filter_test.cc b/test/extensions/filters/http/decompressor/decompressor_filter_test.cc index ded8ae26c78e..97c3d624c733 100644 --- a/test/extensions/filters/http/decompressor/decompressor_filter_test.cc +++ b/test/extensions/filters/http/decompressor/decompressor_filter_test.cc @@ -10,6 +10,7 @@ #include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/stats/mocks.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -205,6 +206,33 @@ class DecompressorFilterTest : public testing::TestWithParam { expectDecompression(decompressor_ptr, end_with_data); } + void testAcceptEncodingFilter(bool legacy, const std::string& original_accept_encoding, + const std::string& final_accept_encoding) { + TestScopedRuntime scoped_runtime; + if (legacy) { + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.append_to_accept_content_encoding_only_once", "false"}}); + + } else { + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.append_to_accept_content_encoding_only_once", "true"}}); + } + setUpFilter(R"EOF( +decompressor_library: + typed_config: + "@type": "type.googleapis.com/envoy.extensions.compression.gzip.decompressor.v3.Gzip" +request_direction_config: + advertise_accept_encoding: true +)EOF"); + Http::TestRequestHeaderMapImpl headers_before_filter{{"content-encoding", "mock"}, + {"content-length", "256"}}; + if (isRequestDirection()) { + headers_before_filter.addCopy("accept-encoding", original_accept_encoding); + } + decompressionActive(headers_before_filter, true /* end_with_data */, absl::nullopt, + final_accept_encoding); + } + Compression::Decompressor::MockDecompressorFactory* decompressor_factory_{}; DecompressorFilterConfigSharedPtr config_; std::unique_ptr filter_; @@ -299,6 +327,28 @@ TEST_P(DecompressorFilterTest, ExplicitlyEnableAdvertiseAcceptEncoding) { "br,mock" /* expected_accept_encoding */); } +TEST_P(DecompressorFilterTest, ExplicitlyEnableAdvertiseAcceptEncodingOnlyOnce) { + // Do not duplicate accept-encoding values. Remove extra accept-encoding values for the + // content-type we specify. Also remove q-values from our content-type (if not set, it defaults + // to 1.0). Test also whitespace in accept-encoding value string. + testAcceptEncodingFilter(false, "br,mock, mock\t,mock ;q=0.3", "br,mock"); +} + +TEST_P(DecompressorFilterTest, ExplicitlyEnableAdvertiseAcceptEncodingOnlyOnceLegacy) { + // legacy test to avoid a breaking change + testAcceptEncodingFilter(true, "br,mock, mock\t,mock ;q=0.3", "br,mock, mock\t,mock ;q=0.3,mock"); +} + +TEST_P(DecompressorFilterTest, ExplicitlyEnableAdvertiseAcceptEncodingRemoveQValue) { + // If the accept-encoding header had a q-value, it needs to be removed. + testAcceptEncodingFilter(false, "mock;q=0.6", "mock"); +} + +TEST_P(DecompressorFilterTest, ExplicitlyEnableAdvertiseAcceptEncodingRemoveQValueLegacy) { + // legacy test to avoid a breaking change + testAcceptEncodingFilter(true, "mock;q=0.6", "mock;q=0.6,mock"); +} + TEST_P(DecompressorFilterTest, DecompressionDisabled) { setUpFilter(R"EOF( decompressor_library: From 0dc77e2743e75c8a86bd62f4736d68991b584812 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 16:28:15 +0000 Subject: [PATCH 62/68] build(deps): bump envoy-dependency-pip-check in /tools/base (#20240) Bumps [envoy-dependency-pip-check](https://github.com/envoyproxy/pytooling) from 0.1.2 to 0.1.3. - [Release notes](https://github.com/envoyproxy/pytooling/releases) - [Commits](https://github.com/envoyproxy/pytooling/compare/0.1.2...0.1.3) --- updated-dependencies: - dependency-name: envoy-dependency-pip-check dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 38fc1b1a9d2b..0fe9b2d62d4d 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -317,9 +317,9 @@ envoy-dependency-check==0.0.5 \ --hash=sha256:39a8b25ae88e0305eaf38a90c3c7956db3b3c53e967464368319e41f3309df36 \ --hash=sha256:c0eb679f3af45ab075560a74a0b13131efa9be17b624cf905f9fe4fef776ec40 # via -r requirements.in -envoy-dependency-pip-check==0.1.2 \ - --hash=sha256:30959cf67450566dd81b91df444c0484c0b7c8bec2d81797f3fb22ba248c20f0 \ - --hash=sha256:e0613e156d53765b53f2d7f25ca84489988732771c6114e63d21f81aa54a079f +envoy-dependency-pip-check==0.1.3 \ + --hash=sha256:b07ca0c1f2786b13b0dfcdfc82647afece3918d44dc7ddaca5739dfaadc9d0e8 \ + --hash=sha256:f7bafe720f499009173625268af15374dea9e733b8dadb0fc26bfa7c822a7028 # via -r requirements.in envoy-distribution-distrotest==0.0.7 \ --hash=sha256:3de711ba72cc78158cc70aa6957dc7f029f4c0877196d42a4d5a11757fda03e0 \ @@ -428,10 +428,6 @@ imagesize==1.2.0 \ --hash=sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1 \ --hash=sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1 # via sphinx -importlib-metadata==4.11.1 \ - --hash=sha256:175f4ee440a0317f6e8d81b7f8d4869f93316170a65ad2b007d2929186c8052c \ - --hash=sha256:e0bc84ff355328a4adfc5240c4f211e0ab386f80aa640d1b11f0618a1d282094 - # via sphinx iniconfig==1.1.1 \ --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 @@ -995,10 +991,6 @@ yarl==1.6.3 \ --hash=sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a \ --hash=sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71 # via aiohttp -zipp==3.7.0 \ - --hash=sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d \ - --hash=sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375 - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: setuptools==60.9.3 \ From 4d29c1c82e953994e559824e26eaa4826e03d905 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Mon, 14 Mar 2022 11:02:41 -0700 Subject: [PATCH 63/68] Update QUICHE from cf1588207 to 9386be8fb (#20302) Update QUICHE from cf1588207 to 9386be8fb https://github.com/google/quiche/compare/cf1588207..9386be8fb $ git log cf1588207..9386be8fb --date=short --no-merges --format="%ad %al %s" 2022-03-10 bnc Make http2/platform/api/http2_test_helpers.h depend on quiche platform api instead of impl. 2022-03-10 quiche-dev Ensures proper queuing of requests when too many are in flight. 2022-03-10 bnc Remove QuicSleep from platform. 2022-03-10 wub Add a missing case in SerializedPacketFateToString for DISCARD. 2022-03-10 bnc Add some missing includes. 2022-03-09 vasilvv Move quic_iovec.h into QUICHE platform. 2022-03-09 quiche-dev Add testing for connection window flow control violations. 2022-03-09 quiche-dev Migrates a QUICHE_BUG to a QUICHE_LOG(ERROR). 2022-03-08 quiche-dev Unit tests for QuicBufferedPacketStore::IngestPacketForTlsChloExtraction 2022-03-08 vasilvv Provide default implementation of QuicheReferenceCounted. 2022-03-08 fayang Internal change 2022-03-08 bnc Merge http2/platform/impl/http2_test_helpers_impl.h into quiche/common/platform/impl/quiche_test_helpers_impl.h. 2022-03-08 bnc Merge quic_test_impl into quiche_test_impl. 2022-03-08 vasilvv Move QuicReferenceCounted into QUICHE and rename it into QuicheReferenceCounted. 2022-03-07 martinduke Pass span by value to QuicConnectionId() 2022-03-07 fayang Automated g4 rollback of changelist 433064452. 2022-03-07 renjietang Delete redundant brackets. 2022-03-07 fayang Deprecate gfe2_reloadable_flag_quic_donot_check_amplification_limit_with_pending_timer_credit. 2022-03-07 fayang Deprecate gfe2_reloadable_flag_quic_reset_per_packet_state_for_undecryptable_packets. 2022-03-07 bnc Deprecate --gfe2_reloadable_flag_quic_limit_encoder_stream_buffering. 2022-03-07 fayang Deprecate gfe2_reloadable_flag_quic_accept_empty_crypto_frame. 2022-03-07 bnc Internal change 2022-03-04 martinduke Add a convenient contstructor to QuicConnectionId that accepts a Span. QUIC-LB has many tests that write specific connection IDs, and a constructor that accepts array literals is way less cumbersome. 2022-03-03 quiche-dev Add a configuration option for a custom WINDOW_UPDATE strategy in OgHttp2Session. 2022-03-03 quiche-dev Rename WindowManager::(ShouldNotifyListener --> ShouldWindowUpdateFn). 2022-03-03 wub Add trace messages for QuicCryptoServerConfig::ProcessClientHelloContext. 2022-03-03 quiche-dev Enable custom WINDOW_UPDATE notifying criteria in WindowManager. 2022-03-03 bnc Simplify QuicStreamSendBuffer::SaveStreamData(). 2022-03-03 quiche-dev Automated g4 rollback of changelist 432178898. 2022-03-03 bnc Simplify QuicStreamSendBuffer::SaveStreamData(). 2022-03-03 bnc Move QuicBufferAllocator and SimpleBufferAllocator to third_party/quiche. 2022-03-02 vasilvv Move WebTransportFingerprintProofVerifier to quic/core/crypto 2022-03-02 vasilvv Fix implicit conversion from absl::string_view to std::string. 2022-03-02 quiche-dev Uses an existing "using" declaration for conciseness. 2022-03-02 quiche-dev Fixes a bug where HandleOutboundSettings is invoked twice for initial SETTINGS. 2022-03-02 wub Fix QuicClientTest.DoNotLeakSocketFDs under TSAN. 2022-03-01 wub Use a smaller lower bound(5ms) to clamp the initial rtt, when setting initial rtt from a trusted value. Currently only the min_rtt from CachedNetworkParameters is considered trusted. 2022-03-01 rch Deprecate --gfe2_restart_flag_quic_no_common_cert_set. 2022-03-01 vasilvv Provide a default implementaion of QuicheMemSlice. 2022-03-01 quiche-dev Allows OgHttp2Adapter to send GOAWAYs when operating as a client. 2022-03-01 quiche-dev Adds test coverage for the nul char appearing in header field names or values. 2022-03-01 fayang Default enable PTO with configuration: 1) Send 1 packet per PTO with skipped packet number. 2) Arm the 1st PTO based on the earliest in flight sent time while making sure at least 1.5 * srtt has passed since the last in flight packet. 3) PTO delay = srtt + 2 * rttvar + ack_delay. Signed-off-by: Ryan Hamilton --- bazel/external/quiche.BUILD | 157 +++++++++++------- bazel/external/quiche.genrule_cmd | 1 + bazel/repository_locations.bzl | 6 +- source/common/quic/BUILD | 1 - .../quic/envoy_quic_connection_helper.h | 8 +- .../common/quic/envoy_quic_packet_writer.cc | 1 + source/common/quic/envoy_quic_proof_source.cc | 4 +- source/common/quic/envoy_quic_proof_source.h | 2 +- .../quic/envoy_quic_proof_source_base.h | 1 - .../common/quic/envoy_quic_server_stream.cc | 1 + source/common/quic/platform/BUILD | 40 ++++- .../quic_stream_buffer_allocator_impl.h | 4 +- ...{quic_iovec_impl.h => quiche_iovec_impl.h} | 0 .../quic/platform/quiche_mem_slice_impl.cc | 6 +- .../quic/platform/quiche_mem_slice_impl.h | 4 +- .../quic_filter_manager_connection_impl.h | 1 + .../quic/envoy_quic_proof_source_test.cc | 8 +- .../quic/envoy_quic_server_session_test.cc | 2 +- test/common/quic/platform/BUILD | 10 +- .../quic/platform/quic_platform_test.cc | 3 - test/common/quic/platform/quic_test_impl.h | 28 ---- test/common/quic/platform/quiche_test_impl.h | 16 ++ test/common/quic/test_proof_source.h | 4 +- test/common/quic/test_utils.h | 5 +- 24 files changed, 180 insertions(+), 133 deletions(-) rename source/common/quic/platform/{quic_iovec_impl.h => quiche_iovec_impl.h} (100%) delete mode 100644 test/common/quic/platform/quic_test_impl.h diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index 201fefd3aa19..190791826279 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -69,7 +69,6 @@ test_suite( name = "ci_tests", tests = [ "http2_platform_api_test", - "quic_platform_api_test", "quiche_common_test", ], ) @@ -1122,9 +1121,7 @@ envoy_cc_library( "quiche/quic/platform/api/quic_exported_stats.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_reference_counted.h", "quiche/quic/platform/api/quic_server_stats.h", "quiche/quic/platform/api/quic_stack_trace.h", "quiche/quic/platform/api/quic_stream_buffer_allocator.h", @@ -1138,6 +1135,7 @@ envoy_cc_library( visibility = ["//visibility:public"], deps = [ ":quic_platform_export", + ":quiche_common_buffer_allocator_lib", ":quiche_common_lib", ":quiche_common_platform_default_quiche_platform_impl_export_lib", "@envoy//source/common/quic/platform:quic_platform_base_impl_lib", @@ -1230,14 +1228,6 @@ envoy_cc_library( deps = ["@envoy//source/common/quic/platform:quic_platform_udp_socket_impl_lib"], ) -envoy_cc_test_library( - name = "quic_platform_sleep", - hdrs = ["quiche/quic/platform/api/quic_sleep.h"], - repository = "@envoy", - tags = ["nofips"], - deps = [":quiche_common_platform_default_quiche_platform_impl_lib"], -) - envoy_cc_library( name = "quic_platform_socket_address", srcs = ["quiche/quic/platform/api/quic_socket_address.cc"], @@ -1259,7 +1249,7 @@ envoy_cc_test_library( tags = ["nofips"], deps = [ ":quiche_common_platform_test", - "@envoy//test/common/quic/platform:quic_platform_test_impl_lib", + "@envoy//test/common/quic/platform:quiche_common_platform_test_impl_lib", ], ) @@ -1536,22 +1526,6 @@ envoy_cc_library( deps = [":quic_platform_base"], ) -envoy_cc_library( - name = "quic_core_buffer_allocator_lib", - srcs = [ - "quiche/quic/core/quic_buffer_allocator.cc", - "quiche/quic/core/quic_simple_buffer_allocator.cc", - ], - hdrs = [ - "quiche/quic/core/quic_buffer_allocator.h", - "quiche/quic/core/quic_simple_buffer_allocator.h", - ], - repository = "@envoy", - tags = ["nofips"], - visibility = ["//visibility:public"], - deps = [":quic_platform_export"], -) - envoy_cc_library( name = "quic_core_chaos_protector_lib", srcs = [ @@ -1978,7 +1952,6 @@ envoy_cc_library( srcs = [ "quiche/quic/core/crypto/cert_compressor.cc", "quiche/quic/core/crypto/channel_id.cc", - "quiche/quic/core/crypto/common_cert_set.cc", "quiche/quic/core/crypto/crypto_framer.cc", "quiche/quic/core/crypto/crypto_handshake.cc", "quiche/quic/core/crypto/crypto_handshake_message.cc", @@ -1996,7 +1969,6 @@ envoy_cc_library( hdrs = [ "quiche/quic/core/crypto/cert_compressor.h", "quiche/quic/core/crypto/channel_id.h", - "quiche/quic/core/crypto/common_cert_set.h", "quiche/quic/core/crypto/crypto_framer.h", "quiche/quic/core/crypto/crypto_handshake.h", "quiche/quic/core/crypto/crypto_handshake_message.h", @@ -2023,17 +1995,10 @@ envoy_cc_library( "nofips", "pg3", ], - textual_hdrs = [ - "quiche/quic/core/crypto/common_cert_set_2.c", - "quiche/quic/core/crypto/common_cert_set_2a.inc", - "quiche/quic/core/crypto/common_cert_set_2b.inc", - "quiche/quic/core/crypto/common_cert_set_3.c", - "quiche/quic/core/crypto/common_cert_set_3a.inc", - "quiche/quic/core/crypto/common_cert_set_3b.inc", - ], visibility = ["//visibility:public"], deps = [ ":quic_core_clock_lib", + ":quic_core_connection_context_lib", ":quic_core_crypto_certificate_view_lib", ":quic_core_crypto_client_proof_source_lib", ":quic_core_crypto_encryption_lib", @@ -2251,6 +2216,28 @@ envoy_cc_library( tags = ["nofips"], ) +envoy_cc_library( + name = "quiche_common_buffer_allocator_lib", + srcs = [ + "quiche/common/quiche_buffer_allocator.cc", + "quiche/common/simple_buffer_allocator.cc", + ], + hdrs = [ + "quiche/common/quiche_buffer_allocator.h", + "quiche/common/simple_buffer_allocator.h", + ], + repository = "@envoy", + tags = ["nofips"], + visibility = ["//visibility:public"], + deps = [ + ":quiche_common_platform_export", + ":quiche_common_platform_iovec", + ":quiche_common_platform_logging", + ":quiche_common_platform_prefetch", + "@envoy//source/common/quic/platform:quic_platform_logging_impl_lib", + ], +) + envoy_cc_library( name = "quiche_common_circular_deque_lib", hdrs = ["quiche/common/quiche_circular_deque.h"], @@ -2399,7 +2386,6 @@ envoy_cc_library( tags = ["nofips"], visibility = ["//visibility:public"], deps = [ - ":quic_core_buffer_allocator_lib", ":quic_core_constants_lib", ":quic_core_error_codes_lib", ":quic_core_interval_lib", @@ -2407,6 +2393,7 @@ envoy_cc_library( ":quic_core_types_lib", ":quic_core_versions_lib", ":quic_platform_base", + ":quiche_common_buffer_allocator_lib", ], ) @@ -2426,11 +2413,11 @@ envoy_cc_library( copts = quiche_copts, repository = "@envoy", deps = [ - ":quic_core_buffer_allocator_lib", ":quic_core_data_lib", ":quic_core_http_http_frames_lib", ":quic_core_types_lib", ":quic_platform_base", + ":quiche_common_buffer_allocator_lib", ], ) @@ -3680,6 +3667,7 @@ envoy_cc_library( ":quic_core_versions_lib", ":quic_platform_base", ":quic_platform_socket_address", + ":quiche_common_buffer_allocator_lib", ], ) @@ -4004,7 +3992,6 @@ envoy_cc_test_library( repository = "@envoy", tags = ["nofips"], deps = [ - ":quic_core_buffer_allocator_lib", ":quic_core_congestion_control_congestion_control_interface_lib", ":quic_core_connection_lib", ":quic_core_connection_stats_lib", @@ -4037,6 +4024,7 @@ envoy_cc_test_library( ":quic_test_tools_sent_packet_manager_peer_lib", ":quic_test_tools_simple_quic_framer_lib", ":quic_test_tools_stream_peer_lib", + ":quiche_common_buffer_allocator_lib", ":quiche_common_test_tools_test_utils_lib", ":spdy_core_framer_lib", ], @@ -4123,15 +4111,71 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "quiche_common_platform_iovec", + hdrs = [ + "quiche/common/platform/api/quiche_iovec.h", + ], + repository = "@envoy", + tags = ["nofips"], + visibility = ["//visibility:public"], + deps = [ + ":quiche_common_platform_bug_tracker", + ":quiche_common_platform_export", + "@envoy//source/common/quic/platform:quiche_platform_iovec_impl_lib", + ], +) + +envoy_cc_library( + name = "quiche_common_platform_bug_tracker", + hdrs = [ + "quiche/common/platform/api/quiche_bug_tracker.h", + ], + repository = "@envoy", + tags = ["nofips"], + visibility = ["//visibility:public"], + deps = [ + ":quiche_common_platform_export", + "@envoy//source/common/quic/platform:quic_platform_logging_impl_lib", + ], +) + +envoy_cc_library( + name = "quiche_common_platform_logging", + hdrs = [ + "quiche/common/platform/api/quiche_logging.h", + ], + repository = "@envoy", + tags = ["nofips"], + visibility = ["//visibility:public"], + deps = [ + ":quiche_common_platform_export", + "@envoy//source/common/quic/platform:quiche_common_platform_logging_impl_lib", + ], +) + +envoy_cc_library( + name = "quiche_common_platform_prefetch", + hdrs = [ + "quiche/common/platform/api/quiche_prefetch.h", + "quiche/common/platform/default/quiche_platform_impl/quiche_prefetch_impl.h", + ], + repository = "@envoy", + tags = ["nofips"], + visibility = ["//visibility:public"], + deps = [ + ":quiche_common_platform_export", + ], +) + envoy_cc_library( name = "quiche_common_platform", hdrs = [ "quiche/common/platform/api/quiche_bug_tracker.h", "quiche/common/platform/api/quiche_flag_utils.h", "quiche/common/platform/api/quiche_flags.h", - "quiche/common/platform/api/quiche_logging.h", "quiche/common/platform/api/quiche_mem_slice.h", - "quiche/common/platform/api/quiche_prefetch.h", + "quiche/common/platform/api/quiche_reference_counted.h", "quiche/common/platform/api/quiche_thread_local.h", "quiche/common/platform/api/quiche_time_utils.h", ], @@ -4139,9 +4183,12 @@ envoy_cc_library( tags = ["nofips"], visibility = ["//visibility:public"], deps = [ + ":quiche_common_platform_bug_tracker", ":quiche_common_platform_default_quiche_platform_impl_lib", ":quiche_common_platform_export", - "@envoy//source/common/quic/platform:quiche_common_platform_impl_lib", + ":quiche_common_platform_logging", + ":quiche_common_platform_prefetch", + "@envoy//source/common/quic/platform:quiche_common_platform_mem_slice_impl_lib", ], ) @@ -4166,8 +4213,7 @@ envoy_cc_library( hdrs = [ "quiche/common/platform/default/quiche_platform_impl/quic_mutex_impl.h", "quiche/common/platform/default/quiche_platform_impl/quic_testvalue_impl.h", - "quiche/common/platform/default/quiche_platform_impl/quiche_prefetch_impl.h", - "quiche/common/platform/default/quiche_platform_impl/quiche_sleep_impl.h", + "quiche/common/platform/default/quiche_platform_impl/quiche_reference_counted_impl.h", "quiche/common/platform/default/quiche_platform_impl/quiche_time_utils_impl.h", ], repository = "@envoy", @@ -4201,6 +4247,7 @@ envoy_cc_test_library( repository = "@envoy", tags = ["nofips"], deps = [ + ":quiche_common_buffer_allocator_lib", ":quiche_common_platform", "@envoy//test/common/quic/platform:quiche_common_platform_test_impl_lib", ], @@ -4223,7 +4270,6 @@ envoy_cc_test_library( name = "quiche_common_test_tools_test_utils_lib", srcs = ["quiche/common/test_tools/quiche_test_utils.cc"], hdrs = [ - "quiche/common/platform/api/quiche_test.h", "quiche/common/platform/api/quiche_test_helpers.h", "quiche/common/test_tools/quiche_test_utils.h", ], @@ -4231,6 +4277,8 @@ envoy_cc_test_library( tags = ["nofips"], deps = [ ":quiche_common_platform", + ":quiche_common_platform_iovec", + ":quiche_common_platform_test", "@envoy//test/common/quic/platform:quiche_common_platform_test_impl_lib", ], ) @@ -4336,21 +4384,6 @@ envoy_cc_test( ], ) -envoy_cc_test( - name = "quic_platform_api_test", - srcs = [ - "quiche/quic/platform/api/quic_reference_counted_test.cc", - ], - copts = quiche_copts, - repository = "@envoy", - tags = ["nofips"], - deps = [ - ":quic_core_buffer_allocator_lib", - ":quic_platform", - ":quic_platform_test", - ], -) - envoy_cc_test( name = "quic_core_batch_writer_batch_writer_test", srcs = select({ diff --git a/bazel/external/quiche.genrule_cmd b/bazel/external/quiche.genrule_cmd index d5bc39338160..724a442769c7 100644 --- a/bazel/external/quiche.genrule_cmd +++ b/bazel/external/quiche.genrule_cmd @@ -53,6 +53,7 @@ cat <sed_commands /^#include/ s!"quiche_platform_impl/quiche_logging_impl.h!"source/common/quic/platform/quiche_logging_impl.h! /^#include/ s!"quiche_platform_impl/quiche_bug_tracker_impl.h!"source/common/quic/platform/quiche_bug_tracker_impl.h! /^#include/ s!"quiche_platform_impl/quiche_flags_impl.h!"source/common/quic/platform/quiche_flags_impl.h! +/^#include/ s!"quiche_platform_impl/quiche_mem_slice_impl.h!"source/common/quic/platform/quiche_mem_slice_impl.h! /^#include/ s!"quiche_platform_impl/quic_flags_impl.h!"source/common/quic/platform/quic_flags_impl.h! # The reset platform APIs use the QUICHE default implementations. diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 8392dfffdaa5..0a2ac36fe66e 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -824,12 +824,12 @@ 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://github.com/google/quiche", - version = "cf1588207faa6771333052330846acd0c45a045c", - sha256 = "c4f61b07c2f061f8d4669b88fd9a5eb19f2d407b0b0912f5feac1f149ad6f390", + version = "9386be8fb09bf8bc2d1692f70f17b61700c9ce1d", + sha256 = "ee054b81e40705de9c06c9005cb47ffe81b58e757a6d708a94fdce9dae500d90", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["dataplane_core"], - release_date = "2022-02-28", + release_date = "2022-03-10", cpe = "N/A", ), com_googlesource_googleurl = dict( diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index 99718f63d8d9..7c0ce2e2ae95 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -45,7 +45,6 @@ envoy_cc_library( tags = ["nofips"], deps = [ "//source/common/quic/platform:envoy_quic_clock_lib", - "@com_github_google_quiche//:quic_core_buffer_allocator_lib", "@com_github_google_quiche//:quic_core_connection_lib", "@com_github_google_quiche//:quic_core_crypto_random_lib", ], diff --git a/source/common/quic/envoy_quic_connection_helper.h b/source/common/quic/envoy_quic_connection_helper.h index c699263467d6..df1b57b6b333 100644 --- a/source/common/quic/envoy_quic_connection_helper.h +++ b/source/common/quic/envoy_quic_connection_helper.h @@ -2,9 +2,9 @@ #include "source/common/quic/platform/envoy_quic_clock.h" +#include "quiche/common/simple_buffer_allocator.h" #include "quiche/quic/core/crypto/quic_random.h" #include "quiche/quic/core/quic_connection.h" -#include "quiche/quic/core/quic_simple_buffer_allocator.h" namespace Envoy { namespace Quic { @@ -21,12 +21,14 @@ class EnvoyQuicConnectionHelper : public quic::QuicConnectionHelperInterface { // QuicConnectionHelperInterface const quic::QuicClock* GetClock() const override { return &clock_; } quic::QuicRandom* GetRandomGenerator() override { return random_generator_; } - quic::QuicBufferAllocator* GetStreamSendBufferAllocator() override { return &buffer_allocator_; } + quiche::QuicheBufferAllocator* GetStreamSendBufferAllocator() override { + return &buffer_allocator_; + } private: EnvoyQuicClock clock_; quic::QuicRandom* random_generator_ = nullptr; - quic::SimpleBufferAllocator buffer_allocator_; + quiche::SimpleBufferAllocator buffer_allocator_; }; } // namespace Quic diff --git a/source/common/quic/envoy_quic_packet_writer.cc b/source/common/quic/envoy_quic_packet_writer.cc index e2f53bd2df5b..c0e7c6cb623c 100644 --- a/source/common/quic/envoy_quic_packet_writer.cc +++ b/source/common/quic/envoy_quic_packet_writer.cc @@ -2,6 +2,7 @@ #include +#include "source/common/buffer/buffer_impl.h" #include "source/common/quic/envoy_quic_utils.h" namespace Envoy { diff --git a/source/common/quic/envoy_quic_proof_source.cc b/source/common/quic/envoy_quic_proof_source.cc index 56f21560eb8f..ac5d5a7a6160 100644 --- a/source/common/quic/envoy_quic_proof_source.cc +++ b/source/common/quic/envoy_quic_proof_source.cc @@ -13,7 +13,7 @@ namespace Envoy { namespace Quic { -quic::QuicReferenceCountedPointer +quiche::QuicheReferenceCountedPointer EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, bool* cert_matched_sni) { @@ -32,7 +32,7 @@ EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address std::stringstream pem_stream(chain_str); std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); - quic::QuicReferenceCountedPointer cert_chain( + quiche::QuicheReferenceCountedPointer cert_chain( new quic::ProofSource::Chain(chain)); std::string error_details; bssl::UniquePtr cert = parseDERCertificate(cert_chain->certs[0], &error_details); diff --git a/source/common/quic/envoy_quic_proof_source.h b/source/common/quic/envoy_quic_proof_source.h index 84668caf4d90..ade036bb53c2 100644 --- a/source/common/quic/envoy_quic_proof_source.h +++ b/source/common/quic/envoy_quic_proof_source.h @@ -20,7 +20,7 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase { ~EnvoyQuicProofSource() override = default; // quic::ProofSource - quic::QuicReferenceCountedPointer + quiche::QuicheReferenceCountedPointer GetCertChain(const quic::QuicSocketAddress& server_address, const quic::QuicSocketAddress& client_address, const std::string& hostname, bool* cert_matched_sni) override; diff --git a/source/common/quic/envoy_quic_proof_source_base.h b/source/common/quic/envoy_quic_proof_source_base.h index 036fe44a8699..c5a719ec880d 100644 --- a/source/common/quic/envoy_quic_proof_source_base.h +++ b/source/common/quic/envoy_quic_proof_source_base.h @@ -12,7 +12,6 @@ #include "quiche/quic/core/crypto/crypto_protocol.h" #include "quiche/quic/core/crypto/proof_source.h" #include "quiche/quic/core/quic_versions.h" -#include "quiche/quic/platform/api/quic_reference_counted.h" #include "quiche/quic/platform/api/quic_socket_address.h" namespace Envoy { diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc index 4dc1bef8c412..d5963f124e2b 100644 --- a/source/common/quic/envoy_quic_server_stream.cc +++ b/source/common/quic/envoy_quic_server_stream.cc @@ -11,6 +11,7 @@ #include "source/common/http/header_utility.h" #include "source/common/quic/envoy_quic_server_session.h" #include "source/common/quic/envoy_quic_utils.h" +#include "source/common/quic/platform/quiche_mem_slice_impl.h" #include "quiche/quic/core/http/quic_header_list.h" #include "quiche/quic/core/quic_session.h" diff --git a/source/common/quic/platform/BUILD b/source/common/quic/platform/BUILD index 8cf656ccaa27..aeb6be49e8ae 100644 --- a/source/common/quic/platform/BUILD +++ b/source/common/quic/platform/BUILD @@ -74,7 +74,6 @@ envoy_cc_library( "quic_client_stats_impl.h", "quic_error_code_wrappers_impl.h", "quic_flags_impl.h", - "quic_iovec_impl.h", "quic_reference_counted_impl.h", "quic_server_stats_impl.h", "quic_stack_trace_impl.h", @@ -103,6 +102,25 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "quiche_platform_iovec_impl_lib", + hdrs = [ + "quiche_iovec_impl.h", + ], + external_deps = [ + "abseil_base", + "abseil_hash", + "abseil_inlined_vector", + "abseil_memory", + "abseil_node_hash_map", + "abseil_node_hash_set", + ], + tags = ["nofips"], + visibility = ["//visibility:public"], + deps = [ + ], +) + envoy_cc_library( name = "quic_platform_impl_lib", srcs = [ @@ -153,12 +171,11 @@ envoy_cc_library( ) envoy_cc_library( - name = "quiche_common_platform_impl_lib", + name = "quiche_common_platform_mem_slice_impl_lib", srcs = [ "quiche_mem_slice_impl.cc", ], hdrs = [ - "quiche_logging_impl.h", "quiche_mem_slice_impl.h", ], external_deps = [ @@ -170,6 +187,21 @@ envoy_cc_library( ":quic_platform_logging_impl_lib", ":quiche_flags_impl_lib", "//source/common/buffer:buffer_lib", - "@com_github_google_quiche//:quic_core_buffer_allocator_lib", + "@com_github_google_quiche//:quiche_common_buffer_allocator_lib", + ], +) + +envoy_cc_library( + name = "quiche_common_platform_logging_impl_lib", + hdrs = [ + "quiche_logging_impl.h", + ], + external_deps = [ + "abseil_hash", + "abseil_node_hash_map", + ], + visibility = ["//visibility:public"], + deps = [ + ":quic_platform_logging_impl_lib", ], ) diff --git a/source/common/quic/platform/quic_stream_buffer_allocator_impl.h b/source/common/quic/platform/quic_stream_buffer_allocator_impl.h index c2a40a0e7427..08d840cca282 100644 --- a/source/common/quic/platform/quic_stream_buffer_allocator_impl.h +++ b/source/common/quic/platform/quic_stream_buffer_allocator_impl.h @@ -6,13 +6,13 @@ // consumed or referenced directly by other Envoy code. It serves purely as a // porting layer for QUICHE. -#include "quiche/quic/core/quic_simple_buffer_allocator.h" +#include "quiche/common/simple_buffer_allocator.h" namespace quic { // Implements the interface required by // https://quiche.googlesource.com/quiche/+/refs/heads/master/quic/platform/api/quic_stream_buffer_allocator.h // with the default implementation provided by QUICHE. -using QuicStreamBufferAllocatorImpl = SimpleBufferAllocator; +using QuicStreamBufferAllocatorImpl = quiche::SimpleBufferAllocator; } // namespace quic diff --git a/source/common/quic/platform/quic_iovec_impl.h b/source/common/quic/platform/quiche_iovec_impl.h similarity index 100% rename from source/common/quic/platform/quic_iovec_impl.h rename to source/common/quic/platform/quiche_iovec_impl.h diff --git a/source/common/quic/platform/quiche_mem_slice_impl.cc b/source/common/quic/platform/quiche_mem_slice_impl.cc index 020901925a7a..d70a2df1597f 100644 --- a/source/common/quic/platform/quiche_mem_slice_impl.cc +++ b/source/common/quic/platform/quiche_mem_slice_impl.cc @@ -12,9 +12,9 @@ namespace quiche { -QuicheMemSliceImpl::QuicheMemSliceImpl(quic::QuicBuffer buffer) { +QuicheMemSliceImpl::QuicheMemSliceImpl(quiche::QuicheBuffer buffer) { size_t length = buffer.size(); - quic::QuicUniqueBufferPtr buffer_ptr = buffer.Release(); + quiche::QuicheUniqueBufferPtr buffer_ptr = buffer.Release(); fragment_ = std::make_unique( buffer_ptr.get(), length, // TODO(danzh) change the buffer fragment constructor to take the lambda by move instead @@ -22,7 +22,7 @@ QuicheMemSliceImpl::QuicheMemSliceImpl(quic::QuicBuffer buffer) { // here and below to unify and simplify the constructor implementations. [allocator = buffer_ptr.get_deleter().allocator()](const void* p, size_t, const Envoy::Buffer::BufferFragmentImpl*) { - quic::QuicBufferDeleter deleter(allocator); + quiche::QuicheBufferDeleter deleter(allocator); deleter(const_cast(static_cast(p))); }); buffer_ptr.release(); diff --git a/source/common/quic/platform/quiche_mem_slice_impl.h b/source/common/quic/platform/quiche_mem_slice_impl.h index 3ebfd973de13..fdbb05c39419 100644 --- a/source/common/quic/platform/quiche_mem_slice_impl.h +++ b/source/common/quic/platform/quiche_mem_slice_impl.h @@ -12,7 +12,7 @@ #include "source/common/buffer/buffer_impl.h" -#include "quiche/quic/core/quic_buffer_allocator.h" +#include "quiche/common/quiche_buffer_allocator.h" namespace quiche { @@ -26,7 +26,7 @@ class QuicheMemSliceImpl { ~QuicheMemSliceImpl(); // Constructs a QuicheMemSliceImpl by taking ownership of the memory in |buffer|. - QuicheMemSliceImpl(quic::QuicBuffer buffer); + QuicheMemSliceImpl(quiche::QuicheBuffer buffer); QuicheMemSliceImpl(std::unique_ptr buffer, size_t length); // Constructs a QuicheMemSliceImpl from a Buffer::Instance with first |length| bytes in it. diff --git a/source/common/quic/quic_filter_manager_connection_impl.h b/source/common/quic/quic_filter_manager_connection_impl.h index 04094e832dab..69978700302b 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.h +++ b/source/common/quic/quic_filter_manager_connection_impl.h @@ -5,6 +5,7 @@ #include "envoy/event/dispatcher.h" #include "envoy/network/connection.h" +#include "source/common/buffer/buffer_impl.h" #include "source/common/common/empty_string.h" #include "source/common/common/logger.h" #include "source/common/http/http3/codec_stats.h" diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc index 802086062d2f..6f229fb14ae3 100644 --- a/test/common/quic/envoy_quic_proof_source_test.cc +++ b/test/common/quic/envoy_quic_proof_source_test.cc @@ -73,9 +73,9 @@ class SignatureVerifier { verifier_ = std::make_unique(std::move(context)); } - void - verifyCertsAndSignature(const quic::QuicReferenceCountedPointer& chain, - const std::string& payload, const std::string& signature) { + void verifyCertsAndSignature( + const quiche::QuicheReferenceCountedPointer& chain, + const std::string& payload, const std::string& signature) { const std::string& leaf = chain->certs[0]; std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(leaf); @@ -198,7 +198,7 @@ class EnvoyQuicProofSourceTest : public ::testing::Test { TEST_F(EnvoyQuicProofSourceTest, TestGetCerChainAndSignatureAndVerify) { expectCertChainAndPrivateKey(expected_certs_, true); bool cert_matched_sni; - quic::QuicReferenceCountedPointer chain = + quiche::QuicheReferenceCountedPointer chain = proof_source_.GetCertChain(server_address_, client_address_, hostname_, &cert_matched_sni); EXPECT_EQ(2, chain->certs.size()); diff --git a/test/common/quic/envoy_quic_server_session_test.cc b/test/common/quic/envoy_quic_server_session_test.cc index 0a551e3928c3..d5ca034caa8c 100644 --- a/test/common/quic/envoy_quic_server_session_test.cc +++ b/test/common/quic/envoy_quic_server_session_test.cc @@ -111,7 +111,7 @@ class TestEnvoyQuicTlsServerHandshaker : public quic::TlsServerHandshaker, private: std::unique_ptr details_; - quic::QuicReferenceCountedPointer params_; + quiche::QuicheReferenceCountedPointer params_; }; class EnvoyQuicTestCryptoServerStreamFactory : public EnvoyQuicCryptoServerStreamFactoryInterface { diff --git a/test/common/quic/platform/BUILD b/test/common/quic/platform/BUILD index 95368b6071ab..1f21927517f6 100644 --- a/test/common/quic/platform/BUILD +++ b/test/common/quic/platform/BUILD @@ -46,12 +46,10 @@ envoy_cc_test( "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:utility_lib", "@com_github_google_quiche//:epoll_server_lib", - "@com_github_google_quiche//:quic_core_buffer_allocator_lib", "@com_github_google_quiche//:quic_core_error_codes_lib", "@com_github_google_quiche//:quic_core_types_lib", "@com_github_google_quiche//:quic_platform_expect_bug", "@com_github_google_quiche//:quic_platform_mock_log", - "@com_github_google_quiche//:quic_platform_sleep", "@com_github_google_quiche//:quic_platform_system_event_loop", "@com_github_google_quiche//:quic_platform_test", "@com_github_google_quiche//:quic_platform_test_output", @@ -142,13 +140,6 @@ envoy_cc_test_library( ], ) -envoy_cc_test_library( - name = "quic_platform_test_impl_lib", - hdrs = ["quic_test_impl.h"], - tags = ["nofips"], - deps = ["//source/common/common:assert_lib"], -) - envoy_cc_test_library( name = "quic_platform_test_output_impl_lib", srcs = ["quic_test_output_impl.cc"], @@ -164,6 +155,7 @@ envoy_cc_test_library( envoy_cc_test_library( name = "quiche_common_platform_test_impl_lib", hdrs = ["quiche_test_impl.h"], + deps = ["//source/common/common:assert_lib"], ) envoy_cc_test_library( diff --git a/test/common/quic/platform/quic_platform_test.cc b/test/common/quic/platform/quic_platform_test.cc index c0a7f017e971..ea93641f1d89 100644 --- a/test/common/quic/platform/quic_platform_test.cc +++ b/test/common/quic/platform/quic_platform_test.cc @@ -41,7 +41,6 @@ #include "quiche/quic/platform/api/quic_mock_log.h" #include "quiche/quic/platform/api/quic_mutex.h" #include "quiche/quic/platform/api/quic_server_stats.h" -#include "quiche/quic/platform/api/quic_sleep.h" #include "quiche/quic/platform/api/quic_stack_trace.h" #include "quiche/quic/platform/api/quic_stream_buffer_allocator.h" #include "quiche/quic/platform/api/quic_system_event_loop.h" @@ -197,8 +196,6 @@ TEST_F(QuicPlatformTest, QuicStackTraceTest) { #endif } -TEST_F(QuicPlatformTest, QuicSleep) { QuicSleep(QuicTime::Delta::FromMilliseconds(20)); } - TEST_F(QuicPlatformTest, QuicThread) { class AdderThread : public QuicThread { public: diff --git a/test/common/quic/platform/quic_test_impl.h b/test/common/quic/platform/quic_test_impl.h deleted file mode 100644 index ecb5abea3d3b..000000000000 --- a/test/common/quic/platform/quic_test_impl.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include - -#include "source/common/common/assert.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.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. - -// TODO(mpwarres): implement once QUICHE flag mechanism is defined. -class QuicFlagSaverImpl {}; - -// No special setup needed for tests to use threads. -class ScopedEnvironmentForThreadsImpl {}; - -using QuicTestImpl = ::testing::Test; - -template using QuicTestWithParamImpl = ::testing::TestWithParam; - -inline std::string QuicGetTestMemoryCachePathImpl() { // NOLINT(readability-identifier-naming) - PANIC("not implemented"); // TODO(mpwarres): implement -} diff --git a/test/common/quic/platform/quiche_test_impl.h b/test/common/quic/platform/quiche_test_impl.h index 8bd47699341d..bced852b64d2 100644 --- a/test/common/quic/platform/quiche_test_impl.h +++ b/test/common/quic/platform/quiche_test_impl.h @@ -6,16 +6,32 @@ // consumed or referenced directly by other Envoy code. It serves purely as a // porting layer for QUICHE. +#include + +#include "source/common/common/assert.h" + #include "absl/strings/str_cat.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +// TODO(mpwarres): implement once QUICHE flag mechanism is defined. +class QuicheFlagSaverImpl {}; + +// No special setup needed for tests to use threads. +class ScopedEnvironmentForThreadsImpl {}; + +inline std::string QuicheGetTestMemoryCachePathImpl() { // NOLINT(readability-identifier-naming) + PANIC("not implemented"); // TODO(mpwarres): implement +} + namespace quiche { namespace test { using QuicheTest = ::testing::Test; +using QuicTestImpl = QuicheTest; template using QuicheTestWithParamImpl = ::testing::TestWithParam; +template using QuicTestWithParamImpl = QuicheTestWithParamImpl; // NOLINTNEXTLINE(readability-identifier-naming) inline std::string QuicheGetCommonSourcePathImpl() { diff --git a/test/common/quic/test_proof_source.h b/test/common/quic/test_proof_source.h index 1f21b6ea3c2b..03a409b195fd 100644 --- a/test/common/quic/test_proof_source.h +++ b/test/common/quic/test_proof_source.h @@ -15,7 +15,7 @@ namespace Quic { // QUICHE and a fake signature. class TestProofSource : public EnvoyQuicProofSourceBase { public: - quic::QuicReferenceCountedPointer + quiche::QuicheReferenceCountedPointer GetCertChain(const quic::QuicSocketAddress& /*server_address*/, const quic::QuicSocketAddress& /*client_address*/, const std::string& /*hostname*/, bool* cert_matched_sni) override { @@ -36,7 +36,7 @@ class TestProofSource : public EnvoyQuicProofSourceBase { } private: - quic::QuicReferenceCountedPointer cert_chain_{ + quiche::QuicheReferenceCountedPointer cert_chain_{ new quic::ProofSource::Chain( std::vector{std::string(quic::test::kTestCertificate)})}; diff --git a/test/common/quic/test_utils.h b/test/common/quic/test_utils.h index df18a7d9f6f8..a1d3ca958683 100644 --- a/test/common/quic/test_utils.h +++ b/test/common/quic/test_utils.h @@ -239,8 +239,9 @@ std::string spdyHeaderToHttp3StreamPayload(const spdy::SpdyHeaderBlock& header) } std::string bodyToHttp3StreamPayload(const std::string& body) { - quic::SimpleBufferAllocator allocator; - quic::QuicBuffer header = quic::HttpEncoder::SerializeDataFrameHeader(body.length(), &allocator); + quiche::SimpleBufferAllocator allocator; + quiche::QuicheBuffer header = + quic::HttpEncoder::SerializeDataFrameHeader(body.length(), &allocator); return absl::StrCat(header.AsStringView(), body); } From 7fd0f24835e875c66f1ea08aafe748b82626e2a6 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 14 Mar 2022 14:05:05 -0400 Subject: [PATCH 64/68] docs: updates to contributing (#20330) Signed-off-by: Alyssa Wilk --- CONTRIBUTING.md | 4 +++- OWNERS.md | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 314e8135d79f..2a5a13ba2f01 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -230,7 +230,9 @@ temporarily reverted by setting runtime guard ``envoy.reloadable_features.schema * Typically we try to turn around reviews within one business day. * See [OWNERS.md](OWNERS.md) for the current list of maintainers. -* It is generally expected that a senior maintainer should review every PR. +* It is generally expected that a senior maintainer should review every PR to + core code. Test-only or extension-only changes need only be reviewed by a + maintainer, or senior extension maintainer. * It is also generally expected that a "domain expert" for the code the PR touches should review the PR. This person does not necessarily need to have commit access. * The previous two points generally mean that every PR should have two approvals. (Exceptions can diff --git a/OWNERS.md b/OWNERS.md index d84cac63a47f..95827e15c1bc 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -61,6 +61,8 @@ without further review. * Wasm * Raúl Gutiérrez Segalés ([rgs1](https://github.com/rgs1)) (rgs@pinterest.com) * Thrift +* Ryan Hamilton ([RyanTheOptimist](https://github.com/ryantheoptimist)) (rch@google.com) + * HTTP/3 # Envoy security team From 1a50832d3210647e3ab7485c522927402045578a Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 14 Mar 2022 14:19:51 -0400 Subject: [PATCH 65/68] Add missing dependency for //test/common/matcher:matcher_test (#20293) Signed-off-by: Yan Avlasov --- test/common/matcher/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/test/common/matcher/BUILD b/test/common/matcher/BUILD index c63baa3f5710..6a3594dfb0c7 100644 --- a/test/common/matcher/BUILD +++ b/test/common/matcher/BUILD @@ -72,6 +72,7 @@ envoy_cc_test( "//test/mocks/server:factory_context_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/test_common:registry_lib", + "@com_github_cncf_udpa//xds/type/matcher/v3:pkg_cc_proto", "@envoy_api//envoy/config/common/matcher/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], From 1a3284fcd6b88836b3163be4685ce01d6c7b30f1 Mon Sep 17 00:00:00 2001 From: Kuat Date: Mon, 14 Mar 2022 13:37:03 -0700 Subject: [PATCH 66/68] filter_state: add shared pointer accessor (#20243) Signed-off-by: Kuat Yessenov --- envoy/stream_info/filter_state.h | 7 +++ .../common/stream_info/filter_state_impl.cc | 12 +++-- source/common/stream_info/filter_state_impl.h | 1 + .../stream_info/filter_state_impl_test.cc | 47 ++++++++++++++----- 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/envoy/stream_info/filter_state.h b/envoy/stream_info/filter_state.h index d664be2adc06..a8bd8587c819 100644 --- a/envoy/stream_info/filter_state.h +++ b/envoy/stream_info/filter_state.h @@ -116,6 +116,13 @@ class FilterState { */ virtual Object* getDataMutableGeneric(absl::string_view data_name) PURE; + /** + * @param data_name the name of the data being looked up (mutable/readonly). + * @return a shared pointer to the stored data or nullptr if the data does not exist. + * An exception will be thrown if the data is not mutable. + */ + virtual std::shared_ptr getDataSharedMutableGeneric(absl::string_view data_name) PURE; + /** * @param data_name the name of the data being probed. * @return Whether data of the type and name specified exists in the diff --git a/source/common/stream_info/filter_state_impl.cc b/source/common/stream_info/filter_state_impl.cc index 626a1b5fd7e2..b973881019c6 100644 --- a/source/common/stream_info/filter_state_impl.cc +++ b/source/common/stream_info/filter_state_impl.cc @@ -61,22 +61,26 @@ FilterStateImpl::getDataReadOnlyGeneric(absl::string_view data_name) const { } FilterState::Object* FilterStateImpl::getDataMutableGeneric(absl::string_view data_name) { + return getDataSharedMutableGeneric(data_name).get(); +} + +std::shared_ptr +FilterStateImpl::getDataSharedMutableGeneric(absl::string_view data_name) { const auto& it = data_storage_.find(data_name); if (it == data_storage_.end()) { if (parent_) { - return parent_->getDataMutableGeneric(data_name); + return parent_->getDataSharedMutableGeneric(data_name); } return nullptr; } FilterStateImpl::FilterObject* current = it->second.get(); if (current->state_type_ == FilterState::StateType::ReadOnly) { - throw EnvoyException( - "FilterState::getDataMutable tried to access immutable data as mutable."); + throw EnvoyException("FilterState tried to access immutable data as mutable."); } - return current->data_.get(); + return current->data_; } bool FilterStateImpl::hasDataAtOrAboveLifeSpan(FilterState::LifeSpan life_span) const { diff --git a/source/common/stream_info/filter_state_impl.h b/source/common/stream_info/filter_state_impl.h index 319026e959ac..da08ceadaa10 100644 --- a/source/common/stream_info/filter_state_impl.h +++ b/source/common/stream_info/filter_state_impl.h @@ -46,6 +46,7 @@ class FilterStateImpl : public FilterState { bool hasDataWithName(absl::string_view) const override; const Object* getDataReadOnlyGeneric(absl::string_view data_name) const override; Object* getDataMutableGeneric(absl::string_view data_name) override; + std::shared_ptr getDataSharedMutableGeneric(absl::string_view data_name) override; bool hasDataAtOrAboveLifeSpan(FilterState::LifeSpan life_span) const override; FilterState::LifeSpan lifeSpan() const override { return life_span_; } diff --git a/test/common/stream_info/filter_state_impl_test.cc b/test/common/stream_info/filter_state_impl_test.cc index 8e450c14ee2d..ae69c438f230 100644 --- a/test/common/stream_info/filter_state_impl_test.cc +++ b/test/common/stream_info/filter_state_impl_test.cc @@ -77,6 +77,29 @@ TEST_F(FilterStateImplTest, Simple) { EXPECT_EQ(1u, destruction_count); } +TEST_F(FilterStateImplTest, SharedPointerAccessor) { + size_t access_count = 0u; + size_t destruction_count = 0u; + filter_state().setData( + "test_name", std::make_shared(5, &access_count, &destruction_count), + FilterState::StateType::Mutable, FilterState::LifeSpan::FilterChain); + EXPECT_EQ(0u, access_count); + EXPECT_EQ(0u, destruction_count); + + { + auto obj = filter_state().getDataSharedMutableGeneric("test_name"); + EXPECT_EQ(5, dynamic_cast(obj.get())->access()); + EXPECT_EQ(1u, access_count); + EXPECT_EQ(0u, destruction_count); + + resetFilterState(); + EXPECT_EQ(1u, access_count); + EXPECT_EQ(0u, destruction_count); + } + + EXPECT_EQ(1u, destruction_count); +} + TEST_F(FilterStateImplTest, SameTypes) { size_t access_count_1 = 0u; size_t access_count_2 = 0u; @@ -187,6 +210,7 @@ TEST_F(FilterStateImplTest, NoNameConflictMutableAndMutable) { TEST_F(FilterStateImplTest, UnknownName) { EXPECT_EQ(nullptr, filter_state().getDataReadOnly("test_1")); EXPECT_EQ(nullptr, filter_state().getDataMutable("test_1")); + EXPECT_EQ(nullptr, filter_state().getDataSharedMutableGeneric("test_1")); } TEST_F(FilterStateImplTest, WrongTypeGet) { @@ -200,9 +224,11 @@ TEST_F(FilterStateImplTest, ErrorAccessingReadOnlyAsMutable) { // Accessing read only data as mutable should throw error filter_state().setData("test_name", std::make_unique(5, nullptr, nullptr), FilterState::StateType::ReadOnly, FilterState::LifeSpan::FilterChain); - EXPECT_THROW_WITH_MESSAGE( - filter_state().getDataMutable("test_name"), EnvoyException, - "FilterState::getDataMutable tried to access immutable data as mutable."); + EXPECT_THROW_WITH_MESSAGE(filter_state().getDataMutable("test_name"), + EnvoyException, + "FilterState tried to access immutable data as mutable."); + EXPECT_THROW_WITH_MESSAGE(filter_state().getDataSharedMutableGeneric("test_name"), EnvoyException, + "FilterState tried to access immutable data as mutable."); } namespace { @@ -261,15 +287,13 @@ TEST_F(FilterStateImplTest, LifeSpanInitFromParent) { EXPECT_TRUE(new_filter_state.hasDataWithName("test_4")); EXPECT_TRUE(new_filter_state.hasDataWithName("test_5")); EXPECT_TRUE(new_filter_state.hasDataWithName("test_6")); - EXPECT_THROW_WITH_MESSAGE( - new_filter_state.getDataMutable("test_3"), EnvoyException, - "FilterState::getDataMutable tried to access immutable data as mutable."); + EXPECT_THROW_WITH_MESSAGE(new_filter_state.getDataMutable("test_3"), EnvoyException, + "FilterState tried to access immutable data as mutable."); EXPECT_EQ(4, new_filter_state.getDataMutable("test_4")->access()); - EXPECT_THROW_WITH_MESSAGE( - new_filter_state.getDataMutable("test_5"), EnvoyException, - "FilterState::getDataMutable tried to access immutable data as mutable."); + EXPECT_THROW_WITH_MESSAGE(new_filter_state.getDataMutable("test_5"), EnvoyException, + "FilterState tried to access immutable data as mutable."); EXPECT_EQ(6, new_filter_state.getDataMutable("test_6")->access()); } @@ -296,9 +320,8 @@ TEST_F(FilterStateImplTest, LifeSpanInitFromGrandparent) { EXPECT_FALSE(new_filter_state.hasDataWithName("test_4")); EXPECT_TRUE(new_filter_state.hasDataWithName("test_5")); EXPECT_TRUE(new_filter_state.hasDataWithName("test_6")); - EXPECT_THROW_WITH_MESSAGE( - new_filter_state.getDataMutable("test_5"), EnvoyException, - "FilterState::getDataMutable tried to access immutable data as mutable."); + EXPECT_THROW_WITH_MESSAGE(new_filter_state.getDataMutable("test_5"), EnvoyException, + "FilterState tried to access immutable data as mutable."); EXPECT_EQ(6, new_filter_state.getDataMutable("test_6")->access()); } From c4fa7078c8c5feb9336bcfe6aa540df6aea6e477 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 14 Mar 2022 16:50:03 -0400 Subject: [PATCH 67/68] runtime: simplifying absl-flags based implementation (#20169) moving from a statically constructed envoy-flag-name -> absl flag map to using reflection to construct an envoy-flag-name -> commandline flag map, to avoid having to add 2 lines per runtime guard. Risk Level: Medium Testing: existing tests cover Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- source/common/runtime/BUILD | 1 + source/common/runtime/runtime_features.cc | 187 ++++++++-------------- source/common/runtime/runtime_features.h | 25 +-- test/common/http/http1/codec_impl_test.cc | 3 - test/test_common/test_runtime.h | 6 +- test/test_listener.cc | 5 +- test/test_listener.h | 6 +- 7 files changed, 82 insertions(+), 151 deletions(-) diff --git a/source/common/runtime/BUILD b/source/common/runtime/BUILD index 115d2636f56b..8e9c5d3bb70a 100644 --- a/source/common/runtime/BUILD +++ b/source/common/runtime/BUILD @@ -21,6 +21,7 @@ envoy_cc_library( # AVOID ADDING TO THESE DEPENDENCIES IF POSSIBLE # Any code using runtime guards depends on this library, and the more dependencies there are, # the harder it is to runtime-guard without dependency loops. + "@com_google_absl//absl/flags:commandlineflag", "@com_google_absl//absl/flags:flag", "//envoy/runtime:runtime_interface", "//source/common/common:hash_lib", diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 3ebedf93a72c..9c2cb5c27897 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -1,26 +1,34 @@ #include "source/common/runtime/runtime_features.h" +#include "absl/flags/commandlineflag.h" #include "absl/flags/flag.h" #include "absl/strings/match.h" #include "absl/strings/str_replace.h" -/* To add a runtime guard to Envoy, add 2 lines to this file. - * - * RUNTIME_GUARD(envoy_reloadable_features_flag_name) - * - * to the sorted macro block below and - * - * &FLAGS_envoy_reloadable_features_flag_name - * - * to the runtime features constexpr. - * - * The runtime guard to use in source and release notes will then be of the form - * "envoy.reloadable_features.flag_name" due to the prior naming scheme and swapPrefix. - **/ - #define RUNTIME_GUARD(name) ABSL_FLAG(bool, name, true, ""); // NOLINT #define FALSE_RUNTIME_GUARD(name) ABSL_FLAG(bool, name, false, ""); // NOLINT +// Add additional features here to enable the new code paths by default. +// +// Per documentation in CONTRIBUTING.md is expected that new high risk code paths be guarded +// by runtime feature guards. If you add a guard of the form +// RUNTIME_GUARD(envoy_reloadable_features_my_feature_name) +// here you can guard code checking against "envoy.reloadable_features.my_feature_name". +// Please note the swap of envoy_reloadable_features_ to envoy.reloadable_features.! +// +// if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.my_feature_name")) { +// [new code path] +// else { +// [old_code_path] +// } +// +// Runtime features are true by default, so the new code path is exercised. +// To make a runtime feature false by default, use FALSE_RUNTIME_GUARD, and add +// a TODO to change it to true. +// +// If issues are found that require a runtime feature to be disabled, it should be reported +// ASAP by filing a bug on github. Overriding non-buggy code is strongly discouraged to avoid the +// problem of the bugs being found after the old code path has been removed. RUNTIME_GUARD(envoy_reloadable_features_allow_upstream_inline_write); RUNTIME_GUARD(envoy_reloadable_features_append_or_truncate); RUNTIME_GUARD(envoy_reloadable_features_append_to_accept_content_encoding_only_once); @@ -81,18 +89,61 @@ ABSL_FLAG(uint64_t, re2_max_program_size_warn_level, // NOLINT namespace Envoy { namespace Runtime { +namespace { + +std::string swapPrefix(std::string name) { + return absl::StrReplaceAll(name, {{"envoy_", "envoy."}, {"features_", "features."}}); +} + +} // namespace + +// This is a singleton class to map Envoy style flag names to absl flags +class RuntimeFeatures { +public: + RuntimeFeatures(); + + // Get the command line flag corresponding to the Envoy style feature name, or + // nullptr if it is not a registered flag. + absl::CommandLineFlag* getFlag(absl::string_view feature) const { + auto it = all_features_.find(feature); + if (it == all_features_.end()) { + return nullptr; + } + return it->second; + } + +private: + absl::flat_hash_map all_features_; +}; + +using RuntimeFeaturesDefaults = ConstSingleton; + +RuntimeFeatures::RuntimeFeatures() { + absl::flat_hash_map flags = absl::GetAllFlags(); + for (auto& it : flags) { + absl::string_view name = it.second->Name(); + if ((!absl::StartsWith(name, "envoy_reloadable_features_") && + !absl::StartsWith(name, "envoy_restart_features_")) || + !it.second->TryGet().has_value()) { + continue; + } + std::string envoy_name = swapPrefix(std::string(name)); + all_features_.emplace(envoy_name, it.second); + } +} bool isRuntimeFeature(absl::string_view feature) { return RuntimeFeaturesDefaults::get().getFlag(feature) != nullptr; } bool runtimeFeatureEnabled(absl::string_view feature) { - auto* flag = RuntimeFeaturesDefaults::get().getFlag(feature); + absl::CommandLineFlag* flag = RuntimeFeaturesDefaults::get().getFlag(feature); if (flag == nullptr) { IS_ENVOY_BUG(absl::StrCat("Unable to find runtime feature ", feature)); return false; } - return absl::GetFlag(*flag); + // We validate in map creation that the flag is a boolean. + return flag->TryGet().value(); } uint64_t getInteger(absl::string_view feature, uint64_t default_value) { @@ -113,81 +164,14 @@ uint64_t getInteger(absl::string_view feature, uint64_t default_value) { return default_value; } -// Add additional features here to enable the new code paths by default. -// -// Per documentation in CONTRIBUTING.md is expected that new high risk code paths be guarded -// by runtime feature guards, i.e -// -// if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.my_feature_name")) { -// [new code path] -// else { -// [old_code_path] -// } -// -// Runtime features are false by default, so the old code path is exercised. -// To make a runtime feature true by default, add it to the array below. -// New features should be true-by-default for an Envoy release cycle before the -// old code path is removed. -// -// If issues are found that require a runtime feature to be disabled, it should be reported -// ASAP by filing a bug on github. Overriding non-buggy code is strongly discouraged to avoid the -// problem of the bugs being found after the old code path has been removed. -// clang-format off -constexpr absl::Flag* runtime_features[] = { - // Test flags - &FLAGS_envoy_reloadable_features_test_feature_false, - &FLAGS_envoy_reloadable_features_test_feature_true, - // Begin alphabetically sorted section_ - &FLAGS_envoy_reloadable_features_allow_multiple_dns_addresses, - &FLAGS_envoy_reloadable_features_allow_upstream_inline_write, - &FLAGS_envoy_reloadable_features_append_or_truncate, - &FLAGS_envoy_reloadable_features_append_to_accept_content_encoding_only_once, - &FLAGS_envoy_reloadable_features_conn_pool_delete_when_idle, - &FLAGS_envoy_reloadable_features_conn_pool_new_stream_with_early_data_and_http3, - &FLAGS_envoy_reloadable_features_correct_scheme_and_xfp, - &FLAGS_envoy_reloadable_features_correctly_validate_alpn, - &FLAGS_envoy_reloadable_features_defer_processing_backedup_streams, - &FLAGS_envoy_reloadable_features_deprecate_global_ints, - &FLAGS_envoy_reloadable_features_disable_tls_inspector_injection, - &FLAGS_envoy_reloadable_features_do_not_await_headers_on_upstream_timeout_to_emit_stats, - &FLAGS_envoy_reloadable_features_enable_grpc_async_client_cache, - &FLAGS_envoy_reloadable_features_fix_added_trailers, - &FLAGS_envoy_reloadable_features_handle_stream_reset_during_hcm_encoding, - &FLAGS_envoy_reloadable_features_http1_lazy_read_disable, - &FLAGS_envoy_reloadable_features_http2_allow_capacity_increase_by_settings, - &FLAGS_envoy_reloadable_features_http2_new_codec_wrapper, - &FLAGS_envoy_reloadable_features_http_ext_authz_do_not_skip_direct_response_and_redirect, - &FLAGS_envoy_reloadable_features_http_reject_path_with_fragment, - &FLAGS_envoy_reloadable_features_http_strip_fragment_from_path_unsafe_if_disabled, - &FLAGS_envoy_reloadable_features_internal_address, - &FLAGS_envoy_reloadable_features_listener_wildcard_match_ip_family, - &FLAGS_envoy_reloadable_features_new_tcp_connection_pool, - &FLAGS_envoy_reloadable_features_postpone_h3_client_connect_to_next_loop, - &FLAGS_envoy_reloadable_features_proxy_102_103, - &FLAGS_envoy_reloadable_features_sanitize_http_header_referer, - &FLAGS_envoy_reloadable_features_skip_delay_close, - &FLAGS_envoy_reloadable_features_skip_dispatching_frames_for_closed_connection, - &FLAGS_envoy_reloadable_features_strict_check_on_ipv4_compat, - &FLAGS_envoy_reloadable_features_support_locality_update_on_eds_cluster_endpoints, - &FLAGS_envoy_reloadable_features_udp_listener_updates_filter_chain_in_place, - &FLAGS_envoy_reloadable_features_unified_mux, - &FLAGS_envoy_reloadable_features_update_expected_rq_timeout_on_retry, - &FLAGS_envoy_reloadable_features_update_grpc_response_error_tag, - &FLAGS_envoy_reloadable_features_use_dns_ttl, - &FLAGS_envoy_reloadable_features_validate_connect, - &FLAGS_envoy_restart_features_explicit_wildcard_resource, - &FLAGS_envoy_restart_features_use_apple_api_for_dns_lookups, - &FLAGS_envoy_restart_features_no_runtime_singleton, -}; -// clang-format on - void maybeSetRuntimeGuard(absl::string_view name, bool value) { - auto* flag = RuntimeFeaturesDefaults::get().getFlag(name); + absl::CommandLineFlag* flag = RuntimeFeaturesDefaults::get().getFlag(name); if (flag == nullptr) { IS_ENVOY_BUG(absl::StrCat("Unable to find runtime feature ", name)); return; } - absl::SetFlag(flag, value); + std::string err; + flag->ParseFrom(value ? "true" : "false", &err); } void maybeSetDeprecatedInts(absl::string_view name, uint32_t value) { @@ -211,36 +195,5 @@ void maybeSetDeprecatedInts(absl::string_view name, uint32_t value) { } } -std::string swapPrefix(std::string name) { - return absl::StrReplaceAll(name, {{"envoy_", "envoy."}, {"features_", "features."}}); -} - -RuntimeFeatures::RuntimeFeatures() { - for (auto& feature : runtime_features) { - auto& reflection = absl::GetFlagReflectionHandle(*feature); - std::string envoy_name = swapPrefix(std::string(reflection.Name())); - all_features_.emplace(envoy_name, feature); - absl::optional value = reflection.TryGet(); - ASSERT(value.has_value()); - if (value.value()) { - enabled_features_.emplace(envoy_name, feature); - } else { - disabled_features_.emplace(envoy_name, feature); - } - } -} - -void RuntimeFeatures::restoreDefaults() const { - for (const auto& feature : enabled_features_) { - absl::SetFlag(feature.second, true); - } - for (const auto& feature : disabled_features_) { - absl::SetFlag(feature.second, false); - } - absl::SetFlag(&FLAGS_envoy_headermap_lazy_map_min_size, 3); - absl::SetFlag(&FLAGS_re2_max_program_size_error_level, 100); - absl::SetFlag(&FLAGS_re2_max_program_size_warn_level, std::numeric_limits::max()); -} - } // namespace Runtime } // namespace Envoy diff --git a/source/common/runtime/runtime_features.h b/source/common/runtime/runtime_features.h index 6c0d1f6cbe42..3ac72f1f215a 100644 --- a/source/common/runtime/runtime_features.h +++ b/source/common/runtime/runtime_features.h @@ -8,6 +8,7 @@ #include "absl/container/flat_hash_set.h" #include "absl/flags/flag.h" +#include "absl/flags/reflection.h" namespace Envoy { namespace Runtime { @@ -24,29 +25,5 @@ constexpr absl::string_view conn_pool_new_stream_with_early_data_and_http3 = constexpr absl::string_view defer_processing_backedup_streams = "envoy.reloadable_features.defer_processing_backedup_streams"; -class RuntimeFeatures { -public: - RuntimeFeatures(); - - absl::Flag* getFlag(absl::string_view feature) const { - auto it = all_features_.find(feature); - if (it == all_features_.end()) { - return nullptr; - } - return it->second; - } - - void restoreDefaults() const; - -private: - friend class RuntimeFeaturesPeer; - - absl::flat_hash_map*> enabled_features_; - absl::flat_hash_map*> disabled_features_; - absl::flat_hash_map*> all_features_; -}; - -using RuntimeFeaturesDefaults = ConstSingleton; - } // namespace Runtime } // namespace Envoy diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 06d90dffa418..c1ef54a8f42b 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -3002,7 +3002,6 @@ TEST_F(Http1ServerConnectionImplTest, ManyLargeRequestHeadersAccepted) { TEST_F(Http1ServerConnectionImplTest, RuntimeLazyReadDisableTest) { TestScopedRuntime scoped_runtime; - Runtime::RuntimeFeaturesDefaults::get().restoreDefaults(); // No readDisable for normal non-piped HTTP request. { @@ -3077,7 +3076,6 @@ TEST_F(Http1ServerConnectionImplTest, RuntimeLazyReadDisableTest) { // same time. TEST_F(Http1ServerConnectionImplTest, PipedRequestWithSingleEvent) { TestScopedRuntime scoped_runtime; - Runtime::RuntimeFeaturesDefaults::get().restoreDefaults(); initialize(); @@ -3114,7 +3112,6 @@ TEST_F(Http1ServerConnectionImplTest, PipedRequestWithSingleEvent) { // before the end of the first request. TEST_F(Http1ServerConnectionImplTest, PipedRequestWithMutipleEvent) { TestScopedRuntime scoped_runtime; - Runtime::RuntimeFeaturesDefaults::get().restoreDefaults(); initialize(); diff --git a/test/test_common/test_runtime.h b/test/test_common/test_runtime.h index d763309c3be7..59d109f39377 100644 --- a/test/test_common/test_runtime.h +++ b/test/test_common/test_runtime.h @@ -43,12 +43,8 @@ class TestScopedRuntime { loader().mergeValues(values); } - ~TestScopedRuntime() { - Runtime::RuntimeFeatures features; - features.restoreDefaults(); - } - protected: + absl::FlagSaver saver_; Event::MockDispatcher dispatcher_; testing::NiceMock tls_; Stats::TestUtil::TestStore store_; diff --git a/test/test_listener.cc b/test/test_listener.cc index a6ea54f09921..51ca1970a53a 100644 --- a/test/test_listener.cc +++ b/test/test_listener.cc @@ -20,7 +20,10 @@ void TestListener::OnTestEnd(const ::testing::TestInfo& test_info) { absl::StrCat("MainThreadLeak: [", test_info.test_suite_name(), ".", test_info.name(), "] test exited before main thread shut down")); } - Runtime::RuntimeFeaturesDefaults::get().restoreDefaults(); + // We must do this in two phases; reset the old flags to get back to the clean state before + // constructing a new flag saver to latch the clean values. + saver_.reset(); + saver_ = std::make_unique(); } } // namespace Envoy diff --git a/test/test_listener.h b/test/test_listener.h index 647dae4e2f83..e83f78937247 100644 --- a/test/test_listener.h +++ b/test/test_listener.h @@ -1,5 +1,6 @@ #pragma once +#include "absl/flags/reflection.h" #include "gtest/gtest.h" namespace Envoy { @@ -21,10 +22,13 @@ namespace Envoy { // be a tax paid by every test method in the codebase. class TestListener : public ::testing::EmptyTestEventListener { public: - TestListener(bool validate_singletons = true) : validate_singletons_(validate_singletons) {} + TestListener(bool validate_singletons = true) + : saver_(std::make_unique()), validate_singletons_(validate_singletons) {} void OnTestEnd(const ::testing::TestInfo& test_info) override; private: + // Make sure runtime guards are restored to defaults on test completion. + std::unique_ptr saver_; bool validate_singletons_; }; From 5c4d4bd957f9402eca80bef82e7cc3ae714e04b4 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Mon, 14 Mar 2022 22:22:17 +0100 Subject: [PATCH 68/68] Bump fmtlib and spdlog (#20066) Signed-off-by: Krzesimir Nowak --- bazel/repository_locations.bzl | 12 ++++++------ .../filters/network/source/mysql_filter.cc | 3 ++- .../filters/network/source/conn_manager.cc | 2 +- .../network/source/router/router_impl.cc | 4 ++-- source/common/filter/config_discovery_impl.cc | 3 ++- source/common/network/connection_impl.cc | 5 +++-- .../common/network/io_socket_handle_impl.cc | 3 ++- source/common/router/config_impl.cc | 2 +- source/common/upstream/health_checker_impl.cc | 2 +- .../filters/http/ext_proc/ext_proc.cc | 12 ++++++------ .../filters/http/ext_proc/processor_state.cc | 3 ++- .../filters/http/ratelimit/ratelimit.cc | 2 +- .../network/dubbo_proxy/router/router_impl.cc | 2 +- .../filters/udp/dns_filter/dns_parser.cc | 5 +++-- .../dns_resolver/apple/apple_dns_impl.cc | 5 +++-- .../network/dns_resolver/cares/dns_impl.cc | 4 ++-- .../http/common/fuzz/http_filter_fuzzer.h | 4 ++-- test/integration/fake_upstream.cc | 2 +- test/integration/protocol_integration_test.cc | 3 ++- test/server/admin/stats_handler_test.cc | 19 +++++++++---------- 20 files changed, 52 insertions(+), 45 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 0a2ac36fe66e..2b158e2146af 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -194,24 +194,24 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "fmt", project_desc = "{fmt} is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams", project_url = "https://fmt.dev", - version = "7.0.3", - sha256 = "decfdf9ad274070fa85f26407b816f5a4d82205ae86bac1990be658d0795ea4d", + version = "8.1.1", + sha256 = "23778bad8edba12d76e4075da06db591f3b0e3c6c04928ced4a7282ca3400e5d", strip_prefix = "fmt-{version}", urls = ["https://github.com/fmtlib/fmt/releases/download/{version}/fmt-{version}.zip"], use_category = ["dataplane_core", "controlplane"], - release_date = "2020-08-07", + release_date = "2022-01-06", cpe = "cpe:2.3:a:fmt:fmt:*", ), com_github_gabime_spdlog = dict( project_name = "spdlog", project_desc = "Very fast, header-only/compiled, C++ logging library", project_url = "https://github.com/gabime/spdlog", - version = "1.7.0", - sha256 = "f0114a4d3c88be9e696762f37a7c379619443ce9d668546c61b21d41affe5b62", + version = "1.9.2", + sha256 = "6fff9215f5cb81760be4cc16d033526d1080427d236e86d70bb02994f85e3d38", strip_prefix = "spdlog-{version}", urls = ["https://github.com/gabime/spdlog/archive/v{version}.tar.gz"], use_category = ["dataplane_core", "controlplane"], - release_date = "2020-07-09", + release_date = "2021-08-12", cpe = "N/A", ), com_github_google_libprotobuf_mutator = dict( diff --git a/contrib/mysql_proxy/filters/network/source/mysql_filter.cc b/contrib/mysql_proxy/filters/network/source/mysql_filter.cc index cc92cca92aa8..6c1f450d35bf 100644 --- a/contrib/mysql_proxy/filters/network/source/mysql_filter.cc +++ b/contrib/mysql_proxy/filters/network/source/mysql_filter.cc @@ -115,7 +115,8 @@ void MySQLFilter::onCommand(Command& command) { decoder_->getAttributes(), metadata); ENVOY_CONN_LOG(trace, "mysql_proxy: query processed {}, result {}, cmd type {}", - read_callbacks_->connection(), command.getData(), result, command.getCmd()); + read_callbacks_->connection(), command.getData(), result, + static_cast(command.getCmd())); if (!result) { config_->stats_.queries_parse_error_.inc(); diff --git a/contrib/sip_proxy/filters/network/source/conn_manager.cc b/contrib/sip_proxy/filters/network/source/conn_manager.cc index 1b4d765d62b2..6d2193e97c16 100644 --- a/contrib/sip_proxy/filters/network/source/conn_manager.cc +++ b/contrib/sip_proxy/filters/network/source/conn_manager.cc @@ -288,7 +288,7 @@ void ConnectionManager::initializeReadFilterCallbacks(Network::ReadFilterCallbac } void ConnectionManager::onEvent(Network::ConnectionEvent event) { - ENVOY_CONN_LOG(info, "received event {}", read_callbacks_->connection(), event); + ENVOY_CONN_LOG(info, "received event {}", read_callbacks_->connection(), static_cast(event)); resetAllTrans(event == Network::ConnectionEvent::LocalClose); } diff --git a/contrib/sip_proxy/filters/network/source/router/router_impl.cc b/contrib/sip_proxy/filters/network/source/router/router_impl.cc index 7c2454b707ed..0d525617542e 100644 --- a/contrib/sip_proxy/filters/network/source/router/router_impl.cc +++ b/contrib/sip_proxy/filters/network/source/router/router_impl.cc @@ -510,7 +510,7 @@ void UpstreamRequest::resetStream() { releaseConnection(true); } void UpstreamRequest::onPoolFailure(ConnectionPool::PoolFailureReason reason, absl::string_view, Upstream::HostDescriptionConstSharedPtr host) { - ENVOY_LOG(info, "on pool failure {}", reason); + ENVOY_LOG(info, "on pool failure {}", static_cast(reason)); conn_state_ = ConnectionState::NotConnected; conn_pool_handle_ = nullptr; @@ -600,7 +600,7 @@ void UpstreamRequest::onUpstreamData(Buffer::Instance& data, bool end_stream) { } void UpstreamRequest::onEvent(Network::ConnectionEvent event) { - ENVOY_LOG(info, "received upstream event {}", event); + ENVOY_LOG(info, "received upstream event {}", static_cast(event)); switch (event) { case Network::ConnectionEvent::RemoteClose: ENVOY_STREAM_LOG(debug, "upstream remote close", *callbacks_); diff --git a/source/common/filter/config_discovery_impl.cc b/source/common/filter/config_discovery_impl.cc index 840035f4a740..aae2fa7be7e4 100644 --- a/source/common/filter/config_discovery_impl.cc +++ b/source/common/filter/config_discovery_impl.cc @@ -160,7 +160,8 @@ void FilterConfigSubscription::onConfigUpdate( void FilterConfigSubscription::onConfigUpdateFailed(Config::ConfigUpdateFailureReason reason, const EnvoyException*) { - ENVOY_LOG(debug, "Updating filter config {} failed due to {}", filter_config_name_, reason); + ENVOY_LOG(debug, "Updating filter config {} failed due to {}", filter_config_name_, + static_cast(reason)); stats_.config_fail_.inc(); // Make sure to make progress in case the control plane is temporarily failing. init_target_.ready(); diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index 9b66476c69a6..d9e4c9021be0 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -415,7 +415,7 @@ void ConnectionImpl::readDisable(bool disable) { } void ConnectionImpl::raiseEvent(ConnectionEvent event) { - ENVOY_CONN_LOG(trace, "raising connection event {}", *this, event); + ENVOY_CONN_LOG(trace, "raising connection event {}", *this, static_cast(event)); ConnectionImplBase::raiseConnectionEvent(event); // We may have pending data in the write buffer on transport handshake // completion, which may also have completed in the context of onReadReady(), @@ -598,7 +598,8 @@ void ConnectionImpl::onFileEvent(uint32_t events) { } void ConnectionImpl::onReadReady() { - ENVOY_CONN_LOG(trace, "read ready. dispatch_buffered_data={}", *this, dispatch_buffered_data_); + ENVOY_CONN_LOG(trace, "read ready. dispatch_buffered_data={}", *this, + static_cast(dispatch_buffered_data_)); const bool latched_dispatch_buffered_data = dispatch_buffered_data_; dispatch_buffered_data_ = false; diff --git a/source/common/network/io_socket_handle_impl.cc b/source/common/network/io_socket_handle_impl.cc index 9f7a2962fda0..309a5386aed5 100644 --- a/source/common/network/io_socket_handle_impl.cc +++ b/source/common/network/io_socket_handle_impl.cc @@ -621,7 +621,8 @@ absl::optional IoSocketHandleImpl::interfaceName() { interface_address_value = interface_address.interface_addr_->ip()->ipv6()->address(); break; default: - ENVOY_BUG(false, fmt::format("unexpected IP family {}", socket_address->ip()->version())); + ENVOY_BUG(false, fmt::format("unexpected IP family {}", + static_cast(socket_address->ip()->version()))); } if (socket_address_value == interface_address_value) { diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index e05144ac1b0e..4fc7ec42edfa 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -1605,7 +1605,7 @@ RouteConstSharedPtr VirtualHostImpl::getRouteFromEntries(const RouteCallback& cb return nullptr; } - ENVOY_LOG(debug, "failed to match incoming request: {}", match.match_state_); + ENVOY_LOG(debug, "failed to match incoming request: {}", static_cast(match.match_state_)); return nullptr; } else { diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index 16621b7fc0a9..4090ba9dc082 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -323,7 +323,7 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onResetStream(Http::St void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onGoAway( Http::GoAwayErrorCode error_code) { ENVOY_CONN_LOG(debug, "connection going away goaway_code={}, health_flags={}", *client_, - error_code, HostUtility::healthFlagsToString(*host_)); + static_cast(error_code), HostUtility::healthFlagsToString(*host_)); if (request_in_flight_ && error_code == Http::GoAwayErrorCode::NoError) { // The server is starting a graceful shutdown. Allow the in flight request diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index f81b88fca1b8..736bd4cd9d06 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -151,7 +151,7 @@ FilterHeadersStatus Filter::decodeHeaders(RequestHeaderMap& headers, bool end_st } const auto status = onHeaders(decoding_state_, headers, end_stream); - ENVOY_LOG(trace, "decodeHeaders returning {}", status); + ENVOY_LOG(trace, "decodeHeaders returning {}", static_cast(status)); return status; } @@ -358,7 +358,7 @@ FilterDataStatus Filter::onData(ProcessorState& state, Buffer::Instance& data, b FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_stream) { ENVOY_LOG(trace, "decodeData({}): end_stream = {}", data.length(), end_stream); const auto status = onData(decoding_state_, data, end_stream); - ENVOY_LOG(trace, "decodeData returning {}", status); + ENVOY_LOG(trace, "decodeData returning {}", static_cast(status)); return status; } @@ -412,7 +412,7 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap& FilterTrailersStatus Filter::decodeTrailers(RequestTrailerMap& trailers) { ENVOY_LOG(trace, "decodeTrailers"); const auto status = onTrailers(decoding_state_, trailers); - ENVOY_LOG(trace, "encodeTrailers returning {}", status); + ENVOY_LOG(trace, "encodeTrailers returning {}", static_cast(status)); return status; } @@ -428,21 +428,21 @@ FilterHeadersStatus Filter::encodeHeaders(ResponseHeaderMap& headers, bool end_s } const auto status = onHeaders(encoding_state_, headers, end_stream); - ENVOY_LOG(trace, "encodeHeaders returns {}", status); + ENVOY_LOG(trace, "encodeHeaders returns {}", static_cast(status)); return status; } FilterDataStatus Filter::encodeData(Buffer::Instance& data, bool end_stream) { ENVOY_LOG(trace, "encodeData({}): end_stream = {}", data.length(), end_stream); const auto status = onData(encoding_state_, data, end_stream); - ENVOY_LOG(trace, "encodeData returning {}", status); + ENVOY_LOG(trace, "encodeData returning {}", static_cast(status)); return status; } FilterTrailersStatus Filter::encodeTrailers(ResponseTrailerMap& trailers) { ENVOY_LOG(trace, "encodeTrailers"); const auto status = onTrailers(encoding_state_, trailers); - ENVOY_LOG(trace, "encodeTrailers returning {}", status); + ENVOY_LOG(trace, "encodeTrailers returning {}", static_cast(status)); return status; } diff --git a/source/extensions/filters/http/ext_proc/processor_state.cc b/source/extensions/filters/http/ext_proc/processor_state.cc index 8a6c5464dc69..f908c99ada9a 100644 --- a/source/extensions/filters/http/ext_proc/processor_state.cc +++ b/source/extensions/filters/http/ext_proc/processor_state.cc @@ -169,7 +169,8 @@ absl::Status ProcessorState::handleBodyResponse(const BodyResponse& response) { } } if (common_response.has_body_mutation()) { - ENVOY_LOG(debug, "Applying body response to buffered data. State = {}", callback_state_); + ENVOY_LOG(debug, "Applying body response to buffered data. State = {}", + static_cast(callback_state_)); if (headers_ != nullptr) { // Always reset the content length here to prevent later problems. headers_->removeContentLength(); diff --git a/source/extensions/filters/http/ratelimit/ratelimit.cc b/source/extensions/filters/http/ratelimit/ratelimit.cc index 26167175af94..61bb6d698b2d 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit.cc +++ b/source/extensions/filters/http/ratelimit/ratelimit.cc @@ -158,7 +158,7 @@ void Filter::complete(Filters::Common::RateLimit::LimitStatus status, break; case Filters::Common::RateLimit::LimitStatus::Error: ENVOY_LOG_TO_LOGGER(Logger::Registry::getLog(Logger::Id::filter), debug, - "rate limit status, status={}", status); + "rate limit status, status={}", static_cast(status)); cluster_->statsScope().counterFromStatName(stat_names.error_).inc(); break; case Filters::Common::RateLimit::LimitStatus::OverLimit: diff --git a/source/extensions/filters/network/dubbo_proxy/router/router_impl.cc b/source/extensions/filters/network/dubbo_proxy/router/router_impl.cc index 46e5153269f2..5c494c94fe8e 100644 --- a/source/extensions/filters/network/dubbo_proxy/router/router_impl.cc +++ b/source/extensions/filters/network/dubbo_proxy/router/router_impl.cc @@ -127,7 +127,7 @@ FilterStatus Router::onMessageEncoded(MessageMetadataSharedPtr metadata, Context } ENVOY_STREAM_LOG(trace, "dubbo router: response status: {}", *encoder_callbacks_, - metadata->responseStatus()); + static_cast(metadata->responseStatus())); switch (metadata->responseStatus()) { case ResponseStatus::Ok: diff --git a/source/extensions/filters/udp/dns_filter/dns_parser.cc b/source/extensions/filters/udp/dns_filter/dns_parser.cc index e4a0d517189d..36619cf936d9 100644 --- a/source/extensions/filters/udp/dns_filter/dns_parser.cc +++ b/source/extensions/filters/udp/dns_filter/dns_parser.cc @@ -201,7 +201,7 @@ bool DnsMessageParser::parseDnsObject(DnsQueryContextPtr& context, if (context->header_.questions != 1) { context->response_code_ = DNS_RESPONSE_CODE_FORMAT_ERROR; ENVOY_LOG(debug, "Unexpected number [{}] of questions in DNS query", - context->header_.questions); + static_cast(context->header_.questions)); return false; } @@ -214,7 +214,8 @@ bool DnsMessageParser::parseDnsObject(DnsQueryContextPtr& context, // Almost always, we will have only one query here. Per the RFC, QDCOUNT is usually 1 context->queries_.reserve(context->header_.questions); for (auto index = 0; index < context->header_.questions; index++) { - ENVOY_LOG(trace, "Parsing [{}/{}] questions", index, context->header_.questions); + ENVOY_LOG(trace, "Parsing [{}/{}] questions", index, + static_cast(context->header_.questions)); auto rec = parseDnsQueryRecord(buffer, offset); if (rec == nullptr) { context->counters_.query_parsing_failure.inc(); diff --git a/source/extensions/network/dns_resolver/apple/apple_dns_impl.cc b/source/extensions/network/dns_resolver/apple/apple_dns_impl.cc index 59ec9e16c7a2..9d0433f07bbe 100644 --- a/source/extensions/network/dns_resolver/apple/apple_dns_impl.cc +++ b/source/extensions/network/dns_resolver/apple/apple_dns_impl.cc @@ -168,7 +168,8 @@ void AppleDnsResolverImpl::PendingResolution::cancel(Network::ActiveDnsQuery::Ca // TODO(mattklein123): If cancel reason is timeout, do something more aggressive about destroying // and recreating the DNS system to maximize the chance of success in following queries. ENVOY_LOG_EVENT(debug, "apple_dns_resolution_cancelled", - "dns resolution cancelled for {} with reason={}", dns_name_, reason); + "dns resolution cancelled for {} with reason={}", dns_name_, + static_cast(reason)); ASSERT(owned_); // Because the query is self-owned, delete now. delete this; @@ -224,7 +225,7 @@ std::list& AppleDnsResolverImpl::PendingResolution::finalAddressLis void AppleDnsResolverImpl::PendingResolution::finishResolve() { ENVOY_LOG_EVENT(debug, "apple_dns_resolution_complete", "dns resolution for {} completed with status {}", dns_name_, - pending_response_.status_); + static_cast(pending_response_.status_)); callback_(pending_response_.status_, std::move(finalAddressList())); if (owned_) { diff --git a/source/extensions/network/dns_resolver/cares/dns_impl.cc b/source/extensions/network/dns_resolver/cares/dns_impl.cc index abb9514cb2d6..47fdc4233da7 100644 --- a/source/extensions/network/dns_resolver/cares/dns_impl.cc +++ b/source/extensions/network/dns_resolver/cares/dns_impl.cc @@ -233,7 +233,7 @@ void DnsResolverImpl::AddrInfoPendingResolution::onAresGetAddrInfoCallback( void DnsResolverImpl::PendingResolution::finishResolve() { ENVOY_LOG_EVENT(debug, "cares_dns_resolution_complete", "dns resolution for {} completed with status {}", dns_name_, - pending_response_.status_); + static_cast(pending_response_.status_)); if (!cancelled_) { // Use a raw try here because it is used in both main thread and filter. @@ -260,7 +260,7 @@ void DnsResolverImpl::PendingResolution::finishResolve() { } else { ENVOY_LOG_EVENT(debug, "cares_dns_callback_cancelled", "dns resolution callback for {} not issued. Cancelled with reason={}", - dns_name_, cancel_reason_); + dns_name_, static_cast(cancel_reason_)); } if (owned_) { delete this; diff --git a/test/extensions/filters/http/common/fuzz/http_filter_fuzzer.h b/test/extensions/filters/http/common/fuzz/http_filter_fuzzer.h index 861c50f4220a..2712d71f5d82 100644 --- a/test/extensions/filters/http/common/fuzz/http_filter_fuzzer.h +++ b/test/extensions/filters/http/common/fuzz/http_filter_fuzzer.h @@ -88,7 +88,7 @@ void HttpFilterFuzzer::runData(FilterType* filter, const test::fuzz::HttpData& d end_stream = true; } const auto& headersStatus = sendHeaders(filter, data, end_stream); - ENVOY_LOG_MISC(debug, "Finished with FilterHeadersStatus: {}", headersStatus); + ENVOY_LOG_MISC(debug, "Finished with FilterHeadersStatus: {}", static_cast(headersStatus)); if ((headersStatus != Http::FilterHeadersStatus::Continue && headersStatus != Http::FilterHeadersStatus::StopIteration) || !enabled_) { @@ -102,7 +102,7 @@ void HttpFilterFuzzer::runData(FilterType* filter, const test::fuzz::HttpData& d } Buffer::OwnedImpl buffer(data_chunks[i]); const auto& dataStatus = sendData(filter, buffer, end_stream); - ENVOY_LOG_MISC(debug, "Finished with FilterDataStatus: {}", dataStatus); + ENVOY_LOG_MISC(debug, "Finished with FilterDataStatus: {}", static_cast(dataStatus)); if (dataStatus != Http::FilterDataStatus::Continue || !enabled_) { return; } diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index 9675e7b63e01..a556cf454cf7 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -399,7 +399,7 @@ void FakeHttpConnection::onGoAway(Http::GoAwayErrorCode code) { ASSERT(type_ >= Http::CodecType::HTTP2); // Usually indicates connection level errors, no operations are needed since // the connection will be closed soon. - ENVOY_LOG(info, "FakeHttpConnection receives GOAWAY: ", code); + ENVOY_LOG(info, "FakeHttpConnection receives GOAWAY: ", static_cast(code)); } void FakeHttpConnection::encodeGoAway() { diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 78798e1437fd..ad7d6a611b32 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -1493,7 +1493,8 @@ TEST_P(ProtocolIntegrationTest, HeadersWithUnderscoresDropped) { stat_name = "http3.dropped_headers_with_underscores"; break; default: - RELEASE_ASSERT(false, fmt::format("Unknown downstream protocol {}", downstream_protocol_)); + RELEASE_ASSERT(false, fmt::format("Unknown downstream protocol {}", + static_cast(downstream_protocol_))); }; EXPECT_EQ(1L, TestUtility::findCounter(stats, stat_name)->value()); } diff --git a/test/server/admin/stats_handler_test.cc b/test/server/admin/stats_handler_test.cc index 876aae5df6c7..1ea814a91b26 100644 --- a/test/server/admin/stats_handler_test.cc +++ b/test/server/admin/stats_handler_test.cc @@ -163,16 +163,15 @@ TEST_P(AdminStatsTest, HandlerStatsPlainText) { CodeResponse code_response = handlerStats(url); EXPECT_EQ(Http::Code::OK, code_response.first); - constexpr char expected[] = - "t: \"hello world\"\n" - "c1: 10\n" - "c2: 20\n" - "h1: P0(200.0,200.0) P25(202.5,202.5) P50(205.0,205.0) P75(207.5,207.5) " - "P90(209.0,209.0) P95(209.5,209.5) P99(209.9,209.9) P99.5(209.95,209.95) " - "P99.9(209.99,209.99) P100(210.0,210.0)\n" - "h2: P0(100.0,100.0) P25(102.5,102.5) P50(105.0,105.0) P75(107.5,107.5) " - "P90(109.0,109.0) P95(109.5,109.5) P99(109.9,109.9) P99.5(109.95,109.95) " - "P99.9(109.99,109.99) P100(110.0,110.0)\n"; + constexpr char expected[] = "t: \"hello world\"\n" + "c1: 10\n" + "c2: 20\n" + "h1: P0(200,200) P25(202.5,202.5) P50(205,205) P75(207.5,207.5) " + "P90(209,209) P95(209.5,209.5) P99(209.9,209.9) P99.5(209.95,209.95) " + "P99.9(209.99,209.99) P100(210,210)\n" + "h2: P0(100,100) P25(102.5,102.5) P50(105,105) P75(107.5,107.5) " + "P90(109,109) P95(109.5,109.5) P99(109.9,109.9) P99.5(109.95,109.95) " + "P99.9(109.99,109.99) P100(110,110)\n"; EXPECT_EQ(expected, code_response.second); code_response = handlerStats(url + "?usedonly");