From a0b62438a643c453f73d24b70b0bf7e7dec3b930 Mon Sep 17 00:00:00 2001 From: Matt Rice Date: Wed, 18 Jul 2018 19:32:16 -0400 Subject: [PATCH 01/46] Move well known names from ALL_CAPS to CamelCase. (#3893) Move away from macro style constants to avoid conflicting with #defines. Risk Level: Low Testing: N/A Docs Changes: N/A Release Notes: N/A Signed-off-by: Matt Rice --- source/common/config/bootstrap_json.cc | 4 +- source/common/config/filter_json.cc | 2 +- source/common/config/rds_json.cc | 2 +- source/common/router/config_impl.cc | 2 +- source/common/upstream/health_checker_impl.cc | 2 +- source/common/upstream/upstream_impl.cc | 4 +- .../extensions/access_loggers/file/config.cc | 2 +- .../access_loggers/http_grpc/config.cc | 2 +- .../access_loggers/well_known_names.h | 4 +- .../filters/http/buffer/buffer_filter.cc | 2 +- .../extensions/filters/http/buffer/config.h | 2 +- source/extensions/filters/http/cors/config.h | 2 +- .../extensions/filters/http/dynamo/config.h | 2 +- .../filters/http/ext_authz/config.h | 2 +- source/extensions/filters/http/fault/config.h | 2 +- .../filters/http/fault/fault_filter.cc | 2 +- .../filters/http/grpc_http1_bridge/config.h | 2 +- .../http/grpc_json_transcoder/config.h | 2 +- .../extensions/filters/http/grpc_web/config.h | 2 +- source/extensions/filters/http/gzip/config.h | 2 +- .../filters/http/header_to_metadata/config.h | 2 +- .../header_to_metadata_filter.cc | 2 +- .../filters/http/health_check/config.h | 2 +- .../filters/http/ip_tagging/config.h | 2 +- .../filters/http/jwt_authn/filter_factory.h | 2 +- source/extensions/filters/http/lua/config.h | 2 +- .../extensions/filters/http/lua/lua_filter.h | 2 +- .../filters/http/ratelimit/config.h | 2 +- source/extensions/filters/http/rbac/config.h | 2 +- .../filters/http/rbac/rbac_filter.cc | 2 +- .../extensions/filters/http/router/config.h | 2 +- .../extensions/filters/http/squash/config.h | 2 +- .../filters/http/well_known_names.h | 42 +++++++++---------- .../filters/listener/original_dst/config.cc | 2 +- .../filters/listener/proxy_protocol/config.cc | 2 +- .../filters/listener/tls_inspector/config.cc | 2 +- .../listener/tls_inspector/tls_inspector.cc | 2 +- .../filters/listener/well_known_names.h | 6 +-- .../filters/network/client_ssl_auth/config.h | 2 +- .../extensions/filters/network/echo/config.cc | 2 +- .../filters/network/ext_authz/config.h | 2 +- .../network/http_connection_manager/config.h | 2 +- .../filters/network/mongo_proxy/config.h | 2 +- .../filters/network/ratelimit/config.h | 2 +- .../filters/network/redis_proxy/config.h | 2 +- .../filters/network/tcp_proxy/config.h | 2 +- .../filters/network/thrift_proxy/config.h | 2 +- .../filters/network/well_known_names.h | 22 +++++----- .../grpc_credentials/example/config.h | 2 +- .../file_based_metadata/config.cc | 2 +- .../file_based_metadata/config.h | 2 +- .../grpc_credentials/well_known_names.h | 4 +- .../extensions/health_checkers/redis/config.h | 2 +- .../health_checkers/well_known_names.h | 2 +- .../stat_sinks/dog_statsd/config.cc | 2 +- .../extensions/stat_sinks/hystrix/config.cc | 2 +- .../stat_sinks/metrics_service/config.cc | 2 +- source/extensions/stat_sinks/statsd/config.cc | 2 +- .../extensions/stat_sinks/well_known_names.h | 8 ++-- .../extensions/tracers/dynamic_ot/config.cc | 2 +- source/extensions/tracers/lightstep/config.cc | 2 +- source/extensions/tracers/well_known_names.h | 6 +-- source/extensions/tracers/zipkin/config.cc | 2 +- .../transport_sockets/capture/config.h | 2 +- .../transport_sockets/raw_buffer/config.h | 2 +- .../extensions/transport_sockets/ssl/config.h | 2 +- .../transport_sockets/well_known_names.h | 6 +-- source/server/connection_handler_impl.cc | 2 +- source/server/listener_manager_impl.cc | 12 +++--- .../grpc/grpc_client_integration_test.cc | 10 ++--- test/common/tcp_proxy/tcp_proxy_test.cc | 6 +-- .../access_loggers/file/config_test.cc | 4 +- .../access_loggers/http_grpc/config_test.cc | 2 +- .../filters/http/buffer/buffer_filter_test.cc | 4 +- .../filters/http/fault/fault_filter_test.cc | 4 +- .../http/jwt_authn/filter_integration_test.cc | 2 +- .../filters/http/lua/lua_integration_test.cc | 2 +- .../http/rbac/rbac_filter_integration_test.cc | 2 +- .../filters/http/rbac/rbac_filter_test.cc | 2 +- .../filters/http/router/config_test.cc | 2 +- .../network/client_ssl_auth/config_test.cc | 2 +- ...le_based_metadata_grpc_credentials_test.cc | 8 ++-- .../stats_sinks/dog_statsd/config_test.cc | 2 +- .../stats_sinks/hystrix/config_test.cc | 2 +- .../stats_sinks/statsd/config_test.cc | 12 +++--- test/server/configuration_impl_test.cc | 2 +- 86 files changed, 151 insertions(+), 151 deletions(-) diff --git a/source/common/config/bootstrap_json.cc b/source/common/config/bootstrap_json.cc index 6af1394ec723..4373f5f69e37 100644 --- a/source/common/config/bootstrap_json.cc +++ b/source/common/config/bootstrap_json.cc @@ -78,7 +78,7 @@ void BootstrapJson::translateBootstrap(const Json::Object& json_config, auto* stats_sinks = bootstrap.mutable_stats_sinks(); if (json_config.hasObject("statsd_udp_ip_address")) { auto* stats_sink = stats_sinks->Add(); - stats_sink->set_name(Extensions::StatSinks::StatsSinkNames::get().STATSD); + stats_sink->set_name(Extensions::StatSinks::StatsSinkNames::get().Statsd); envoy::config::metrics::v2::StatsdSink statsd_sink; AddressJson::translateAddress(json_config.getString("statsd_udp_ip_address"), false, true, *statsd_sink.mutable_address()); @@ -87,7 +87,7 @@ void BootstrapJson::translateBootstrap(const Json::Object& json_config, if (json_config.hasObject("statsd_tcp_cluster_name")) { auto* stats_sink = stats_sinks->Add(); - stats_sink->set_name(Extensions::StatSinks::StatsSinkNames::get().STATSD); + stats_sink->set_name(Extensions::StatSinks::StatsSinkNames::get().Statsd); envoy::config::metrics::v2::StatsdSink statsd_sink; statsd_sink.set_tcp_cluster_name(json_config.getString("statsd_tcp_cluster_name")); MessageUtil::jsonConvert(statsd_sink, *stats_sink->mutable_config()); diff --git a/source/common/config/filter_json.cc b/source/common/config/filter_json.cc index a00b45280088..1b912766e9c6 100644 --- a/source/common/config/filter_json.cc +++ b/source/common/config/filter_json.cc @@ -116,7 +116,7 @@ void FilterJson::translateAccessLog(const Json::Object& json_config, // Statically registered access logs are a v2-only feature, so use the standard internal file // access log for json config conversion. - proto_config.set_name(Extensions::AccessLoggers::AccessLogNames::get().FILE); + proto_config.set_name(Extensions::AccessLoggers::AccessLogNames::get().File); if (json_config.hasObject("filter")) { translateAccessLogFilter(*json_config.getObject("filter"), *proto_config.mutable_filter()); diff --git a/source/common/config/rds_json.cc b/source/common/config/rds_json.cc index 946623b54ba7..8e91e524ecbf 100644 --- a/source/common/config/rds_json.cc +++ b/source/common/config/rds_json.cc @@ -342,7 +342,7 @@ void RdsJson::translateRoute(const Json::Object& json_route, envoy::api::v2::rou const Json::ObjectSharedPtr obj = json_route.getObject("opaque_config"); auto& filter_metadata = (*route.mutable_metadata() - ->mutable_filter_metadata())[Extensions::HttpFilters::HttpFilterNames::get().ROUTER]; + ->mutable_filter_metadata())[Extensions::HttpFilters::HttpFilterNames::get().Router]; obj->iterate([&filter_metadata](const std::string& name, const Json::Object& value) { (*filter_metadata.mutable_fields())[name].set_string_value(value.asString()); return true; diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 10e4a680c638..cb0b726f9182 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -462,7 +462,7 @@ RouteEntryImplBase::parseOpaqueConfig(const envoy::api::v2::route::Route& route) std::multimap ret; if (route.has_metadata()) { const auto filter_metadata = route.metadata().filter_metadata().find( - Extensions::HttpFilters::HttpFilterNames::get().ROUTER); + Extensions::HttpFilters::HttpFilterNames::get().Router); if (filter_metadata == route.metadata().filter_metadata().end()) { return ret; } diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index 32afce10eb2b..cc90ab28aab6 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -73,7 +73,7 @@ HealthCheckerFactory::create(const envoy::api::v2::core::HealthCheck& hc_config, auto& factory = Config::Utility::getAndCheckFactory( hc_config.has_redis_health_check() - ? Extensions::HealthCheckers::HealthCheckerNames::get().REDIS_HEALTH_CHECKER + ? Extensions::HealthCheckers::HealthCheckerNames::get().RedisHealthChecker : std::string(hc_config.custom_health_check().name())); std::unique_ptr context( new HealthCheckerFactoryContextImpl(cluster, runtime, random, dispatcher, diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 2c10cfd68c6c..014c9a6c9400 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -299,11 +299,11 @@ ClusterInfoImpl::ClusterInfoImpl(const envoy::api::v2::Cluster& config, auto transport_socket = config.transport_socket(); if (!config.has_transport_socket()) { if (config.has_tls_context()) { - transport_socket.set_name(Extensions::TransportSockets::TransportSocketNames::get().TLS); + transport_socket.set_name(Extensions::TransportSockets::TransportSocketNames::get().Tls); MessageUtil::jsonConvert(config.tls_context(), *transport_socket.mutable_config()); } else { transport_socket.set_name( - Extensions::TransportSockets::TransportSocketNames::get().RAW_BUFFER); + Extensions::TransportSockets::TransportSocketNames::get().RawBuffer); } } diff --git a/source/extensions/access_loggers/file/config.cc b/source/extensions/access_loggers/file/config.cc index 8494f39db931..8490a72eccb0 100644 --- a/source/extensions/access_loggers/file/config.cc +++ b/source/extensions/access_loggers/file/config.cc @@ -35,7 +35,7 @@ ProtobufTypes::MessagePtr FileAccessLogFactory::createEmptyConfigProto() { return ProtobufTypes::MessagePtr{new envoy::config::accesslog::v2::FileAccessLog()}; } -std::string FileAccessLogFactory::name() const { return AccessLogNames::get().FILE; } +std::string FileAccessLogFactory::name() const { return AccessLogNames::get().File; } /** * Static registration for the file access log. @see RegisterFactory. diff --git a/source/extensions/access_loggers/http_grpc/config.cc b/source/extensions/access_loggers/http_grpc/config.cc index ec24ebaea407..4953c2bd3d59 100644 --- a/source/extensions/access_loggers/http_grpc/config.cc +++ b/source/extensions/access_loggers/http_grpc/config.cc @@ -44,7 +44,7 @@ ProtobufTypes::MessagePtr HttpGrpcAccessLogFactory::createEmptyConfigProto() { return ProtobufTypes::MessagePtr{new envoy::config::accesslog::v2::HttpGrpcAccessLogConfig()}; } -std::string HttpGrpcAccessLogFactory::name() const { return AccessLogNames::get().HTTP_GRPC; } +std::string HttpGrpcAccessLogFactory::name() const { return AccessLogNames::get().HttpGrpc; } /** * Static registration for the HTTP gRPC access log. @see RegisterFactory. diff --git a/source/extensions/access_loggers/well_known_names.h b/source/extensions/access_loggers/well_known_names.h index 1b1c22fa758a..737a4b9c04d3 100644 --- a/source/extensions/access_loggers/well_known_names.h +++ b/source/extensions/access_loggers/well_known_names.h @@ -13,9 +13,9 @@ namespace AccessLoggers { class AccessLogNameValues { public: // File access log - const std::string FILE = "envoy.file_access_log"; + const std::string File = "envoy.file_access_log"; // HTTP gRPC access log - const std::string HTTP_GRPC = "envoy.http_grpc_access_log"; + const std::string HttpGrpc = "envoy.http_grpc_access_log"; }; typedef ConstSingleton AccessLogNames; diff --git a/source/extensions/filters/http/buffer/buffer_filter.cc b/source/extensions/filters/http/buffer/buffer_filter.cc index 36b45f31fd93..6db61bdc0745 100644 --- a/source/extensions/filters/http/buffer/buffer_filter.cc +++ b/source/extensions/filters/http/buffer/buffer_filter.cc @@ -58,7 +58,7 @@ void BufferFilter::initConfig() { return; } - const std::string& name = HttpFilterNames::get().BUFFER; + const std::string& name = HttpFilterNames::get().Buffer; const auto* entry = callbacks_->route()->routeEntry(); const BufferFilterSettings* route_local = diff --git a/source/extensions/filters/http/buffer/config.h b/source/extensions/filters/http/buffer/config.h index 98c14afb3868..232acd431592 100644 --- a/source/extensions/filters/http/buffer/config.h +++ b/source/extensions/filters/http/buffer/config.h @@ -17,7 +17,7 @@ class BufferFilterFactory : public Common::FactoryBase { public: - BufferFilterFactory() : FactoryBase(HttpFilterNames::get().BUFFER) {} + BufferFilterFactory() : FactoryBase(HttpFilterNames::get().Buffer) {} Http::FilterFactoryCb createFilterFactory(const Json::Object& json_config, const std::string& stats_prefix, diff --git a/source/extensions/filters/http/cors/config.h b/source/extensions/filters/http/cors/config.h index b1e2f947a08c..9ce14197fc43 100644 --- a/source/extensions/filters/http/cors/config.h +++ b/source/extensions/filters/http/cors/config.h @@ -15,7 +15,7 @@ namespace Cors { */ class CorsFilterConfig : public Common::EmptyHttpFilterConfig { public: - CorsFilterConfig() : Common::EmptyHttpFilterConfig(HttpFilterNames::get().CORS) {} + CorsFilterConfig() : Common::EmptyHttpFilterConfig(HttpFilterNames::get().Cors) {} Http::FilterFactoryCb createFilter(const std::string&, Server::Configuration::FactoryContext&) override; diff --git a/source/extensions/filters/http/dynamo/config.h b/source/extensions/filters/http/dynamo/config.h index ce75bebb150f..d452985beb2f 100644 --- a/source/extensions/filters/http/dynamo/config.h +++ b/source/extensions/filters/http/dynamo/config.h @@ -15,7 +15,7 @@ namespace Dynamo { */ class DynamoFilterConfig : public Common::EmptyHttpFilterConfig { public: - DynamoFilterConfig() : Common::EmptyHttpFilterConfig(HttpFilterNames::get().DYNAMO) {} + DynamoFilterConfig() : Common::EmptyHttpFilterConfig(HttpFilterNames::get().Dynamo) {} Http::FilterFactoryCb createFilter(const std::string& stat_prefix, Server::Configuration::FactoryContext& context) override; diff --git a/source/extensions/filters/http/ext_authz/config.h b/source/extensions/filters/http/ext_authz/config.h index e9ba463b4044..71f003c8fe97 100644 --- a/source/extensions/filters/http/ext_authz/config.h +++ b/source/extensions/filters/http/ext_authz/config.h @@ -16,7 +16,7 @@ namespace ExtAuthz { class ExtAuthzFilterConfig : public Common::FactoryBase { public: - ExtAuthzFilterConfig() : FactoryBase(HttpFilterNames::get().EXT_AUTHORIZATION) {} + ExtAuthzFilterConfig() : FactoryBase(HttpFilterNames::get().ExtAuthorization) {} private: static constexpr uint64_t DefaultTimeout = 200; diff --git a/source/extensions/filters/http/fault/config.h b/source/extensions/filters/http/fault/config.h index ae41ebc42261..240c2f7dab52 100644 --- a/source/extensions/filters/http/fault/config.h +++ b/source/extensions/filters/http/fault/config.h @@ -16,7 +16,7 @@ namespace Fault { class FaultFilterFactory : public Common::FactoryBase { public: - FaultFilterFactory() : FactoryBase(HttpFilterNames::get().FAULT) {} + FaultFilterFactory() : FactoryBase(HttpFilterNames::get().Fault) {} Http::FilterFactoryCb createFilterFactory(const Json::Object& json_config, const std::string& stats_prefix, diff --git a/source/extensions/filters/http/fault/fault_filter.cc b/source/extensions/filters/http/fault/fault_filter.cc index 9669b8b041d7..47bfbec25900 100644 --- a/source/extensions/filters/http/fault/fault_filter.cc +++ b/source/extensions/filters/http/fault/fault_filter.cc @@ -76,7 +76,7 @@ Http::FilterHeadersStatus FaultFilter::decodeHeaders(Http::HeaderMap& headers, b // configured at the filter level. fault_settings_ = config_->settings(); if (callbacks_->route() && callbacks_->route()->routeEntry()) { - const std::string& name = Extensions::HttpFilters::HttpFilterNames::get().FAULT; + const std::string& name = Extensions::HttpFilters::HttpFilterNames::get().Fault; const auto* route_entry = callbacks_->route()->routeEntry(); const FaultSettings* per_route_settings_ = diff --git a/source/extensions/filters/http/grpc_http1_bridge/config.h b/source/extensions/filters/http/grpc_http1_bridge/config.h index 7edb9106adec..b8a698f0d785 100644 --- a/source/extensions/filters/http/grpc_http1_bridge/config.h +++ b/source/extensions/filters/http/grpc_http1_bridge/config.h @@ -16,7 +16,7 @@ namespace GrpcHttp1Bridge { class GrpcHttp1BridgeFilterConfig : public Common::EmptyHttpFilterConfig { public: GrpcHttp1BridgeFilterConfig() - : Common::EmptyHttpFilterConfig(HttpFilterNames::get().GRPC_HTTP1_BRIDGE) {} + : Common::EmptyHttpFilterConfig(HttpFilterNames::get().GrpcHttp1Bridge) {} Http::FilterFactoryCb createFilter(const std::string&, Server::Configuration::FactoryContext& context) override; diff --git a/source/extensions/filters/http/grpc_json_transcoder/config.h b/source/extensions/filters/http/grpc_json_transcoder/config.h index 8c332d342a3d..8b910725c690 100644 --- a/source/extensions/filters/http/grpc_json_transcoder/config.h +++ b/source/extensions/filters/http/grpc_json_transcoder/config.h @@ -16,7 +16,7 @@ namespace GrpcJsonTranscoder { class GrpcJsonTranscoderFilterConfig : public Common::FactoryBase { public: - GrpcJsonTranscoderFilterConfig() : FactoryBase(HttpFilterNames::get().GRPC_JSON_TRANSCODER) {} + GrpcJsonTranscoderFilterConfig() : FactoryBase(HttpFilterNames::get().GrpcJsonTranscoder) {} Http::FilterFactoryCb createFilterFactory(const Json::Object& json_config, const std::string& stats_prefix, diff --git a/source/extensions/filters/http/grpc_web/config.h b/source/extensions/filters/http/grpc_web/config.h index 98fabcf17c81..a1402b15fce5 100644 --- a/source/extensions/filters/http/grpc_web/config.h +++ b/source/extensions/filters/http/grpc_web/config.h @@ -12,7 +12,7 @@ namespace GrpcWeb { class GrpcWebFilterConfig : public Common::EmptyHttpFilterConfig { public: - GrpcWebFilterConfig() : Common::EmptyHttpFilterConfig(HttpFilterNames::get().GRPC_WEB) {} + GrpcWebFilterConfig() : Common::EmptyHttpFilterConfig(HttpFilterNames::get().GrpcWeb) {} Http::FilterFactoryCb createFilter(const std::string&, Server::Configuration::FactoryContext& context) override; diff --git a/source/extensions/filters/http/gzip/config.h b/source/extensions/filters/http/gzip/config.h index 06e2e70a5ae6..8dd48de622d3 100644 --- a/source/extensions/filters/http/gzip/config.h +++ b/source/extensions/filters/http/gzip/config.h @@ -15,7 +15,7 @@ namespace Gzip { */ class GzipFilterFactory : public Common::FactoryBase { public: - GzipFilterFactory() : FactoryBase(HttpFilterNames::get().ENVOY_GZIP) {} + GzipFilterFactory() : FactoryBase(HttpFilterNames::get().EnvoyGzip) {} private: Http::FilterFactoryCb diff --git a/source/extensions/filters/http/header_to_metadata/config.h b/source/extensions/filters/http/header_to_metadata/config.h index bbc548376bc7..11ced03b1cbf 100644 --- a/source/extensions/filters/http/header_to_metadata/config.h +++ b/source/extensions/filters/http/header_to_metadata/config.h @@ -16,7 +16,7 @@ namespace HeaderToMetadataFilter { class HeaderToMetadataConfig : public Common::FactoryBase { public: - HeaderToMetadataConfig() : FactoryBase(HttpFilterNames::get().HEADER_TO_METADATA) {} + HeaderToMetadataConfig() : FactoryBase(HttpFilterNames::get().HeaderToMetadata) {} private: Http::FilterFactoryCb createFilterFactoryFromProtoTyped( diff --git a/source/extensions/filters/http/header_to_metadata/header_to_metadata_filter.cc b/source/extensions/filters/http/header_to_metadata/header_to_metadata_filter.cc index 83f7a356cb94..722c0d16a59d 100644 --- a/source/extensions/filters/http/header_to_metadata/header_to_metadata_filter.cc +++ b/source/extensions/filters/http/header_to_metadata/header_to_metadata_filter.cc @@ -132,7 +132,7 @@ bool HeaderToMetadataFilter::addMetadata(StructMap& map, const std::string& meta } const std::string& HeaderToMetadataFilter::decideNamespace(const std::string& nspace) const { - return nspace.empty() ? HttpFilterNames::get().HEADER_TO_METADATA : nspace; + return nspace.empty() ? HttpFilterNames::get().HeaderToMetadata : nspace; } void HeaderToMetadataFilter::writeHeaderToMetadata(Http::HeaderMap& headers, diff --git a/source/extensions/filters/http/health_check/config.h b/source/extensions/filters/http/health_check/config.h index 71bd8c91baad..d7763d6f0e96 100644 --- a/source/extensions/filters/http/health_check/config.h +++ b/source/extensions/filters/http/health_check/config.h @@ -13,7 +13,7 @@ namespace HealthCheck { class HealthCheckFilterConfig : public Common::FactoryBase { public: - HealthCheckFilterConfig() : FactoryBase(HttpFilterNames::get().HEALTH_CHECK) {} + HealthCheckFilterConfig() : FactoryBase(HttpFilterNames::get().HealthCheck) {} Http::FilterFactoryCb createFilterFactory(const Json::Object& json_config, const std::string&, diff --git a/source/extensions/filters/http/ip_tagging/config.h b/source/extensions/filters/http/ip_tagging/config.h index 98bd55cb6304..01da576853bc 100644 --- a/source/extensions/filters/http/ip_tagging/config.h +++ b/source/extensions/filters/http/ip_tagging/config.h @@ -16,7 +16,7 @@ namespace IpTagging { class IpTaggingFilterFactory : public Common::FactoryBase { public: - IpTaggingFilterFactory() : FactoryBase(HttpFilterNames::get().IP_TAGGING) {} + IpTaggingFilterFactory() : FactoryBase(HttpFilterNames::get().IpTagging) {} private: Http::FilterFactoryCb createFilterFactoryFromProtoTyped( diff --git a/source/extensions/filters/http/jwt_authn/filter_factory.h b/source/extensions/filters/http/jwt_authn/filter_factory.h index 67f653bf053a..6bdee97bae50 100644 --- a/source/extensions/filters/http/jwt_authn/filter_factory.h +++ b/source/extensions/filters/http/jwt_authn/filter_factory.h @@ -17,7 +17,7 @@ namespace JwtAuthn { class FilterFactory : public Common::FactoryBase< ::envoy::config::filter::http::jwt_authn::v2alpha::JwtAuthentication> { public: - FilterFactory() : FactoryBase(HttpFilterNames::get().JWT_AUTHN) {} + FilterFactory() : FactoryBase(HttpFilterNames::get().JwtAuthn) {} private: Http::FilterFactoryCb createFilterFactoryFromProtoTyped( diff --git a/source/extensions/filters/http/lua/config.h b/source/extensions/filters/http/lua/config.h index 82d9ec287fbd..6921fc6b349a 100644 --- a/source/extensions/filters/http/lua/config.h +++ b/source/extensions/filters/http/lua/config.h @@ -15,7 +15,7 @@ namespace Lua { */ class LuaFilterConfig : public Common::FactoryBase { public: - LuaFilterConfig() : FactoryBase(HttpFilterNames::get().LUA) {} + LuaFilterConfig() : FactoryBase(HttpFilterNames::get().Lua) {} Http::FilterFactoryCb createFilterFactory(const Json::Object& json_config, const std::string& stats_prefix, diff --git a/source/extensions/filters/http/lua/lua_filter.h b/source/extensions/filters/http/lua/lua_filter.h index 5ba7a9b0191b..bd306801606a 100644 --- a/source/extensions/filters/http/lua/lua_filter.h +++ b/source/extensions/filters/http/lua/lua_filter.h @@ -18,7 +18,7 @@ const ProtobufWkt::Struct& getMetadata(Http::StreamFilterCallbacks* callbacks) { return ProtobufWkt::Struct::default_instance(); } const auto& metadata = callbacks->route()->routeEntry()->metadata(); - const auto& filter_it = metadata.filter_metadata().find(HttpFilterNames::get().LUA); + const auto& filter_it = metadata.filter_metadata().find(HttpFilterNames::get().Lua); if (filter_it == metadata.filter_metadata().end()) { return ProtobufWkt::Struct::default_instance(); } diff --git a/source/extensions/filters/http/ratelimit/config.h b/source/extensions/filters/http/ratelimit/config.h index 91ec643de135..bf9ebadfe256 100644 --- a/source/extensions/filters/http/ratelimit/config.h +++ b/source/extensions/filters/http/ratelimit/config.h @@ -16,7 +16,7 @@ namespace RateLimitFilter { class RateLimitFilterConfig : public Common::FactoryBase { public: - RateLimitFilterConfig() : FactoryBase(HttpFilterNames::get().RATE_LIMIT) {} + RateLimitFilterConfig() : FactoryBase(HttpFilterNames::get().RateLimit) {} Http::FilterFactoryCb createFilterFactory(const Json::Object& json_config, const std::string&, diff --git a/source/extensions/filters/http/rbac/config.h b/source/extensions/filters/http/rbac/config.h index c6002fb28401..f24235eb783f 100644 --- a/source/extensions/filters/http/rbac/config.h +++ b/source/extensions/filters/http/rbac/config.h @@ -18,7 +18,7 @@ class RoleBasedAccessControlFilterConfigFactory : public Common::FactoryBase { public: - RoleBasedAccessControlFilterConfigFactory() : FactoryBase(HttpFilterNames::get().RBAC) {} + RoleBasedAccessControlFilterConfigFactory() : FactoryBase(HttpFilterNames::get().Rbac) {} private: Http::FilterFactoryCb diff --git a/source/extensions/filters/http/rbac/rbac_filter.cc b/source/extensions/filters/http/rbac/rbac_filter.cc index 4fe193a5da6b..08a347a79aa9 100644 --- a/source/extensions/filters/http/rbac/rbac_filter.cc +++ b/source/extensions/filters/http/rbac/rbac_filter.cc @@ -36,7 +36,7 @@ RoleBasedAccessControlFilterConfig::engine(const Router::RouteConstSharedPtr rou return engine(mode); } - const std::string& name = HttpFilterNames::get().RBAC; + const std::string& name = HttpFilterNames::get().Rbac; const auto* entry = route->routeEntry(); const auto* route_local = diff --git a/source/extensions/filters/http/router/config.h b/source/extensions/filters/http/router/config.h index dcf0957ca4b1..11c4d74f8a45 100644 --- a/source/extensions/filters/http/router/config.h +++ b/source/extensions/filters/http/router/config.h @@ -18,7 +18,7 @@ namespace RouterFilter { class RouterFilterConfig : public Common::FactoryBase { public: - RouterFilterConfig() : FactoryBase(HttpFilterNames::get().ROUTER) {} + RouterFilterConfig() : FactoryBase(HttpFilterNames::get().Router) {} Http::FilterFactoryCb createFilterFactory(const Json::Object& json_config, const std::string& stat_prefix, diff --git a/source/extensions/filters/http/squash/config.h b/source/extensions/filters/http/squash/config.h index 9cb83b0096f2..10647ebdb6b0 100644 --- a/source/extensions/filters/http/squash/config.h +++ b/source/extensions/filters/http/squash/config.h @@ -16,7 +16,7 @@ namespace Squash { class SquashFilterConfigFactory : public Common::FactoryBase { public: - SquashFilterConfigFactory() : FactoryBase(HttpFilterNames::get().SQUASH) {} + SquashFilterConfigFactory() : FactoryBase(HttpFilterNames::get().Squash) {} Http::FilterFactoryCb createFilterFactory(const Json::Object& json_config, const std::string&, diff --git a/source/extensions/filters/http/well_known_names.h b/source/extensions/filters/http/well_known_names.h index f71960625e0e..238671e6da28 100644 --- a/source/extensions/filters/http/well_known_names.h +++ b/source/extensions/filters/http/well_known_names.h @@ -13,50 +13,50 @@ namespace HttpFilters { class HttpFilterNameValues { public: // Buffer filter - const std::string BUFFER = "envoy.buffer"; + const std::string Buffer = "envoy.buffer"; // CORS filter - const std::string CORS = "envoy.cors"; + const std::string Cors = "envoy.cors"; // Dynamo filter - const std::string DYNAMO = "envoy.http_dynamo_filter"; + const std::string Dynamo = "envoy.http_dynamo_filter"; // Fault filter - const std::string FAULT = "envoy.fault"; + const std::string Fault = "envoy.fault"; // GRPC http1 bridge filter - const std::string GRPC_HTTP1_BRIDGE = "envoy.grpc_http1_bridge"; + const std::string GrpcHttp1Bridge = "envoy.grpc_http1_bridge"; // GRPC json transcoder filter - const std::string GRPC_JSON_TRANSCODER = "envoy.grpc_json_transcoder"; + const std::string GrpcJsonTranscoder = "envoy.grpc_json_transcoder"; // GRPC web filter - const std::string GRPC_WEB = "envoy.grpc_web"; + const std::string GrpcWeb = "envoy.grpc_web"; // Gzip filter - const std::string ENVOY_GZIP = "envoy.gzip"; + const std::string EnvoyGzip = "envoy.gzip"; // IP tagging filter - const std::string IP_TAGGING = "envoy.ip_tagging"; + const std::string IpTagging = "envoy.ip_tagging"; // Rate limit filter - const std::string RATE_LIMIT = "envoy.rate_limit"; + const std::string RateLimit = "envoy.rate_limit"; // Router filter - const std::string ROUTER = "envoy.router"; + const std::string Router = "envoy.router"; // Health checking filter - const std::string HEALTH_CHECK = "envoy.health_check"; + const std::string HealthCheck = "envoy.health_check"; // Lua filter - const std::string LUA = "envoy.lua"; + const std::string Lua = "envoy.lua"; // Squash filter - const std::string SQUASH = "envoy.squash"; + const std::string Squash = "envoy.squash"; // External Authorization filter - const std::string EXT_AUTHORIZATION = "envoy.ext_authz"; + const std::string ExtAuthorization = "envoy.ext_authz"; // RBAC HTTP Authorization filter - const std::string RBAC = "envoy.filters.http.rbac"; + const std::string Rbac = "envoy.filters.http.rbac"; // JWT authentication filter - const std::string JWT_AUTHN = "envoy.filters.http.jwt_authn"; + const std::string JwtAuthn = "envoy.filters.http.jwt_authn"; // Header to metadata filter - const std::string HEADER_TO_METADATA = "envoy.filters.http.header_to_metadata"; + const std::string HeaderToMetadata = "envoy.filters.http.header_to_metadata"; // Converts names from v1 to v2 const Config::V1Converter v1_converter_; // NOTE: Do not add any new filters to this list. All future filters are v2 only. HttpFilterNameValues() - : v1_converter_({BUFFER, CORS, DYNAMO, FAULT, GRPC_HTTP1_BRIDGE, GRPC_JSON_TRANSCODER, - GRPC_WEB, HEADER_TO_METADATA, HEALTH_CHECK, IP_TAGGING, RATE_LIMIT, ROUTER, - LUA, EXT_AUTHORIZATION}) {} + : v1_converter_({Buffer, Cors, Dynamo, Fault, GrpcHttp1Bridge, GrpcJsonTranscoder, GrpcWeb, + HeaderToMetadata, HealthCheck, IpTagging, RateLimit, Router, Lua, + ExtAuthorization}) {} }; typedef ConstSingleton HttpFilterNames; diff --git a/source/extensions/filters/listener/original_dst/config.cc b/source/extensions/filters/listener/original_dst/config.cc index 1176508cb103..63e858ee463f 100644 --- a/source/extensions/filters/listener/original_dst/config.cc +++ b/source/extensions/filters/listener/original_dst/config.cc @@ -29,7 +29,7 @@ class OriginalDstConfigFactory : public Server::Configuration::NamedListenerFilt return std::make_unique(); } - std::string name() override { return ListenerFilterNames::get().ORIGINAL_DST; } + std::string name() override { return ListenerFilterNames::get().OriginalDst; } }; /** diff --git a/source/extensions/filters/listener/proxy_protocol/config.cc b/source/extensions/filters/listener/proxy_protocol/config.cc index 3ce2d851f808..6cbe967666e0 100644 --- a/source/extensions/filters/listener/proxy_protocol/config.cc +++ b/source/extensions/filters/listener/proxy_protocol/config.cc @@ -28,7 +28,7 @@ class ProxyProtocolConfigFactory : public Server::Configuration::NamedListenerFi return std::make_unique(); } - std::string name() override { return ListenerFilterNames::get().PROXY_PROTOCOL; } + std::string name() override { return ListenerFilterNames::get().ProxyProtocol; } }; /** diff --git a/source/extensions/filters/listener/tls_inspector/config.cc b/source/extensions/filters/listener/tls_inspector/config.cc index a75bb315da48..af8f8170683c 100644 --- a/source/extensions/filters/listener/tls_inspector/config.cc +++ b/source/extensions/filters/listener/tls_inspector/config.cc @@ -30,7 +30,7 @@ class TlsInspectorConfigFactory : public Server::Configuration::NamedListenerFil return std::make_unique(); } - std::string name() override { return ListenerFilterNames::get().TLS_INSPECTOR; } + std::string name() override { return ListenerFilterNames::get().TlsInspector; } }; /** diff --git a/source/extensions/filters/listener/tls_inspector/tls_inspector.cc b/source/extensions/filters/listener/tls_inspector/tls_inspector.cc index 407cabdddbce..a462a812c870 100644 --- a/source/extensions/filters/listener/tls_inspector/tls_inspector.cc +++ b/source/extensions/filters/listener/tls_inspector/tls_inspector.cc @@ -210,7 +210,7 @@ void Filter::parseClientHello(const void* data, size_t len) { } else { config_->stats().alpn_not_found_.inc(); } - cb_->socket().setDetectedTransportProtocol(TransportSockets::TransportSocketNames::get().TLS); + cb_->socket().setDetectedTransportProtocol(TransportSockets::TransportSocketNames::get().Tls); } else { config_->stats().tls_not_found_.inc(); } diff --git a/source/extensions/filters/listener/well_known_names.h b/source/extensions/filters/listener/well_known_names.h index f9a4fbce5518..ae66726a10a2 100644 --- a/source/extensions/filters/listener/well_known_names.h +++ b/source/extensions/filters/listener/well_known_names.h @@ -13,11 +13,11 @@ namespace ListenerFilters { class ListenerFilterNameValues { public: // Original destination listener filter - const std::string ORIGINAL_DST = "envoy.listener.original_dst"; + const std::string OriginalDst = "envoy.listener.original_dst"; // Proxy Protocol listener filter - const std::string PROXY_PROTOCOL = "envoy.listener.proxy_protocol"; + const std::string ProxyProtocol = "envoy.listener.proxy_protocol"; // TLS Inspector listener filter - const std::string TLS_INSPECTOR = "envoy.listener.tls_inspector"; + const std::string TlsInspector = "envoy.listener.tls_inspector"; }; typedef ConstSingleton ListenerFilterNames; diff --git a/source/extensions/filters/network/client_ssl_auth/config.h b/source/extensions/filters/network/client_ssl_auth/config.h index 896e29923ba3..8953d3be6b2c 100644 --- a/source/extensions/filters/network/client_ssl_auth/config.h +++ b/source/extensions/filters/network/client_ssl_auth/config.h @@ -17,7 +17,7 @@ class ClientSslAuthConfigFactory : public Common::FactoryBase< envoy::config::filter::network::client_ssl_auth::v2::ClientSSLAuth> { public: - ClientSslAuthConfigFactory() : FactoryBase(NetworkFilterNames::get().CLIENT_SSL_AUTH) {} + ClientSslAuthConfigFactory() : FactoryBase(NetworkFilterNames::get().ClientSslAuth) {} // NamedNetworkFilterConfigFactory Network::FilterFactoryCb diff --git a/source/extensions/filters/network/echo/config.cc b/source/extensions/filters/network/echo/config.cc index d092b074ae2d..990a3bf5ac01 100644 --- a/source/extensions/filters/network/echo/config.cc +++ b/source/extensions/filters/network/echo/config.cc @@ -34,7 +34,7 @@ class EchoConfigFactory : public Server::Configuration::NamedNetworkFilterConfig return ProtobufTypes::MessagePtr{new Envoy::ProtobufWkt::Empty()}; } - std::string name() override { return NetworkFilterNames::get().ECHO; } + std::string name() override { return NetworkFilterNames::get().Echo; } }; /** diff --git a/source/extensions/filters/network/ext_authz/config.h b/source/extensions/filters/network/ext_authz/config.h index 6c801436aa88..05fccafbb1f5 100644 --- a/source/extensions/filters/network/ext_authz/config.h +++ b/source/extensions/filters/network/ext_authz/config.h @@ -17,7 +17,7 @@ namespace ExtAuthz { class ExtAuthzConfigFactory : public Common::FactoryBase { public: - ExtAuthzConfigFactory() : FactoryBase(NetworkFilterNames::get().EXT_AUTHORIZATION) {} + ExtAuthzConfigFactory() : FactoryBase(NetworkFilterNames::get().ExtAuthorization) {} private: Network::FilterFactoryCb createFilterFactoryFromProtoTyped( diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 9e5863b0efcb..f8f5c6fd4cb0 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -32,7 +32,7 @@ class HttpConnectionManagerFilterConfigFactory envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager> { public: HttpConnectionManagerFilterConfigFactory() - : FactoryBase(NetworkFilterNames::get().HTTP_CONNECTION_MANAGER) {} + : FactoryBase(NetworkFilterNames::get().HttpConnectionManager) {} // NamedNetworkFilterConfigFactory Network::FilterFactoryCb diff --git a/source/extensions/filters/network/mongo_proxy/config.h b/source/extensions/filters/network/mongo_proxy/config.h index 78ae8f8eb8c3..c69ce12d5b2d 100644 --- a/source/extensions/filters/network/mongo_proxy/config.h +++ b/source/extensions/filters/network/mongo_proxy/config.h @@ -19,7 +19,7 @@ namespace MongoProxy { class MongoProxyFilterConfigFactory : public Common::FactoryBase { public: - MongoProxyFilterConfigFactory() : FactoryBase(NetworkFilterNames::get().MONGO_PROXY) {} + MongoProxyFilterConfigFactory() : FactoryBase(NetworkFilterNames::get().MongoProxy) {} // NamedNetworkFilterConfigFactory Network::FilterFactoryCb diff --git a/source/extensions/filters/network/ratelimit/config.h b/source/extensions/filters/network/ratelimit/config.h index fd68087efd4f..19c77ddd748e 100644 --- a/source/extensions/filters/network/ratelimit/config.h +++ b/source/extensions/filters/network/ratelimit/config.h @@ -17,7 +17,7 @@ namespace RateLimitFilter { class RateLimitConfigFactory : public Common::FactoryBase { public: - RateLimitConfigFactory() : FactoryBase(NetworkFilterNames::get().RATE_LIMIT) {} + RateLimitConfigFactory() : FactoryBase(NetworkFilterNames::get().RateLimit) {} // NamedNetworkFilterConfigFactory Network::FilterFactoryCb diff --git a/source/extensions/filters/network/redis_proxy/config.h b/source/extensions/filters/network/redis_proxy/config.h index a499edc6acd1..51562452b0bb 100644 --- a/source/extensions/filters/network/redis_proxy/config.h +++ b/source/extensions/filters/network/redis_proxy/config.h @@ -19,7 +19,7 @@ namespace RedisProxy { class RedisProxyFilterConfigFactory : public Common::FactoryBase { public: - RedisProxyFilterConfigFactory() : FactoryBase(NetworkFilterNames::get().REDIS_PROXY) {} + RedisProxyFilterConfigFactory() : FactoryBase(NetworkFilterNames::get().RedisProxy) {} // NamedNetworkFilterConfigFactory Network::FilterFactoryCb diff --git a/source/extensions/filters/network/tcp_proxy/config.h b/source/extensions/filters/network/tcp_proxy/config.h index 8e7deba8fc12..e5664ed45a7f 100644 --- a/source/extensions/filters/network/tcp_proxy/config.h +++ b/source/extensions/filters/network/tcp_proxy/config.h @@ -16,7 +16,7 @@ namespace TcpProxy { class ConfigFactory : public Common::FactoryBase { public: - ConfigFactory() : FactoryBase(NetworkFilterNames::get().TCP_PROXY) {} + ConfigFactory() : FactoryBase(NetworkFilterNames::get().TcpProxy) {} // NamedNetworkFilterConfigFactory Network::FilterFactoryCb diff --git a/source/extensions/filters/network/thrift_proxy/config.h b/source/extensions/filters/network/thrift_proxy/config.h index d578bd96591a..40aa4b418774 100644 --- a/source/extensions/filters/network/thrift_proxy/config.h +++ b/source/extensions/filters/network/thrift_proxy/config.h @@ -20,7 +20,7 @@ class ThriftProxyFilterConfigFactory : public Common::FactoryBase< envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy> { public: - ThriftProxyFilterConfigFactory() : FactoryBase(NetworkFilterNames::get().THRIFT_PROXY) {} + ThriftProxyFilterConfigFactory() : FactoryBase(NetworkFilterNames::get().ThriftProxy) {} private: Network::FilterFactoryCb createFilterFactoryFromProtoTyped( diff --git a/source/extensions/filters/network/well_known_names.h b/source/extensions/filters/network/well_known_names.h index deffa01fb759..f9014eb0de7c 100644 --- a/source/extensions/filters/network/well_known_names.h +++ b/source/extensions/filters/network/well_known_names.h @@ -13,31 +13,31 @@ namespace NetworkFilters { class NetworkFilterNameValues { public: // Client ssl auth filter - const std::string CLIENT_SSL_AUTH = "envoy.client_ssl_auth"; + const std::string ClientSslAuth = "envoy.client_ssl_auth"; // Echo filter - const std::string ECHO = "envoy.echo"; + const std::string Echo = "envoy.echo"; // HTTP connection manager filter - const std::string HTTP_CONNECTION_MANAGER = "envoy.http_connection_manager"; + const std::string HttpConnectionManager = "envoy.http_connection_manager"; // Mongo proxy filter - const std::string MONGO_PROXY = "envoy.mongo_proxy"; + const std::string MongoProxy = "envoy.mongo_proxy"; // Rate limit filter - const std::string RATE_LIMIT = "envoy.ratelimit"; + const std::string RateLimit = "envoy.ratelimit"; // Redis proxy filter - const std::string REDIS_PROXY = "envoy.redis_proxy"; + const std::string RedisProxy = "envoy.redis_proxy"; // IP tagging filter - const std::string TCP_PROXY = "envoy.tcp_proxy"; + const std::string TcpProxy = "envoy.tcp_proxy"; // Authorization filter - const std::string EXT_AUTHORIZATION = "envoy.ext_authz"; + const std::string ExtAuthorization = "envoy.ext_authz"; // Thrift proxy filter - const std::string THRIFT_PROXY = "envoy.filters.network.thrift_proxy"; + const std::string ThriftProxy = "envoy.filters.network.thrift_proxy"; // Converts names from v1 to v2 const Config::V1Converter v1_converter_; // NOTE: Do not add any new filters to this list. All future filters are v2 only. NetworkFilterNameValues() - : v1_converter_({CLIENT_SSL_AUTH, ECHO, HTTP_CONNECTION_MANAGER, MONGO_PROXY, RATE_LIMIT, - REDIS_PROXY, TCP_PROXY, EXT_AUTHORIZATION}) {} + : v1_converter_({ClientSslAuth, Echo, HttpConnectionManager, MongoProxy, RateLimit, + RedisProxy, TcpProxy, ExtAuthorization}) {} }; typedef ConstSingleton NetworkFilterNames; diff --git a/source/extensions/grpc_credentials/example/config.h b/source/extensions/grpc_credentials/example/config.h index 1313dbd9ac02..053b79335dc3 100644 --- a/source/extensions/grpc_credentials/example/config.h +++ b/source/extensions/grpc_credentials/example/config.h @@ -30,7 +30,7 @@ class AccessTokenExampleGrpcCredentialsFactory : public Grpc::GoogleGrpcCredenti virtual std::shared_ptr getChannelCredentials(const envoy::api::v2::core::GrpcService& grpc_service_config) override; - std::string name() const override { return GrpcCredentialsNames::get().ACCESS_TOKEN_EXAMPLE; } + std::string name() const override { return GrpcCredentialsNames::get().AccessTokenExample; } }; /* diff --git a/source/extensions/grpc_credentials/file_based_metadata/config.cc b/source/extensions/grpc_credentials/file_based_metadata/config.cc index 987827a637de..951f11c6c6ee 100644 --- a/source/extensions/grpc_credentials/file_based_metadata/config.cc +++ b/source/extensions/grpc_credentials/file_based_metadata/config.cc @@ -25,7 +25,7 @@ FileBasedMetadataGrpcCredentialsFactory::getChannelCredentials( for (const auto& credential : google_grpc.call_credentials()) { switch (credential.credential_specifier_case()) { case envoy::api::v2::core::GrpcService::GoogleGrpc::CallCredentials::kFromPlugin: { - if (credential.from_plugin().name() == GrpcCredentialsNames::get().FILE_BASED_METADATA) { + if (credential.from_plugin().name() == GrpcCredentialsNames::get().FileBasedMetadata) { FileBasedMetadataGrpcCredentialsFactory file_based_metadata_credentials_factory; const Envoy::ProtobufTypes::MessagePtr file_based_metadata_config_message = Envoy::Config::Utility::translateToFactoryConfig( diff --git a/source/extensions/grpc_credentials/file_based_metadata/config.h b/source/extensions/grpc_credentials/file_based_metadata/config.h index 1880d62c9ffe..9325e0b7d3d0 100644 --- a/source/extensions/grpc_credentials/file_based_metadata/config.h +++ b/source/extensions/grpc_credentials/file_based_metadata/config.h @@ -30,7 +30,7 @@ class FileBasedMetadataGrpcCredentialsFactory : public Grpc::GoogleGrpcCredentia return std::make_unique(); } - std::string name() const override { return GrpcCredentialsNames::get().FILE_BASED_METADATA; } + std::string name() const override { return GrpcCredentialsNames::get().FileBasedMetadata; } }; class FileBasedMetadataAuthenticator : public grpc::MetadataCredentialsPlugin { diff --git a/source/extensions/grpc_credentials/well_known_names.h b/source/extensions/grpc_credentials/well_known_names.h index 95678f1edbdc..81ee6d22ef0b 100644 --- a/source/extensions/grpc_credentials/well_known_names.h +++ b/source/extensions/grpc_credentials/well_known_names.h @@ -13,9 +13,9 @@ namespace GrpcCredentials { class GrpcCredentialsNameValues { public: // Access Token Example. - const std::string ACCESS_TOKEN_EXAMPLE = "envoy.grpc_credentials.access_token_example"; + const std::string AccessTokenExample = "envoy.grpc_credentials.access_token_example"; // File Based Metadata credentials - const std::string FILE_BASED_METADATA = "envoy.grpc_credentials.file_based_metadata"; + const std::string FileBasedMetadata = "envoy.grpc_credentials.file_based_metadata"; }; typedef ConstSingleton GrpcCredentialsNames; diff --git a/source/extensions/health_checkers/redis/config.h b/source/extensions/health_checkers/redis/config.h index 1f15a8f44d16..534496050c05 100644 --- a/source/extensions/health_checkers/redis/config.h +++ b/source/extensions/health_checkers/redis/config.h @@ -19,7 +19,7 @@ class RedisHealthCheckerFactory : public Server::Configuration::CustomHealthChec createCustomHealthChecker(const envoy::api::v2::core::HealthCheck& config, Server::Configuration::HealthCheckerFactoryContext& context) override; - std::string name() override { return HealthCheckerNames::get().REDIS_HEALTH_CHECKER; } + std::string name() override { return HealthCheckerNames::get().RedisHealthChecker; } }; } // namespace RedisHealthChecker diff --git a/source/extensions/health_checkers/well_known_names.h b/source/extensions/health_checkers/well_known_names.h index 9120d9cfd6b8..26271e859b6a 100644 --- a/source/extensions/health_checkers/well_known_names.h +++ b/source/extensions/health_checkers/well_known_names.h @@ -13,7 +13,7 @@ namespace HealthCheckers { class HealthCheckerNameValues { public: // Redis health checker. - const std::string REDIS_HEALTH_CHECKER = "envoy.health_checkers.redis"; + const std::string RedisHealthChecker = "envoy.health_checkers.redis"; }; typedef ConstSingleton HealthCheckerNames; diff --git a/source/extensions/stat_sinks/dog_statsd/config.cc b/source/extensions/stat_sinks/dog_statsd/config.cc index 4e8a4b7d32bb..9ae4436ba5ea 100644 --- a/source/extensions/stat_sinks/dog_statsd/config.cc +++ b/source/extensions/stat_sinks/dog_statsd/config.cc @@ -30,7 +30,7 @@ ProtobufTypes::MessagePtr DogStatsdSinkFactory::createEmptyConfigProto() { new envoy::config::metrics::v2::DogStatsdSink()); } -std::string DogStatsdSinkFactory::name() { return StatsSinkNames::get().DOG_STATSD; } +std::string DogStatsdSinkFactory::name() { return StatsSinkNames::get().DogStatsd; } /** * Static registration for the this sink factory. @see RegisterFactory. diff --git a/source/extensions/stat_sinks/hystrix/config.cc b/source/extensions/stat_sinks/hystrix/config.cc index 736c0811be8c..59abdb0d29c3 100644 --- a/source/extensions/stat_sinks/hystrix/config.cc +++ b/source/extensions/stat_sinks/hystrix/config.cc @@ -26,7 +26,7 @@ ProtobufTypes::MessagePtr HystrixSinkFactory::createEmptyConfigProto() { new envoy::config::metrics::v2::HystrixSink()); } -std::string HystrixSinkFactory::name() { return StatsSinkNames::get().HYSTRIX; } +std::string HystrixSinkFactory::name() { return StatsSinkNames::get().Hystrix; } /** * Static registration for the statsd sink factory. @see RegisterFactory. diff --git a/source/extensions/stat_sinks/metrics_service/config.cc b/source/extensions/stat_sinks/metrics_service/config.cc index 4020c35f9b45..234b46b37740 100644 --- a/source/extensions/stat_sinks/metrics_service/config.cc +++ b/source/extensions/stat_sinks/metrics_service/config.cc @@ -37,7 +37,7 @@ ProtobufTypes::MessagePtr MetricsServiceSinkFactory::createEmptyConfigProto() { std::make_unique()); } -std::string MetricsServiceSinkFactory::name() { return StatsSinkNames::get().METRICS_SERVICE; } +std::string MetricsServiceSinkFactory::name() { return StatsSinkNames::get().MetricsService; } /** * Static registration for the this sink factory. @see RegisterFactory. diff --git a/source/extensions/stat_sinks/statsd/config.cc b/source/extensions/stat_sinks/statsd/config.cc index 38c19257f1e0..ddb28d0d0d08 100644 --- a/source/extensions/stat_sinks/statsd/config.cc +++ b/source/extensions/stat_sinks/statsd/config.cc @@ -43,7 +43,7 @@ ProtobufTypes::MessagePtr StatsdSinkFactory::createEmptyConfigProto() { new envoy::config::metrics::v2::StatsdSink()); } -std::string StatsdSinkFactory::name() { return StatsSinkNames::get().STATSD; } +std::string StatsdSinkFactory::name() { return StatsSinkNames::get().Statsd; } /** * Static registration for the statsd sink factory. @see RegisterFactory. diff --git a/source/extensions/stat_sinks/well_known_names.h b/source/extensions/stat_sinks/well_known_names.h index 033f40a2334e..8b4b6022c867 100644 --- a/source/extensions/stat_sinks/well_known_names.h +++ b/source/extensions/stat_sinks/well_known_names.h @@ -13,13 +13,13 @@ namespace StatSinks { class StatsSinkNameValues { public: // Statsd sink - const std::string STATSD = "envoy.statsd"; + const std::string Statsd = "envoy.statsd"; // DogStatsD compatible stastsd sink - const std::string DOG_STATSD = "envoy.dog_statsd"; + const std::string DogStatsd = "envoy.dog_statsd"; // MetricsService sink - const std::string METRICS_SERVICE = "envoy.metrics_service"; + const std::string MetricsService = "envoy.metrics_service"; // Hystrix sink - const std::string HYSTRIX = "envoy.stat_sinks.hystrix"; + const std::string Hystrix = "envoy.stat_sinks.hystrix"; }; typedef ConstSingleton StatsSinkNames; diff --git a/source/extensions/tracers/dynamic_ot/config.cc b/source/extensions/tracers/dynamic_ot/config.cc index 0c94a4be4f9b..b82f3f8e8b8c 100644 --- a/source/extensions/tracers/dynamic_ot/config.cc +++ b/source/extensions/tracers/dynamic_ot/config.cc @@ -23,7 +23,7 @@ DynamicOpenTracingTracerFactory::createHttpTracer(const Json::Object& json_confi return std::make_unique(std::move(dynamic_driver), server.localInfo()); } -std::string DynamicOpenTracingTracerFactory::name() { return TracerNames::get().DYNAMIC_OT; } +std::string DynamicOpenTracingTracerFactory::name() { return TracerNames::get().DynamicOt; } /** * Static registration for the dynamic opentracing tracer. @see RegisterFactory. diff --git a/source/extensions/tracers/lightstep/config.cc b/source/extensions/tracers/lightstep/config.cc index 22a1b83428d4..e1b1c5a947b3 100644 --- a/source/extensions/tracers/lightstep/config.cc +++ b/source/extensions/tracers/lightstep/config.cc @@ -31,7 +31,7 @@ Tracing::HttpTracerPtr LightstepTracerFactory::createHttpTracer(const Json::Obje return std::make_unique(std::move(lightstep_driver), server.localInfo()); } -std::string LightstepTracerFactory::name() { return TracerNames::get().LIGHTSTEP; } +std::string LightstepTracerFactory::name() { return TracerNames::get().Lightstep; } /** * Static registration for the lightstep tracer. @see RegisterFactory. diff --git a/source/extensions/tracers/well_known_names.h b/source/extensions/tracers/well_known_names.h index 630649a7d0b4..d545eecedad9 100644 --- a/source/extensions/tracers/well_known_names.h +++ b/source/extensions/tracers/well_known_names.h @@ -13,11 +13,11 @@ namespace Tracers { class TracerNameValues { public: // Lightstep tracer - const std::string LIGHTSTEP = "envoy.lightstep"; + const std::string Lightstep = "envoy.lightstep"; // Zipkin tracer - const std::string ZIPKIN = "envoy.zipkin"; + const std::string Zipkin = "envoy.zipkin"; // Dynamic tracer - const std::string DYNAMIC_OT = "envoy.dynamic.ot"; + const std::string DynamicOt = "envoy.dynamic.ot"; }; typedef ConstSingleton TracerNames; diff --git a/source/extensions/tracers/zipkin/config.cc b/source/extensions/tracers/zipkin/config.cc index da0562478ffe..278aebadfbc4 100644 --- a/source/extensions/tracers/zipkin/config.cc +++ b/source/extensions/tracers/zipkin/config.cc @@ -26,7 +26,7 @@ Tracing::HttpTracerPtr ZipkinTracerFactory::createHttpTracer(const Json::Object& new Tracing::HttpTracerImpl(std::move(zipkin_driver), server.localInfo())); } -std::string ZipkinTracerFactory::name() { return TracerNames::get().ZIPKIN; } +std::string ZipkinTracerFactory::name() { return TracerNames::get().Zipkin; } /** * Static registration for the lightstep tracer. @see RegisterFactory. diff --git a/source/extensions/transport_sockets/capture/config.h b/source/extensions/transport_sockets/capture/config.h index c844430a3f35..02b7ae481b74 100644 --- a/source/extensions/transport_sockets/capture/config.h +++ b/source/extensions/transport_sockets/capture/config.h @@ -17,7 +17,7 @@ class CaptureSocketConfigFactory : public virtual Server::Configuration::TransportSocketConfigFactory { public: virtual ~CaptureSocketConfigFactory() {} - std::string name() const override { return TransportSocketNames::get().CAPTURE; } + std::string name() const override { return TransportSocketNames::get().Capture; } ProtobufTypes::MessagePtr createEmptyConfigProto() override; }; diff --git a/source/extensions/transport_sockets/raw_buffer/config.h b/source/extensions/transport_sockets/raw_buffer/config.h index 20f5bc06f9f2..e4f909be49a2 100644 --- a/source/extensions/transport_sockets/raw_buffer/config.h +++ b/source/extensions/transport_sockets/raw_buffer/config.h @@ -16,7 +16,7 @@ namespace RawBuffer { class RawBufferSocketFactory : public virtual Server::Configuration::TransportSocketConfigFactory { public: virtual ~RawBufferSocketFactory() {} - std::string name() const override { return TransportSocketNames::get().RAW_BUFFER; } + std::string name() const override { return TransportSocketNames::get().RawBuffer; } ProtobufTypes::MessagePtr createEmptyConfigProto() override; }; diff --git a/source/extensions/transport_sockets/ssl/config.h b/source/extensions/transport_sockets/ssl/config.h index 1b5a84dea64e..5fba4de55f6d 100644 --- a/source/extensions/transport_sockets/ssl/config.h +++ b/source/extensions/transport_sockets/ssl/config.h @@ -16,7 +16,7 @@ namespace SslTransport { class SslSocketConfigFactory : public virtual Server::Configuration::TransportSocketConfigFactory { public: virtual ~SslSocketConfigFactory() {} - std::string name() const override { return TransportSocketNames::get().TLS; } + std::string name() const override { return TransportSocketNames::get().Tls; } }; class UpstreamSslSocketFactory : public Server::Configuration::UpstreamTransportSocketConfigFactory, diff --git a/source/extensions/transport_sockets/well_known_names.h b/source/extensions/transport_sockets/well_known_names.h index 2fd92d7cea9d..0cf096fe30e4 100644 --- a/source/extensions/transport_sockets/well_known_names.h +++ b/source/extensions/transport_sockets/well_known_names.h @@ -12,9 +12,9 @@ namespace TransportSockets { */ class TransportSocketNameValues { public: - const std::string CAPTURE = "envoy.transport_sockets.capture"; - const std::string RAW_BUFFER = "raw_buffer"; - const std::string TLS = "tls"; + const std::string Capture = "envoy.transport_sockets.capture"; + const std::string RawBuffer = "raw_buffer"; + const std::string Tls = "tls"; }; typedef ConstSingleton TransportSocketNames; diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc index 854180cd4914..f37255621d75 100644 --- a/source/server/connection_handler_impl.cc +++ b/source/server/connection_handler_impl.cc @@ -153,7 +153,7 @@ void ConnectionHandlerImpl::ActiveSocket::continueFilterChain(bool success) { // Set default transport protocol if none of the listener filters did it. if (socket_->detectedTransportProtocol().empty()) { socket_->setDetectedTransportProtocol( - Extensions::TransportSockets::TransportSocketNames::get().RAW_BUFFER); + Extensions::TransportSockets::TransportSocketNames::get().RawBuffer); } // Create a new connection on this listener. listener_.newConnection(std::move(socket_)); diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 84ecdac8f911..393fdfe1e35e 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -154,7 +154,7 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st if (PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, use_original_dst, false)) { auto& factory = Config::Utility::getAndCheckFactory( - Extensions::ListenerFilters::ListenerFilterNames::get().ORIGINAL_DST); + Extensions::ListenerFilters::ListenerFilterNames::get().OriginalDst); listener_filter_factories_.push_back( factory.createFilterFactoryFromProto(Envoy::ProtobufWkt::Empty(), *this)); } @@ -165,7 +165,7 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st if (PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.filter_chains()[0], use_proxy_proto, false)) { auto& factory = Config::Utility::getAndCheckFactory( - Extensions::ListenerFilters::ListenerFilterNames::get().PROXY_PROTOCOL); + Extensions::ListenerFilters::ListenerFilterNames::get().ProxyProtocol); listener_filter_factories_.push_back( factory.createFilterFactoryFromProto(Envoy::ProtobufWkt::Empty(), *this)); } @@ -189,11 +189,11 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st auto transport_socket = filter_chain.transport_socket(); if (!filter_chain.has_transport_socket()) { if (filter_chain.has_tls_context()) { - transport_socket.set_name(Extensions::TransportSockets::TransportSocketNames::get().TLS); + transport_socket.set_name(Extensions::TransportSockets::TransportSocketNames::get().Tls); MessageUtil::jsonConvert(filter_chain.tls_context(), *transport_socket.mutable_config()); } else { transport_socket.set_name( - Extensions::TransportSockets::TransportSocketNames::get().RAW_BUFFER); + Extensions::TransportSockets::TransportSocketNames::get().RawBuffer); } } @@ -257,7 +257,7 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st // Automatically inject TLS Inspector if it wasn't configured explicitly and it's needed. if (need_tls_inspector) { for (const auto& filter : config.listener_filters()) { - if (filter.name() == Extensions::ListenerFilters::ListenerFilterNames::get().TLS_INSPECTOR) { + if (filter.name() == Extensions::ListenerFilters::ListenerFilterNames::get().TlsInspector) { need_tls_inspector = false; break; } @@ -272,7 +272,7 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st auto& factory = Config::Utility::getAndCheckFactory( - Extensions::ListenerFilters::ListenerFilterNames::get().TLS_INSPECTOR); + Extensions::ListenerFilters::ListenerFilterNames::get().TlsInspector); listener_filter_factories_.push_back( factory.createFilterFactoryFromProto(Envoy::ProtobufWkt::Empty(), *this)); } diff --git a/test/common/grpc/grpc_client_integration_test.cc b/test/common/grpc/grpc_client_integration_test.cc index 163089293b04..65046f2052d7 100644 --- a/test/common/grpc/grpc_client_integration_test.cc +++ b/test/common/grpc/grpc_client_integration_test.cc @@ -396,7 +396,7 @@ TEST_P(GrpcAccessTokenClientIntegrationTest, AccessTokenAuthRequest) { SKIP_IF_GRPC_CLIENT(ClientType::EnvoyGrpc); access_token_value_ = "accesstokenvalue"; credentials_factory_name_ = - Extensions::GrpcCredentials::GrpcCredentialsNames::get().ACCESS_TOKEN_EXAMPLE; + Extensions::GrpcCredentials::GrpcCredentialsNames::get().AccessTokenExample; initialize(); auto request = createRequest(empty_metadata_); request->sendReply(); @@ -408,7 +408,7 @@ TEST_P(GrpcAccessTokenClientIntegrationTest, AccessTokenAuthStream) { SKIP_IF_GRPC_CLIENT(ClientType::EnvoyGrpc); access_token_value_ = "accesstokenvalue"; credentials_factory_name_ = - Extensions::GrpcCredentials::GrpcCredentialsNames::get().ACCESS_TOKEN_EXAMPLE; + Extensions::GrpcCredentials::GrpcCredentialsNames::get().AccessTokenExample; initialize(); auto stream = createStream(empty_metadata_); stream->sendServerInitialMetadata(empty_metadata_); @@ -424,7 +424,7 @@ TEST_P(GrpcAccessTokenClientIntegrationTest, MultipleAccessTokens) { access_token_value_ = "accesstokenvalue"; access_token_value_2_ = "accesstokenvalue2"; credentials_factory_name_ = - Extensions::GrpcCredentials::GrpcCredentialsNames::get().ACCESS_TOKEN_EXAMPLE; + Extensions::GrpcCredentials::GrpcCredentialsNames::get().AccessTokenExample; initialize(); auto request = createRequest(empty_metadata_); request->sendReply(); @@ -437,7 +437,7 @@ TEST_P(GrpcAccessTokenClientIntegrationTest, ExtraCredentialParams) { access_token_value_ = "accesstokenvalue"; refresh_token_value_ = "refreshtokenvalue"; credentials_factory_name_ = - Extensions::GrpcCredentials::GrpcCredentialsNames::get().ACCESS_TOKEN_EXAMPLE; + Extensions::GrpcCredentials::GrpcCredentialsNames::get().AccessTokenExample; initialize(); auto request = createRequest(empty_metadata_); request->sendReply(); @@ -448,7 +448,7 @@ TEST_P(GrpcAccessTokenClientIntegrationTest, ExtraCredentialParams) { TEST_P(GrpcAccessTokenClientIntegrationTest, NoAccessTokens) { SKIP_IF_GRPC_CLIENT(ClientType::EnvoyGrpc); credentials_factory_name_ = - Extensions::GrpcCredentials::GrpcCredentialsNames::get().ACCESS_TOKEN_EXAMPLE; + Extensions::GrpcCredentials::GrpcCredentialsNames::get().AccessTokenExample; initialize(); auto request = createRequest(empty_metadata_); request->sendReply(); diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index b8c61ed9f7c7..9fc16635fef6 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -315,7 +315,7 @@ TEST(ConfigTest, EmptyRouteConfig) { TEST(ConfigTest, AccessLogConfig) { envoy::config::filter::network::tcp_proxy::v2::TcpProxy config; envoy::config::filter::accesslog::v2::AccessLog* log = config.mutable_access_log()->Add(); - log->set_name(Extensions::AccessLoggers::AccessLogNames::get().FILE); + log->set_name(Extensions::AccessLoggers::AccessLogNames::get().File); { envoy::config::accesslog::v2::FileAccessLog file_access_log; file_access_log.set_path("some_path"); @@ -325,7 +325,7 @@ TEST(ConfigTest, AccessLogConfig) { } log = config.mutable_access_log()->Add(); - log->set_name(Extensions::AccessLoggers::AccessLogNames::get().FILE); + log->set_name(Extensions::AccessLoggers::AccessLogNames::get().File); { envoy::config::accesslog::v2::FileAccessLog file_access_log; file_access_log.set_path("another path"); @@ -365,7 +365,7 @@ class TcpProxyTest : public testing::Test { envoy::config::filter::network::tcp_proxy::v2::TcpProxy config = defaultConfig(); envoy::config::filter::accesslog::v2::AccessLog* access_log = config.mutable_access_log()->Add(); - access_log->set_name(Extensions::AccessLoggers::AccessLogNames::get().FILE); + access_log->set_name(Extensions::AccessLoggers::AccessLogNames::get().File); envoy::config::accesslog::v2::FileAccessLog file_access_log; file_access_log.set_path("unused"); file_access_log.set_format(access_log_format); diff --git a/test/extensions/access_loggers/file/config_test.cc b/test/extensions/access_loggers/file/config_test.cc index 6e7facb54c4b..7bd56fea2110 100644 --- a/test/extensions/access_loggers/file/config_test.cc +++ b/test/extensions/access_loggers/file/config_test.cc @@ -37,7 +37,7 @@ TEST(FileAccessLogConfigTest, ConfigureFromProto) { EXPECT_THROW_WITH_MESSAGE(AccessLog::AccessLogFactory::fromProto(config, context), EnvoyException, "Provided name for static registration lookup was empty."); - config.set_name(AccessLogNames::get().FILE); + config.set_name(AccessLogNames::get().File); AccessLog::InstanceSharedPtr log = AccessLog::AccessLogFactory::fromProto(config, context); @@ -53,7 +53,7 @@ TEST(FileAccessLogConfigTest, ConfigureFromProto) { TEST(FileAccessLogConfigTest, FileAccessLogTest) { auto factory = Registry::FactoryRegistry::getFactory( - AccessLogNames::get().FILE); + AccessLogNames::get().File); ASSERT_NE(nullptr, factory); ProtobufTypes::MessagePtr message = factory->createEmptyConfigProto(); diff --git a/test/extensions/access_loggers/http_grpc/config_test.cc b/test/extensions/access_loggers/http_grpc/config_test.cc index c04331e52c1c..04bc4049d34c 100644 --- a/test/extensions/access_loggers/http_grpc/config_test.cc +++ b/test/extensions/access_loggers/http_grpc/config_test.cc @@ -23,7 +23,7 @@ class HttpGrpcAccessLogConfigTest : public testing::Test { void SetUp() override { factory_ = Registry::FactoryRegistry::getFactory( - AccessLogNames::get().HTTP_GRPC); + AccessLogNames::get().HttpGrpc); ASSERT_NE(nullptr, factory_); message_ = factory_->createEmptyConfigProto(); diff --git a/test/extensions/filters/http/buffer/buffer_filter_test.cc b/test/extensions/filters/http/buffer/buffer_filter_test.cc index b1749f70cbf4..6a2cf88b6dde 100644 --- a/test/extensions/filters/http/buffer/buffer_filter_test.cc +++ b/test/extensions/filters/http/buffer/buffer_filter_test.cc @@ -46,10 +46,10 @@ class BufferFilterTest : public testing::Test { void routeLocalConfig(const Router::RouteSpecificFilterConfig* route_settings, const Router::RouteSpecificFilterConfig* vhost_settings) { - ON_CALL(callbacks_.route_->route_entry_, perFilterConfig(HttpFilterNames::get().BUFFER)) + ON_CALL(callbacks_.route_->route_entry_, perFilterConfig(HttpFilterNames::get().Buffer)) .WillByDefault(Return(route_settings)); ON_CALL(callbacks_.route_->route_entry_.virtual_host_, - perFilterConfig(HttpFilterNames::get().BUFFER)) + perFilterConfig(HttpFilterNames::get().Buffer)) .WillByDefault(Return(vhost_settings)); } diff --git a/test/extensions/filters/http/fault/fault_filter_test.cc b/test/extensions/filters/http/fault/fault_filter_test.cc index c231a6796bd1..420c065208b5 100644 --- a/test/extensions/filters/http/fault/fault_filter_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_test.cc @@ -756,10 +756,10 @@ void FaultFilterTest::TestPerFilterConfigFault( const Router::RouteSpecificFilterConfig* vhost_fault) { ON_CALL(filter_callbacks_.route_->route_entry_, - perFilterConfig(Extensions::HttpFilters::HttpFilterNames::get().FAULT)) + perFilterConfig(Extensions::HttpFilters::HttpFilterNames::get().Fault)) .WillByDefault(Return(route_fault)); ON_CALL(filter_callbacks_.route_->route_entry_.virtual_host_, - perFilterConfig(Extensions::HttpFilters::HttpFilterNames::get().FAULT)) + perFilterConfig(Extensions::HttpFilters::HttpFilterNames::get().Fault)) .WillByDefault(Return(vhost_fault)); const std::string upstream_cluster("www1"); diff --git a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc index b6a2870014c2..283481bdd482 100644 --- a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc @@ -26,7 +26,7 @@ std::string getFilterConfig(bool use_local_jwks) { } HttpFilter filter; - filter.set_name(HttpFilterNames::get().JWT_AUTHN); + filter.set_name(HttpFilterNames::get().JwtAuthn); MessageUtil::jsonConvert(proto_config, *filter.mutable_config()); return MessageUtil::getJsonStringFromMessage(filter); } diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index 353e40b82e06..83b877e378c0 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -45,7 +45,7 @@ class LuaIntegrationTest : public HttpIntegrationTest, new_route->mutable_match()->set_prefix("/alt/route"); new_route->mutable_route()->set_cluster("alt_cluster"); - const std::string key = Extensions::HttpFilters::HttpFilterNames::get().LUA; + const std::string key = Extensions::HttpFilters::HttpFilterNames::get().Lua; const std::string yaml = R"EOF( foo.bar: diff --git a/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc b/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc index fd41f57beb8e..9e766f7bc4f5 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc @@ -77,7 +77,7 @@ TEST_P(RBACIntegrationTest, RouteOverride) { ->Mutable(0) ->mutable_per_filter_config(); - (*config)[Extensions::HttpFilters::HttpFilterNames::get().RBAC] = pfc; + (*config)[Extensions::HttpFilters::HttpFilterNames::get().Rbac] = pfc; }); config_helper_.addFilter(RBAC_CONFIG); diff --git a/test/extensions/filters/http/rbac/rbac_filter_test.cc b/test/extensions/filters/http/rbac/rbac_filter_test.cc index 68f39ef1ea2b..7e1d89ff852b 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_test.cc @@ -101,7 +101,7 @@ TEST_F(RoleBasedAccessControlFilterTest, RouteLocalOverride) { EXPECT_CALL(engine, allowed(_, _, _)).WillRepeatedly(Return(true)); EXPECT_CALL(per_route_config_, engine()).WillRepeatedly(ReturnRef(engine)); - EXPECT_CALL(callbacks_.route_->route_entry_, perFilterConfig(HttpFilterNames::get().RBAC)) + EXPECT_CALL(callbacks_.route_->route_entry_, perFilterConfig(HttpFilterNames::get().Rbac)) .WillRepeatedly(Return(&per_route_config_)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(headers_, true)); diff --git a/test/extensions/filters/http/router/config_test.cc b/test/extensions/filters/http/router/config_test.cc index fe6943aa4041..89732808e578 100644 --- a/test/extensions/filters/http/router/config_test.cc +++ b/test/extensions/filters/http/router/config_test.cc @@ -73,7 +73,7 @@ TEST(RouterFilterConfigTest, DoubleRegistrationTest) { (Registry::RegisterFactory()), EnvoyException, - fmt::format("Double registration for name: '{}'", HttpFilterNames::get().ROUTER)); + fmt::format("Double registration for name: '{}'", HttpFilterNames::get().Router)); } } // namespace RouterFilter diff --git a/test/extensions/filters/network/client_ssl_auth/config_test.cc b/test/extensions/filters/network/client_ssl_auth/config_test.cc index a3c19a211087..d815680aec90 100644 --- a/test/extensions/filters/network/client_ssl_auth/config_test.cc +++ b/test/extensions/filters/network/client_ssl_auth/config_test.cc @@ -99,7 +99,7 @@ TEST(ClientSslAuthConfigFactoryTest, DoubleRegistrationTest) { (Registry::RegisterFactory()), EnvoyException, - fmt::format("Double registration for name: '{}'", NetworkFilterNames::get().CLIENT_SSL_AUTH)); + fmt::format("Double registration for name: '{}'", NetworkFilterNames::get().ClientSslAuth)); } } // namespace ClientSslAuth diff --git a/test/extensions/grpc_credentials/file_based_metadata/file_based_metadata_grpc_credentials_test.cc b/test/extensions/grpc_credentials/file_based_metadata/file_based_metadata_grpc_credentials_test.cc index dec34bb1c9ab..40e10ab1b5e2 100644 --- a/test/extensions/grpc_credentials/file_based_metadata/file_based_metadata_grpc_credentials_test.cc +++ b/test/extensions/grpc_credentials/file_based_metadata/file_based_metadata_grpc_credentials_test.cc @@ -86,7 +86,7 @@ TEST_P(GrpcFileBasedMetadataClientIntegrationTest, FileBasedMetadataGrpcAuthRequ header_prefix_1_ = "prefix1"; header_value_1_ = "secretvalue"; credentials_factory_name_ = - Extensions::GrpcCredentials::GrpcCredentialsNames::get().FILE_BASED_METADATA; + Extensions::GrpcCredentials::GrpcCredentialsNames::get().FileBasedMetadata; initialize(); auto request = createRequest(empty_metadata_); request->sendReply(); @@ -101,7 +101,7 @@ TEST_P(GrpcFileBasedMetadataClientIntegrationTest, DoubleFileBasedMetadataGrpcAu header_value_1_ = "secretvalue"; header_value_2_ = "secret2"; credentials_factory_name_ = - Extensions::GrpcCredentials::GrpcCredentialsNames::get().FILE_BASED_METADATA; + Extensions::GrpcCredentials::GrpcCredentialsNames::get().FileBasedMetadata; initialize(); auto request = createRequest(empty_metadata_); request->sendReply(); @@ -112,7 +112,7 @@ TEST_P(GrpcFileBasedMetadataClientIntegrationTest, DoubleFileBasedMetadataGrpcAu TEST_P(GrpcFileBasedMetadataClientIntegrationTest, EmptyFileBasedMetadataGrpcAuthRequest) { SKIP_IF_GRPC_CLIENT(ClientType::EnvoyGrpc); credentials_factory_name_ = - Extensions::GrpcCredentials::GrpcCredentialsNames::get().FILE_BASED_METADATA; + Extensions::GrpcCredentials::GrpcCredentialsNames::get().FileBasedMetadata; initialize(); auto request = createRequest(empty_metadata_); request->sendReply(); @@ -127,7 +127,7 @@ TEST_P(GrpcFileBasedMetadataClientIntegrationTest, ExtraConfigFileBasedMetadataG header_prefix_1_ = "prefix1"; header_value_1_ = "secretvalue"; credentials_factory_name_ = - Extensions::GrpcCredentials::GrpcCredentialsNames::get().FILE_BASED_METADATA; + Extensions::GrpcCredentials::GrpcCredentialsNames::get().FileBasedMetadata; initialize(); auto request = createRequest(empty_metadata_); request->sendReply(); diff --git a/test/extensions/stats_sinks/dog_statsd/config_test.cc b/test/extensions/stats_sinks/dog_statsd/config_test.cc index d3afecfa8f99..c54a1687772f 100644 --- a/test/extensions/stats_sinks/dog_statsd/config_test.cc +++ b/test/extensions/stats_sinks/dog_statsd/config_test.cc @@ -32,7 +32,7 @@ INSTANTIATE_TEST_CASE_P(IpVersions, DogStatsdConfigLoopbackTest, TestUtility::ipTestParamsToString); TEST_P(DogStatsdConfigLoopbackTest, ValidUdpIp) { - const std::string name = StatsSinkNames::get().DOG_STATSD; + const std::string name = StatsSinkNames::get().DogStatsd; envoy::config::metrics::v2::DogStatsdSink sink_config; envoy::api::v2::core::Address& address = *sink_config.mutable_address(); diff --git a/test/extensions/stats_sinks/hystrix/config_test.cc b/test/extensions/stats_sinks/hystrix/config_test.cc index 520b36ae51a2..ec224c4ddb91 100644 --- a/test/extensions/stats_sinks/hystrix/config_test.cc +++ b/test/extensions/stats_sinks/hystrix/config_test.cc @@ -26,7 +26,7 @@ namespace StatSinks { namespace Hystrix { TEST(StatsConfigTest, ValidHystrixSink) { - const std::string name = StatsSinkNames::get().HYSTRIX; + const std::string name = StatsSinkNames::get().Hystrix; envoy::config::metrics::v2::HystrixSink sink_config; diff --git a/test/extensions/stats_sinks/statsd/config_test.cc b/test/extensions/stats_sinks/statsd/config_test.cc index 68b31829242d..fc4eeee983c4 100644 --- a/test/extensions/stats_sinks/statsd/config_test.cc +++ b/test/extensions/stats_sinks/statsd/config_test.cc @@ -28,7 +28,7 @@ namespace StatSinks { namespace Statsd { TEST(StatsConfigTest, ValidTcpStatsd) { - const std::string name = StatsSinkNames::get().STATSD; + const std::string name = StatsSinkNames::get().Statsd; envoy::config::metrics::v2::StatsdSink sink_config; sink_config.set_tcp_cluster_name("fake_cluster"); @@ -53,7 +53,7 @@ INSTANTIATE_TEST_CASE_P(IpVersions, StatsConfigParameterizedTest, TestUtility::ipTestParamsToString); TEST_P(StatsConfigParameterizedTest, UdpSinkDefaultPrefix) { - const std::string name = StatsSinkNames::get().STATSD; + const std::string name = StatsSinkNames::get().Statsd; auto defaultPrefix = Common::Statsd::getDefaultPrefix(); envoy::config::metrics::v2::StatsdSink sink_config; @@ -84,7 +84,7 @@ TEST_P(StatsConfigParameterizedTest, UdpSinkDefaultPrefix) { } TEST_P(StatsConfigParameterizedTest, UdpSinkCustomPrefix) { - const std::string name = StatsSinkNames::get().STATSD; + const std::string name = StatsSinkNames::get().Statsd; const std::string customPrefix = "prefix.test"; envoy::config::metrics::v2::StatsdSink sink_config; @@ -116,7 +116,7 @@ TEST_P(StatsConfigParameterizedTest, UdpSinkCustomPrefix) { } TEST(StatsConfigTest, TcpSinkDefaultPrefix) { - const std::string name = StatsSinkNames::get().STATSD; + const std::string name = StatsSinkNames::get().Statsd; envoy::config::metrics::v2::StatsdSink sink_config; auto defaultPrefix = Common::Statsd::getDefaultPrefix(); @@ -139,7 +139,7 @@ TEST(StatsConfigTest, TcpSinkDefaultPrefix) { } TEST(StatsConfigTest, TcpSinkCustomPrefix) { - const std::string name = StatsSinkNames::get().STATSD; + const std::string name = StatsSinkNames::get().Statsd; envoy::config::metrics::v2::StatsdSink sink_config; ProtobufTypes::String prefix = "prefixTest"; @@ -169,7 +169,7 @@ INSTANTIATE_TEST_CASE_P(IpVersions, StatsConfigLoopbackTest, TestUtility::ipTestParamsToString); TEST_P(StatsConfigLoopbackTest, ValidUdpIpStatsd) { - const std::string name = StatsSinkNames::get().STATSD; + const std::string name = StatsSinkNames::get().Statsd; envoy::config::metrics::v2::StatsdSink sink_config; envoy::api::v2::core::Address& address = *sink_config.mutable_address(); diff --git a/test/server/configuration_impl_test.cc b/test/server/configuration_impl_test.cc index bec9bb6d0567..eb18e1b86288 100644 --- a/test/server/configuration_impl_test.cc +++ b/test/server/configuration_impl_test.cc @@ -242,7 +242,7 @@ TEST_F(ConfigurationImplTest, ProtoSpecifiedStatsSink) { envoy::config::bootstrap::v2::Bootstrap bootstrap = TestUtility::parseBootstrapFromJson(json); auto& sink = *bootstrap.mutable_stats_sinks()->Add(); - sink.set_name(Extensions::StatSinks::StatsSinkNames::get().STATSD); + sink.set_name(Extensions::StatSinks::StatsSinkNames::get().Statsd); auto& field_map = *sink.mutable_config()->mutable_fields(); field_map["tcp_cluster_name"].set_string_value("fake_cluster"); From 3a56d213b6e327a9ad184a1083b9b5f32023a0c4 Mon Sep 17 00:00:00 2001 From: Stephan Zuercher Date: Wed, 18 Jul 2018 17:28:14 -0700 Subject: [PATCH 02/46] coverage: remove deprecated NOT_IMPLEMENTED (#3889) Removes NOT_IMPLEMENTED in favor of NOT_IMPLEMENTED_GCOVR_EXCL_LINE now that envoy-filter-example has been updated. Also adds a test case to hit some uncovered code in NamedHttpFilterConfigFactory. Risk Level: low, no functional changes Testing: unit testing Docs Changes: n/a Release Notes: n/a Signed-off-by: Stephan Zuercher --- ci/build_setup.sh | 2 +- source/common/common/assert.h | 3 -- test/common/router/config_impl_test.cc | 46 +++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/ci/build_setup.sh b/ci/build_setup.sh index ec5b4579799e..dba93c0dd78d 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -87,7 +87,7 @@ if [ "$1" != "-nofetch" ]; then fi # This is the hash on https://github.com/envoyproxy/envoy-filter-example.git we pin to. - (cd "${ENVOY_FILTER_EXAMPLE_SRCDIR}" && git fetch origin && git checkout -f 92307d723a1ead25c39f025a734fa091443efdbc) + (cd "${ENVOY_FILTER_EXAMPLE_SRCDIR}" && git fetch origin && git checkout -f 3e5b73305b961526ffcee7584251692a9a3ce4b3) cp -f "${ENVOY_SRCDIR}"/ci/WORKSPACE.filter.example "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/WORKSPACE fi diff --git a/source/common/common/assert.h b/source/common/common/assert.h index 52e587d817f6..9d4f4c527521 100644 --- a/source/common/common/assert.h +++ b/source/common/common/assert.h @@ -53,9 +53,6 @@ namespace Envoy { // reports. #define NOT_IMPLEMENTED_GCOVR_EXCL_LINE PANIC("not implemented") -// Deprecated: use NOT_IMPLEMENTED_GCOVR_EXCL_LINE instead. -#define NOT_IMPLEMENTED NOT_IMPLEMENTED_GCOVR_EXCL_LINE - // NOT_REACHED_GCOVR_EXCL_LINE is for spots the compiler insists on having a return, but where we // know that it shouldn't be possible to arrive there, assuming no horrendous bugs. For example, // after a switch (some_enum) with all enum values included in the cases. The macro name includes diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 5596dc255319..e8d82f40e0aa 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -4434,7 +4434,9 @@ name: NoIdleTimeout class PerFilterConfigsTest : public testing::Test { public: - PerFilterConfigsTest() : factory_(), registered_factory_(factory_) {} + PerFilterConfigsTest() + : factory_(), registered_factory_(factory_), default_factory_(), + registered_default_factory_(default_factory_) {} struct DerivedFilterConfig : public RouteSpecificFilterConfig { ProtobufWkt::Timestamp config_; @@ -4458,6 +4460,15 @@ class PerFilterConfigsTest : public testing::Test { return obj; } }; + class DefaultTestFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpFilterConfig { + public: + DefaultTestFilterConfig() : EmptyHttpFilterConfig("test.default.filter") {} + + Http::FilterFactoryCb createFilter(const std::string&, + Server::Configuration::FactoryContext&) override { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } + }; void checkEach(const std::string& yaml, uint32_t expected_entry, uint32_t expected_route, uint32_t expected_vhost) { @@ -4481,8 +4492,24 @@ class PerFilterConfigsTest : public testing::Test { << "config value does not match expected for source: " << source; } + void checkNoPerFilterConfig(const std::string& yaml) { + const ConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true); + + const auto route = config.route(genHeaders("www.foo.com", "/", "GET"), 0); + const auto* route_entry = route->routeEntry(); + const auto& vhost = route_entry->virtualHost(); + + EXPECT_EQ(nullptr, + route_entry->perFilterConfigTyped(default_factory_.name())); + EXPECT_EQ(nullptr, route->perFilterConfigTyped(default_factory_.name())); + EXPECT_EQ(nullptr, vhost.perFilterConfigTyped(default_factory_.name())); + } + TestFilterConfig factory_; Registry::InjectFactory registered_factory_; + DefaultTestFilterConfig default_factory_; + Registry::InjectFactory + registered_default_factory_; NiceMock factory_context_; }; @@ -4503,6 +4530,23 @@ name: foo "Didn't find a registered implementation for name: 'unknown.filter'"); } +// Test that a trivially specified NamedHttpFilterConfigFactory ignores per_filter_config without +// error. +TEST_F(PerFilterConfigsTest, DefaultFilterImplementation) { + std::string yaml = R"EOF( +name: foo +virtual_hosts: + - name: bar + domains: ["*"] + routes: + - match: { prefix: "/" } + route: { cluster: baz } + per_filter_config: { test.default.filter: { unknown_key: 123} } +)EOF"; + + checkNoPerFilterConfig(yaml); +} + TEST_F(PerFilterConfigsTest, RouteLocalConfig) { std::string yaml = R"EOF( name: foo From 0e71582b338b97d5667be7deaff831a062a96d1f Mon Sep 17 00:00:00 2001 From: Elisha Ziskind Date: Wed, 18 Jul 2018 21:16:40 -0400 Subject: [PATCH 03/46] add resource monitor framework (#3848) Add an extensible resource monitor framework for monitoring resource "pressures" (usage/limit). This will be used by the overload manager to implement downstream circuit breaking (issue #373 - see design doc linked from there). Risk Level: low (not yet used in envoy main) Signed-off-by: Elisha Ziskind --- .../resource_monitor/fixed_heap/v2alpha/BUILD | 8 +++ .../fixed_heap/v2alpha/fixed_heap.proto | 10 ++++ include/envoy/server/BUILD | 17 ++++++ include/envoy/server/resource_monitor.h | 52 +++++++++++++++++ .../envoy/server/resource_monitor_config.h | 53 ++++++++++++++++++ source/common/memory/stats.cc | 7 +++ source/common/memory/stats.h | 5 ++ source/extensions/extensions_build_config.bzl | 6 ++ source/extensions/resource_monitors/BUILD | 17 ++++++ .../extensions/resource_monitors/common/BUILD | 18 ++++++ .../resource_monitors/common/factory_base.h | 38 +++++++++++++ .../resource_monitors/fixed_heap/BUILD | 34 +++++++++++ .../resource_monitors/fixed_heap/config.cc | 30 ++++++++++ .../resource_monitors/fixed_heap/config.h | 30 ++++++++++ .../fixed_heap/fixed_heap_monitor.cc | 37 ++++++++++++ .../fixed_heap/fixed_heap_monitor.h | 44 +++++++++++++++ .../resource_monitors/well_known_names.h | 23 ++++++++ source/server/BUILD | 8 +++ source/server/resource_monitor_config_impl.h | 21 +++++++ .../resource_monitors/fixed_heap/BUILD | 35 ++++++++++++ .../fixed_heap/config_test.cc | 34 +++++++++++ .../fixed_heap/fixed_heap_monitor_test.cc | 56 +++++++++++++++++++ 22 files changed, 583 insertions(+) create mode 100644 api/envoy/config/resource_monitor/fixed_heap/v2alpha/BUILD create mode 100644 api/envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.proto create mode 100644 include/envoy/server/resource_monitor.h create mode 100644 include/envoy/server/resource_monitor_config.h create mode 100644 source/extensions/resource_monitors/BUILD create mode 100644 source/extensions/resource_monitors/common/BUILD create mode 100644 source/extensions/resource_monitors/common/factory_base.h create mode 100644 source/extensions/resource_monitors/fixed_heap/BUILD create mode 100644 source/extensions/resource_monitors/fixed_heap/config.cc create mode 100644 source/extensions/resource_monitors/fixed_heap/config.h create mode 100644 source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.cc create mode 100644 source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h create mode 100644 source/extensions/resource_monitors/well_known_names.h create mode 100644 source/server/resource_monitor_config_impl.h create mode 100644 test/extensions/resource_monitors/fixed_heap/BUILD create mode 100644 test/extensions/resource_monitors/fixed_heap/config_test.cc create mode 100644 test/extensions/resource_monitors/fixed_heap/fixed_heap_monitor_test.cc diff --git a/api/envoy/config/resource_monitor/fixed_heap/v2alpha/BUILD b/api/envoy/config/resource_monitor/fixed_heap/v2alpha/BUILD new file mode 100644 index 000000000000..adc77e5b5e0d --- /dev/null +++ b/api/envoy/config/resource_monitor/fixed_heap/v2alpha/BUILD @@ -0,0 +1,8 @@ +load("//bazel:api_build_system.bzl", "api_proto_library_internal") + +licenses(["notice"]) # Apache 2 + +api_proto_library_internal( + name = "fixed_heap", + srcs = ["fixed_heap.proto"], +) diff --git a/api/envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.proto b/api/envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.proto new file mode 100644 index 000000000000..08e3c6536f5d --- /dev/null +++ b/api/envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package envoy.config.resource_monitor.fixed_heap.v2alpha; +option go_package = "v2alpha"; + +message FixedHeapConfig { + // Limit of the Envoy process heap size. This is used to calculate heap memory pressure which + // is defined as (current heap size)/max_heap_size_bytes. + uint64 max_heap_size_bytes = 1; +} diff --git a/include/envoy/server/BUILD b/include/envoy/server/BUILD index 33adc7526afb..fc6e2e04a906 100644 --- a/include/envoy/server/BUILD +++ b/include/envoy/server/BUILD @@ -181,3 +181,20 @@ envoy_cc_library( "//source/common/protobuf", ], ) + +envoy_cc_library( + name = "resource_monitor_interface", + hdrs = ["resource_monitor.h"], + deps = [ + "//source/common/protobuf", + ], +) + +envoy_cc_library( + name = "resource_monitor_config_interface", + hdrs = ["resource_monitor_config.h"], + deps = [ + ":resource_monitor_interface", + "//include/envoy/event:dispatcher_interface", + ], +) diff --git a/include/envoy/server/resource_monitor.h b/include/envoy/server/resource_monitor.h new file mode 100644 index 000000000000..3fd01b52ac3b --- /dev/null +++ b/include/envoy/server/resource_monitor.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include "envoy/common/exception.h" +#include "envoy/common/pure.h" + +namespace Envoy { +namespace Server { + +// Struct for reporting usage for a particular resource. +struct ResourceUsage { + // Fraction of (resource usage)/(resource limit). + double resource_pressure_; +}; + +class ResourceMonitor { +public: + virtual ~ResourceMonitor() {} + + /** + * Notifies caller of updated resource usage. + */ + class Callbacks { + public: + virtual ~Callbacks() {} + + /** + * Called when the request for updated resource usage succeeds. + * @param usage the updated resource usage + */ + virtual void onSuccess(const ResourceUsage& usage) PURE; + + /** + * Called when the request for updated resource usage fails. + * @param error the exception caught when trying to get updated resource usage + */ + virtual void onFailure(const EnvoyException& error) PURE; + }; + + /** + * Recalculate resource usage. + * This must be non-blocking so if RPCs need to be made they should be + * done asynchronously and invoke the callback when finished. + */ + virtual void updateResourceUsage(Callbacks& callbacks) PURE; +}; + +typedef std::unique_ptr ResourceMonitorPtr; + +} // namespace Server +} // namespace Envoy diff --git a/include/envoy/server/resource_monitor_config.h b/include/envoy/server/resource_monitor_config.h new file mode 100644 index 000000000000..3ab8328f3de7 --- /dev/null +++ b/include/envoy/server/resource_monitor_config.h @@ -0,0 +1,53 @@ +#pragma once + +#include "envoy/common/pure.h" +#include "envoy/event/dispatcher.h" +#include "envoy/server/resource_monitor.h" + +#include "common/protobuf/protobuf.h" + +namespace Envoy { +namespace Server { +namespace Configuration { + +class ResourceMonitorFactoryContext { +public: + virtual ~ResourceMonitorFactoryContext() {} + + /** + * @return Event::Dispatcher& the main thread's dispatcher. This dispatcher should be used + * for all singleton processing. + */ + virtual Event::Dispatcher& dispatcher() PURE; +}; + +/** + * Implemented by each resource monitor and registered via Registry::registerFactory() + * or the convenience class RegistryFactory. + */ +class ResourceMonitorFactory { +public: + virtual ~ResourceMonitorFactory() {} + + /** + * Create a particular resource monitor implementation. + * @param config const ProtoBuf::Message& supplies the config for the resource monitor + * implementation. + * @param context ResourceMonitorFactoryContext& supplies the resource monitor's context. + * @return ResourceMonitorPtr the resource monitor instance. Should not be nullptr. + * @throw EnvoyException if the implementation is unable to produce an instance with + * the provided parameters. + */ + virtual ResourceMonitorPtr createResourceMonitor(const Protobuf::Message& config, + ResourceMonitorFactoryContext& context) PURE; + + /** + * @return std::string the identifying name for a particular implementation of a resource + * monitor produced by the factory. + */ + virtual std::string name() PURE; +}; + +} // namespace Configuration +} // namespace Server +} // namespace Envoy diff --git a/source/common/memory/stats.cc b/source/common/memory/stats.cc index 38fc68fe6dee..f73930bc81ce 100644 --- a/source/common/memory/stats.cc +++ b/source/common/memory/stats.cc @@ -21,6 +21,12 @@ uint64_t Stats::totalCurrentlyReserved() { return value; } +uint64_t Stats::totalPageHeapUnmapped() { + size_t value = 0; + MallocExtension::instance()->GetNumericProperty("tcmalloc.pageheap_unmapped_bytes", &value); + return value; +} + } // namespace Memory } // namespace Envoy @@ -31,6 +37,7 @@ namespace Memory { uint64_t Stats::totalCurrentlyAllocated() { return 0; } uint64_t Stats::totalCurrentlyReserved() { return 0; } +uint64_t Stats::totalPageHeapUnmapped() { return 0; } } // namespace Memory } // namespace Envoy diff --git a/source/common/memory/stats.h b/source/common/memory/stats.h index 7dba0850e337..ccfe9785bed4 100644 --- a/source/common/memory/stats.h +++ b/source/common/memory/stats.h @@ -20,6 +20,11 @@ class Stats { * allocated. */ static uint64_t totalCurrentlyReserved(); + + /** + * @return uint64_t the number of bytes in free, unmapped pages in the page heap. + */ + static uint64_t totalPageHeapUnmapped(); }; } // namespace Memory diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 04b1d40a6adb..dad0243f89dc 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -70,6 +70,12 @@ EXTENSIONS = { "envoy.filters.network.tcp_proxy": "//source/extensions/filters/network/tcp_proxy:config", "envoy.filters.network.thrift_proxy": "//source/extensions/filters/network/thrift_proxy:config", + # + # Resource monitors + # + + "envoy.resource_monitors.fixed_heap": "//source/extensions/resource_monitors/fixed_heap:config", + # # Stat sinks # diff --git a/source/extensions/resource_monitors/BUILD b/source/extensions/resource_monitors/BUILD new file mode 100644 index 000000000000..6156949edef6 --- /dev/null +++ b/source/extensions/resource_monitors/BUILD @@ -0,0 +1,17 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "well_known_names", + hdrs = ["well_known_names.h"], + deps = [ + "//source/common/singleton:const_singleton", + ], +) diff --git a/source/extensions/resource_monitors/common/BUILD b/source/extensions/resource_monitors/common/BUILD new file mode 100644 index 000000000000..ff6773aaa8d1 --- /dev/null +++ b/source/extensions/resource_monitors/common/BUILD @@ -0,0 +1,18 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "factory_base_lib", + hdrs = ["factory_base.h"], + deps = [ + "//include/envoy/server:resource_monitor_config_interface", + "//source/common/protobuf:utility_lib", + ], +) diff --git a/source/extensions/resource_monitors/common/factory_base.h b/source/extensions/resource_monitors/common/factory_base.h new file mode 100644 index 000000000000..899f35237e13 --- /dev/null +++ b/source/extensions/resource_monitors/common/factory_base.h @@ -0,0 +1,38 @@ +#pragma once + +#include "envoy/server/resource_monitor_config.h" + +#include "common/protobuf/utility.h" + +namespace Envoy { +namespace Extensions { +namespace ResourceMonitors { +namespace Common { + +template +class FactoryBase : public Server::Configuration::ResourceMonitorFactory { +public: + Server::ResourceMonitorPtr + createResourceMonitor(const Protobuf::Message& config, + Server::Configuration::ResourceMonitorFactoryContext& context) override { + return createResourceMonitorFromProtoTyped( + MessageUtil::downcastAndValidate(config), context); + } + + std::string name() override { return name_; } + +protected: + FactoryBase(const std::string& name) : name_(name) {} + +private: + virtual Server::ResourceMonitorPtr createResourceMonitorFromProtoTyped( + const ConfigProto& config, + Server::Configuration::ResourceMonitorFactoryContext& context) PURE; + + const std::string name_; +}; + +} // namespace Common +} // namespace ResourceMonitors +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/resource_monitors/fixed_heap/BUILD b/source/extensions/resource_monitors/fixed_heap/BUILD new file mode 100644 index 000000000000..f9042c54305e --- /dev/null +++ b/source/extensions/resource_monitors/fixed_heap/BUILD @@ -0,0 +1,34 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "fixed_heap_monitor", + srcs = ["fixed_heap_monitor.cc"], + hdrs = ["fixed_heap_monitor.h"], + deps = [ + "//include/envoy/server:resource_monitor_config_interface", + "//source/common/common:assert_lib", + "//source/common/memory:stats_lib", + "@envoy_api//envoy/config/resource_monitor/fixed_heap/v2alpha:fixed_heap_cc", + ], +) + +envoy_cc_library( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":fixed_heap_monitor", + "//include/envoy/registry", + "//source/common/common:assert_lib", + "//source/extensions/resource_monitors:well_known_names", + "//source/extensions/resource_monitors/common:factory_base_lib", + ], +) diff --git a/source/extensions/resource_monitors/fixed_heap/config.cc b/source/extensions/resource_monitors/fixed_heap/config.cc new file mode 100644 index 000000000000..d0313789ff01 --- /dev/null +++ b/source/extensions/resource_monitors/fixed_heap/config.cc @@ -0,0 +1,30 @@ +#include "extensions/resource_monitors/fixed_heap/config.h" + +#include "envoy/registry/registry.h" + +#include "common/protobuf/utility.h" + +#include "extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h" + +namespace Envoy { +namespace Extensions { +namespace ResourceMonitors { +namespace FixedHeapMonitor { + +Server::ResourceMonitorPtr FixedHeapMonitorFactory::createResourceMonitorFromProtoTyped( + const envoy::config::resource_monitor::fixed_heap::v2alpha::FixedHeapConfig& config, + Server::Configuration::ResourceMonitorFactoryContext& /*unused_context*/) { + return std::make_unique(config); +} + +/** + * Static registration for the fixed heap resource monitor factory. @see RegistryFactory. + */ +static Registry::RegisterFactory + registered_; + +} // namespace FixedHeapMonitor +} // namespace ResourceMonitors +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/resource_monitors/fixed_heap/config.h b/source/extensions/resource_monitors/fixed_heap/config.h new file mode 100644 index 000000000000..2a619c6813d9 --- /dev/null +++ b/source/extensions/resource_monitors/fixed_heap/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include "envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.pb.validate.h" +#include "envoy/server/resource_monitor_config.h" + +#include "extensions/resource_monitors/common/factory_base.h" +#include "extensions/resource_monitors/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace ResourceMonitors { +namespace FixedHeapMonitor { + +class FixedHeapMonitorFactory + : public Common::FactoryBase< + envoy::config::resource_monitor::fixed_heap::v2alpha::FixedHeapConfig> { +public: + FixedHeapMonitorFactory() + : FactoryBase(ResourceMonitorNames::get().FIXED_HEAP_RESOURCE_MONITOR) {} + +private: + Server::ResourceMonitorPtr createResourceMonitorFromProtoTyped( + const envoy::config::resource_monitor::fixed_heap::v2alpha::FixedHeapConfig& config, + Server::Configuration::ResourceMonitorFactoryContext& context) override; +}; + +} // namespace FixedHeapMonitor +} // namespace ResourceMonitors +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.cc b/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.cc new file mode 100644 index 000000000000..a968856aa04d --- /dev/null +++ b/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.cc @@ -0,0 +1,37 @@ +#include "extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h" + +#include "common/common/assert.h" +#include "common/memory/stats.h" + +namespace Envoy { +namespace Extensions { +namespace ResourceMonitors { +namespace FixedHeapMonitor { + +uint64_t MemoryStatsReader::reservedHeapBytes() { return Memory::Stats::totalCurrentlyReserved(); } + +uint64_t MemoryStatsReader::unmappedHeapBytes() { return Memory::Stats::totalPageHeapUnmapped(); } + +FixedHeapMonitor::FixedHeapMonitor( + const envoy::config::resource_monitor::fixed_heap::v2alpha::FixedHeapConfig& config, + std::unique_ptr stats) + : max_heap_(config.max_heap_size_bytes()), stats_(std::move(stats)) { + ASSERT(max_heap_ > 0); +} + +void FixedHeapMonitor::updateResourceUsage(Server::ResourceMonitor::Callbacks& callbacks) { + const size_t physical = stats_->reservedHeapBytes(); + const size_t unmapped = stats_->unmappedHeapBytes(); + ASSERT(physical >= unmapped); + const size_t used = physical - unmapped; + + Server::ResourceUsage usage; + usage.resource_pressure_ = used / static_cast(max_heap_); + + callbacks.onSuccess(usage); +} + +} // namespace FixedHeapMonitor +} // namespace ResourceMonitors +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h b/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h new file mode 100644 index 000000000000..9bc4bb2697c0 --- /dev/null +++ b/source/extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h @@ -0,0 +1,44 @@ +#pragma once + +#include "envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.pb.validate.h" +#include "envoy/server/resource_monitor.h" + +namespace Envoy { +namespace Extensions { +namespace ResourceMonitors { +namespace FixedHeapMonitor { + +/** + * Helper class for getting memory heap stats. + */ +class MemoryStatsReader { +public: + MemoryStatsReader() {} + virtual ~MemoryStatsReader() {} + + // Memory reserved for the process by the heap. + virtual uint64_t reservedHeapBytes(); + // Memory in free, unmapped pages in the page heap. + virtual uint64_t unmappedHeapBytes(); +}; + +/** + * Heap memory monitor with a statically configured maximum. + */ +class FixedHeapMonitor : public Server::ResourceMonitor { +public: + FixedHeapMonitor( + const envoy::config::resource_monitor::fixed_heap::v2alpha::FixedHeapConfig& config, + std::unique_ptr stats = std::make_unique()); + + void updateResourceUsage(Server::ResourceMonitor::Callbacks& callbacks) override; + +private: + const uint64_t max_heap_; + std::unique_ptr stats_; +}; + +} // namespace FixedHeapMonitor +} // namespace ResourceMonitors +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/resource_monitors/well_known_names.h b/source/extensions/resource_monitors/well_known_names.h new file mode 100644 index 000000000000..e573b74753fb --- /dev/null +++ b/source/extensions/resource_monitors/well_known_names.h @@ -0,0 +1,23 @@ +#pragma once + +#include "common/singleton/const_singleton.h" + +namespace Envoy { +namespace Extensions { +namespace ResourceMonitors { + +/** + * Well-known resource monior names. + * NOTE: New resource monitors should use the well known name: envoy.resource_monitors.name. + */ +class ResourceMonitorNameValues { +public: + // Heap monitor with statically configured max. + const std::string FIXED_HEAP_RESOURCE_MONITOR = "envoy.resource_monitors.fixed_heap"; +}; + +typedef ConstSingleton ResourceMonitorNames; + +} // namespace ResourceMonitors +} // namespace Extensions +} // namespace Envoy diff --git a/source/server/BUILD b/source/server/BUILD index b017cfb4d5ba..0ad1f1db3dc4 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -244,6 +244,14 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "resource_monitor_config_lib", + hdrs = ["resource_monitor_config_impl.h"], + deps = [ + "//include/envoy/server:resource_monitor_config_interface", + ], +) + envoy_cc_library( name = "server_lib", srcs = ["server.cc"], diff --git a/source/server/resource_monitor_config_impl.h b/source/server/resource_monitor_config_impl.h new file mode 100644 index 000000000000..2fcfcc443907 --- /dev/null +++ b/source/server/resource_monitor_config_impl.h @@ -0,0 +1,21 @@ +#pragma once + +#include "envoy/server/resource_monitor_config.h" + +namespace Envoy { +namespace Server { +namespace Configuration { + +class ResourceMonitorFactoryContextImpl : public ResourceMonitorFactoryContext { +public: + ResourceMonitorFactoryContextImpl(Event::Dispatcher& dispatcher) : dispatcher_(dispatcher) {} + + Event::Dispatcher& dispatcher() override { return dispatcher_; } + +private: + Event::Dispatcher& dispatcher_; +}; + +} // namespace Configuration +} // namespace Server +} // namespace Envoy diff --git a/test/extensions/resource_monitors/fixed_heap/BUILD b/test/extensions/resource_monitors/fixed_heap/BUILD new file mode 100644 index 000000000000..3d1c8eff0ab3 --- /dev/null +++ b/test/extensions/resource_monitors/fixed_heap/BUILD @@ -0,0 +1,35 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +envoy_package() + +envoy_extension_cc_test( + name = "fixed_heap_monitor_test", + srcs = ["fixed_heap_monitor_test.cc"], + extension_name = "envoy.resource_monitors.fixed_heap", + external_deps = ["abseil_optional"], + deps = [ + "//source/extensions/resource_monitors/fixed_heap:fixed_heap_monitor", + ], +) + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_name = "envoy.resource_monitors.fixed_heap", + deps = [ + "//include/envoy/registry", + "//source/extensions/resource_monitors/fixed_heap:config", + "//source/server:resource_monitor_config_lib", + "//test/mocks/event:event_mocks", + "@envoy_api//envoy/config/resource_monitor/fixed_heap/v2alpha:fixed_heap_cc", + ], +) diff --git a/test/extensions/resource_monitors/fixed_heap/config_test.cc b/test/extensions/resource_monitors/fixed_heap/config_test.cc new file mode 100644 index 000000000000..5bdd672804d2 --- /dev/null +++ b/test/extensions/resource_monitors/fixed_heap/config_test.cc @@ -0,0 +1,34 @@ +#include "envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.pb.validate.h" +#include "envoy/registry/registry.h" + +#include "server/resource_monitor_config_impl.h" + +#include "extensions/resource_monitors/fixed_heap/config.h" + +#include "test/mocks/event/mocks.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace ResourceMonitors { +namespace FixedHeapMonitor { + +TEST(FixedHeapMonitorFactoryTest, CreateMonitor) { + auto factory = + Registry::FactoryRegistry::getFactory( + "envoy.resource_monitors.fixed_heap"); + EXPECT_NE(factory, nullptr); + + envoy::config::resource_monitor::fixed_heap::v2alpha::FixedHeapConfig config; + config.set_max_heap_size_bytes(std::numeric_limits::max()); + Event::MockDispatcher dispatcher; + Server::Configuration::ResourceMonitorFactoryContextImpl context(dispatcher); + auto monitor = factory->createResourceMonitor(config, context); + EXPECT_NE(monitor, nullptr); +} + +} // namespace FixedHeapMonitor +} // namespace ResourceMonitors +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/resource_monitors/fixed_heap/fixed_heap_monitor_test.cc b/test/extensions/resource_monitors/fixed_heap/fixed_heap_monitor_test.cc new file mode 100644 index 000000000000..637d5fe2a943 --- /dev/null +++ b/test/extensions/resource_monitors/fixed_heap/fixed_heap_monitor_test.cc @@ -0,0 +1,56 @@ +#include "extensions/resource_monitors/fixed_heap/fixed_heap_monitor.h" + +#include "absl/types/optional.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace ResourceMonitors { +namespace FixedHeapMonitor { + +class MockMemoryStatsReader : public MemoryStatsReader { +public: + MockMemoryStatsReader() {} + + MOCK_METHOD0(reservedHeapBytes, uint64_t()); + MOCK_METHOD0(unmappedHeapBytes, uint64_t()); +}; + +class ResourcePressure : public Server::ResourceMonitor::Callbacks { +public: + void onSuccess(const Server::ResourceUsage& usage) override { + pressure_ = usage.resource_pressure_; + } + + void onFailure(const EnvoyException& error) override { error_ = error; } + + bool hasPressure() const { return pressure_.has_value(); } + bool hasError() const { return error_.has_value(); } + + double pressure() const { return *pressure_; } + +private: + absl::optional pressure_; + absl::optional error_; +}; + +TEST(FixedHeapMonitorTest, ComputesCorrectUsage) { + envoy::config::resource_monitor::fixed_heap::v2alpha::FixedHeapConfig config; + config.set_max_heap_size_bytes(1000); + auto stats_reader = std::make_unique(); + EXPECT_CALL(*stats_reader, reservedHeapBytes()).WillOnce(testing::Return(800)); + EXPECT_CALL(*stats_reader, unmappedHeapBytes()).WillOnce(testing::Return(100)); + std::unique_ptr monitor(new FixedHeapMonitor(config, std::move(stats_reader))); + + ResourcePressure resource; + monitor->updateResourceUsage(resource); + EXPECT_TRUE(resource.hasPressure()); + EXPECT_FALSE(resource.hasError()); + EXPECT_EQ(resource.pressure(), 0.7); +} + +} // namespace FixedHeapMonitor +} // namespace ResourceMonitors +} // namespace Extensions +} // namespace Envoy From 0f68948434039eb5e2fe3610e9b315989d843296 Mon Sep 17 00:00:00 2001 From: Tal Nordan Date: Wed, 18 Jul 2018 21:25:13 -0700 Subject: [PATCH 04/46] authz: fix RBAC filter config PB docs (#3895) Fix the description of an `RBAC` Protobuf message example, so that it matches the changes made to the YAML code block in PR #3477. Risk Level: Low Testing: N/A Docs Changes: N/A Release Notes: N/A Signed-off-by: Tal Nordan --- api/envoy/config/rbac/v2alpha/rbac.proto | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/api/envoy/config/rbac/v2alpha/rbac.proto b/api/envoy/config/rbac/v2alpha/rbac.proto index 4fdebe3bf331..ab32aaf475fd 100644 --- a/api/envoy/config/rbac/v2alpha/rbac.proto +++ b/api/envoy/config/rbac/v2alpha/rbac.proto @@ -16,12 +16,11 @@ option go_package = "v2alpha"; // // Here is an example of RBAC configuration. It has two policies: // -// * Service account "cluster.local/ns/default/sa/admin" has full access (empty permission entry -// means full access) to the service. +// * Service account "cluster.local/ns/default/sa/admin" has full access to the service, and so +// does "cluster.local/ns/default/sa/superuser". // -// * Any user (empty principal entry means any user) can read ("GET") the service at paths with -// prefix "/products" or suffix "/reviews" when request header "version" set to either "v1" or -// "v2". +// * Any user can read ("GET") the service at paths with prefix "/products", so long as the +// destination port is either 80 or 443. // // .. code-block:: yaml // From a8fa0c65aec703f613d952fc7d4f7b1ed5b1bd14 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Wed, 18 Jul 2018 21:25:34 -0700 Subject: [PATCH 05/46] http/2: use hpack_table_size to control both encoder and decoder. (#3659) Previously, hpack_table_size was used to configure maximum table size used by the local endpoint for HPACK decoding, however, there was no way to configure table size used for HPACK enoding. Since this option is mostly used to disable header compression by setting the size to 0, it means that Envoy only asked the remote endpoint not to compress headers, but it was still compressing them itself (unless asked not to by the remote endpoint). Re-using hpack_table_size instead of adding a new option, since both: encoder and decoder will usually use the same value anyway. *Level*: Medium (some broken libraries don't support header table updates) *Testing*: bazel test //test/... *Docs Changes*: n/a *Release Notes*: n/a Signed-off-by: Piotr Sikora --- docs/root/intro/version_history.rst | 8 ++++--- source/common/http/http2/codec_impl.cc | 17 +++++++++----- source/common/http/http2/codec_impl.h | 6 ++--- test/common/http/http2/codec_impl_test.cc | 28 +++++++++++++++++++++++ 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index e6f55e59f3c5..eb2bfe7f6752 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -18,16 +18,18 @@ Version history `. This defaults to 5 minutes; if you have other timeouts (e.g. connection idle timeout, upstream response per-retry) that are longer than this in duration, you may want to consider setting a non-default per-stream idle timeout. +* http: added generic :ref:`Upgrade support + `. * http: better handling of HEAD requests. Now sending transfer-encoding: chunked rather than content-length: 0. * http: response filters not applied to early error paths such as http_parser generated 400s. -* proxy_protocol: added support for HAProxy Proxy Protocol v2 (AF_INET/AF_INET6 only). -* http: added generic +:ref:`Upgrade support - ` +* http: :ref:`hpack_table_size ` now controls + dynamic table size of both: encoder and decoder. * listeners: added the ability to match :ref:`FilterChain ` using :ref:`destination_port ` and :ref:`prefix_ranges `. * lua: added :ref:`connection() ` wrapper and *ssl()* API. * lua: added :ref:`requestInfo() ` wrapper and *protocol()* API. +* proxy_protocol: added support for HAProxy Proxy Protocol v2 (AF_INET/AF_INET6 only). * ratelimit: added support for :repo:`api/envoy/service/ratelimit/v2/rls.proto`. Lyft's reference implementation of the `ratelimit `_ service also supports the data-plane-api proto as of v1.1.0. Envoy can use either proto to send client requests to a ratelimit server with the use of the diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index 504582e7d5f5..b5cc7b66c2cc 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -37,8 +37,6 @@ bool Utility::reconstituteCrumbledCookies(const HeaderString& key, const HeaderS } ConnectionImpl::Http2Callbacks ConnectionImpl::http2_callbacks_; -ConnectionImpl::Http2Options ConnectionImpl::http2_options_; -ConnectionImpl::ClientHttp2Options ConnectionImpl::client_http2_options_; /** * Helper to remove const during a cast. nghttp2 takes non-const pointers for headers even though @@ -724,7 +722,7 @@ ConnectionImpl::Http2Callbacks::Http2Callbacks() { ConnectionImpl::Http2Callbacks::~Http2Callbacks() { nghttp2_session_callbacks_del(callbacks_); } -ConnectionImpl::Http2Options::Http2Options() { +ConnectionImpl::Http2Options::Http2Options(const Http2Settings& http2_settings) { nghttp2_option_new(&options_); // Currently we do not do anything with stream priority. Setting the following option prevents // nghttp2 from keeping around closed streams for use during stream priority dependency graph @@ -732,11 +730,16 @@ ConnectionImpl::Http2Options::Http2Options() { // of kept alive HTTP/2 connections. nghttp2_option_set_no_closed_streams(options_, 1); nghttp2_option_set_no_auto_window_update(options_, 1); + + if (http2_settings.hpack_table_size_ != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) { + nghttp2_option_set_max_deflate_dynamic_table_size(options_, http2_settings.hpack_table_size_); + } } ConnectionImpl::Http2Options::~Http2Options() { nghttp2_option_del(options_); } -ConnectionImpl::ClientHttp2Options::ClientHttp2Options() : Http2Options() { +ConnectionImpl::ClientHttp2Options::ClientHttp2Options(const Http2Settings& http2_settings) + : Http2Options(http2_settings) { // Temporarily disable initial max streams limit/protection, since we might want to create // more than 100 streams before receiving the HTTP/2 SETTINGS frame from the server. // @@ -749,8 +752,9 @@ ClientConnectionImpl::ClientConnectionImpl(Network::Connection& connection, Http::ConnectionCallbacks& callbacks, Stats::Scope& stats, const Http2Settings& http2_settings) : ConnectionImpl(connection, stats, http2_settings), callbacks_(callbacks) { + ClientHttp2Options client_http2_options(http2_settings); nghttp2_session_client_new2(&session_, http2_callbacks_.callbacks(), base(), - client_http2_options_.options()); + client_http2_options.options()); sendSettings(http2_settings, true); } @@ -794,8 +798,9 @@ ServerConnectionImpl::ServerConnectionImpl(Network::Connection& connection, Http::ServerConnectionCallbacks& callbacks, Stats::Scope& scope, const Http2Settings& http2_settings) : ConnectionImpl(connection, scope, http2_settings), callbacks_(callbacks) { + Http2Options http2_options(http2_settings); nghttp2_session_server_new2(&session_, http2_callbacks_.callbacks(), base(), - http2_options_.options()); + http2_options.options()); sendSettings(http2_settings, false); } diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index b835a5b864a5..6b4e0016a72c 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -118,7 +118,7 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable active_streams_; nghttp2_session* session_{}; diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index 7e679543ca36..2894363830ef 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -783,6 +783,34 @@ TEST_P(Http2CodecImplTest, TestCodecHeaderLimits) { request_encoder_->encodeHeaders(request_headers, false); } +TEST_P(Http2CodecImplTest, TestCodecHeaderCompression) { + initialize(); + + TestHeaderMapImpl request_headers; + HttpTestUtility::addDefaultHeaders(request_headers); + EXPECT_CALL(request_decoder_, decodeHeaders_(_, true)); + request_encoder_->encodeHeaders(request_headers, true); + + TestHeaderMapImpl response_headers{{":status", "200"}, {"compression", "test"}}; + EXPECT_CALL(response_decoder_, decodeHeaders_(_, true)); + response_encoder_->encodeHeaders(response_headers, true); + + // Sanity check to verify that state of encoders and decoders matches. + EXPECT_EQ(nghttp2_session_get_hd_deflate_dynamic_table_size(server_.session()), + nghttp2_session_get_hd_inflate_dynamic_table_size(client_.session())); + EXPECT_EQ(nghttp2_session_get_hd_deflate_dynamic_table_size(client_.session()), + nghttp2_session_get_hd_inflate_dynamic_table_size(server_.session())); + + // Verify that headers are compressed only when both client and server advertise table size > 0: + if (client_http2settings_.hpack_table_size_ && server_http2settings_.hpack_table_size_) { + EXPECT_NE(0, nghttp2_session_get_hd_deflate_dynamic_table_size(client_.session())); + EXPECT_NE(0, nghttp2_session_get_hd_deflate_dynamic_table_size(server_.session())); + } else { + EXPECT_EQ(0, nghttp2_session_get_hd_deflate_dynamic_table_size(client_.session())); + EXPECT_EQ(0, nghttp2_session_get_hd_deflate_dynamic_table_size(server_.session())); + } +} + } // namespace Http2 } // namespace Http } // namespace Envoy From b14ce1d45fbe60ba85019dae7ac7d47206277597 Mon Sep 17 00:00:00 2001 From: Venil Noronha Date: Wed, 18 Jul 2018 21:28:44 -0700 Subject: [PATCH 06/46] syscall: latch errno deeper in the buffer implementation (#3880) The errno set by a syscall can be overwritten by code (e.g. logging) as it propagates up through the call stack. This commit refactors the buffer API to allow for returning the errno from deeper down the call stack i.e. as soon as a syscall is performed. Signed-off-by: Venil Noronha --- include/envoy/buffer/buffer.h | 13 ++++--- source/common/buffer/buffer_impl.cc | 16 +++++---- source/common/buffer/buffer_impl.h | 4 +-- source/common/buffer/watermark_buffer.cc | 12 +++---- source/common/buffer/watermark_buffer.h | 4 +-- source/common/network/raw_buffer_socket.cc | 10 +++--- .../network/thrift_proxy/buffer_helper.h | 4 +-- test/common/buffer/owned_impl_test.cc | 36 +++++++++---------- test/common/buffer/watermark_buffer_test.cc | 6 ++-- test/common/network/connection_impl_test.cc | 4 +-- test/mocks/buffer/mocks.h | 14 ++++---- 11 files changed, 66 insertions(+), 57 deletions(-) diff --git a/include/envoy/buffer/buffer.h b/include/envoy/buffer/buffer.h index 8882cc72dc82..eb4d3fc0f400 100644 --- a/include/envoy/buffer/buffer.h +++ b/include/envoy/buffer/buffer.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "envoy/common/pure.h" @@ -142,9 +143,11 @@ class Instance { * Read from a file descriptor directly into the buffer. * @param fd supplies the descriptor to read from. * @param max_length supplies the maximum length to read. - * @return the number of bytes read or -1 if there was an error. + * @return a tuple with the number of bytes read and the errno. If an error occurred, the + * number of bytes read would indicate -1 and the errno would be non-zero. Otherwise, if + * bytes were read, errno shouldn't be used. */ - virtual int read(int fd, uint64_t max_length) PURE; + virtual std::tuple read(int fd, uint64_t max_length) PURE; /** * Reserve space in the buffer. @@ -173,9 +176,11 @@ class Instance { /** * Write the buffer out to a file descriptor. * @param fd supplies the descriptor to write to. - * @return the number of bytes written or -1 if there was an error. + * @return a tuple with the number of bytes written and the errno. If an error occurred, the + * number of bytes written would indicate -1 and the errno would be non-zero. Otherwise, if + * bytes were written, errno shouldn't be used. */ - virtual int write(int fd) PURE; + virtual std::tuple write(int fd) PURE; }; typedef std::unique_ptr InstancePtr; diff --git a/source/common/buffer/buffer_impl.cc b/source/common/buffer/buffer_impl.cc index f7bdfcd12aa4..3e70b1d70b88 100644 --- a/source/common/buffer/buffer_impl.cc +++ b/source/common/buffer/buffer_impl.cc @@ -94,9 +94,9 @@ void OwnedImpl::move(Instance& rhs, uint64_t length) { static_cast(rhs).postProcess(); } -int OwnedImpl::read(int fd, uint64_t max_length) { +std::tuple OwnedImpl::read(int fd, uint64_t max_length) { if (max_length == 0) { - return 0; + return std::make_tuple(0, 0); } constexpr uint64_t MaxSlices = 2; RawSlice slices[MaxSlices]; @@ -115,8 +115,9 @@ int OwnedImpl::read(int fd, uint64_t max_length) { ASSERT(num_bytes_to_read <= max_length); auto& os_syscalls = Api::OsSysCallsSingleton::get(); const ssize_t rc = os_syscalls.readv(fd, iov, static_cast(num_slices_to_read)); + const int error = errno; if (rc < 0) { - return rc; + return std::make_tuple(rc, error); } uint64_t num_slices_to_commit = 0; uint64_t bytes_to_commit = rc; @@ -130,7 +131,7 @@ int OwnedImpl::read(int fd, uint64_t max_length) { } ASSERT(num_slices_to_commit <= num_slices); commit(slices, num_slices_to_commit); - return rc; + return std::make_tuple(rc, error); } uint64_t OwnedImpl::reserve(uint64_t length, RawSlice* iovecs, uint64_t num_iovecs) { @@ -151,7 +152,7 @@ ssize_t OwnedImpl::search(const void* data, uint64_t size, size_t start) const { return result_ptr.pos; } -int OwnedImpl::write(int fd) { +std::tuple OwnedImpl::write(int fd) { constexpr uint64_t MaxSlices = 16; RawSlice slices[MaxSlices]; const uint64_t num_slices = std::min(getRawSlices(slices, MaxSlices), MaxSlices); @@ -165,14 +166,15 @@ int OwnedImpl::write(int fd) { } } if (num_slices_to_write == 0) { - return 0; + return std::make_tuple(0, 0); } auto& os_syscalls = Api::OsSysCallsSingleton::get(); const ssize_t rc = os_syscalls.writev(fd, iov, num_slices_to_write); + const int error = errno; if (rc > 0) { drain(static_cast(rc)); } - return static_cast(rc); + return std::make_tuple(static_cast(rc), error); } OwnedImpl::OwnedImpl() : buffer_(evbuffer_new()) {} diff --git a/source/common/buffer/buffer_impl.h b/source/common/buffer/buffer_impl.h index 81245e430461..e4adc74d0f66 100644 --- a/source/common/buffer/buffer_impl.h +++ b/source/common/buffer/buffer_impl.h @@ -80,10 +80,10 @@ class OwnedImpl : public LibEventInstance { void* linearize(uint32_t size) override; void move(Instance& rhs) override; void move(Instance& rhs, uint64_t length) override; - int read(int fd, uint64_t max_length) override; + std::tuple read(int fd, uint64_t max_length) override; uint64_t reserve(uint64_t length, RawSlice* iovecs, uint64_t num_iovecs) override; ssize_t search(const void* data, uint64_t size, size_t start) const override; - int write(int fd) override; + std::tuple write(int fd) override; void postProcess() override {} std::string toString() const override; diff --git a/source/common/buffer/watermark_buffer.cc b/source/common/buffer/watermark_buffer.cc index 9eb32b1815ee..f09f9fb3cc94 100644 --- a/source/common/buffer/watermark_buffer.cc +++ b/source/common/buffer/watermark_buffer.cc @@ -40,10 +40,10 @@ void WatermarkBuffer::move(Instance& rhs, uint64_t length) { checkHighWatermark(); } -int WatermarkBuffer::read(int fd, uint64_t max_length) { - int bytes_read = OwnedImpl::read(fd, max_length); +std::tuple WatermarkBuffer::read(int fd, uint64_t max_length) { + std::tuple result = OwnedImpl::read(fd, max_length); checkHighWatermark(); - return bytes_read; + return result; } uint64_t WatermarkBuffer::reserve(uint64_t length, RawSlice* iovecs, uint64_t num_iovecs) { @@ -52,10 +52,10 @@ uint64_t WatermarkBuffer::reserve(uint64_t length, RawSlice* iovecs, uint64_t nu return bytes_reserved; } -int WatermarkBuffer::write(int fd) { - int bytes_written = OwnedImpl::write(fd); +std::tuple WatermarkBuffer::write(int fd) { + std::tuple result = OwnedImpl::write(fd); checkLowWatermark(); - return bytes_written; + return result; } void WatermarkBuffer::setWatermarks(uint32_t low_watermark, uint32_t high_watermark) { diff --git a/source/common/buffer/watermark_buffer.h b/source/common/buffer/watermark_buffer.h index 5be55409ef1e..9524530c23b9 100644 --- a/source/common/buffer/watermark_buffer.h +++ b/source/common/buffer/watermark_buffer.h @@ -28,9 +28,9 @@ class WatermarkBuffer : public OwnedImpl { void drain(uint64_t size) override; void move(Instance& rhs) override; void move(Instance& rhs, uint64_t length) override; - int read(int fd, uint64_t max_length) override; + std::tuple read(int fd, uint64_t max_length) override; uint64_t reserve(uint64_t length, RawSlice* iovecs, uint64_t num_iovecs) override; - int write(int fd) override; + std::tuple write(int fd) override; void postProcess() override { checkLowWatermark(); } void setWatermarks(uint32_t watermark) { setWatermarks(watermark / 2, watermark); } diff --git a/source/common/network/raw_buffer_socket.cc b/source/common/network/raw_buffer_socket.cc index 56f0584b1d60..0ab70d60ea6c 100644 --- a/source/common/network/raw_buffer_socket.cc +++ b/source/common/network/raw_buffer_socket.cc @@ -17,8 +17,9 @@ IoResult RawBufferSocket::doRead(Buffer::Instance& buffer) { bool end_stream = false; do { // 16K read is arbitrary. TODO(mattklein123) PERF: Tune the read size. - int rc = buffer.read(callbacks_->fd(), 16384); - const int error = errno; // Latch errno before any logging calls can overwrite it. + std::tuple result = buffer.read(callbacks_->fd(), 16384); + const int rc = std::get<0>(result); + const int error = std::get<1>(result); ENVOY_CONN_LOG(trace, "read returns: {}", callbacks_->connection(), rc); if (rc == 0) { @@ -60,8 +61,9 @@ IoResult RawBufferSocket::doWrite(Buffer::Instance& buffer, bool end_stream) { action = PostIoAction::KeepOpen; break; } - int rc = buffer.write(callbacks_->fd()); - const int error = errno; // Latch errno before any logging calls can overwrite it. + std::tuple result = buffer.write(callbacks_->fd()); + const int rc = std::get<0>(result); + const int error = std::get<1>(result); ENVOY_CONN_LOG(trace, "write returns: {}", callbacks_->connection(), rc); if (rc == -1) { ENVOY_CONN_LOG(trace, "write error: {} ({})", callbacks_->connection(), error, diff --git a/source/extensions/filters/network/thrift_proxy/buffer_helper.h b/source/extensions/filters/network/thrift_proxy/buffer_helper.h index 8efbebf1e5cf..8eb633a0140a 100644 --- a/source/extensions/filters/network/thrift_proxy/buffer_helper.h +++ b/source/extensions/filters/network/thrift_proxy/buffer_helper.h @@ -48,12 +48,12 @@ class BufferWrapper : public Buffer::Instance { } void move(Buffer::Instance&) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } void move(Buffer::Instance&, uint64_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - int read(int, uint64_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + std::tuple read(int, uint64_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } uint64_t reserve(uint64_t, Buffer::RawSlice*, uint64_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } ssize_t search(const void*, uint64_t, size_t) const override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - int write(int) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + std::tuple write(int) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } std::string toString() const override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } private: diff --git a/test/common/buffer/owned_impl_test.cc b/test/common/buffer/owned_impl_test.cc index 9977ea5112c9..b48dc4bb01a1 100644 --- a/test/common/buffer/owned_impl_test.cc +++ b/test/common/buffer/owned_impl_test.cc @@ -83,34 +83,34 @@ TEST_F(OwnedImplTest, Write) { Buffer::OwnedImpl buffer; buffer.add("example"); EXPECT_CALL(os_sys_calls, writev(_, _, _)).WillOnce(Return(7)); - int rc = buffer.write(-1); - EXPECT_EQ(7, rc); + std::tuple result = buffer.write(-1); + EXPECT_EQ(7, std::get<0>(result)); EXPECT_EQ(0, buffer.length()); buffer.add("example"); EXPECT_CALL(os_sys_calls, writev(_, _, _)).WillOnce(Return(6)); - rc = buffer.write(-1); - EXPECT_EQ(6, rc); + result = buffer.write(-1); + EXPECT_EQ(6, std::get<0>(result)); EXPECT_EQ(1, buffer.length()); EXPECT_CALL(os_sys_calls, writev(_, _, _)).WillOnce(Return(0)); - rc = buffer.write(-1); - EXPECT_EQ(0, rc); + result = buffer.write(-1); + EXPECT_EQ(0, std::get<0>(result)); EXPECT_EQ(1, buffer.length()); EXPECT_CALL(os_sys_calls, writev(_, _, _)).WillOnce(Return(-1)); - rc = buffer.write(-1); - EXPECT_EQ(-1, rc); + result = buffer.write(-1); + EXPECT_EQ(-1, std::get<0>(result)); EXPECT_EQ(1, buffer.length()); EXPECT_CALL(os_sys_calls, writev(_, _, _)).WillOnce(Return(1)); - rc = buffer.write(-1); - EXPECT_EQ(1, rc); + result = buffer.write(-1); + EXPECT_EQ(1, std::get<0>(result)); EXPECT_EQ(0, buffer.length()); EXPECT_CALL(os_sys_calls, writev(_, _, _)).Times(0); - rc = buffer.write(-1); - EXPECT_EQ(0, rc); + result = buffer.write(-1); + EXPECT_EQ(0, std::get<0>(result)); EXPECT_EQ(0, buffer.length()); } @@ -120,18 +120,18 @@ TEST_F(OwnedImplTest, Read) { Buffer::OwnedImpl buffer; EXPECT_CALL(os_sys_calls, readv(_, _, _)).WillOnce(Return(0)); - int rc = buffer.read(-1, 100); - EXPECT_EQ(0, rc); + std::tuple result = buffer.read(-1, 100); + EXPECT_EQ(0, std::get<0>(result)); EXPECT_EQ(0, buffer.length()); EXPECT_CALL(os_sys_calls, readv(_, _, _)).WillOnce(Return(-1)); - rc = buffer.read(-1, 100); - EXPECT_EQ(-1, rc); + result = buffer.read(-1, 100); + EXPECT_EQ(-1, std::get<0>(result)); EXPECT_EQ(0, buffer.length()); EXPECT_CALL(os_sys_calls, readv(_, _, _)).Times(0); - rc = buffer.read(-1, 0); - EXPECT_EQ(0, rc); + result = buffer.read(-1, 0); + EXPECT_EQ(0, std::get<0>(result)); EXPECT_EQ(0, buffer.length()); } diff --git a/test/common/buffer/watermark_buffer_test.cc b/test/common/buffer/watermark_buffer_test.cc index 1514de59a15a..4fe667ee5e3d 100644 --- a/test/common/buffer/watermark_buffer_test.cc +++ b/test/common/buffer/watermark_buffer_test.cc @@ -131,7 +131,8 @@ TEST_F(WatermarkBufferTest, WatermarkFdFunctions) { int bytes_written_total = 0; while (bytes_written_total < 20) { - int bytes_written = buffer_.write(pipe_fds[1]); + std::tuple result = buffer_.write(pipe_fds[1]); + int bytes_written = std::get<0>(result); if (bytes_written < 0) { ASSERT_EQ(EAGAIN, errno); } else { @@ -144,7 +145,8 @@ TEST_F(WatermarkBufferTest, WatermarkFdFunctions) { int bytes_read_total = 0; while (bytes_read_total < 20) { - bytes_read_total += buffer_.read(pipe_fds[0], 20); + std::tuple result = buffer_.read(pipe_fds[0], 20); + bytes_read_total += std::get<0>(result); } EXPECT_EQ(2, times_high_watermark_called_); EXPECT_EQ(20, buffer_.length()); diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index d19c552b91e6..1258534038c2 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -678,7 +678,7 @@ TEST_P(ConnectionImplTest, WriteWithWatermarks) { EXPECT_CALL(*client_write_buffer_, move(_)) .WillRepeatedly(DoAll(AddBufferToStringWithoutDraining(&data_written), Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove))); - EXPECT_CALL(*client_write_buffer_, write(_)).WillOnce(Invoke([&](int fd) -> int { + EXPECT_CALL(*client_write_buffer_, write(_)).WillOnce(Invoke([&](int fd) -> std::tuple { dispatcher_->exit(); return client_write_buffer_->failWrite(fd); })); @@ -764,7 +764,7 @@ TEST_P(ConnectionImplTest, WatermarkFuzzing) { .WillOnce(Invoke(client_write_buffer_, &MockWatermarkBuffer::baseMove)); EXPECT_CALL(*client_write_buffer_, write(_)) .WillOnce(DoAll(Invoke([&](int) -> void { client_write_buffer_->drain(bytes_to_flush); }), - Return(bytes_to_flush))) + Return(std::make_tuple(bytes_to_flush, 0)))) .WillRepeatedly(testing::Invoke(client_write_buffer_, &MockWatermarkBuffer::failWrite)); client_connection_->write(buffer_to_write, false); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); diff --git a/test/mocks/buffer/mocks.h b/test/mocks/buffer/mocks.h index cc0182374391..2be1903c621c 100644 --- a/test/mocks/buffer/mocks.h +++ b/test/mocks/buffer/mocks.h @@ -16,7 +16,7 @@ template class MockBufferBase : public BaseClass { MockBufferBase(); MockBufferBase(std::function below_low, std::function above_high); - MOCK_METHOD1(write, int(int fd)); + MOCK_METHOD1(write, std::tuple(int fd)); MOCK_METHOD1(move, void(Buffer::Instance& rhs)); MOCK_METHOD2(move, void(Buffer::Instance& rhs, uint64_t length)); MOCK_METHOD1(drain, void(uint64_t size)); @@ -24,12 +24,13 @@ template class MockBufferBase : public BaseClass { void baseMove(Buffer::Instance& rhs) { BaseClass::move(rhs); } void baseDrain(uint64_t size) { BaseClass::drain(size); } - int trackWrites(int fd) { - int bytes_written = BaseClass::write(fd); + std::tuple trackWrites(int fd) { + std::tuple result = BaseClass::write(fd); + int bytes_written = std::get<0>(result); if (bytes_written > 0) { bytes_written_ += bytes_written; } - return bytes_written; + return result; } void trackDrains(uint64_t size) { @@ -38,10 +39,7 @@ template class MockBufferBase : public BaseClass { } // A convenience function to invoke on write() which fails the write with EAGAIN. - int failWrite(int) { - errno = EAGAIN; - return -1; - } + std::tuple failWrite(int) { return std::make_tuple(-1, EAGAIN); } int bytes_written() const { return bytes_written_; } uint64_t bytes_drained() const { return bytes_drained_; } From 982ebd25e417eea2705f60eebfee4ea9c3df986d Mon Sep 17 00:00:00 2001 From: htuch Date: Thu, 19 Jul 2018 09:27:39 -0400 Subject: [PATCH 07/46] load_stats: fix for Google import. (#3900) Risk level: Low Testing: OSS/Google unit/integration tests for LRS. Signed-off-by: Harvey Tuch --- source/common/upstream/load_stats_reporter.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/common/upstream/load_stats_reporter.cc b/source/common/upstream/load_stats_reporter.cc index 14067f0391ce..2bc986a3d590 100644 --- a/source/common/upstream/load_stats_reporter.cc +++ b/source/common/upstream/load_stats_reporter.cc @@ -120,8 +120,11 @@ void LoadStatsReporter::onReceiveMessage( void LoadStatsReporter::startLoadReportPeriod() { // Once a cluster is tracked, we don't want to reset its stats between reports // to avoid racing between request/response. - std::unordered_map - existing_clusters; + // TODO(htuch): They key here could be absl::string_view, but this causes + // problems due to referencing of temporaries in the below loop with Google's + // internal string type. Consider this optimization when the string types + // converge. + std::unordered_map existing_clusters; for (const std::string& cluster_name : message_->clusters()) { if (clusters_.count(cluster_name) > 0) { existing_clusters.emplace(cluster_name, clusters_[cluster_name]); From b202f894fd24c96309e71657c9a033a08bd99415 Mon Sep 17 00:00:00 2001 From: Dhi Aurrahman Date: Thu, 19 Jul 2018 21:39:00 +0700 Subject: [PATCH 08/46] Remove the deprecated redis_health_check field (#3896) Fixes https://github.com/envoyproxy/envoy/issues/3713 Signed-off-by: Dhi Aurrahman --- api/envoy/api/v2/core/health_check.proto | 7 +- .../configuration/health_checkers/redis.rst | 24 ++++-- docs/root/intro/arch_overview/redis.rst | 6 +- source/common/config/cds_json.cc | 6 +- source/common/upstream/health_checker_impl.cc | 8 +- .../health_checkers/redis/utility.h | 12 --- .../health_checkers/redis/config_test.cc | 32 ++++---- .../health_checkers/redis/redis_test.cc | 73 ------------------- 8 files changed, 44 insertions(+), 124 deletions(-) diff --git a/api/envoy/api/v2/core/health_check.proto b/api/envoy/api/v2/core/health_check.proto index 3d21e7d413a1..94c3f7817962 100644 --- a/api/envoy/api/v2/core/health_check.proto +++ b/api/envoy/api/v2/core/health_check.proto @@ -152,9 +152,6 @@ message HealthCheck { // TCP health check. TcpHealthCheck tcp_health_check = 9; - // Redis health check. - RedisHealthCheck redis_health_check = 10; - // gRPC health check. GrpcHealthCheck grpc_health_check = 11; @@ -162,6 +159,10 @@ message HealthCheck { CustomHealthCheck custom_health_check = 13; } + reserved 10; // redis_health_check is deprecated by :ref:`custom_health_check + // ` + reserved "redis_health_check"; + // The "no traffic interval" is a special health check interval that is used when a cluster has // never had traffic routed to it. This lower interval allows cluster information to be kept up to // date, without sending a potentially large amount of active health checking traffic for no diff --git a/docs/root/configuration/health_checkers/redis.rst b/docs/root/configuration/health_checkers/redis.rst index 2439982ced8d..1859c005adb1 100644 --- a/docs/root/configuration/health_checkers/redis.rst +++ b/docs/root/configuration/health_checkers/redis.rst @@ -3,12 +3,22 @@ Redis ===== -The Redis health checker is a custom health checker which checks Redis upstream hosts. It sends -a Redis PING command and expect a PONG response. The upstream Redis server can respond with -anything other than PONG to cause an immediate active health check failure. Optionally, Envoy can -perform EXISTS on a user-specified key. If the key does not exist it is considered a passing healthcheck. -This allows the user to mark a Redis instance for maintenance by setting the specified -:ref:`key ` to any value and waiting for -traffic to drain. +The Redis health checker is a custom health checker (with :code:`envoy.health_checkers.redis` as name) +which checks Redis upstream hosts. It sends a Redis PING command and expect a PONG response. The upstream +Redis server can respond with anything other than PONG to cause an immediate active health check failure. +Optionally, Envoy can perform EXISTS on a user-specified key. If the key does not exist it is considered a +passing healthcheck. This allows the user to mark a Redis instance for maintenance by setting the +specified :ref:`key ` to any value and waiting +for traffic to drain. + +An example setting for :ref:`custom_health_check ` as a +Redis health checker is shown below: + +.. code-block:: yaml + + custom_health_check: + name: envoy.health_checkers.redis + config: + key: foo * :ref:`v2 API reference ` \ No newline at end of file diff --git a/docs/root/intro/arch_overview/redis.rst b/docs/root/intro/arch_overview/redis.rst index b93830edba16..ff7d4696a4ab 100644 --- a/docs/root/intro/arch_overview/redis.rst +++ b/docs/root/intro/arch_overview/redis.rst @@ -43,8 +43,10 @@ For filter configuration details, see the Redis proxy filter The corresponding cluster definition should be configured with :ref:`ring hash load balancing `. -If active healthchecking is desired, the cluster should be configured with a -:ref:`Redis healthcheck `. +If :ref:`active health checking ` is desired, the +cluster should be configured with a :ref:`custom health check +` which configured as a +:ref:`Redis health checker `. If passive healthchecking is desired, also configure :ref:`outlier detection `. diff --git a/source/common/config/cds_json.cc b/source/common/config/cds_json.cc index cbf9ede748c4..a7a684a590d6 100644 --- a/source/common/config/cds_json.cc +++ b/source/common/config/cds_json.cc @@ -53,9 +53,11 @@ void CdsJson::translateHealthCheck(const Json::Object& json_health_check, } } else { ASSERT(hc_type == "redis"); - auto* redis_health_check = health_check.mutable_redis_health_check(); + auto* redis_health_check = health_check.mutable_custom_health_check(); + redis_health_check->set_name("envoy.health_checkers.redis"); if (json_health_check.hasObject("redis_key")) { - redis_health_check->set_key(json_health_check.getString("redis_key")); + redis_health_check->mutable_config()->MergeFrom( + MessageUtil::keyValueStruct("key", json_health_check.getString("redis_key"))); } } } diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index cc90ab28aab6..faeeaf1a0672 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -65,16 +65,10 @@ HealthCheckerFactory::create(const envoy::api::v2::core::HealthCheck& hc_config, } return std::make_shared(cluster, hc_config, dispatcher, runtime, random, std::move(event_logger)); - // Deprecated redis_health_check, preserving using old config until it is removed. - case envoy::api::v2::core::HealthCheck::HealthCheckerCase::kRedisHealthCheck: - ENVOY_LOG(warn, "redis_health_check is deprecated, use custom_health_check instead"); - FALLTHRU; case envoy::api::v2::core::HealthCheck::HealthCheckerCase::kCustomHealthCheck: { auto& factory = Config::Utility::getAndCheckFactory( - hc_config.has_redis_health_check() - ? Extensions::HealthCheckers::HealthCheckerNames::get().RedisHealthChecker - : std::string(hc_config.custom_health_check().name())); + std::string(hc_config.custom_health_check().name())); std::unique_ptr context( new HealthCheckerFactoryContextImpl(cluster, runtime, random, dispatcher, std::move(event_logger))); diff --git a/source/extensions/health_checkers/redis/utility.h b/source/extensions/health_checkers/redis/utility.h index 52b95ca4899a..f41b95de8f2e 100644 --- a/source/extensions/health_checkers/redis/utility.h +++ b/source/extensions/health_checkers/redis/utility.h @@ -10,20 +10,8 @@ namespace RedisHealthChecker { namespace { -static const envoy::config::health_checker::redis::v2::Redis translateFromRedisHealthCheck( - const envoy::api::v2::core::HealthCheck::RedisHealthCheck& deprecated_redis_config) { - envoy::config::health_checker::redis::v2::Redis config; - config.set_key(deprecated_redis_config.key()); - return config; -} - static const envoy::config::health_checker::redis::v2::Redis getRedisHealthCheckConfig(const envoy::api::v2::core::HealthCheck& hc_config) { - // TODO(dio): redis_health_check is deprecated. - if (hc_config.has_redis_health_check()) { - return translateFromRedisHealthCheck(hc_config.redis_health_check()); - } - ProtobufTypes::MessagePtr config = ProtobufTypes::MessagePtr{new envoy::config::health_checker::redis::v2::Redis()}; MessageUtil::jsonConvert(hc_config.custom_health_check().config(), *config); diff --git a/test/extensions/health_checkers/redis/config_test.cc b/test/extensions/health_checkers/redis/config_test.cc index 21ef2eb46525..1f70f8115b03 100644 --- a/test/extensions/health_checkers/redis/config_test.cc +++ b/test/extensions/health_checkers/redis/config_test.cc @@ -39,7 +39,7 @@ TEST(HealthCheckerFactoryTest, createRedis) { .get())); } -TEST(HealthCheckerFactoryTest, createRedisViaUpstreamHealthCheckerFactory) { +TEST(HealthCheckerFactoryTest, createRedisWithoutKey) { const std::string yaml = R"EOF( timeout: 1s interval: 1s @@ -50,22 +50,19 @@ TEST(HealthCheckerFactoryTest, createRedisViaUpstreamHealthCheckerFactory) { custom_health_check: name: envoy.health_checkers.redis config: - key: foo )EOF"; - NiceMock cluster; - Runtime::MockLoader runtime; - Runtime::MockRandomGenerator random; - Event::MockDispatcher dispatcher; - AccessLog::MockAccessLogManager log_manager; - EXPECT_NE(nullptr, dynamic_cast( - Upstream::HealthCheckerFactory::create( - Upstream::parseHealthCheckFromV2Yaml(yaml), cluster, runtime, random, - dispatcher, log_manager) - .get())); + NiceMock context; + + RedisHealthCheckerFactory factory; + EXPECT_NE( + nullptr, + dynamic_cast( + factory.createCustomHealthChecker(Upstream::parseHealthCheckFromV2Yaml(yaml), context) + .get())); } -TEST(HealthCheckerFactoryTest, createRedisWithDeprecatedConfig) { +TEST(HealthCheckerFactoryTest, createRedisViaUpstreamHealthCheckerFactory) { const std::string yaml = R"EOF( timeout: 1s interval: 1s @@ -73,9 +70,10 @@ TEST(HealthCheckerFactoryTest, createRedisWithDeprecatedConfig) { interval_jitter: 1s unhealthy_threshold: 1 healthy_threshold: 1 - # Using the deprecated redis_health_check should work. - redis_health_check: - key: foo + custom_health_check: + name: envoy.health_checkers.redis + config: + key: foo )EOF"; NiceMock cluster; @@ -84,8 +82,6 @@ TEST(HealthCheckerFactoryTest, createRedisWithDeprecatedConfig) { Event::MockDispatcher dispatcher; AccessLog::MockAccessLogManager log_manager; EXPECT_NE(nullptr, dynamic_cast( - // Always use Upstream's HealthCheckerFactory when creating instance using - // deprecated config. Upstream::HealthCheckerFactory::create( Upstream::parseHealthCheckFromV2Yaml(yaml), cluster, runtime, random, dispatcher, log_manager) diff --git a/test/extensions/health_checkers/redis/redis_test.cc b/test/extensions/health_checkers/redis/redis_test.cc index 9aee0c0fdf52..015a7c08c9b5 100644 --- a/test/extensions/health_checkers/redis/redis_test.cc +++ b/test/extensions/health_checkers/redis/redis_test.cc @@ -30,27 +30,6 @@ class RedisHealthCheckerTest : cluster_(new NiceMock()), event_logger_(new Upstream::MockHealthCheckEventLogger()) {} - void setupExistsHealthcheckDeprecated() { - const std::string yaml = R"EOF( - timeout: 1s - interval: 1s - no_traffic_interval: 5s - interval_jitter: 1s - unhealthy_threshold: 1 - healthy_threshold: 1 - # Using the deprecated redis_health_check should work. - redis_health_check: - key: foo - )EOF"; - - const auto& hc_config = Upstream::parseHealthCheckFromV2Yaml(yaml); - const auto& redis_config = getRedisHealthCheckConfig(hc_config); - - health_checker_.reset( - new RedisHealthChecker(*cluster_, hc_config, redis_config, dispatcher_, runtime_, random_, - Upstream::HealthCheckEventLoggerPtr(event_logger_), *this)); - } - void setup() { const std::string yaml = R"EOF( timeout: 1s @@ -280,58 +259,6 @@ TEST_F(RedisHealthCheckerTest, Exists) { EXPECT_EQ(2UL, cluster_->info_->stats_store_.counter("health_check.failure").value()); } -TEST_F(RedisHealthCheckerTest, ExistsDeprecated) { - InSequence s; - setupExistsHealthcheckDeprecated(); - - cluster_->prioritySet().getMockHostSet(0)->hosts_ = { - Upstream::makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; - - expectSessionCreate(); - expectClientCreate(); - expectExistsRequestCreate(); - health_checker_->start(); - - client_->runHighWatermarkCallbacks(); - client_->runLowWatermarkCallbacks(); - - // Success - EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); - Extensions::NetworkFilters::RedisProxy::RespValuePtr response( - new Extensions::NetworkFilters::RedisProxy::RespValue()); - response->type(Extensions::NetworkFilters::RedisProxy::RespType::Integer); - response->asInteger() = 0; - pool_callbacks_->onResponse(std::move(response)); - - expectExistsRequestCreate(); - interval_timer_->callback_(); - - // Failure, exists - EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); - response.reset(new Extensions::NetworkFilters::RedisProxy::RespValue()); - response->type(Extensions::NetworkFilters::RedisProxy::RespType::Integer); - response->asInteger() = 1; - pool_callbacks_->onResponse(std::move(response)); - - expectExistsRequestCreate(); - interval_timer_->callback_(); - - // Failure, no value - EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); - response.reset(new Extensions::NetworkFilters::RedisProxy::RespValue()); - pool_callbacks_->onResponse(std::move(response)); - - EXPECT_CALL(*client_, close()); - - EXPECT_EQ(3UL, cluster_->info_->stats_store_.counter("health_check.attempt").value()); - EXPECT_EQ(1UL, cluster_->info_->stats_store_.counter("health_check.success").value()); - EXPECT_EQ(2UL, cluster_->info_->stats_store_.counter("health_check.failure").value()); -} - // Tests that redis client will behave appropriately when reuse_connection is false. TEST_F(RedisHealthCheckerTest, NoConnectionReuse) { InSequence s; From 8ed7c157c4b672763ae74e526e69ef43dda52549 Mon Sep 17 00:00:00 2001 From: htuch Date: Thu, 19 Jul 2018 10:44:03 -0400 Subject: [PATCH 09/46] api: ensure HeaderValue keys are non-empty. (#3901) Some sites where we consume HeaderValue, e.g. in Google gRPC client library metadata, require non-empty keys as a precondition. This seems a general property; there shouldn't be any use case for a header key that is empty. Found with server_fuzz_test under oss-fuzz (issue 9373). As a bonus, also fixed another proto descriptor crash that occurs with this corpus addition due to missing proto descriptor pool entries in server_fuzz_test. Risk level: Low Testing: New server_fuzz corpus entry. Signed-off-by: Harvey Tuch --- api/envoy/api/v2/core/base.proto | 2 +- test/server/BUILD | 1 + ...testcase-server_fuzz_test-6036175623028736 | 26 +++++++++++++++++++ test/server/server_fuzz_test.cc | 3 +++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6036175623028736 diff --git a/api/envoy/api/v2/core/base.proto b/api/envoy/api/v2/core/base.proto index ffdd03fdfe59..1e86c529c46e 100644 --- a/api/envoy/api/v2/core/base.proto +++ b/api/envoy/api/v2/core/base.proto @@ -133,7 +133,7 @@ enum RequestMethod { // Header name/value pair. message HeaderValue { // Header name. - string key = 1; + string key = 1 [(validate.rules).string.min_bytes = 1]; // Header value. // diff --git a/test/server/BUILD b/test/server/BUILD index e629484276d7..ed98ace7d7f5 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -161,6 +161,7 @@ envoy_cc_fuzz_test( corpus = "server_corpus", deps = [ "//source/common/thread_local:thread_local_lib", + "//source/server:proto_descriptors_lib", "//source/server:server_lib", "//test/integration:integration_lib", "//test/mocks/server:server_mocks", diff --git a/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6036175623028736 b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6036175623028736 new file mode 100644 index 000000000000..9161317f6aff --- /dev/null +++ b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6036175623028736 @@ -0,0 +1,26 @@ +dynamic_resources { + ads_config { + api_type: GRPC + grpc_services { + google_grpc { + target_uri: "\177\177" + stat_prefix: "\177\001D\177" + } + timeout { + seconds: 2048 + } + initial_metadata { + value: "\177\177\177\177" + } + } + } +} +flags_path: "\'" +admin { + access_log_path: "@" + address { + pipe { + path: "^" + } + } +} diff --git a/test/server/server_fuzz_test.cc b/test/server/server_fuzz_test.cc index 48906b07bddc..6b8d057ce682 100644 --- a/test/server/server_fuzz_test.cc +++ b/test/server/server_fuzz_test.cc @@ -3,6 +3,7 @@ #include "common/network/address_impl.h" #include "common/thread_local/thread_local_impl.h" +#include "server/proto_descriptors.h" #include "server/server.h" #include "server/test_hooks.h" @@ -24,6 +25,8 @@ DEFINE_PROTO_FUZZER(const envoy::config::bootstrap::v2::Bootstrap& input) { TestComponentFactory component_factory; ThreadLocal::InstanceImpl thread_local_instance; + RELEASE_ASSERT(Envoy::Server::validateProtoDescriptors(), ""); + { const std::string bootstrap_path = TestEnvironment::temporaryPath("bootstrap.pb_text"); std::ofstream bootstrap_file(bootstrap_path); From 20296c51462cfc1e8a449adf2a4ec53c5d108998 Mon Sep 17 00:00:00 2001 From: htuch Date: Fri, 20 Jul 2018 15:54:50 -0400 Subject: [PATCH 10/46] http: ensure the per-stream idle timer is disabled at stream end. (#3914) Previously, this could have left the idle timer active during deferred delete. Thanks to @MattKlein123 for spotting this. Risk Level: Low Testing: New unit test. Signed-off-by: Harvey Tuch --- source/common/http/conn_manager_impl.cc | 4 +++ test/common/http/conn_manager_impl_test.cc | 31 ++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index c05ffcbdf726..643e3a25c0b0 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -164,6 +164,10 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream) { } void ConnectionManagerImpl::doDeferredStreamDestroy(ActiveStream& stream) { + if (stream.idle_timer_ != nullptr) { + stream.idle_timer_->disableTimer(); + stream.idle_timer_ = nullptr; + } stream.state_.destroyed_ = true; for (auto& filter : stream.decoder_filters_) { filter->handle_->onDestroy(); diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 7b4b1a2007bd..f328225ed405 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -1133,6 +1133,7 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeaders // Expect resetIdleTimer() to be called for the response // encodeHeaders()/encodeData(). EXPECT_CALL(*idle_timer, enableTimer(_)).Times(2); + EXPECT_CALL(*idle_timer, disableTimer()); idle_timer->callback_(); data.drain(4); @@ -1153,6 +1154,33 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeaders EXPECT_EQ(1U, stats_.named_.downstream_rq_idle_timeout_.value()); } +// Validate the per-stream idle timer is properly disabled when the stream terminates normally. +TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutNormalTermination) { + setup(false, ""); + ON_CALL(route_config_provider_.route_config_->route_->route_entry_, idleTimeout()) + .WillByDefault(Return(std::chrono::milliseconds(10))); + + // Codec sends downstream request headers. + Event::MockTimer* idle_timer = new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { + StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); + + HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + EXPECT_CALL(*idle_timer, enableTimer(_)); + decoder->decodeHeaders(std::move(headers), false); + + data.drain(4); + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); + + EXPECT_CALL(*idle_timer, disableTimer()); + conn_manager_->onEvent(Network::ConnectionEvent::RemoteClose); + + EXPECT_EQ(0U, stats_.named_.downstream_rq_idle_timeout_.value()); +} + // Validate the per-stream idle timeout after having sent downstream // headers+body. TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeadersAndBody) { @@ -1175,6 +1203,7 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeaders // Expect resetIdleTimer() to be called for the response // encodeHeaders()/encodeData(). EXPECT_CALL(*idle_timer, enableTimer(_)).Times(2); + EXPECT_CALL(*idle_timer, disableTimer()); idle_timer->callback_(); data.drain(4); @@ -1224,6 +1253,7 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterUpstreamHeaders) EXPECT_CALL(*idle_timer, enableTimer(_)); filter->callbacks_->encodeHeaders(std::move(response_headers), false); + EXPECT_CALL(*idle_timer, disableTimer()); idle_timer->callback_(); data.drain(4); @@ -1286,6 +1316,7 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterBidiData) { EXPECT_CALL(*idle_timer, enableTimer(_)); filter->callbacks_->encodeData(fake_response, false); + EXPECT_CALL(*idle_timer, disableTimer()); idle_timer->callback_(); data.drain(4); From b8e019fc5eda5e9bac2820d1b14a47c0735dca33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Guti=C3=A9rrez=20Segal=C3=A9s?= Date: Fri, 20 Jul 2018 14:25:51 -0700 Subject: [PATCH 11/46] Update gauges when a subset LB is destroyed (#3917) Without these updates, the gauges will be incorrect after a hot restart. Fixes https://github.com/envoyproxy/envoy/issues/3916 --- source/common/upstream/subset_lb.cc | 12 +++++++++++- test/common/upstream/subset_lb_test.cc | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/source/common/upstream/subset_lb.cc b/source/common/upstream/subset_lb.cc index 162eb31e28d6..e15f6250ac3f 100644 --- a/source/common/upstream/subset_lb.cc +++ b/source/common/upstream/subset_lb.cc @@ -54,7 +54,17 @@ SubsetLoadBalancer::SubsetLoadBalancer( }); } -SubsetLoadBalancer::~SubsetLoadBalancer() { original_priority_set_callback_handle_->remove(); } +SubsetLoadBalancer::~SubsetLoadBalancer() { + original_priority_set_callback_handle_->remove(); + + // Ensure gauges reflect correct values. + forEachSubset(subsets_, [&](LbSubsetEntryPtr entry) { + if (entry->initialized() && entry->active()) { + stats_.lb_subsets_removed_.inc(); + stats_.lb_subsets_active_.dec(); + } + }); +} void SubsetLoadBalancer::refreshSubsets() { for (auto& host_set : original_priority_set_.hostSetsPerPriority()) { diff --git a/test/common/upstream/subset_lb_test.cc b/test/common/upstream/subset_lb_test.cc index 21ccceb04797..4da2cfbb0811 100644 --- a/test/common/upstream/subset_lb_test.cc +++ b/test/common/upstream/subset_lb_test.cc @@ -1433,6 +1433,26 @@ TEST_F(SubsetLoadBalancerTest, EnabledLocalityWeightAwareness) { EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(&context)); } +TEST_P(SubsetLoadBalancerTest, GaugesUpdatedOnDestroy) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::api::v2::Cluster::LbSubsetConfig::ANY_ENDPOINT)); + + std::vector> subset_keys = {{"version"}}; + EXPECT_CALL(subset_info_, subsetKeys()).WillRepeatedly(ReturnRef(subset_keys)); + + init({ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + }); + + EXPECT_EQ(1U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(0U, stats_.lb_subsets_removed_.value()); + + lb_ = nullptr; + + EXPECT_EQ(0U, stats_.lb_subsets_active_.value()); + EXPECT_EQ(1U, stats_.lb_subsets_removed_.value()); +} + INSTANTIATE_TEST_CASE_P(UpdateOrderings, SubsetLoadBalancerTest, testing::ValuesIn({REMOVES_FIRST, SIMULTANEOUS})); From 598f5c9433707019169669c787d606ff0b07e9d4 Mon Sep 17 00:00:00 2001 From: Rama Chavali Date: Sat, 21 Jul 2018 03:19:11 +0530 Subject: [PATCH 12/46] listener: fix ipv6 error (#3912) * fix ipV6 error * support ipv6 env only Signed-off-by: Rama --- source/common/network/address_impl.cc | 22 +++++++++++----------- source/common/network/address_impl.h | 6 ++++++ source/server/listener_manager_impl.cc | 20 +++++++++++--------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/source/common/network/address_impl.cc b/source/common/network/address_impl.cc index e7d1e6d0a0cd..6faf57ac090d 100644 --- a/source/common/network/address_impl.cc +++ b/source/common/network/address_impl.cc @@ -21,19 +21,10 @@ namespace Address { namespace { -// Check if an IP family is supported on this machine. -bool ipFamilySupported(int domain) { - const int fd = ::socket(domain, SOCK_STREAM, 0); - if (fd >= 0) { - RELEASE_ASSERT(::close(fd) == 0, ""); - } - return fd != -1; -} - // Validate that IPv4 is supported on this platform, raise an exception for the // given address if not. void validateIpv4Supported(const std::string& address) { - static const bool supported = ipFamilySupported(AF_INET); + static const bool supported = Network::Address::ipFamilySupported(AF_INET); if (!supported) { throw EnvoyException( fmt::format("IPv4 addresses are not supported on this machine: {}", address)); @@ -43,7 +34,7 @@ void validateIpv4Supported(const std::string& address) { // Validate that IPv6 is supported on this platform, raise an exception for the // given address if not. void validateIpv6Supported(const std::string& address) { - static const bool supported = ipFamilySupported(AF_INET6); + static const bool supported = Network::Address::ipFamilySupported(AF_INET6); if (!supported) { throw EnvoyException( fmt::format("IPv6 addresses are not supported on this machine: {}", address)); @@ -52,6 +43,15 @@ void validateIpv6Supported(const std::string& address) { } // namespace +// Check if an IP family is supported on this machine. +bool ipFamilySupported(int domain) { + const int fd = ::socket(domain, SOCK_STREAM, 0); + if (fd >= 0) { + RELEASE_ASSERT(::close(fd) == 0, ""); + } + return fd != -1; +} + Address::InstanceConstSharedPtr addressFromSockAddr(const sockaddr_storage& ss, socklen_t ss_len, bool v6only) { RELEASE_ASSERT(ss_len == 0 || ss_len >= sizeof(sa_family_t), ""); diff --git a/source/common/network/address_impl.h b/source/common/network/address_impl.h index c48003624c13..0bb41d26e422 100644 --- a/source/common/network/address_impl.h +++ b/source/common/network/address_impl.h @@ -15,6 +15,12 @@ namespace Envoy { namespace Network { namespace Address { +/** + * Returns true if the given family is supported on this machine. + * @param domain the IP family. + */ +bool ipFamilySupported(int domain); + /** * Convert an address in the form of the socket address struct defined by Posix, Linux, etc. into * a Network::Address::Instance and return a pointer to it. Raises an EnvoyException on failure. diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 393fdfe1e35e..e69bebe4ba3f 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -380,17 +380,19 @@ void ListenerImpl::convertDestinationIPsMapToTrie() { for (const auto& entry : destination_ips_map) { std::vector subnets; if (entry.first == EMPTY_STRING) { - list.push_back( - std::make_pair>( - std::make_shared(entry.second), - {Network::Address::CidrRange::create("0.0.0.0/0"), - Network::Address::CidrRange::create("::/0")})); + if (Network::Address::ipFamilySupported(AF_INET)) { + subnets.push_back(Network::Address::CidrRange::create("0.0.0.0/0")); + } + if (Network::Address::ipFamilySupported(AF_INET6)) { + subnets.push_back(Network::Address::CidrRange::create("::/0")); + } } else { - list.push_back( - std::make_pair>( - std::make_shared(entry.second), - {Network::Address::CidrRange::create(entry.first)})); + subnets.push_back(Network::Address::CidrRange::create(entry.first)); } + list.push_back( + std::make_pair>( + std::make_shared(entry.second), + std::vector(subnets))); } destination_ips_pair.second = std::make_unique(list, true); } From 3cc6e3cb4d776eaa86c58170972c288d8a97b784 Mon Sep 17 00:00:00 2001 From: htuch Date: Sun, 22 Jul 2018 19:02:50 -0400 Subject: [PATCH 13/46] fuzz: fuzzer for HeaderMapImpl. (#3921) This implementation has been flagged during security audit and has had previous bugs. Risk Level: Low Testing: Example corpus. Signed-off-by: Harvey Tuch --- test/common/http/BUILD | 18 ++ test/common/http/header_map_impl_corpus/empty | 0 .../http/header_map_impl_corpus/example | 233 ++++++++++++++++++ test/common/http/header_map_impl_fuzz.proto | 69 ++++++ test/common/http/header_map_impl_fuzz_test.cc | 170 +++++++++++++ 5 files changed, 490 insertions(+) create mode 100644 test/common/http/header_map_impl_corpus/empty create mode 100644 test/common/http/header_map_impl_corpus/example create mode 100644 test/common/http/header_map_impl_fuzz.proto create mode 100644 test/common/http/header_map_impl_fuzz_test.cc diff --git a/test/common/http/BUILD b/test/common/http/BUILD index a48257922c3c..7ccee9f708ce 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -2,9 +2,11 @@ licenses(["notice"]) # Apache 2 load( "//bazel:envoy_build_system.bzl", + "envoy_cc_fuzz_test", "envoy_cc_test", "envoy_cc_test_library", "envoy_package", + "envoy_proto_library", ) envoy_package() @@ -168,6 +170,22 @@ envoy_cc_test( ], ) +envoy_proto_library( + name = "header_map_impl_fuzz_proto", + srcs = ["header_map_impl_fuzz.proto"], + external_deps = ["well_known_protos"], +) + +envoy_cc_fuzz_test( + name = "header_map_impl_fuzz_test", + srcs = ["header_map_impl_fuzz_test.cc"], + corpus = "header_map_impl_corpus", + deps = [ + ":header_map_impl_fuzz_proto", + "//source/common/http:header_map_lib", + ], +) + envoy_cc_test( name = "header_utility_test", srcs = ["header_utility_test.cc"], diff --git a/test/common/http/header_map_impl_corpus/empty b/test/common/http/header_map_impl_corpus/empty new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/common/http/header_map_impl_corpus/example b/test/common/http/header_map_impl_corpus/example new file mode 100644 index 000000000000..e49ae93468aa --- /dev/null +++ b/test/common/http/header_map_impl_corpus/example @@ -0,0 +1,233 @@ +actions { + add_reference { + key: "foo" + value: "bar" + } +} +actions { + add_reference { + key: "foo" + value: "baz" + } +} +actions { + add_reference_key { + key: "foo_string_key" + string_value: "barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr" + } +} +actions { + add_reference_key { + key: "foo_string_key" + string_value: "baz" + } +} +actions { + add_reference_key { + key: "foo_uint64_key" + uint64_value: 42 + } +} +actions { + add_reference_key { + key: "foo_uint64_key" + uint64_value: 37 + } +} +actions { + add_copy { + key: "foo_string_key" + string_value: "barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr" + } +} +actions { + add_copy { + key: "foo_string_key" + string_value: "baz" + } +} +actions { + add_copy { + key: "foo_uint64_key" + uint64_value: 42 + } +} +actions { + add_copy { + key: "foo_uint64_key" + uint64_value: 37 + } +} +actions { + set_reference { + key: "foo" + value: "bar" + } +} +actions { + set_reference { + key: "foo" + value: "baz" + } +} +actions { + set_reference_key { + key: "foo" + value: "bar" + } +} +actions { + set_reference_key { + key: "foo" + value: "baz" + } +} + +actions { + add_reference { + key: ":method" + value: "bar" + } +} +actions { + add_reference { + key: ":method" + value: "baz" + } +} +actions { + add_reference_key { + key: ":method" + string_value: "bar" + } +} +actions { + add_reference_key { + key: ":method" + string_value: "baz" + } +} +actions { + add_reference_key { + key: ":method" + uint64_value: 42 + } +} +actions { + add_reference_key { + key: ":method" + uint64_value: 37 + } +} +actions { + add_copy { + key: ":method" + string_value: "bar" + } +} +actions { + add_copy { + key: ":method" + string_value: "baz" + } +} +actions { + add_copy { + key: ":method" + uint64_value: 42 + } +} +actions { + add_copy { + key: ":method" + uint64_value: 37 + } +} +actions { + set_reference { + key: ":method" + value: "bar" + } +} +actions { + set_reference { + key: ":method" + value: "baz" + } +} +actions { + set_reference_key { + key: ":method" + value: "bar" + } +} +actions { + set_reference_key { + key: ":method" + value: "baz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "aa" + } +} +actions { + get_and_mutate { + key: ":method" + clear: {} + } +} +actions { + get_and_mutate { + key: ":method" + find: "a" + } +} +actions { + get_and_mutate { + key: ":method" + set_copy: "a" + } +} +actions { + get_and_mutate { + key: ":method" + set_integer: 0 + } +} +actions { + get_and_mutate { + key: ":method" + set_reference: "a" + } +} +actions { + copy: {} +} +actions { + lookup: ":method" +} +actions { + lookup: "foo" +} +actions { + remove: "f" +} +actions { + remove_prefix: "foo" +} +actions { + remove: ":m" +} +actions { + remove_prefix: ":m" +} diff --git a/test/common/http/header_map_impl_fuzz.proto b/test/common/http/header_map_impl_fuzz.proto new file mode 100644 index 000000000000..fd1e0155c94e --- /dev/null +++ b/test/common/http/header_map_impl_fuzz.proto @@ -0,0 +1,69 @@ +syntax = "proto3"; + +package test.common.http; + +import "google/protobuf/empty.proto"; + +// Structured input for header_map_impl_fuzz_test. + +message AddReference { + string key = 1; + string value = 2; +} + +message AddReferenceKey { + string key = 1; + oneof value_selector { + string string_value = 2; + uint64 uint64_value = 3; + } +} + +message AddCopy { + string key = 1; + oneof value_selector { + string string_value = 2; + uint64 uint64_value = 3; + } +} + +message SetReference { + string key = 1; + string value = 2; +} + +message SetReferenceKey { + string key = 1; + string value = 2; +} + +message GetAndMutate { + string key = 1; + oneof mutate_selector { + string append = 2; + google.protobuf.Empty clear = 3; + string find = 4; + string set_copy = 5; + uint64 set_integer = 6; + string set_reference = 7; + } +} + +message Action { + oneof action_selector { + AddReference add_reference = 1; + AddReferenceKey add_reference_key = 2; + AddCopy add_copy = 3; + SetReference set_reference = 4; + SetReferenceKey set_reference_key = 5; + GetAndMutate get_and_mutate = 6; + google.protobuf.Empty copy = 7; + string lookup = 8; + string remove = 9; + string remove_prefix = 10; + } +} + +message HeaderMapImplFuzzTestCase { + repeated Action actions = 1; +} diff --git a/test/common/http/header_map_impl_fuzz_test.cc b/test/common/http/header_map_impl_fuzz_test.cc new file mode 100644 index 000000000000..4e8f07658add --- /dev/null +++ b/test/common/http/header_map_impl_fuzz_test.cc @@ -0,0 +1,170 @@ +#include + +#include "common/common/assert.h" +#include "common/common/logger.h" +#include "common/http/header_map_impl.h" + +#include "test/common/http/header_map_impl_fuzz.pb.h" +#include "test/fuzz/fuzz_runner.h" + +namespace Envoy { + +// Fuzz the header map implementation. +DEFINE_PROTO_FUZZER(const test::common::http::HeaderMapImplFuzzTestCase& input) { + Http::HeaderMapImplPtr header_map = std::make_unique(); + const auto predefined_exists = [&header_map](const std::string& s) -> bool { + const Http::HeaderEntry* entry; + return header_map->lookup(Http::LowerCaseString(s), &entry) == Http::HeaderMap::Lookup::Found; + }; + std::vector> lower_case_strings; + std::vector> strings; + for (int i = 0; i < input.actions().size(); ++i) { + const auto& action = input.actions(i); + ENVOY_LOG_MISC(debug, "Action {}", action.DebugString()); + switch (action.action_selector_case()) { + case test::common::http::Action::kAddReference: { + const auto& add_reference = action.add_reference(); + // Workaround for https://github.com/envoyproxy/envoy/issues/3919. + if (predefined_exists(add_reference.key())) { + continue; + } + lower_case_strings.emplace_back(std::make_unique(add_reference.key())); + strings.emplace_back(std::make_unique(add_reference.value())); + header_map->addReference(*lower_case_strings.back(), *strings.back()); + break; + } + case test::common::http::Action::kAddReferenceKey: { + const auto& add_reference_key = action.add_reference_key(); + // Workaround for https://github.com/envoyproxy/envoy/issues/3919. + if (predefined_exists(add_reference_key.key())) { + continue; + } + lower_case_strings.emplace_back( + std::make_unique(add_reference_key.key())); + switch (add_reference_key.value_selector_case()) { + case test::common::http::AddReferenceKey::kStringValue: + header_map->addReferenceKey(*lower_case_strings.back(), add_reference_key.string_value()); + break; + case test::common::http::AddReferenceKey::kUint64Value: + header_map->addReferenceKey(*lower_case_strings.back(), add_reference_key.uint64_value()); + break; + default: + break; + } + break; + } + case test::common::http::Action::kAddCopy: { + const auto& add_copy = action.add_copy(); + // Workaround for https://github.com/envoyproxy/envoy/issues/3919. + if (predefined_exists(add_copy.key())) { + continue; + } + const Http::LowerCaseString key{add_copy.key()}; + switch (add_copy.value_selector_case()) { + case test::common::http::AddCopy::kStringValue: + header_map->addCopy(key, add_copy.string_value()); + break; + case test::common::http::AddCopy::kUint64Value: + header_map->addCopy(key, add_copy.uint64_value()); + break; + default: + break; + } + break; + } + case test::common::http::Action::kSetReference: { + const auto& set_reference = action.set_reference(); + lower_case_strings.emplace_back(std::make_unique(set_reference.key())); + strings.emplace_back(std::make_unique(set_reference.value())); + header_map->setReference(*lower_case_strings.back(), *strings.back()); + break; + } + case test::common::http::Action::kSetReferenceKey: { + const auto& set_reference_key = action.set_reference_key(); + lower_case_strings.emplace_back( + std::make_unique(set_reference_key.key())); + header_map->setReferenceKey(*lower_case_strings.back(), set_reference_key.value()); + break; + } + case test::common::http::Action::kGetAndMutate: { + const auto& get_and_mutate = action.get_and_mutate(); + auto* header_entry = header_map->get(Http::LowerCaseString(get_and_mutate.key())); + if (header_entry != nullptr) { + // Do some read-only stuff. + (void)strlen(header_entry->key().c_str()); + (void)strlen(header_entry->value().c_str()); + (void)strlen(header_entry->value().buffer()); + header_entry->key().empty(); + header_entry->value().empty(); + // Do some mutation or parameterized action. + switch (get_and_mutate.mutate_selector_case()) { + case test::common::http::GetAndMutate::kAppend: + header_entry->value().append(get_and_mutate.append().c_str(), + get_and_mutate.append().size()); + break; + case test::common::http::GetAndMutate::kClear: + header_entry->value().clear(); + break; + case test::common::http::GetAndMutate::kFind: + header_entry->value().find(get_and_mutate.find().c_str()); + break; + case test::common::http::GetAndMutate::kSetCopy: + header_entry->value().setCopy(get_and_mutate.set_copy().c_str(), + get_and_mutate.set_copy().size()); + break; + case test::common::http::GetAndMutate::kSetInteger: + header_entry->value().setInteger(get_and_mutate.set_integer()); + break; + case test::common::http::GetAndMutate::kSetReference: + strings.emplace_back(std::make_unique(get_and_mutate.set_reference())); + header_entry->value().setReference(*strings.back()); + break; + default: + break; + } + } + break; + } + case test::common::http::Action::kCopy: { + header_map = std::make_unique( + *reinterpret_cast(header_map.get())); + break; + } + case test::common::http::Action::kLookup: { + const Http::HeaderEntry* header_entry; + header_map->lookup(Http::LowerCaseString(action.lookup()), &header_entry); + break; + } + case test::common::http::Action::kRemove: { + header_map->remove(Http::LowerCaseString(action.remove())); + break; + } + case test::common::http::Action::kRemovePrefix: { + header_map->removePrefix(Http::LowerCaseString(action.remove_prefix())); + break; + } + default: + // Maybe nothing is set? + break; + } + // Exercise some read-only accessors. + header_map->byteSize(); + header_map->size(); + header_map->iterate( + [](const Http::HeaderEntry& header, void * /*context*/) -> Http::HeaderMap::Iterate { + header.key(); + header.value(); + return Http::HeaderMap::Iterate::Continue; + }, + nullptr); + header_map->iterateReverse( + [](const Http::HeaderEntry& header, void * /*context*/) -> Http::HeaderMap::Iterate { + header.key(); + header.value(); + return Http::HeaderMap::Iterate::Continue; + }, + nullptr); + } +} + +} // namespace Envoy From a5d9885be44d7c507591dc282c17dc5db3f177d6 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Sun, 22 Jul 2018 20:34:42 -0400 Subject: [PATCH 14/46] util: Add test for checking the format of proto files. (#3923) Add test for checking the format of proto files. Risk Level: none Testing: check_format Docs Changes: N/A Release Notes: N/A Fixes: #3812 Signed-off-by: Joshua Marantz --- tools/check_format_test_helper.py | 3 +++ tools/testdata/check_format/proto_format.proto | 1 + tools/testdata/check_format/proto_format.proto.gold | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 tools/testdata/check_format/proto_format.proto create mode 100644 tools/testdata/check_format/proto_format.proto.gold diff --git a/tools/check_format_test_helper.py b/tools/check_format_test_helper.py index 7ac9f5af94ea..17ad7edf9f58 100755 --- a/tools/check_format_test_helper.py +++ b/tools/check_format_test_helper.py @@ -134,6 +134,7 @@ def checkFileExpectingOK(filename): errors += fixFileExpectingSuccess("header_order.cc") errors += fixFileExpectingSuccess("license.BUILD") errors += fixFileExpectingSuccess("bad_envoy_build_sys_ref.BUILD") + errors += fixFileExpectingSuccess("proto_format.proto") errors += fixFileExpectingFailure("no_namespace_envoy.cc", "Unable to find Envoy namespace or NOLINT(namespace-envoy)") errors += fixFileExpectingFailure("mutex.cc", @@ -161,6 +162,8 @@ def checkFileExpectingOK(filename): errors += checkFileExpectingError("license.BUILD", "envoy_build_fixer check failed") errors += checkFileExpectingError("bad_envoy_build_sys_ref.BUILD", "Superfluous '@envoy//' prefix") + errors += checkFileExpectingError("proto_format.proto", "clang-format check failed") + errors += checkFileExpectingOK("ok_file.cc") errors += fixFileExpectingFailure("proto.BUILD", diff --git a/tools/testdata/check_format/proto_format.proto b/tools/testdata/check_format/proto_format.proto new file mode 100644 index 000000000000..b31d9dfa47db --- /dev/null +++ b/tools/testdata/check_format/proto_format.proto @@ -0,0 +1 @@ +// This commment is too long for the line-limit built into our clang configuration, so it will need to be wrapped. diff --git a/tools/testdata/check_format/proto_format.proto.gold b/tools/testdata/check_format/proto_format.proto.gold new file mode 100644 index 000000000000..db5b8dea9cc7 --- /dev/null +++ b/tools/testdata/check_format/proto_format.proto.gold @@ -0,0 +1,2 @@ +// This commment is too long for the line-limit built into our clang +// configuration, so it will need to be wrapped. From 783fda92de0c78df0c39f2bf1201ad6bf3b4a9d7 Mon Sep 17 00:00:00 2001 From: Matt Klein Date: Mon, 23 Jul 2018 06:20:30 -0700 Subject: [PATCH 15/46] http1: fix crash when upstream sends extra CR/LF between responses (#3913) Requires pulling upstream http-parser fix. Fixes https://github.com/envoyproxy/envoy/issues/3337 Signed-off-by: Matt Klein --- bazel/repository_locations.bzl | 6 ++- source/common/http/http1/codec_impl.cc | 2 + .../upstream_extra_crlf.pb_text | 38 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 test/integration/h1_capture_corpus/upstream_extra_crlf.pb_text diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index c9e1446d8e73..d952af941cf5 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -68,9 +68,11 @@ REPOSITORY_LOCATIONS = dict( remote = "https://github.com/google/jwt_verify_lib", ), com_github_nodejs_http_parser = dict( - # 2018-05-30 snapshot to pick up a performance fix, nodejs/http-parser PR 422 + # 2018-07-20 snapshot to pick up: + # A performance fix, nodejs/http-parser PR 422. + # A bug fix, nodejs/http-parser PR 432. # TODO(brian-pane): Upgrade to the next http-parser release once it's available - commit = "cf69c8eda9fe79e4682598a7b3d39338dea319a3", + commit = "77310eeb839c4251c07184a5db8885a572a08352", remote = "https://github.com/nodejs/http-parser", ), com_github_pallets_jinja = dict( diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index ec5217164f6d..a08878e609d1 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -436,6 +436,7 @@ void ConnectionImpl::onMessageCompleteBase() { } void ConnectionImpl::onMessageBeginBase() { + ENVOY_CONN_LOG(trace, "message begin", connection_); ASSERT(!current_header_map_); current_header_map_.reset(new HeaderMapImpl()); header_parsing_state_ = HeaderParsingState::Field; @@ -725,6 +726,7 @@ void ClientConnectionImpl::onBody(const char* data, size_t length) { } void ClientConnectionImpl::onMessageComplete() { + ENVOY_CONN_LOG(trace, "message complete", connection_); if (ignore_message_complete_for_100_continue_) { ignore_message_complete_for_100_continue_ = false; return; diff --git a/test/integration/h1_capture_corpus/upstream_extra_crlf.pb_text b/test/integration/h1_capture_corpus/upstream_extra_crlf.pb_text new file mode 100644 index 000000000000..6407b0be219e --- /dev/null +++ b/test/integration/h1_capture_corpus/upstream_extra_crlf.pb_text @@ -0,0 +1,38 @@ +events { + downstream_send_bytes: "POST /test/long/url HTTP/1.1\r\nhost: host\r\nx-lyft-user-id: 123\r\nx-forwarded-for: 10.0.0.1\r\ntransfer-encoding: chunked\r\n\r\n400\r\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa;aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n0\r\n\r\n" +} +events { + upstream_recv_bytes { + } +} +events { + upstream_recv_bytes { + } +} +events { + upstream_send_bytes: "\r" +} +events { +} +events { + upstream_send_bytes: "\nStack trace:\n" +} +events { + upstream_recv_bytes { + } +} +events { + upstream_send_bytes: "\nStack trace:\n" +} +events { + upstream_send_bytes: "" +} +events { + upstream_recv_bytes { + } +} +events { + upstream_send_bytes: "1" +} +events { +} From 569d70ab1cff67e862d305901350337678f47638 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 23 Jul 2018 10:23:37 -0400 Subject: [PATCH 16/46] test: fixing a downstream tsan failure (#3926) Adding a lock for checking response completion since as far as tsan can tell it's possibly accessed by the main thread and test thread simultaneously. Risk Level: Low (test only) Testing: ran idle_timeouts_test with internal tsan Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- test/integration/fake_upstream.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index 97c0f2a7ac91..d9259044feef 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -43,7 +43,10 @@ class FakeStream : public Http::StreamDecoder, uint64_t bodyLength() { return body_.length(); } Buffer::Instance& body() { return body_; } - bool complete() { return end_stream_; } + bool complete() { + Thread::LockGuard lock(lock_); + return end_stream_; + } void encode100ContinueHeaders(const Http::HeaderMapImpl& headers); void encodeHeaders(const Http::HeaderMapImpl& headers, bool end_stream); void encodeData(uint64_t size, bool end_stream); From a5478ee3ba613aaad4f05353209a56c1ab833ea5 Mon Sep 17 00:00:00 2001 From: Lizan Zhou Date: Mon, 23 Jul 2018 09:24:38 -0700 Subject: [PATCH 17/46] alts: add gRPC TSI frame protector (#3873) 2nd PR for #3429, add frame protector. Risk Level: Low (not enabled in main) Testing: bazel test //test/... Docs Changes: N/A Release Notes: N/A Signed-off-by: Lizan Zhou --- .../extensions/transport_sockets/alts/BUILD | 15 ++ .../alts/tsi_frame_protector.cc | 77 +++++++++ .../alts/tsi_frame_protector.h | 49 ++++++ test/extensions/transport_sockets/alts/BUILD | 10 ++ .../alts/tsi_frame_protector_test.cc | 150 ++++++++++++++++++ 5 files changed, 301 insertions(+) create mode 100644 source/extensions/transport_sockets/alts/tsi_frame_protector.cc create mode 100644 source/extensions/transport_sockets/alts/tsi_frame_protector.h create mode 100644 test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc diff --git a/source/extensions/transport_sockets/alts/BUILD b/source/extensions/transport_sockets/alts/BUILD index 28cc6960e715..da086ff2d6e2 100644 --- a/source/extensions/transport_sockets/alts/BUILD +++ b/source/extensions/transport_sockets/alts/BUILD @@ -24,6 +24,21 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "tsi_frame_protector", + srcs = [ + "tsi_frame_protector.cc", + ], + hdrs = [ + "tsi_frame_protector.h", + ], + repository = "@envoy", + deps = [ + ":grpc_tsi_wrapper", + "//source/common/buffer:buffer_lib", + ], +) + envoy_cc_library( name = "tsi_handshaker", srcs = [ diff --git a/source/extensions/transport_sockets/alts/tsi_frame_protector.cc b/source/extensions/transport_sockets/alts/tsi_frame_protector.cc new file mode 100644 index 000000000000..1cb8cc22494b --- /dev/null +++ b/source/extensions/transport_sockets/alts/tsi_frame_protector.cc @@ -0,0 +1,77 @@ +#include "extensions/transport_sockets/alts/tsi_frame_protector.h" + +#include "common/common/assert.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Alts { + +// TODO(lizan): tune size later +static constexpr uint32_t BUFFER_SIZE = 16384; + +TsiFrameProtector::TsiFrameProtector(CFrameProtectorPtr&& frame_protector) + : frame_protector_(std::move(frame_protector)) {} + +tsi_result TsiFrameProtector::protect(Buffer::Instance& input, Buffer::Instance& output) { + ASSERT(frame_protector_); + + unsigned char protected_buffer[BUFFER_SIZE]; + while (input.length() > 0) { + auto* message_bytes = reinterpret_cast(input.linearize(input.length())); + size_t protected_buffer_size = BUFFER_SIZE; + size_t processed_message_size = input.length(); + tsi_result result = + tsi_frame_protector_protect(frame_protector_.get(), message_bytes, &processed_message_size, + protected_buffer, &protected_buffer_size); + if (result != TSI_OK) { + ASSERT(result != TSI_INVALID_ARGUMENT && result != TSI_UNIMPLEMENTED); + return result; + } + output.add(protected_buffer, protected_buffer_size); + input.drain(processed_message_size); + } + + // TSI may buffer some of the input internally. Flush its buffer to protected_buffer. + size_t still_pending_size; + do { + size_t protected_buffer_size = BUFFER_SIZE; + tsi_result result = tsi_frame_protector_protect_flush( + frame_protector_.get(), protected_buffer, &protected_buffer_size, &still_pending_size); + if (result != TSI_OK) { + ASSERT(result != TSI_INVALID_ARGUMENT && result != TSI_UNIMPLEMENTED); + return result; + } + output.add(protected_buffer, protected_buffer_size); + } while (still_pending_size > 0); + + return TSI_OK; +} + +tsi_result TsiFrameProtector::unprotect(Buffer::Instance& input, Buffer::Instance& output) { + ASSERT(frame_protector_); + + unsigned char unprotected_buffer[BUFFER_SIZE]; + + while (input.length() > 0) { + auto* message_bytes = reinterpret_cast(input.linearize(input.length())); + size_t unprotected_buffer_size = BUFFER_SIZE; + size_t processed_message_size = input.length(); + tsi_result result = tsi_frame_protector_unprotect(frame_protector_.get(), message_bytes, + &processed_message_size, unprotected_buffer, + &unprotected_buffer_size); + if (result != TSI_OK) { + ASSERT(result != TSI_INVALID_ARGUMENT && result != TSI_UNIMPLEMENTED); + return result; + } + output.add(unprotected_buffer, unprotected_buffer_size); + input.drain(processed_message_size); + } + + return TSI_OK; +} + +} // namespace Alts +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/alts/tsi_frame_protector.h b/source/extensions/transport_sockets/alts/tsi_frame_protector.h new file mode 100644 index 000000000000..ac2fe1fc8f7f --- /dev/null +++ b/source/extensions/transport_sockets/alts/tsi_frame_protector.h @@ -0,0 +1,49 @@ +#pragma once + +#include "envoy/buffer/buffer.h" + +#include "extensions/transport_sockets/alts/grpc_tsi.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Alts { + +/** + * A C++ wrapper for tsi_frame_protector interface. + * For detail of tsi_frame_protector, see + * https://github.com/grpc/grpc/blob/v1.10.0/src/core/tsi/transport_security_interface.h#L70 + * + * TODO(lizan): migrate to tsi_zero_copy_grpc_protector for further optimization + */ +class TsiFrameProtector final { +public: + explicit TsiFrameProtector(CFrameProtectorPtr&& frame_protector); + + /** + * Wrapper for tsi_frame_protector_protect + * @param input supplies the input data to protect, the method will drain it when it is processed. + * @param output supplies the buffer where the protected data will be stored. + * @return tsi_result the status. + */ + tsi_result protect(Buffer::Instance& input, Buffer::Instance& output); + + /** + * Wrapper for tsi_frame_protector_unprotect + * @param input supplies the input data to unprotect, the method will drain it when it is + * processed. + * @param output supplies the buffer where the unprotected data will be stored. + * @return tsi_result the status. + */ + tsi_result unprotect(Buffer::Instance& input, Buffer::Instance& output); + +private: + CFrameProtectorPtr frame_protector_; +}; + +typedef std::unique_ptr TsiFrameProtectorPtr; + +} // namespace Alts +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/alts/BUILD b/test/extensions/transport_sockets/alts/BUILD index 87a6a480ff3f..171a97fd28e4 100644 --- a/test/extensions/transport_sockets/alts/BUILD +++ b/test/extensions/transport_sockets/alts/BUILD @@ -11,6 +11,16 @@ load( envoy_package() +envoy_extension_cc_test( + name = "tsi_frame_protector_test", + srcs = ["tsi_frame_protector_test.cc"], + extension_name = "envoy.transport_sockets.alts", + deps = [ + "//source/extensions/transport_sockets/alts:tsi_frame_protector", + "//test/mocks/buffer:buffer_mocks", + ], +) + envoy_extension_cc_test( name = "tsi_handshaker_test", srcs = ["tsi_handshaker_test.cc"], diff --git a/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc b/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc new file mode 100644 index 000000000000..2604e837c8cd --- /dev/null +++ b/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc @@ -0,0 +1,150 @@ +#include "common/buffer/buffer_impl.h" + +#include "extensions/transport_sockets/alts/tsi_frame_protector.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "src/core/tsi/fake_transport_security.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Alts { + +using testing::InSequence; +using testing::Invoke; +using testing::NiceMock; +using testing::SaveArg; +using testing::Test; +using testing::_; +using namespace std::string_literals; + +/** + * Test with fake frame protector. The protected frame header is 4 byte length (little endian, + * include header itself) and following the body. + */ +class TsiFrameProtectorTest : public Test { +public: + TsiFrameProtectorTest() + : raw_frame_protector_(tsi_create_fake_frame_protector(nullptr)), + frame_protector_(CFrameProtectorPtr{raw_frame_protector_}) {} + +protected: + tsi_frame_protector* raw_frame_protector_; + TsiFrameProtector frame_protector_; +}; + +TEST_F(TsiFrameProtectorTest, Protect) { + { + Buffer::OwnedImpl input, encrypted; + input.add("foo"); + + EXPECT_EQ(TSI_OK, frame_protector_.protect(input, encrypted)); + EXPECT_EQ("\x07\0\0\0foo"s, encrypted.toString()); + } + + { + Buffer::OwnedImpl input, encrypted; + input.add("foo"); + + EXPECT_EQ(TSI_OK, frame_protector_.protect(input, encrypted)); + EXPECT_EQ("\x07\0\0\0foo"s, encrypted.toString()); + + input.add("bar"); + EXPECT_EQ(TSI_OK, frame_protector_.protect(input, encrypted)); + EXPECT_EQ("\x07\0\0\0foo\x07\0\0\0bar"s, encrypted.toString()); + } + + { + Buffer::OwnedImpl input, encrypted; + input.add(std::string(20000, 'a')); + + EXPECT_EQ(TSI_OK, frame_protector_.protect(input, encrypted)); + + // fake frame protector will split long buffer to 2 "encrypted" frames with length 16K. + std::string expected = + "\0\x40\0\0"s + std::string(16380, 'a') + "\x28\x0e\0\0"s + std::string(3620, 'a'); + EXPECT_EQ(expected, encrypted.toString()); + } +} + +TEST_F(TsiFrameProtectorTest, ProtectError) { + const tsi_frame_protector_vtable* vtable = raw_frame_protector_->vtable; + tsi_frame_protector_vtable mock_vtable = *raw_frame_protector_->vtable; + mock_vtable.protect = [](tsi_frame_protector*, const unsigned char*, size_t*, unsigned char*, + size_t*) { return TSI_INTERNAL_ERROR; }; + raw_frame_protector_->vtable = &mock_vtable; + + Buffer::OwnedImpl input, encrypted; + input.add("foo"); + + EXPECT_EQ(TSI_INTERNAL_ERROR, frame_protector_.protect(input, encrypted)); + + raw_frame_protector_->vtable = vtable; +} + +TEST_F(TsiFrameProtectorTest, ProtectFlushError) { + const tsi_frame_protector_vtable* vtable = raw_frame_protector_->vtable; + tsi_frame_protector_vtable mock_vtable = *raw_frame_protector_->vtable; + mock_vtable.protect_flush = [](tsi_frame_protector*, unsigned char*, size_t*, size_t*) { + return TSI_INTERNAL_ERROR; + }; + raw_frame_protector_->vtable = &mock_vtable; + + Buffer::OwnedImpl input, encrypted; + input.add("foo"); + + EXPECT_EQ(TSI_INTERNAL_ERROR, frame_protector_.protect(input, encrypted)); + + raw_frame_protector_->vtable = vtable; +} + +TEST_F(TsiFrameProtectorTest, Unprotect) { + { + Buffer::OwnedImpl input, decrypted; + input.add("\x07\0\0\0bar"s); + + EXPECT_EQ(TSI_OK, frame_protector_.unprotect(input, decrypted)); + EXPECT_EQ("bar", decrypted.toString()); + } + + { + Buffer::OwnedImpl input, decrypted; + input.add("\x0a\0\0\0foo"s); + + EXPECT_EQ(TSI_OK, frame_protector_.unprotect(input, decrypted)); + EXPECT_EQ("", decrypted.toString()); + + input.add("bar"); + EXPECT_EQ(TSI_OK, frame_protector_.unprotect(input, decrypted)); + EXPECT_EQ("foobar", decrypted.toString()); + } + + { + Buffer::OwnedImpl input, decrypted; + input.add("\0\x40\0\0"s + std::string(16380, 'a')); + input.add("\x28\x0e\0\0"s + std::string(3620, 'a')); + + EXPECT_EQ(TSI_OK, frame_protector_.unprotect(input, decrypted)); + EXPECT_EQ(std::string(20000, 'a'), decrypted.toString()); + } +} +TEST_F(TsiFrameProtectorTest, UnprotectError) { + const tsi_frame_protector_vtable* vtable = raw_frame_protector_->vtable; + tsi_frame_protector_vtable mock_vtable = *raw_frame_protector_->vtable; + mock_vtable.unprotect = [](tsi_frame_protector*, const unsigned char*, size_t*, unsigned char*, + size_t*) { return TSI_INTERNAL_ERROR; }; + raw_frame_protector_->vtable = &mock_vtable; + + Buffer::OwnedImpl input, decrypted; + input.add("\x0a\0\0\0foo"s); + + EXPECT_EQ(TSI_INTERNAL_ERROR, frame_protector_.unprotect(input, decrypted)); + + raw_frame_protector_->vtable = vtable; +} + +} // namespace Alts +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy From 391150ae675e8b23a77d5aa1c750c809b4d74561 Mon Sep 17 00:00:00 2001 From: htuch Date: Mon, 23 Jul 2018 13:25:49 -0400 Subject: [PATCH 18/46] coverage: coverage runs for a single test target. (#3922) Useful when exploring fuzzer code coverage with the checked-in corpus, e.g. #3921. Signed-off-by: Harvey Tuch --- bazel/README.md | 8 ++++++++ test/run_envoy_bazel_coverage.sh | 24 ++++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/bazel/README.md b/bazel/README.md index f084017dc561..4a1b9ff55432 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -357,6 +357,14 @@ then log back in and it should start working. The latest coverage report for master is available [here](https://s3.amazonaws.com/lyft-envoy/coverage/report-master/coverage.html). +It's also possible to specialize the coverage build to a single test target. This is useful +when doing things like exploring the coverage of a fuzzer over its corpus. This can be done with +the `COVERAGE_TARGET` and `VALIDATE_COVERAGE` environment variables, e.g.: + +``` +COVERAGE_TARGET=//test/common/common:base64_fuzz_test VALIDATE_COVERAGE=false test/run_envoy_bazel_coverage.sh +``` + # Cleaning the build and test artifacts `bazel clean` will nuke all the build/test artifacts from the Bazel cache for diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index 5a8900ce3991..a336d6949e44 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -8,6 +8,7 @@ set -e [[ -z "${BAZEL_COVERAGE}" ]] && BAZEL_COVERAGE=bazel [[ -z "${GCOVR}" ]] && GCOVR=gcovr [[ -z "${WORKSPACE}" ]] && WORKSPACE=envoy +[[ -z "${VALIDATE_COVERAGE}" ]] && VALIDATE_COVERAGE=true # This is the target that will be run to generate coverage data. It can be overriden by consumer # projects that want to run coverage on a different/combined target. @@ -56,7 +57,7 @@ COVERAGE_SUMMARY="${COVERAGE_DIR}/coverage_summary.txt" pushd "${GCOVR_DIR}" for f in $(find -L bazel-out/ -name "*.gcno") do - cp --parents "$f" bazel-out/k8-dbg/bin/test/coverage/coverage_tests.runfiles/"${WORKSPACE}" + cp --parents "$f" bazel-out/k8-dbg/bin/"${COVERAGE_TARGET/:/\/}".runfiles/"${WORKSPACE}" done popd @@ -76,13 +77,16 @@ rm "${SRCDIR}"/test/coverage/BUILD [[ -z "${ENVOY_COVERAGE_DIR}" ]] || rsync -av "${COVERAGE_DIR}"/ "${ENVOY_COVERAGE_DIR}" -COVERAGE_VALUE=$(grep -Po 'lines: \K(\d|\.)*' "${COVERAGE_SUMMARY}") -COVERAGE_THRESHOLD=98.0 -COVERAGE_FAILED=$(echo "${COVERAGE_VALUE}<${COVERAGE_THRESHOLD}" | bc) -if test ${COVERAGE_FAILED} -eq 1; then - echo Code coverage ${COVERAGE_VALUE} is lower than limit of ${COVERAGE_THRESHOLD} - exit 1 -else - echo Code coverage ${COVERAGE_VALUE} is good and higher than limit of ${COVERAGE_THRESHOLD} +if [ "$VALIDATE_COVERAGE" == "true" ] +then + COVERAGE_VALUE=$(grep -Po 'lines: \K(\d|\.)*' "${COVERAGE_SUMMARY}") + COVERAGE_THRESHOLD=98.0 + COVERAGE_FAILED=$(echo "${COVERAGE_VALUE}<${COVERAGE_THRESHOLD}" | bc) + if test ${COVERAGE_FAILED} -eq 1; then + echo Code coverage ${COVERAGE_VALUE} is lower than limit of ${COVERAGE_THRESHOLD} + exit 1 + else + echo Code coverage ${COVERAGE_VALUE} is good and higher than limit of ${COVERAGE_THRESHOLD} + fi + echo "HTML coverage report is in ${COVERAGE_DIR}/coverage.html" fi -echo "HTML coverage report is in ${COVERAGE_DIR}/coverage.html" From 672083c7ba4e92fe9548252c45d84d583a95102d Mon Sep 17 00:00:00 2001 From: htuch Date: Mon, 23 Jul 2018 13:26:58 -0400 Subject: [PATCH 19/46] http: global connection manager per-stream idle timeouts. (#3879) This is a followup to #3841, where we introduce HCM-wide stream idle timeouts. This has two effects: 1. We can now timeout immediately after stream creation, potentially before receiving request headers and routing. 2. A default timeout can be configured across all routes. This is overridable on a per-route basis. The default and overriding semantics are explained in the docs. Also added as a bonus some docs about how timeouts interact more generally in Envoy. Fixes #3853. Risk Level: Low. While there is some change to the per-route vs. HCM wide semantics for stream idle timeouts, it's not anticipated this feature is in common use yet (it's only a couple of days since landing), and the caveats in #3841 with the new 5 minute default timeout should already apply. Testing: Unit/integration tests added. Signed-off-by: Harvey Tuch --- api/envoy/api/v2/route/route.proto | 11 +-- .../v2/http_connection_manager.proto | 29 +++++- .../http_connection_management.rst | 24 +++++ docs/root/intro/version_history.rst | 6 ++ include/envoy/router/router.h | 3 +- source/common/http/conn_manager_config.h | 8 +- source/common/http/conn_manager_impl.cc | 25 ++++- source/common/router/config_impl.cc | 3 +- source/common/router/config_impl.h | 10 +- .../network/http_connection_manager/config.cc | 7 +- .../network/http_connection_manager/config.h | 7 +- source/server/http/admin.h | 3 +- test/common/http/conn_manager_impl_test.cc | 91 ++++++++++++++++++- test/common/http/conn_manager_utility_test.cc | 3 +- test/common/router/config_impl_test.cc | 14 +-- .../http_connection_manager/config_test.cc | 25 +++++ .../idle_timeout_integration_test.cc | 35 +++++-- 17 files changed, 260 insertions(+), 44 deletions(-) diff --git a/api/envoy/api/v2/route/route.proto b/api/envoy/api/v2/route/route.proto index 1ac5b46e4ba2..0a3bb20ee749 100644 --- a/api/envoy/api/v2/route/route.proto +++ b/api/envoy/api/v2/route/route.proto @@ -439,12 +439,11 @@ message RouteAction { google.protobuf.Duration per_try_timeout = 3 [(gogoproto.stdduration) = true]; } - // Specifies the idle timeout for the route. If not specified, this defaults - // to 5 minutes. The default value was select so as not to interfere with any - // smaller configured timeouts that may have existed in configurations prior - // to the introduction of this feature, while introducing robustness to TCP - // connections that terminate without FIN. A value of 0 will completely - // disable the idle timeout. + // Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout + // specified, although the connection manager wide :ref:`stream_idle_timeout + // ` + // will still apply. A value of 0 will completely disable the route's idle timeout, even if a + // connection manager stream idle timeout is configured. // // The idle timeout is distinct to :ref:`timeout // `, which provides an upper bound diff --git a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto index d4c8cd3077d1..1e6999cf2077 100644 --- a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +++ b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto @@ -19,7 +19,7 @@ import "gogoproto/gogo.proto"; // [#protodoc-title: HTTP connection manager] // HTTP connection manager :ref:`configuration overview `. -// [#comment:next free field: 24] +// [#comment:next free field: 25] message HttpConnectionManager { enum CodecType { option (gogoproto.goproto_enum_prefix) = false; @@ -137,6 +137,33 @@ message HttpConnectionManager { // `. google.protobuf.Duration idle_timeout = 11 [(gogoproto.stdduration) = true]; + // The stream idle timeout for connections managed by the connection manager. + // If not specified, this defaults to 5 minutes. The default value was selected + // so as not to interfere with any smaller configured timeouts that may have + // existed in configurations prior to the introduction of this feature, while + // introducing robustness to TCP connections that terminate without a FIN. + // + // This idle timeout applies to new streams and is overridable by the + // :ref:`route-level idle_timeout + // `. Even on a stream in + // which the override applies, prior to receipt of the initial request + // headers, the :ref:`stream_idle_timeout + // ` + // applies. Each time an encode/decode event for headers or data is processed + // for the stream, the timer will be reset. If the timeout fires, the stream + // is terminated with a 408 Request Timeout error code if no upstream response + // header has been received, otherwise a stream reset occurs. + // + // Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due + // to the granularity of events presented to the connection manager. For example, while receiving + // very large request headers, it may be the case that there is traffic regularly arriving on the + // wire while the connection manage is only able to observe the end-of-headers event, hence the + // stream may still idle timeout. + // + // A value of 0 will completely disable the connection manager stream idle + // timeout, although per-route idle timeout overrides will continue to apply. + google.protobuf.Duration stream_idle_timeout = 24 [(gogoproto.stdduration) = true]; + // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. // This is used so that Envoy provides a grace period for new streams that diff --git a/docs/root/intro/arch_overview/http_connection_management.rst b/docs/root/intro/arch_overview/http_connection_management.rst index 4f1d415b48e3..40415481322f 100644 --- a/docs/root/intro/arch_overview/http_connection_management.rst +++ b/docs/root/intro/arch_overview/http_connection_management.rst @@ -42,3 +42,27 @@ table `. The route table can be specified in one of * Statically. * Dynamically via the :ref:`RDS API `. + +Timeouts +-------- + +Various configurable timeouts apply to an HTTP connection and its constituent streams: + +* Connection-level :ref:`idle timeout + `: + this applies to the idle period where no streams are active. +* Connection-level :ref:`drain timeout + `: + this spans between an Envoy originated GOAWAY and connection termination. +* Stream-level idle timeout: this applies to each individual stream. It may be configured at both + the :ref:`connection manager + ` + and :ref:`per-route ` granularity. + Header/data/trailer events on the stream reset the idle timeout. +* Stream-level :ref:`per-route upstream timeout `: this + applies to the upstream response, i.e. a maximum bound on the time from the end of the downstream + request until the end of the upstream response. This may also be specified at the :ref:`per-retry + ` granularity. +* Stream-level :ref:`per-route gRPC max timeout + `: this bounds the upstream timeout and allows + the timeout to be overriden via the *grpc-timeout* request header. diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index eb2bfe7f6752..f1c728ea040a 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -14,6 +14,12 @@ Version history * health check: added support for :ref:`custom health check `. * health check: added support for :ref:`specifying jitter as a percentage `. * health_check: added support for :ref:`health check event logging `. +* http: added support for a per-stream idle timeout. This applies at both :ref:`connection manager + ` + and :ref:`per-route granularity `. The timeout + defaults to 5 minutes; if you have other timeouts (e.g. connection idle timeout, upstream + response per-retry) that are longer than this in duration, you may want to consider setting a + non-default per-stream idle timeout. * http: added support for a :ref:`per-stream idle timeout `. This defaults to 5 minutes; if you have other timeouts (e.g. connection idle timeout, upstream response per-retry) that are longer than diff --git a/include/envoy/router/router.h b/include/envoy/router/router.h index 9aa244f29d05..a69c40225191 100644 --- a/include/envoy/router/router.h +++ b/include/envoy/router/router.h @@ -473,7 +473,8 @@ class RouteEntry : public ResponseEntry { virtual std::chrono::milliseconds timeout() const PURE; /** - * @return absl::optional the route's idle timeout. + * @return optional the route's idle timeout. Zero indicates a + * disabled idle timeout, while nullopt indicates deference to the global timeout. */ virtual absl::optional idleTimeout() const PURE; diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 4d6ca959adba..7c172903f7a4 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -192,7 +192,13 @@ class ConnectionManagerConfig { /** * @return optional idle timeout for incoming connection manager connections. */ - virtual const absl::optional& idleTimeout() PURE; + virtual absl::optional idleTimeout() const PURE; + + /** + * @return per-stream idle timeout for incoming connection manager connections. Zero indicates a + * disabled idle timeout. + */ + virtual std::chrono::milliseconds streamIdleTimeout() const PURE; /** * @return Router::RouteConfigProvider& the configuration provider used to acquire a route diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 643e3a25c0b0..ab0f7bbf935d 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -373,6 +373,13 @@ ConnectionManagerImpl::ActiveStream::ActiveStream(ConnectionManagerImpl& connect // prevents surprises for logging code in edge cases. request_info_.setDownstreamRemoteAddress( connection_manager_.read_callbacks_->connection().remoteAddress()); + + if (connection_manager_.config_.streamIdleTimeout().count()) { + idle_timeout_ms_ = connection_manager_.config_.streamIdleTimeout(); + idle_timer_ = connection_manager_.read_callbacks_->connection().dispatcher().createTimer( + [this]() -> void { onIdleTimeout(); }); + resetIdleTimer(); + } } ConnectionManagerImpl::ActiveStream::~ActiveStream() { @@ -609,9 +616,18 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, const Router::RouteEntry* route_entry = cached_route_.value()->routeEntry(); if (route_entry != nullptr && route_entry->idleTimeout()) { idle_timeout_ms_ = route_entry->idleTimeout().value(); - idle_timer_ = connection_manager_.read_callbacks_->connection().dispatcher().createTimer( - [this]() -> void { onIdleTimeout(); }); - resetIdleTimer(); + if (idle_timeout_ms_.count()) { + // If we have a route-level idle timeout but no global stream idle timeout, create a timer. + if (idle_timer_ == nullptr) { + idle_timer_ = connection_manager_.read_callbacks_->connection().dispatcher().createTimer( + [this]() -> void { onIdleTimeout(); }); + } + } else if (idle_timer_ != nullptr) { + // If we had a global stream idle timeout but the route-level idle timeout is set to zero + // (to override), we disable the idle timer. + idle_timer_->disableTimer(); + idle_timer_ = nullptr; + } } } @@ -621,6 +637,9 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, } decodeHeaders(nullptr, *request_headers_, end_stream); + + // Reset it here for both global and overriden cases. + resetIdleTimer(); } void ConnectionManagerImpl::ActiveStream::traceRequest() { diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index cb0b726f9182..5288a8143640 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -258,8 +258,7 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost, cluster_not_found_response_code_(ConfigUtility::parseClusterNotFoundResponseCode( route.route().cluster_not_found_response_code())), timeout_(PROTOBUF_GET_MS_OR_DEFAULT(route.route(), timeout, DEFAULT_ROUTE_TIMEOUT_MS)), - idle_timeout_( - PROTOBUF_GET_MS_OR_DEFAULT(route.route(), idle_timeout, DEFAULT_ROUTE_IDLE_TIMEOUT_MS)), + idle_timeout_(PROTOBUF_GET_OPTIONAL_MS(route.route(), idle_timeout)), max_grpc_timeout_(PROTOBUF_GET_OPTIONAL_MS(route.route(), max_grpc_timeout)), runtime_(loadRuntimeData(route.match())), loader_(factory_context.runtime()), host_redirect_(route.redirect().host_redirect()), diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index ac274dd28e7b..e5c79a6c6222 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -311,10 +311,7 @@ class RouteEntryImplBase : public RouteEntry, return vhost_.virtualClusterFromEntries(headers); } std::chrono::milliseconds timeout() const override { return timeout_; } - absl::optional idleTimeout() const override { - return idle_timeout_.count() == 0 ? absl::nullopt - : absl::optional(idle_timeout_); - } + absl::optional idleTimeout() const override { return idle_timeout_; } absl::optional maxGrpcTimeout() const override { return max_grpc_timeout_; } @@ -510,9 +507,6 @@ class RouteEntryImplBase : public RouteEntry, // Default timeout is 15s if nothing is specified in the route config. static const uint64_t DEFAULT_ROUTE_TIMEOUT_MS = 15000; - // Default idle timeout is 5 minutes if nothing is specified in the route config. - static const uint64_t DEFAULT_ROUTE_IDLE_TIMEOUT_MS = 5 * 60 * 1000; - std::unique_ptr cors_policy_; const VirtualHostImpl& vhost_; // See note in RouteEntryImplBase::clusterEntry() on why raw ref // to virtual host is currently safe. @@ -522,7 +516,7 @@ class RouteEntryImplBase : public RouteEntry, const Http::LowerCaseString cluster_header_name_; const Http::Code cluster_not_found_response_code_; const std::chrono::milliseconds timeout_; - const std::chrono::milliseconds idle_timeout_; + const absl::optional idle_timeout_; const absl::optional max_grpc_timeout_; const absl::optional runtime_; Runtime::Loader& loader_; diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index d80645d01849..0503e5296b9a 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -129,6 +129,9 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( route_config_provider_manager_(route_config_provider_manager), http2_settings_(Http::Utility::parseHttp2Settings(config.http2_protocol_options())), http1_settings_(Http::Utility::parseHttp1Settings(config.http_protocol_options())), + idle_timeout_(PROTOBUF_GET_OPTIONAL_MS(config, idle_timeout)), + stream_idle_timeout_( + PROTOBUF_GET_MS_OR_DEFAULT(config, stream_idle_timeout, StreamIdleTimeoutMs)), drain_timeout_(PROTOBUF_GET_MS_OR_DEFAULT(config, drain_timeout, 5000)), generate_request_id_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, generate_request_id, true)), date_provider_(date_provider), @@ -216,10 +219,6 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( overall_sampling})); } - if (config.has_idle_timeout()) { - idle_timeout_ = std::chrono::milliseconds(PROTOBUF_GET_MS_REQUIRED(config, idle_timeout)); - } - for (const auto& access_log : config.access_log()) { AccessLog::InstanceSharedPtr current_access_log = AccessLog::AccessLogFactory::fromProto(access_log, context_); diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index f8f5c6fd4cb0..f4d173059c37 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -87,7 +87,8 @@ class HttpConnectionManagerConfig : Logger::Loggable, std::chrono::milliseconds drainTimeout() override { return drain_timeout_; } FilterChainFactory& filterFactory() override { return *this; } bool generateRequestId() override { return generate_request_id_; } - const absl::optional& idleTimeout() override { return idle_timeout_; } + absl::optional idleTimeout() const override { return idle_timeout_; } + std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } Router::RouteConfigProvider& routeConfigProvider() override { return *route_config_provider_; } const std::string& serverName() override { return server_name_; } Http::ConnectionManagerStats& stats() override { return stats_; } @@ -137,12 +138,16 @@ class HttpConnectionManagerConfig : Logger::Loggable, Http::TracingConnectionManagerConfigPtr tracing_config_; absl::optional user_agent_; absl::optional idle_timeout_; + std::chrono::milliseconds stream_idle_timeout_; Router::RouteConfigProviderSharedPtr route_config_provider_; std::chrono::milliseconds drain_timeout_; bool generate_request_id_; Http::DateProvider& date_provider_; Http::ConnectionManagerListenerStats listener_stats_; const bool proxy_100_continue_; + + // Default idle timeout is 5 minutes if nothing is specified in the HCM config. + static const uint64_t StreamIdleTimeoutMs = 5 * 60 * 1000; }; } // namespace HttpConnectionManager diff --git a/source/server/http/admin.h b/source/server/http/admin.h index e5e798851d49..b16a58e2b285 100644 --- a/source/server/http/admin.h +++ b/source/server/http/admin.h @@ -89,7 +89,8 @@ class AdminImpl : public Admin, std::chrono::milliseconds drainTimeout() override { return std::chrono::milliseconds(100); } Http::FilterChainFactory& filterFactory() override { return *this; } bool generateRequestId() override { return false; } - const absl::optional& idleTimeout() override { return idle_timeout_; } + absl::optional idleTimeout() const override { return idle_timeout_; } + std::chrono::milliseconds streamIdleTimeout() const override { return {}; } Router::RouteConfigProvider& routeConfigProvider() override { return route_config_provider_; } const std::string& serverName() override { return Http::DefaultServerString::get(); } Http::ConnectionManagerStats& stats() override { return stats_; } diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index f328225ed405..66a564a7a98e 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -254,7 +254,8 @@ class HttpConnectionManagerImplTest : public Test, public ConnectionManagerConfi std::chrono::milliseconds drainTimeout() override { return std::chrono::milliseconds(100); } FilterChainFactory& filterFactory() override { return filter_factory_; } bool generateRequestId() override { return true; } - const absl::optional& idleTimeout() override { return idle_timeout_; } + absl::optional idleTimeout() const override { return idle_timeout_; } + std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } Router::RouteConfigProvider& routeConfigProvider() override { return route_config_provider_; } const std::string& serverName() override { return server_name_; } ConnectionManagerStats& stats() override { return stats_; } @@ -294,6 +295,7 @@ class HttpConnectionManagerImplTest : public Test, public ConnectionManagerConfi std::vector set_current_client_cert_details_; absl::optional user_agent_; absl::optional idle_timeout_; + std::chrono::milliseconds stream_idle_timeout_{}; NiceMock random_; NiceMock local_info_; NiceMock factory_context_; @@ -1093,7 +1095,9 @@ TEST_F(HttpConnectionManagerImplTest, NoPath) { conn_manager_->onData(fake_input, false); } -// No idle timeout when route idle timeout is not configured. +// No idle timeout when route idle timeout is implied at both global and +// per-route level. The connection manager config is responsible for managing +// the default configuration aspects. TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutNotConfigured) { setup(false, ""); @@ -1115,6 +1119,89 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutNotConfigured) { EXPECT_EQ(0U, stats_.named_.downstream_rq_idle_timeout_.value()); } +// When the global timeout is configured, the timer is enabled before we receive +// headers. +TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutGlobal) { + stream_idle_timeout_ = std::chrono::milliseconds(10); + setup(false, ""); + + EXPECT_CALL(*codec_, dispatch(_)) + .Times(1) + .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> void { + Event::MockTimer* idle_timer = + new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); + StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); + + HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); + decoder->decodeHeaders(std::move(headers), false); + + data.drain(4); + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); + + EXPECT_EQ(0U, stats_.named_.downstream_rq_idle_timeout_.value()); +} + +// Per-route timeouts override the global stream idle timeout. +TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutRouteOverride) { + stream_idle_timeout_ = std::chrono::milliseconds(10); + setup(false, ""); + ON_CALL(route_config_provider_.route_config_->route_->route_entry_, idleTimeout()) + .WillByDefault(Return(std::chrono::milliseconds(30))); + + EXPECT_CALL(*codec_, dispatch(_)) + .Times(1) + .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> void { + Event::MockTimer* idle_timer = + new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); + StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); + + HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(30))); + decoder->decodeHeaders(std::move(headers), false); + + data.drain(4); + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); + + EXPECT_EQ(0U, stats_.named_.downstream_rq_idle_timeout_.value()); +} + +// Per-route zero timeout overrides the global stream idle timeout. +TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutRouteZeroOverride) { + stream_idle_timeout_ = std::chrono::milliseconds(10); + setup(false, ""); + ON_CALL(route_config_provider_.route_config_->route_->route_entry_, idleTimeout()) + .WillByDefault(Return(std::chrono::milliseconds(0))); + + EXPECT_CALL(*codec_, dispatch(_)) + .Times(1) + .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> void { + Event::MockTimer* idle_timer = + new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); + StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); + + HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + EXPECT_CALL(*idle_timer, disableTimer()); + decoder->decodeHeaders(std::move(headers), false); + + data.drain(4); + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); + + EXPECT_EQ(0U, stats_.named_.downstream_rq_idle_timeout_.value()); +} + // Validate the per-stream idle timeout after having sent downstream headers. TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeaders) { setup(false, ""); diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index 83d7116ddfeb..91780c603c1d 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -44,7 +44,8 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD0(drainTimeout, std::chrono::milliseconds()); MOCK_METHOD0(filterFactory, FilterChainFactory&()); MOCK_METHOD0(generateRequestId, bool()); - MOCK_METHOD0(idleTimeout, const absl::optional&()); + MOCK_CONST_METHOD0(idleTimeout, absl::optional()); + MOCK_CONST_METHOD0(streamIdleTimeout, std::chrono::milliseconds()); MOCK_METHOD0(routeConfigProvider, Router::RouteConfigProvider&()); MOCK_METHOD0(serverName, const std::string&()); MOCK_METHOD0(stats, ConnectionManagerStats&()); diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index e8d82f40e0aa..63c942be9dc6 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -4383,7 +4383,6 @@ name: NoIdleTimeout - match: { regex: "/regex"} route: cluster: some-cluster - idle_timeout: 0s )EOF"; NiceMock factory_context; @@ -4393,9 +4392,9 @@ name: NoIdleTimeout EXPECT_EQ(absl::nullopt, route_entry->idleTimeout()); } -TEST(RouteConfigurationV2, DefaultIdleTimeout) { - const std::string DefaultIdleTimeot = R"EOF( -name: NoIdleTimeout +TEST(RouteConfigurationV2, ZeroIdleTimeout) { + const std::string ZeroIdleTimeot = R"EOF( +name: ZeroIdleTimeout virtual_hosts: - name: regex domains: [idle.lyft.com] @@ -4403,18 +4402,19 @@ name: NoIdleTimeout - match: { regex: "/regex"} route: cluster: some-cluster + idle_timeout: 0s )EOF"; NiceMock factory_context; - ConfigImpl config(parseRouteConfigurationFromV2Yaml(DefaultIdleTimeot), factory_context, true); + ConfigImpl config(parseRouteConfigurationFromV2Yaml(ZeroIdleTimeot), factory_context, true); Http::TestHeaderMapImpl headers = genRedirectHeaders("idle.lyft.com", "/regex", true, false); const RouteEntry* route_entry = config.route(headers, 0)->routeEntry(); - EXPECT_EQ(5 * 60 * 1000, route_entry->idleTimeout().value().count()); + EXPECT_EQ(0, route_entry->idleTimeout().value().count()); } TEST(RouteConfigurationV2, ExplicitIdleTimeout) { const std::string ExplicitIdleTimeot = R"EOF( -name: NoIdleTimeout +name: ExplicitIdleTimeout virtual_hosts: - name: regex domains: [idle.lyft.com] diff --git a/test/extensions/filters/network/http_connection_manager/config_test.cc b/test/extensions/filters/network/http_connection_manager/config_test.cc index 819f4221731e..1fc646bdba6f 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -35,6 +35,14 @@ parseHttpConnectionManagerFromJson(const std::string& json_string) { return http_connection_manager; } +envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager +parseHttpConnectionManagerFromV2Yaml(const std::string& yaml) { + envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager + http_connection_manager; + MessageUtil::loadFromYaml(yaml, http_connection_manager); + return http_connection_manager; +} + class HttpConnectionManagerConfigTest : public testing::Test { public: NiceMock context_; @@ -120,6 +128,23 @@ TEST_F(HttpConnectionManagerConfigTest, MiscConfig) { ContainerEq(config.tracingConfig()->request_headers_for_tags_)); EXPECT_EQ(*context_.local_info_.address_, config.localAddress()); EXPECT_EQ("foo", config.serverName()); + EXPECT_EQ(5 * 60 * 1000, config.streamIdleTimeout().count()); +} + +// Validated that an explicit zero stream idle timeout disables. +TEST_F(HttpConnectionManagerConfigTest, DisabledStreamIdleTimeout) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + stream_idle_timeout: 0s + route_config: + name: local_route + http_filters: + - name: envoy.router + )EOF"; + + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromV2Yaml(yaml_string), context_, + date_provider_, route_config_provider_manager_); + EXPECT_EQ(0, config.streamIdleTimeout().count()); } TEST_F(HttpConnectionManagerConfigTest, SingleDateProvider) { diff --git a/test/integration/idle_timeout_integration_test.cc b/test/integration/idle_timeout_integration_test.cc index 97b71aefbb13..6aa5fa3b82cd 100644 --- a/test/integration/idle_timeout_integration_test.cc +++ b/test/integration/idle_timeout_integration_test.cc @@ -9,11 +9,17 @@ class IdleTimeoutIntegrationTest : public HttpProtocolIntegrationTest { config_helper_.addConfigModifier( [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) -> void { - auto* route_config = hcm.mutable_route_config(); - auto* virtual_host = route_config->mutable_virtual_hosts(0); - auto* route = virtual_host->mutable_routes(0)->mutable_route(); - route->mutable_idle_timeout()->set_seconds(0); - route->mutable_idle_timeout()->set_nanos(TimeoutMs * 1000 * 1000); + if (enable_global_idle_timeout_) { + hcm.mutable_stream_idle_timeout()->set_seconds(0); + hcm.mutable_stream_idle_timeout()->set_nanos(TimeoutMs * 1000 * 1000); + } + if (enable_per_stream_idle_timeout_) { + auto* route_config = hcm.mutable_route_config(); + auto* virtual_host = route_config->mutable_virtual_hosts(0); + auto* route = virtual_host->mutable_routes(0)->mutable_route(); + route->mutable_idle_timeout()->set_seconds(0); + route->mutable_idle_timeout()->set_nanos(TimeoutMs * 1000 * 1000); + } // For validating encode100ContinueHeaders() timer kick. hcm.set_proxy_100_continue(true); }); @@ -22,6 +28,7 @@ class IdleTimeoutIntegrationTest : public HttpProtocolIntegrationTest { IntegrationStreamDecoderPtr setupPerStreamIdleTimeoutTest() { initialize(); + fake_upstreams_[0]->set_allow_unexpected_disconnects(true); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto encoder_decoder = codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "GET"}, @@ -30,7 +37,6 @@ class IdleTimeoutIntegrationTest : public HttpProtocolIntegrationTest { {":authority", "host"}}); request_encoder_ = &encoder_decoder.first; auto response = std::move(encoder_decoder.second); - fake_upstreams_[0]->set_allow_unexpected_disconnects(true); fake_upstream_connection_ = fake_upstreams_[0]->waitForHttpConnection(*dispatcher_); upstream_request_ = fake_upstream_connection_->waitForNewStream(*dispatcher_); upstream_request_->waitForHeadersComplete(); @@ -52,6 +58,8 @@ class IdleTimeoutIntegrationTest : public HttpProtocolIntegrationTest { // TODO(htuch): This might require scaling for TSAN/ASAN/Valgrind/etc. Bump if // this is the cause of flakes. static constexpr uint64_t TimeoutMs = 200; + bool enable_global_idle_timeout_{}; + bool enable_per_stream_idle_timeout_{true}; }; INSTANTIATE_TEST_CASE_P(Protocols, IdleTimeoutIntegrationTest, @@ -71,6 +79,21 @@ TEST_P(IdleTimeoutIntegrationTest, PerStreamIdleTimeoutAfterDownstreamHeaders) { EXPECT_EQ("stream timeout", response->body()); } +// Global per-stream idle timeout applies if there is no per-stream idle timeout. +TEST_P(IdleTimeoutIntegrationTest, GlobalPerStreamIdleTimeoutAfterDownstreamHeaders) { + enable_global_idle_timeout_ = true; + enable_per_stream_idle_timeout_ = false; + auto response = setupPerStreamIdleTimeoutTest(); + + waitForTimeout(*response); + + EXPECT_FALSE(upstream_request_->complete()); + EXPECT_EQ(0U, upstream_request_->bodyLength()); + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("408", response->headers().Status()->value().c_str()); + EXPECT_EQ("stream timeout", response->body()); +} + // Per-stream idle timeout after having sent downstream headers+body. TEST_P(IdleTimeoutIntegrationTest, PerStreamIdleTimeoutAfterDownstreamHeadersAndBody) { auto response = setupPerStreamIdleTimeoutTest(); From 3a5d12685f0fba901228c7a5cb28d2788a9996d1 Mon Sep 17 00:00:00 2001 From: Dhi Aurrahman Date: Tue, 24 Jul 2018 00:32:06 +0700 Subject: [PATCH 20/46] lua: add requestInfo():dynamicMetadata() API (#3800) Signed-off-by: Dhi Aurrahman --- .../configuration/http_filters/lua_filter.rst | 48 ++++++ docs/root/intro/version_history.rst | 1 + .../extensions/filters/common/lua/wrappers.cc | 8 +- .../extensions/filters/common/lua/wrappers.h | 11 +- source/extensions/filters/http/lua/BUILD | 1 + .../extensions/filters/http/lua/lua_filter.cc | 2 + .../extensions/filters/http/lua/wrappers.cc | 65 ++++++++ source/extensions/filters/http/lua/wrappers.h | 84 ++++++++++- test/extensions/filters/http/lua/BUILD | 2 + .../filters/http/lua/lua_filter_test.cc | 28 ++++ .../filters/http/lua/lua_integration_test.cc | 10 ++ .../filters/http/lua/wrappers_test.cc | 142 ++++++++++++++++++ 12 files changed, 393 insertions(+), 9 deletions(-) diff --git a/docs/root/configuration/http_filters/lua_filter.rst b/docs/root/configuration/http_filters/lua_filter.rst index dae243c4258a..afb2970a5786 100644 --- a/docs/root/configuration/http_filters/lua_filter.rst +++ b/docs/root/configuration/http_filters/lua_filter.rst @@ -441,6 +441,54 @@ protocol() Returns the string representation of :repo:`HTTP protocol ` used by the current request. The possible values are: *HTTP/1.0*, *HTTP/1.1*, and *HTTP/2*. +dynamicMetadata() +^^^^^^^^^^^^^^^^^ + +.. code-block:: lua + + requestInfo:dynamicMetadata() + +Returns a :ref:`dynamic metadata object `. + +.. _config_http_filters_lua_request_info_dynamic_metadata_wrapper: + +Dynamic metadata object API +--------------------------- + +get() +^^^^^ + +.. code-block:: lua + + dynamicMetadata:get(filterName) + + -- to get a value from a returned table. + dynamicMetadata:get(filterName)[key] + +Gets an entry in dynamic metadata struct. *filterName* is a string that supplies the filter name, e.g. *envoy.lb*. +Returns the corresponding *table* of a given *filterName*. + +set() +^^^^^ + +.. code-block:: lua + + dynamicMetadata:set(filterName, key, value) + +Sets key-value pair of a *filterName*'s metadata. *filterName* is a key specifying the target filter name, +e.g. *envoy.lb*. The type of *key* and *value* is *string*. + +__pairs() +^^^^^^^^^ + +.. code-block:: lua + + for key, value in pairs(dynamicMetadata) do + end + +Iterates through every *dynamicMetadata* entry. *key* is a string that supplies a *dynamicMetadata* +key. *value* is *dynamicMetadata* entry value. + .. _config_http_filters_lua_connection_wrapper: Connection object API diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index f1c728ea040a..a4090a47eea4 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -35,6 +35,7 @@ Version history :ref:`prefix_ranges `. * lua: added :ref:`connection() ` wrapper and *ssl()* API. * lua: added :ref:`requestInfo() ` wrapper and *protocol()* API. +* lua: added :ref:`requestInfo():dynamicMetadata() ` API. * proxy_protocol: added support for HAProxy Proxy Protocol v2 (AF_INET/AF_INET6 only). * ratelimit: added support for :repo:`api/envoy/service/ratelimit/v2/rls.proto`. Lyft's reference implementation of the `ratelimit `_ service also supports the data-plane-api proto as of v1.1.0. diff --git a/source/extensions/filters/common/lua/wrappers.cc b/source/extensions/filters/common/lua/wrappers.cc index a1dfd769e48e..efb522fe18a9 100644 --- a/source/extensions/filters/common/lua/wrappers.cc +++ b/source/extensions/filters/common/lua/wrappers.cc @@ -26,7 +26,7 @@ int BufferWrapper::luaGetBytes(lua_State* state) { return 1; } -void MetadataMapWrapper::setValue(lua_State* state, const ProtobufWkt::Value& value) { +void MetadataMapHelper::setValue(lua_State* state, const ProtobufWkt::Value& value) { ProtobufWkt::Value::KindCase kind = value.kind_case(); switch (kind) { @@ -76,7 +76,7 @@ void MetadataMapWrapper::setValue(lua_State* state, const ProtobufWkt::Value& va } } -void MetadataMapWrapper::createTable( +void MetadataMapHelper::createTable( lua_State* state, const Protobuf::Map& fields) { lua_createtable(state, 0, fields.size()); @@ -98,7 +98,7 @@ int MetadataMapIterator::luaPairsIterator(lua_State* state) { } lua_pushstring(state, current_->first.c_str()); - parent_.setValue(state, current_->second); + MetadataMapHelper::setValue(state, current_->second); current_++; return 2; @@ -111,7 +111,7 @@ int MetadataMapWrapper::luaGet(lua_State* state) { return 0; } - setValue(state, filter_it->second); + MetadataMapHelper::setValue(state, filter_it->second); return 1; } diff --git a/source/extensions/filters/common/lua/wrappers.h b/source/extensions/filters/common/lua/wrappers.h index d57a2ec39643..2019a8887961 100644 --- a/source/extensions/filters/common/lua/wrappers.h +++ b/source/extensions/filters/common/lua/wrappers.h @@ -42,6 +42,13 @@ class BufferWrapper : public BaseLuaObject { class MetadataMapWrapper; +struct MetadataMapHelper { + static void setValue(lua_State* state, const ProtobufWkt::Value& value); + static void + createTable(lua_State* state, + const Protobuf::Map& fields); +}; + /** * Iterator over a metadata map. */ @@ -89,10 +96,6 @@ class MetadataMapWrapper : public BaseLuaObject { iterator_.reset(); } - void setValue(lua_State* state, const ProtobufWkt::Value& value); - void createTable(lua_State* state, - const Protobuf::Map& fields); - const ProtobufWkt::Struct metadata_; LuaDeathRef iterator_; diff --git a/source/extensions/filters/http/lua/BUILD b/source/extensions/filters/http/lua/BUILD index e79cf70879f2..fbb03ed551fe 100644 --- a/source/extensions/filters/http/lua/BUILD +++ b/source/extensions/filters/http/lua/BUILD @@ -38,6 +38,7 @@ envoy_cc_library( "//include/envoy/request_info:request_info_interface", "//source/common/http:utility_lib", "//source/extensions/filters/common/lua:lua_lib", + "//source/extensions/filters/common/lua:wrappers_lib", ], ) diff --git a/source/extensions/filters/http/lua/lua_filter.cc b/source/extensions/filters/http/lua/lua_filter.cc index 2a35759838ab..fe6b8ab286bb 100644 --- a/source/extensions/filters/http/lua/lua_filter.cc +++ b/source/extensions/filters/http/lua/lua_filter.cc @@ -435,6 +435,8 @@ FilterConfig::FilterConfig(const std::string& lua_code, ThreadLocal::SlotAllocat lua_state_.registerType(); lua_state_.registerType(); lua_state_.registerType(); + lua_state_.registerType(); + lua_state_.registerType(); lua_state_.registerType(); request_function_slot_ = lua_state_.registerGlobal("envoy_on_request"); diff --git a/source/extensions/filters/http/lua/wrappers.cc b/source/extensions/filters/http/lua/wrappers.cc index 8bdd09ea7c6e..62924f4f4fa0 100644 --- a/source/extensions/filters/http/lua/wrappers.cc +++ b/source/extensions/filters/http/lua/wrappers.cc @@ -2,6 +2,8 @@ #include "common/http/utility.h" +#include "extensions/filters/common/lua/wrappers.h" + namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -107,6 +109,69 @@ int RequestInfoWrapper::luaProtocol(lua_State* state) { return 1; } +int RequestInfoWrapper::luaDynamicMetadata(lua_State* state) { + if (dynamic_metadata_wrapper_.get() != nullptr) { + dynamic_metadata_wrapper_.pushStack(); + } else { + dynamic_metadata_wrapper_.reset(DynamicMetadataMapWrapper::create(state, *this), true); + } + return 1; +} + +DynamicMetadataMapIterator::DynamicMetadataMapIterator(DynamicMetadataMapWrapper& parent) + : parent_{parent}, current_{parent_.requestInfo().dynamicMetadata().filter_metadata().begin()} { +} + +RequestInfo::RequestInfo& DynamicMetadataMapWrapper::requestInfo() { return parent_.request_info_; } + +int DynamicMetadataMapIterator::luaPairsIterator(lua_State* state) { + if (current_ == parent_.requestInfo().dynamicMetadata().filter_metadata().end()) { + parent_.iterator_.reset(); + return 0; + } + + lua_pushstring(state, current_->first.c_str()); + Filters::Common::Lua::MetadataMapHelper::createTable(state, current_->second.fields()); + + current_++; + return 2; +} + +int DynamicMetadataMapWrapper::luaGet(lua_State* state) { + const char* filter_name = luaL_checkstring(state, 2); + const auto& metadata = requestInfo().dynamicMetadata().filter_metadata(); + const auto filter_it = metadata.find(filter_name); + if (filter_it == metadata.end()) { + return 0; + } + + Filters::Common::Lua::MetadataMapHelper::createTable(state, filter_it->second.fields()); + return 1; +} + +int DynamicMetadataMapWrapper::luaSet(lua_State* state) { + if (iterator_.get() != nullptr) { + luaL_error(state, "dynamic metadata map cannot be modified while iterating"); + } + + // TODO(dio): Allow to set dynamic metadata using a table. + const char* filter_name = luaL_checkstring(state, 2); + const char* key = luaL_checkstring(state, 3); + const char* value = luaL_checkstring(state, 4); + requestInfo().setDynamicMetadata(filter_name, MessageUtil::keyValueStruct(key, value)); + return 0; +} + +int DynamicMetadataMapWrapper::luaPairs(lua_State* state) { + if (iterator_.get() != nullptr) { + luaL_error(state, "cannot create a second iterator before completing the first"); + } + + iterator_.reset(DynamicMetadataMapIterator::create(state, *this), true); + lua_pushcclosure(state, DynamicMetadataMapIterator::static_luaPairsIterator, 1); + return 1; +} + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/lua/wrappers.h b/source/extensions/filters/http/lua/wrappers.h index cbdd92d057ae..694170b78c2a 100644 --- a/source/extensions/filters/http/lua/wrappers.h +++ b/source/extensions/filters/http/lua/wrappers.h @@ -99,13 +99,85 @@ class HeaderMapWrapper : public Filters::Common::Lua::BaseLuaObject { +public: + DynamicMetadataMapIterator(DynamicMetadataMapWrapper& parent); + + static ExportedFunctions exportedFunctions() { return {}; } + + DECLARE_LUA_CLOSURE(DynamicMetadataMapIterator, luaPairsIterator); + +private: + DynamicMetadataMapWrapper& parent_; + Protobuf::Map::const_iterator current_; +}; + +/** + * Lua wrapper for a dynamic metadata. + */ +class DynamicMetadataMapWrapper + : public Filters::Common::Lua::BaseLuaObject { +public: + DynamicMetadataMapWrapper(RequestInfoWrapper& parent) : parent_{parent} {} + + static ExportedFunctions exportedFunctions() { + return {{"get", static_luaGet}, {"set", static_luaSet}, {"__pairs", static_luaPairs}}; + } + +private: + /** + * Get a metadata value from the map. + * @param 1 (string): filter name. + * @return value if found or nil. + */ + DECLARE_LUA_FUNCTION(DynamicMetadataMapWrapper, luaGet); + + /** + * Get a metadata value from the map. + * @param 1 (string): filter name. + * @param 2 (string or table): key. + * @param 3 (string or table): value. + * @return nil. + */ + DECLARE_LUA_FUNCTION(DynamicMetadataMapWrapper, luaSet); + + /** + * Implementation of the __pairs metamethod so a dynamic metadata wrapper can be iterated over + * using pairs(). + */ + DECLARE_LUA_FUNCTION(DynamicMetadataMapWrapper, luaPairs); + + // Envoy::Lua::BaseLuaObject + void onMarkDead() override { + // Iterators do not survive yields. + iterator_.reset(); + } + + // To get reference to parent's (RequestInfoWrapper) request info member. + RequestInfo::RequestInfo& requestInfo(); + + RequestInfoWrapper& parent_; + Filters::Common::Lua::LuaDeathRef iterator_; + + friend class DynamicMetadataMapIterator; +}; + /** * Lua wrapper for a request info. */ class RequestInfoWrapper : public Filters::Common::Lua::BaseLuaObject { public: RequestInfoWrapper(RequestInfo::RequestInfo& request_info) : request_info_{request_info} {} - static ExportedFunctions exportedFunctions() { return {{"protocol", static_luaProtocol}}; } + static ExportedFunctions exportedFunctions() { + return {{"protocol", static_luaProtocol}, {"dynamicMetadata", static_luaDynamicMetadata}}; + } private: /** @@ -113,7 +185,17 @@ class RequestInfoWrapper : public Filters::Common::Lua::BaseLuaObject dynamic_metadata_wrapper_; + + friend class DynamicMetadataMapWrapper; }; } // namespace Lua diff --git a/test/extensions/filters/http/lua/BUILD b/test/extensions/filters/http/lua/BUILD index f38ed562875d..5f6c20712a8c 100644 --- a/test/extensions/filters/http/lua/BUILD +++ b/test/extensions/filters/http/lua/BUILD @@ -16,6 +16,7 @@ envoy_extension_cc_test( srcs = ["lua_filter_test.cc"], extension_name = "envoy.filters.http.lua", deps = [ + "//source/common/request_info:request_info_lib", "//source/extensions/filters/http/lua:lua_filter_lib", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", @@ -31,6 +32,7 @@ envoy_extension_cc_test( srcs = ["wrappers_test.cc"], extension_name = "envoy.filters.http.lua", deps = [ + "//source/common/request_info:request_info_lib", "//source/extensions/filters/http/lua:wrappers_lib", "//test/extensions/filters/common/lua:lua_wrappers_lib", "//test/mocks/request_info:request_info_mocks", diff --git a/test/extensions/filters/http/lua/lua_filter_test.cc b/test/extensions/filters/http/lua/lua_filter_test.cc index d5cbd7d7f203..0283baa92e33 100644 --- a/test/extensions/filters/http/lua/lua_filter_test.cc +++ b/test/extensions/filters/http/lua/lua_filter_test.cc @@ -1,5 +1,6 @@ #include "common/buffer/buffer_impl.h" #include "common/http/message_impl.h" +#include "common/request_info/request_info_impl.h" #include "extensions/filters/http/lua/lua_filter.h" @@ -1516,6 +1517,33 @@ TEST_F(LuaHttpFilterTest, GetCurrentProtocol) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); } +// Set and get request info dynamic metadata. +TEST_F(LuaHttpFilterTest, SetGetDynamicMetadata) { + const std::string SCRIPT{R"EOF( + function envoy_on_request(request_handle) + request_handle:requestInfo():dynamicMetadata():set("envoy.lb", "foo", "bar") + request_handle:logTrace(request_handle:requestInfo():dynamicMetadata():get("envoy.lb")["foo"]) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + Http::TestHeaderMapImpl request_headers{{":path", "/"}}; + RequestInfo::RequestInfoImpl request_info(Http::Protocol::Http2); + EXPECT_EQ(0, request_info.dynamicMetadata().filter_metadata_size()); + EXPECT_CALL(decoder_callbacks_, requestInfo()).WillOnce(ReturnRef(request_info)); + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("bar"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + EXPECT_EQ(1, request_info.dynamicMetadata().filter_metadata_size()); + EXPECT_EQ("bar", request_info.dynamicMetadata() + .filter_metadata() + .at("envoy.lb") + .fields() + .at("foo") + .string_value()); +} + // Check the connection. TEST_F(LuaHttpFilterTest, CheckConnection) { const std::string SCRIPT{R"EOF( diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index 83b877e378c0..b7e9060a44e4 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -106,6 +106,10 @@ name: envoy.lua local metadata = request_handle:metadata():get("foo.bar") local body_length = request_handle:body():length() + + request_handle:requestInfo():dynamicMetadata():set("envoy.lb", "foo", "bar") + local dynamic_metadata_value = request_handle:requestInfo():dynamicMetadata():get("envoy.lb")["foo"] + request_handle:headers():add("request_body_size", body_length) request_handle:headers():add("request_metadata_foo", metadata["foo"]) request_handle:headers():add("request_metadata_baz", metadata["baz"]) @@ -115,6 +119,7 @@ name: envoy.lua request_handle:headers():add("request_secure", "true") end request_handle:headers():add("request_protocol", request_handle:requestInfo():protocol()) + request_handle:headers():add("request_dynamic_metadata_value", dynamic_metadata_value) end function envoy_on_response(response_handle) @@ -167,6 +172,11 @@ name: envoy.lua "HTTP/1.1", upstream_request_->headers().get(Http::LowerCaseString("request_protocol"))->value().c_str()); + EXPECT_STREQ("bar", upstream_request_->headers() + .get(Http::LowerCaseString("request_dynamic_metadata_value")) + ->value() + .c_str()); + Http::TestHeaderMapImpl response_headers{{":status", "200"}, {"foo", "bar"}}; upstream_request_->encodeHeaders(response_headers, false); Buffer::OwnedImpl response_data1("good"); diff --git a/test/extensions/filters/http/lua/wrappers_test.cc b/test/extensions/filters/http/lua/wrappers_test.cc index e642bda4537a..15e78db73561 100644 --- a/test/extensions/filters/http/lua/wrappers_test.cc +++ b/test/extensions/filters/http/lua/wrappers_test.cc @@ -1,4 +1,5 @@ #include "common/http/utility.h" +#include "common/request_info/request_info_impl.h" #include "extensions/filters/http/lua/wrappers.h" @@ -7,6 +8,7 @@ #include "test/test_common/utility.h" using testing::InSequence; +using testing::Return; using testing::ReturnPointee; namespace Envoy { @@ -219,6 +221,8 @@ class LuaRequestInfoWrapperTest public: virtual void setup(const std::string& script) { Filters::Common::Lua::LuaWrappersTestBase::setup(script); + state_->registerType(); + state_->registerType(); } protected: @@ -241,6 +245,12 @@ class LuaRequestInfoWrapperTest start("callMe"); wrapper.reset(); } + + envoy::api::v2::core::Metadata parseMetadataFromYaml(const std::string& yaml_string) { + envoy::api::v2::core::Metadata metadata; + MessageUtil::loadFromYaml(yaml_string, metadata); + return metadata; + } }; // Return the current request protocol. @@ -250,6 +260,138 @@ TEST_F(LuaRequestInfoWrapperTest, ReturnCurrentProtocol) { expectToPrintCurrentProtocol(Http::Protocol::Http2); } +// Set, get and iterate request info dynamic metadata. +TEST_F(LuaRequestInfoWrapperTest, SetGetAndIterateDynamicMetadata) { + const std::string SCRIPT{R"EOF( + function callMe(object) + testPrint(type(object:dynamicMetadata())) + object:dynamicMetadata():set("envoy.lb", "foo", "bar") + object:dynamicMetadata():set("envoy.lb", "so", "cool") + + testPrint(object:dynamicMetadata():get("envoy.lb")["foo"]) + testPrint(object:dynamicMetadata():get("envoy.lb")["so"]) + + for filter, entry in pairs(object:dynamicMetadata()) do + for key, value in pairs(entry) do + testPrint(string.format("'%s' '%s'", key, value)) + end + end + + local function nRetVals(...) + return select('#',...) + end + testPrint(tostring(nRetVals(object:dynamicMetadata():get("envoy.ngx")))) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + RequestInfo::RequestInfoImpl request_info(Http::Protocol::Http2); + EXPECT_EQ(0, request_info.dynamicMetadata().filter_metadata_size()); + Filters::Common::Lua::LuaDeathRef wrapper( + RequestInfoWrapper::create(coroutine_->luaState(), request_info), true); + EXPECT_CALL(*this, testPrint("userdata")); + EXPECT_CALL(*this, testPrint("bar")); + EXPECT_CALL(*this, testPrint("cool")); + EXPECT_CALL(*this, testPrint("'foo' 'bar'")); + EXPECT_CALL(*this, testPrint("'so' 'cool'")); + EXPECT_CALL(*this, testPrint("0")); + start("callMe"); + + EXPECT_EQ(1, request_info.dynamicMetadata().filter_metadata_size()); + EXPECT_EQ("bar", request_info.dynamicMetadata() + .filter_metadata() + .at("envoy.lb") + .fields() + .at("foo") + .string_value()); + wrapper.reset(); +} + +// Modify during iteration. +TEST_F(LuaRequestInfoWrapperTest, ModifyDuringIterationForDynamicMetadata) { + const std::string SCRIPT{R"EOF( + function callMe(object) + object:dynamicMetadata():set("envoy.lb", "hello", "world") + for key, value in pairs(object:dynamicMetadata()) do + object:dynamicMetadata():set("envoy.lb", "hello", "envoy") + end + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + RequestInfo::RequestInfoImpl request_info(Http::Protocol::Http2); + Filters::Common::Lua::LuaDeathRef wrapper( + RequestInfoWrapper::create(coroutine_->luaState(), request_info), true); + EXPECT_THROW_WITH_MESSAGE( + start("callMe"), Filters::Common::Lua::LuaException, + "[string \"...\"]:5: dynamic metadata map cannot be modified while iterating"); +} + +// Modify after iteration. +TEST_F(LuaRequestInfoWrapperTest, ModifyAfterIterationForDynamicMetadata) { + const std::string SCRIPT{R"EOF( + function callMe(object) + object:dynamicMetadata():set("envoy.lb", "hello", "world") + for filter, entry in pairs(object:dynamicMetadata()) do + testPrint(filter) + for key, value in pairs(entry) do + testPrint(string.format("'%s' '%s'", key, value)) + end + end + + object:dynamicMetadata():set("envoy.lb", "hello", "envoy") + object:dynamicMetadata():set("envoy.proxy", "proto", "grpc") + for filter, entry in pairs(object:dynamicMetadata()) do + testPrint(filter) + for key, value in pairs(entry) do + testPrint(string.format("'%s' '%s'", key, value)) + end + end + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + RequestInfo::RequestInfoImpl request_info(Http::Protocol::Http2); + EXPECT_EQ(0, request_info.dynamicMetadata().filter_metadata_size()); + Filters::Common::Lua::LuaDeathRef wrapper( + RequestInfoWrapper::create(coroutine_->luaState(), request_info), true); + EXPECT_CALL(*this, testPrint("envoy.lb")); + EXPECT_CALL(*this, testPrint("'hello' 'world'")); + EXPECT_CALL(*this, testPrint("envoy.proxy")); + EXPECT_CALL(*this, testPrint("'proto' 'grpc'")); + EXPECT_CALL(*this, testPrint("envoy.lb")); + EXPECT_CALL(*this, testPrint("'hello' 'envoy'")); + start("callMe"); +} + +// Don't finish iteration. +TEST_F(LuaRequestInfoWrapperTest, DontFinishIterationForDynamicMetadata) { + const std::string SCRIPT{R"EOF( + function callMe(object) + object:dynamicMetadata():set("envoy.lb", "foo", "bar") + iterator = pairs(object:dynamicMetadata()) + key, value = iterator() + iterator2 = pairs(object:dynamicMetadata()) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + RequestInfo::RequestInfoImpl request_info(Http::Protocol::Http2); + Filters::Common::Lua::LuaDeathRef wrapper( + RequestInfoWrapper::create(coroutine_->luaState(), request_info), true); + EXPECT_THROW_WITH_MESSAGE( + start("callMe"), Filters::Common::Lua::LuaException, + "[string \"...\"]:6: cannot create a second iterator before completing the first"); +} + } // namespace Lua } // namespace HttpFilters } // namespace Extensions From c4b6aecdd1bf5fb7a05a3f2a6f58e6ec42dc1393 Mon Sep 17 00:00:00 2001 From: Tal Nordan Date: Mon, 23 Jul 2018 10:32:27 -0700 Subject: [PATCH 21/46] jwt_authn docs: fix Protobuf YAML examples (#3924) Make the YAML examples use lists for `audiences` and avoid starting lists for non-repeating fields. Risk Level: Low Testing: N/A Docs Changes: N/A Release Notes: N/A Signed-off-by: Tal Nordan --- .../http/jwt_authn/v2alpha/config.proto | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto b/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto index d58e960c37f6..1350070f6806 100644 --- a/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto +++ b/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto @@ -22,13 +22,13 @@ import "validate/validate.proto"; // issuer: https://example.com // audiences: // - bookstore_android.apps.googleusercontent.com -// bookstore_web.apps.googleusercontent.com +// - bookstore_web.apps.googleusercontent.com // remote_jwks: -// - http_uri: -// - uri: https://example.com/.well-known/jwks.json +// http_uri: +// uri: https://example.com/.well-known/jwks.json // cluster: example_jwks_cluster // cache_duration: -// - seconds: 300 +// seconds: 300 // // [#not-implemented-hide:] message JwtProvider { @@ -50,7 +50,7 @@ message JwtProvider { // // audiences: // - bookstore_android.apps.googleusercontent.com - // bookstore_web.apps.googleusercontent.com + // - bookstore_web.apps.googleusercontent.com // repeated string audiences = 2; @@ -67,11 +67,11 @@ message JwtProvider { // .. code-block:: yaml // // remote_jwks: - // - http_uri: - // - uri: https://www.googleapis.com/oauth2/v1/certs + // http_uri: + // uri: https://www.googleapis.com/oauth2/v1/certs // cluster: jwt.www.googleapis.com|443 // cache_duration: - // - seconds: 300 + // seconds: 300 // RemoteJwks remote_jwks = 3; @@ -83,14 +83,14 @@ message JwtProvider { // .. code-block:: yaml // // local_jwks: - // - filename: /etc/envoy/jwks/jwks1.txt + // filename: /etc/envoy/jwks/jwks1.txt // // Example: inline_string // // .. code-block:: yaml // // local_jwks: - // - inline_string: "ACADADADADA" + // inline_string: "ACADADADADA" // envoy.api.v2.core.DataSource local_jwks = 4; } @@ -163,7 +163,7 @@ message RemoteJwks { // .. code-block:: yaml // // http_uri: - // - uri: https://www.googleapis.com/oauth2/v1/certs + // uri: https://www.googleapis.com/oauth2/v1/certs // cluster: jwt.www.googleapis.com|443 // envoy.api.v2.core.HttpUri http_uri = 1; From e021e4d6be04865c8dcf5b7e9b87b7def65a3a6e Mon Sep 17 00:00:00 2001 From: Wayne Zhang Date: Mon, 23 Jul 2018 10:33:47 -0700 Subject: [PATCH 22/46] Add integration tests for static secrets (#3910) Signed-off-by: Wayne Zhang --- test/integration/BUILD | 26 +++ .../sds_static_integration_test.cc | 191 ++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 test/integration/sds_static_integration_test.cc diff --git a/test/integration/BUILD b/test/integration/BUILD index e2db60fecc28..034d9682e084 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -423,6 +423,32 @@ envoy_cc_test_library( deps = ["//include/envoy/stats:stats_interface"], ) +envoy_cc_test( + name = "sds_static_integration_test", + srcs = [ + "sds_static_integration_test.cc", + ], + data = [ + "//test/config/integration/certs", + ], + deps = [ + ":http_integration_lib", + "//source/common/event:dispatcher_includes", + "//source/common/event:dispatcher_lib", + "//source/common/network:connection_lib", + "//source/common/network:utility_lib", + "//source/common/ssl:context_config_lib", + "//source/common/ssl:context_lib", + "//source/extensions/filters/listener/tls_inspector:config", + "//source/extensions/transport_sockets/ssl:config", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/secret:secret_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/transport_socket/capture/v2alpha:capture_cc", + "@envoy_api//envoy/data/tap/v2alpha:capture_cc", + ], +) + envoy_cc_test( name = "ssl_integration_test", srcs = [ diff --git a/test/integration/sds_static_integration_test.cc b/test/integration/sds_static_integration_test.cc new file mode 100644 index 000000000000..28d017d24264 --- /dev/null +++ b/test/integration/sds_static_integration_test.cc @@ -0,0 +1,191 @@ +#include +#include + +#include "common/event/dispatcher_impl.h" +#include "common/network/connection_impl.h" +#include "common/network/utility.h" +#include "common/ssl/context_config_impl.h" +#include "common/ssl/context_manager_impl.h" + +#include "test/integration/http_integration.h" +#include "test/integration/server.h" +#include "test/integration/ssl_utility.h" +#include "test/mocks/init/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/secret/mocks.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/utility.h" + +#include "absl/strings/match.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "integration.h" +#include "utility.h" + +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Ssl { + +class SdsStaticDownstreamIntegrationTest + : public HttpIntegrationTest, + public testing::TestWithParam { +public: + SdsStaticDownstreamIntegrationTest() + : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, GetParam()) {} + + void initialize() override { + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* common_tls_context = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_filter_chains(0) + ->mutable_tls_context() + ->mutable_common_tls_context(); + common_tls_context->add_alpn_protocols("http/1.1"); + + auto* validation_context = common_tls_context->mutable_validation_context(); + validation_context->mutable_trusted_ca()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); + validation_context->add_verify_certificate_hash( + "E0:F3:C8:CE:5E:2E:A3:05:F0:70:1F:F5:12:E3:6E:2E:" + "97:92:82:84:A2:28:BC:F7:73:32:D3:39:30:A1:B6:FD"); + + common_tls_context->add_tls_certificate_sds_secret_configs()->set_name("server_cert"); + + auto* secret = bootstrap.mutable_static_resources()->add_secrets(); + secret->set_name("server_cert"); + auto* tls_certificate = secret->mutable_tls_certificate(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/servercert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/serverkey.pem")); + }); + + HttpIntegrationTest::initialize(); + + registerTestServerPorts({"http"}); + + client_ssl_ctx_ = + createClientSslTransportSocketFactory(false, false, context_manager_, secret_manager_); + } + + void TearDown() override { + client_ssl_ctx_.reset(); + cleanupUpstreamAndDownstream(); + fake_upstream_connection_.reset(); + codec_client_.reset(); + } + + Network::ClientConnectionPtr makeSslClientConnection() { + Network::Address::InstanceConstSharedPtr address = getSslAddress(version_, lookupPort("http")); + return dispatcher_->createClientConnection(address, Network::Address::InstanceConstSharedPtr(), + client_ssl_ctx_->createTransportSocket(), nullptr); + } + +private: + Runtime::MockLoader runtime_; + Ssl::ContextManagerImpl context_manager_{runtime_}; + Secret::MockSecretManager secret_manager_; + + Network::TransportSocketFactoryPtr client_ssl_ctx_; +}; + +INSTANTIATE_TEST_CASE_P(IpVersions, SdsStaticDownstreamIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(SdsStaticDownstreamIntegrationTest, RouterRequestAndResponseWithGiantBodyBuffer) { + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(); + }; + testRouterRequestAndResponseWithBody(16 * 1024 * 1024, 16 * 1024 * 1024, false, &creator); +} + +class SdsStaticUpstreamIntegrationTest + : public HttpIntegrationTest, + public testing::TestWithParam { +public: + SdsStaticUpstreamIntegrationTest() + : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, GetParam()) {} + + void initialize() override { + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + bootstrap.mutable_static_resources() + ->mutable_clusters(0) + ->mutable_tls_context() + ->mutable_common_tls_context() + ->add_tls_certificate_sds_secret_configs() + ->set_name("client_cert"); + + auto* secret = bootstrap.mutable_static_resources()->add_secrets(); + secret->set_name("client_cert"); + auto* tls_certificate = secret->mutable_tls_certificate(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/clientcert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/clientkey.pem")); + }); + + HttpIntegrationTest::initialize(); + + registerTestServerPorts({"http"}); + } + + void TearDown() override { + cleanupUpstreamAndDownstream(); + fake_upstream_connection_.reset(); + codec_client_.reset(); + + test_server_.reset(); + fake_upstreams_.clear(); + } + + void createUpstreams() override { + fake_upstreams_.emplace_back( + new FakeUpstream(createUpstreamSslContext(), 0, FakeHttpConnection::Type::HTTP1, version_)); + } + + Network::TransportSocketFactoryPtr createUpstreamSslContext() { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + auto* common_tls_context = tls_context.mutable_common_tls_context(); + common_tls_context->add_alpn_protocols("h2"); + common_tls_context->add_alpn_protocols("http/1.1"); + common_tls_context->mutable_deprecated_v1()->set_alt_alpn_protocols("http/1.1"); + + auto* validation_context = common_tls_context->mutable_validation_context(); + validation_context->mutable_trusted_ca()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); + validation_context->add_verify_certificate_hash( + "E0:F3:C8:CE:5E:2E:A3:05:F0:70:1F:F5:12:E3:6E:2E:" + "97:92:82:84:A2:28:BC:F7:73:32:D3:39:30:A1:B6:FD"); + + auto* tls_certificate = common_tls_context->add_tls_certificates(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/servercert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/serverkey.pem")); + + Ssl::ServerContextConfigImpl cfg(tls_context, secret_manager_); + + static Stats::Scope* upstream_stats_store = new Stats::TestIsolatedStoreImpl(); + return std::make_unique( + cfg, context_manager_, *upstream_stats_store, std::vector{}); + } + +private: + Runtime::MockLoader runtime_; + Ssl::ContextManagerImpl context_manager_{runtime_}; + Secret::MockSecretManager secret_manager_; +}; + +INSTANTIATE_TEST_CASE_P(IpVersions, SdsStaticUpstreamIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(SdsStaticUpstreamIntegrationTest, RouterRequestAndResponseWithGiantBodyBuffer) { + testRouterRequestAndResponseWithBody(16 * 1024 * 1024, 16 * 1024 * 1024, false, nullptr); +} + +} // namespace Ssl +} // namespace Envoy From 7c11e9296535f2ab9d48df4b7606f08d9706df88 Mon Sep 17 00:00:00 2001 From: Stephan Zuercher Date: Mon, 23 Jul 2018 14:13:03 -0700 Subject: [PATCH 23/46] tcp/conn_pool: improve interface for callers (#3903) tcp/conn_pool: improve interface for callers Provides additional pool failure reasons to allow Tcp::ConnectionPool callers to distinguish between time outs and connection failures. Passes through connection and buffer watermark events. A subsequent PR will switch the TCP proxy to use the TCP connection pool (and makes use of these features). Relates to #3818. *Risk Level*: low *Testing*: unit tests *Docs Changes*: n/a *Release Notes*: n/a Signed-off-by: Stephan Zuercher --- include/envoy/tcp/conn_pool.h | 22 +++--- source/common/tcp/conn_pool.cc | 45 ++++++++++-- source/common/tcp/conn_pool.h | 9 ++- test/common/tcp/conn_pool_test.cc | 68 ++++++++++++++++++- .../tcp_conn_pool_integration_test.cc | 5 ++ test/mocks/tcp/BUILD | 1 + test/mocks/tcp/mocks.cc | 37 +++++++++- test/mocks/tcp/mocks.h | 31 ++++++++- 8 files changed, 195 insertions(+), 23 deletions(-) diff --git a/include/envoy/tcp/conn_pool.h b/include/envoy/tcp/conn_pool.h index 42bad77e44ce..c89b25eb9143 100644 --- a/include/envoy/tcp/conn_pool.h +++ b/include/envoy/tcp/conn_pool.h @@ -26,19 +26,25 @@ class Cancellable { }; /** - * Reason that a pool stream could not be obtained. + * Reason that a pool connection could not be obtained. */ enum class PoolFailureReason { - // A resource overflowed and policy prevented a new stream from being created. + // A resource overflowed and policy prevented a new connection from being created. Overflow, - // A connection failure took place and the stream could not be bound. - ConnectionFailure + // A local connection failure took place while creating a new connection. + LocalConnectionFailure, + // A remote connection failure took place while creating a new connection. + RemoteConnectionFailure, + // A timeout occurred while creating a new connection. + Timeout, }; /* - * UpstreamCallbacks for connection pool upstream connection callbacks. + * UpstreamCallbacks for connection pool upstream connection callbacks and data. Note that + * onEvent(Connected) is never triggered since the event always occurs before a ConnectionPool + * caller is assigned a connection. */ -class UpstreamCallbacks { +class UpstreamCallbacks : public Network::ConnectionCallbacks { public: virtual ~UpstreamCallbacks() {} @@ -122,14 +128,14 @@ class Instance : public Event::DeferredDeletable { /** * Register a callback that gets called when the connection pool is fully drained. No actual * draining is done. The owner of the connection pool is responsible for not creating any - * new streams. + * new connections. */ virtual void addDrainedCallback(DrainedCb cb) PURE; /** * Actively drain all existing connection pool connections. This method can be used in cases * where the connection pool is not being destroyed, but the caller wishes to make sure that - * all new streams take place on a new connection. For example, when a health check failure + * all new requests take place on a new connection. For example, when a health check failure * occurs. */ virtual void drainConnections() PURE; diff --git a/source/common/tcp/conn_pool.cc b/source/common/tcp/conn_pool.cc index 077c7840aea1..d6c60c174c46 100644 --- a/source/common/tcp/conn_pool.cc +++ b/source/common/tcp/conn_pool.cc @@ -104,6 +104,15 @@ void ConnPoolImpl::onConnectionEvent(ActiveConn& conn, Network::ConnectionEvent if (event == Network::ConnectionEvent::RemoteClose || event == Network::ConnectionEvent::LocalClose) { ENVOY_CONN_LOG(debug, "client disconnected", *conn.conn_); + + if (event == Network::ConnectionEvent::LocalClose) { + host_->cluster().stats().upstream_cx_destroy_local_.inc(); + } + if (event == Network::ConnectionEvent::RemoteClose) { + host_->cluster().stats().upstream_cx_destroy_remote_.inc(); + } + host_->cluster().stats().upstream_cx_destroy_.inc(); + ActiveConnPtr removed; bool check_for_drained = true; if (conn.wrapper_ != nullptr) { @@ -135,13 +144,21 @@ void ConnPoolImpl::onConnectionEvent(ActiveConn& conn, Network::ConnectionEvent // do with the request. // NOTE: We move the existing pending requests to a temporary list. This is done so that // if retry logic submits a new request to the pool, we don't fail it inline. + ConnectionPool::PoolFailureReason reason; + if (conn.timed_out_) { + reason = ConnectionPool::PoolFailureReason::Timeout; + } else if (event == Network::ConnectionEvent::RemoteClose) { + reason = ConnectionPool::PoolFailureReason::RemoteConnectionFailure; + } else { + reason = ConnectionPool::PoolFailureReason::LocalConnectionFailure; + } + std::list pending_requests_to_purge(std::move(pending_requests_)); while (!pending_requests_to_purge.empty()) { PendingRequestPtr request = pending_requests_to_purge.front()->removeFromList(pending_requests_to_purge); host_->cluster().stats().upstream_rq_pending_failure_eject_.inc(); - request->callbacks_.onPoolFailure(ConnectionPool::PoolFailureReason::ConnectionFailure, - conn.real_host_description_); + request->callbacks_.onPoolFailure(reason, conn.real_host_description_); } } @@ -277,7 +294,7 @@ ConnPoolImpl::PendingRequest::~PendingRequest() { ConnPoolImpl::ActiveConn::ActiveConn(ConnPoolImpl& parent) : parent_(parent), connect_timer_(parent_.dispatcher_.createTimer([this]() -> void { onConnectTimeout(); })), - remaining_requests_(parent_.host_->cluster().maxRequestsPerConnection()) { + remaining_requests_(parent_.host_->cluster().maxRequestsPerConnection()), timed_out_(false) { parent_.conn_connect_ms_.reset( new Stats::Timespan(parent_.host_->cluster().stats().upstream_cx_connect_ms_)); @@ -297,7 +314,6 @@ ConnPoolImpl::ActiveConn::ActiveConn(ConnPoolImpl& parent) parent_.host_->cluster().stats().upstream_cx_total_.inc(); parent_.host_->cluster().stats().upstream_cx_active_.inc(); - parent_.host_->cluster().stats().upstream_cx_http1_total_.inc(); parent_.host_->stats().cx_total_.inc(); parent_.host_->stats().cx_active_.inc(); conn_length_.reset(new Stats::Timespan(parent_.host_->cluster().stats().upstream_cx_length_ms_)); @@ -328,6 +344,7 @@ void ConnPoolImpl::ActiveConn::onConnectTimeout() { // We just close the connection at this point. This will result in both a timeout and a connect // failure and will fold into all the normal connect failure logic. ENVOY_CONN_LOG(debug, "connect timeout", *conn_); + timed_out_ = true; parent_.host_->cluster().stats().upstream_cx_connect_timeout_.inc(); conn_->close(Network::ConnectionCloseType::NoFlush); } @@ -343,5 +360,25 @@ void ConnPoolImpl::ActiveConn::onUpstreamData(Buffer::Instance& data, bool end_s } } +void ConnPoolImpl::ActiveConn::onEvent(Network::ConnectionEvent event) { + if (wrapper_ != nullptr && wrapper_->callbacks_ != nullptr) { + wrapper_->callbacks_->onEvent(event); + } + + parent_.onConnectionEvent(*this, event); +} + +void ConnPoolImpl::ActiveConn::onAboveWriteBufferHighWatermark() { + if (wrapper_ != nullptr && wrapper_->callbacks_ != nullptr) { + wrapper_->callbacks_->onAboveWriteBufferHighWatermark(); + } +} + +void ConnPoolImpl::ActiveConn::onBelowWriteBufferLowWatermark() { + if (wrapper_ != nullptr && wrapper_->callbacks_ != nullptr) { + wrapper_->callbacks_->onBelowWriteBufferLowWatermark(); + } +} + } // namespace Tcp } // namespace Envoy diff --git a/source/common/tcp/conn_pool.h b/source/common/tcp/conn_pool.h index 97b412d47e14..aa06d8b2d8b4 100644 --- a/source/common/tcp/conn_pool.h +++ b/source/common/tcp/conn_pool.h @@ -72,11 +72,9 @@ class ConnPoolImpl : Logger::Loggable, public ConnectionPool:: void onUpstreamData(Buffer::Instance& data, bool end_stream); // Network::ConnectionCallbacks - void onEvent(Network::ConnectionEvent event) override { - parent_.onConnectionEvent(*this, event); - } - void onAboveWriteBufferHighWatermark() override {} - void onBelowWriteBufferLowWatermark() override {} + void onEvent(Network::ConnectionEvent event) override; + void onAboveWriteBufferHighWatermark() override; + void onBelowWriteBufferLowWatermark() override; ConnPoolImpl& parent_; Upstream::HostDescriptionConstSharedPtr real_host_description_; @@ -85,6 +83,7 @@ class ConnPoolImpl : Logger::Loggable, public ConnectionPool:: Event::TimerPtr connect_timer_; Stats::TimespanPtr conn_length_; uint64_t remaining_requests_; + bool timed_out_; }; typedef std::unique_ptr ActiveConnPtr; diff --git a/test/common/tcp/conn_pool_test.cc b/test/common/tcp/conn_pool_test.cc index db4ad249bf03..90961972724e 100644 --- a/test/common/tcp/conn_pool_test.cc +++ b/test/common/tcp/conn_pool_test.cc @@ -41,15 +41,17 @@ struct ConnPoolCallbacks : public Tcp::ConnectionPool::Callbacks { pool_ready_.ready(); } - void onPoolFailure(Tcp::ConnectionPool::PoolFailureReason, + void onPoolFailure(Tcp::ConnectionPool::PoolFailureReason reason, Upstream::HostDescriptionConstSharedPtr host) override { + reason_ = reason; host_ = host; pool_failure_.ready(); } ReadyWatcher pool_failure_; ReadyWatcher pool_ready_; - ConnectionPool::ConnectionData* conn_data_; + ConnectionPool::ConnectionData* conn_data_{}; + absl::optional reason_; Upstream::HostDescriptionConstSharedPtr host_; }; @@ -322,6 +324,16 @@ TEST_F(TcpConnPoolImplTest, UpstreamCallbacks) { EXPECT_EQ(Network::FilterStatus::StopIteration, conn_pool_.test_conns_[0].filter_->onData(buffer, false)); + EXPECT_CALL(callbacks, onAboveWriteBufferHighWatermark()); + for (auto* cb : conn_pool_.test_conns_[0].connection_->callbacks_) { + cb->onAboveWriteBufferHighWatermark(); + } + + EXPECT_CALL(callbacks, onBelowWriteBufferLowWatermark()); + for (auto* cb : conn_pool_.test_conns_[0].connection_->callbacks_) { + cb->onBelowWriteBufferLowWatermark(); + } + // Shutdown normally. EXPECT_CALL(conn_pool_, onConnReleasedForTest()); c1.releaseConn(); @@ -331,6 +343,23 @@ TEST_F(TcpConnPoolImplTest, UpstreamCallbacks) { dispatcher_.clearDeferredDeleteList(); } +TEST_F(TcpConnPoolImplTest, UpstreamCallbacksCloseEvent) { + Buffer::OwnedImpl buffer; + + InSequence s; + ConnectionPool::MockUpstreamCallbacks callbacks; + + // Create connection, set UpstreamCallbacks + ActiveTestConn c1(*this, 0, ActiveTestConn::Type::CreateConnection); + c1.callbacks_.conn_data_->addUpstreamCallbacks(callbacks); + + EXPECT_CALL(callbacks, onEvent(Network::ConnectionEvent::RemoteClose)); + + EXPECT_CALL(conn_pool_, onConnDestroyedForTest()); + conn_pool_.test_conns_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); + dispatcher_.clearDeferredDeleteList(); +} + TEST_F(TcpConnPoolImplTest, NoUpstreamCallbacks) { Buffer::OwnedImpl buffer; @@ -394,6 +423,8 @@ TEST_F(TcpConnPoolImplTest, MaxPendingRequests) { conn_pool_.test_conns_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); dispatcher_.clearDeferredDeleteList(); + EXPECT_EQ(ConnectionPool::PoolFailureReason::Overflow, callbacks2.reason_); + EXPECT_EQ(1U, cluster_->stats_.upstream_rq_pending_overflow_.value()); } @@ -401,7 +432,7 @@ TEST_F(TcpConnPoolImplTest, MaxPendingRequests) { * Tests a connection failure before a request is bound which should result in the pending request * getting purged. */ -TEST_F(TcpConnPoolImplTest, ConnectFailure) { +TEST_F(TcpConnPoolImplTest, RemoteConnectFailure) { InSequence s; // Request 1 should kick off a new connection. @@ -417,6 +448,34 @@ TEST_F(TcpConnPoolImplTest, ConnectFailure) { conn_pool_.test_conns_[0].connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); dispatcher_.clearDeferredDeleteList(); + EXPECT_EQ(ConnectionPool::PoolFailureReason::RemoteConnectionFailure, callbacks.reason_); + + EXPECT_EQ(1U, cluster_->stats_.upstream_cx_connect_fail_.value()); + EXPECT_EQ(1U, cluster_->stats_.upstream_rq_pending_failure_eject_.value()); +} + +/** + * Tests a connection failure before a request is bound which should result in the pending request + * getting purged. + */ +TEST_F(TcpConnPoolImplTest, LocalConnectFailure) { + InSequence s; + + // Request 1 should kick off a new connection. + ConnPoolCallbacks callbacks; + conn_pool_.expectConnCreate(); + Tcp::ConnectionPool::Cancellable* handle = conn_pool_.newConnection(callbacks); + EXPECT_NE(nullptr, handle); + + EXPECT_CALL(callbacks.pool_failure_, ready()); + EXPECT_CALL(*conn_pool_.test_conns_[0].connect_timer_, disableTimer()); + + EXPECT_CALL(conn_pool_, onConnDestroyedForTest()); + conn_pool_.test_conns_[0].connection_->raiseEvent(Network::ConnectionEvent::LocalClose); + dispatcher_.clearDeferredDeleteList(); + + EXPECT_EQ(ConnectionPool::PoolFailureReason::LocalConnectionFailure, callbacks.reason_); + EXPECT_EQ(1U, cluster_->stats_.upstream_cx_connect_fail_.value()); EXPECT_EQ(1U, cluster_->stats_.upstream_rq_pending_failure_eject_.value()); } @@ -446,6 +505,9 @@ TEST_F(TcpConnPoolImplTest, ConnectTimeout) { EXPECT_CALL(conn_pool_, onConnDestroyedForTest()).Times(2); dispatcher_.clearDeferredDeleteList(); + EXPECT_EQ(ConnectionPool::PoolFailureReason::Timeout, callbacks1.reason_); + EXPECT_EQ(ConnectionPool::PoolFailureReason::Timeout, callbacks2.reason_); + EXPECT_EQ(2U, cluster_->stats_.upstream_cx_connect_fail_.value()); EXPECT_EQ(2U, cluster_->stats_.upstream_cx_connect_timeout_.value()); } diff --git a/test/integration/tcp_conn_pool_integration_test.cc b/test/integration/tcp_conn_pool_integration_test.cc index 5bb99fbb0362..4ae2b50eccfc 100644 --- a/test/integration/tcp_conn_pool_integration_test.cc +++ b/test/integration/tcp_conn_pool_integration_test.cc @@ -46,6 +46,7 @@ class TestFilter : public Network::ReadFilter { public: Request(TestFilter& parent, Buffer::Instance& data) : parent_(parent) { data_.move(data); } + // Tcp::ConnectionPool::Callbacks void onPoolFailure(Tcp::ConnectionPool::PoolFailureReason, Upstream::HostDescriptionConstSharedPtr) override { ASSERT(false); @@ -59,6 +60,7 @@ class TestFilter : public Network::ReadFilter { upstream_->connection().write(data_, false); } + // Tcp::ConnectionPool::UpstreamCallbacks void onUpstreamData(Buffer::Instance& data, bool end_stream) override { UNREFERENCED_PARAMETER(end_stream); @@ -67,6 +69,9 @@ class TestFilter : public Network::ReadFilter { upstream_->release(); } + void onEvent(Network::ConnectionEvent) override {} + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} TestFilter& parent_; Buffer::OwnedImpl data_; diff --git a/test/mocks/tcp/BUILD b/test/mocks/tcp/BUILD index 0988722b71d7..8634b86e9c5c 100644 --- a/test/mocks/tcp/BUILD +++ b/test/mocks/tcp/BUILD @@ -15,6 +15,7 @@ envoy_cc_mock( deps = [ "//include/envoy/buffer:buffer_interface", "//include/envoy/tcp:conn_pool_interface", + "//test/mocks/network:network_mocks", "//test/mocks/upstream:host_mocks", ], ) diff --git a/test/mocks/tcp/mocks.cc b/test/mocks/tcp/mocks.cc index f8586de4f041..1713d04e6128 100644 --- a/test/mocks/tcp/mocks.cc +++ b/test/mocks/tcp/mocks.cc @@ -3,6 +3,10 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Invoke; +using testing::ReturnRef; +using testing::_; + namespace Envoy { namespace Tcp { namespace ConnectionPool { @@ -13,9 +17,40 @@ MockCancellable::~MockCancellable() {} MockUpstreamCallbacks::MockUpstreamCallbacks() {} MockUpstreamCallbacks::~MockUpstreamCallbacks() {} -MockInstance::MockInstance() {} +MockConnectionData::MockConnectionData() { + ON_CALL(*this, connection()).WillByDefault(ReturnRef(connection_)); +} +MockConnectionData::~MockConnectionData() {} + +MockInstance::MockInstance() { + ON_CALL(*this, newConnection(_)).WillByDefault(Invoke([&](Callbacks& cb) -> Cancellable* { + return newConnectionImpl(cb); + })); +} MockInstance::~MockInstance() {} +MockCancellable* MockInstance::newConnectionImpl(Callbacks& cb) { + handles_.emplace_back(); + callbacks_.push_back(&cb); + return &handles_.back(); +} + +void MockInstance::poolFailure(PoolFailureReason reason) { + Callbacks* cb = callbacks_.front(); + callbacks_.pop_front(); + handles_.pop_front(); + + cb->onPoolFailure(reason, host_); +} + +void MockInstance::poolReady() { + Callbacks* cb = callbacks_.front(); + callbacks_.pop_front(); + handles_.pop_front(); + + cb->onPoolReady(connection_data_, host_); +} + } // namespace ConnectionPool } // namespace Tcp } // namespace Envoy diff --git a/test/mocks/tcp/mocks.h b/test/mocks/tcp/mocks.h index ed90509a442c..a9cc52fecbbb 100644 --- a/test/mocks/tcp/mocks.h +++ b/test/mocks/tcp/mocks.h @@ -3,11 +3,14 @@ #include "envoy/tcp/conn_pool.h" #include "test/mocks/common.h" +#include "test/mocks/network/mocks.h" #include "test/mocks/upstream/host.h" #include "test/test_common/printers.h" #include "gmock/gmock.h" +using testing::NiceMock; + namespace Envoy { namespace Tcp { namespace ConnectionPool { @@ -28,6 +31,22 @@ class MockUpstreamCallbacks : public UpstreamCallbacks { // Tcp::ConnectionPool::UpstreamCallbacks MOCK_METHOD2(onUpstreamData, void(Buffer::Instance& data, bool end_stream)); + MOCK_METHOD1(onEvent, void(Network::ConnectionEvent event)); + MOCK_METHOD0(onAboveWriteBufferHighWatermark, void()); + MOCK_METHOD0(onBelowWriteBufferLowWatermark, void()); +}; + +class MockConnectionData : public ConnectionData { +public: + MockConnectionData(); + ~MockConnectionData(); + + // Tcp::ConnectionPool::ConnectionData + MOCK_METHOD0(connection, Network::ClientConnection&()); + MOCK_METHOD1(addUpstreamCallbacks, void(ConnectionPool::UpstreamCallbacks&)); + MOCK_METHOD0(release, void()); + + NiceMock connection_; }; class MockInstance : public Instance { @@ -40,8 +59,16 @@ class MockInstance : public Instance { MOCK_METHOD0(drainConnections, void()); MOCK_METHOD1(newConnection, Cancellable*(Tcp::ConnectionPool::Callbacks& callbacks)); - std::shared_ptr> host_{ - new testing::NiceMock()}; + MockCancellable* newConnectionImpl(Callbacks& cb); + void poolFailure(PoolFailureReason reason); + void poolReady(); + + std::list> handles_; + std::list callbacks_; + + std::shared_ptr> host_{ + new NiceMock()}; + NiceMock connection_data_; }; } // namespace ConnectionPool From 445e746fa405ea9ab049ef9739a6e20b27ca79a1 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 23 Jul 2018 17:28:20 -0400 Subject: [PATCH 24/46] docs: adding flaky test instructions (#3931) Documenting some common failure modes and repro instructions. Risk Level: n/a Testing: relying on existing tests Docs Changes: yep Release Notes: n/a Signed-off-by: Alyssa Wilk --- test/integration/README.md | 79 ++++++++++++++++++++++++++++++++ test/integration/fake_upstream.h | 9 +++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/test/integration/README.md b/test/integration/README.md index a6a81daebdad..f87b4aefef1b 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -106,3 +106,82 @@ if the changes will be needed by one specific test file, or will be likely reused in other integration tests. If it's likely be reused, please add the appropriate functions to existing utilities or add new test utilities. If it's likely a one-off change, it can be scoped to the existing test file. + + +# Deflaking tests + +The instructions below assume the developer is running tests natively with bazel +rather than in docker. For developers using docker the best workaround today is +to replace `//test/...` on the relevant `ci/do_ci.sh`with the command lines +referenced below and remember to back those changes out before sending the fix +upstream! + +## Reproducing test flakes + +The first step of fixing test flakes is reproducing the test flake. In general +if you have written a test which flakes, you can start by running + +`` +bazel test [test_name] --runs_per_test=1000 +`` + +Which runs the full test many times. If this works, great! If not, it's worth +trying to stress your system more by running more tests in parallel, by setting +`--jobs` and `--local_resources.` + +Once you've managed to reproduce a failure it may be beneficial to limit your +test run to the specific failing test(s) with `--gtest_filter`. This may cause +the test to flake less often (i.e. if two tests are interfering with each other, +scoping to your specific test name may harm rather than help reproducibility.) +but if it works it lets you iterate faster. + +Another helpful tip for debugging is turn turn up Envoy trace logs with +`--test_arg="-l trace"`. Again if the test failure is due to a race, this may make +it harder to reproduce, and it may also hide any custom logging you add, but it's a +handy thing to know of to follow the general flow. + +The full command might look something like + +``` +bazel test //test/integration:http2_upstream_integration_test \ +--test_arg=--gtest_filter="IpVersions/Http2UpstreamIntegrationTest.RouterRequestAndResponseWithBodyNoBuffer/IPv6" \ +--jobs 60 --local_resources 100000000000,100000000000,10000000 --test_arg="-l trace" +``` + +## Debugging test flakes + +Once you've managed to reproduce your test flake, you get to figure out what's +going on. If your failure mode isn't documented below, ideally some combination +of cerr << logging and trace logs will help you sort out what is going on (and +please add to this document as you figure it out!) + +## Unexpected disconnects + +As commented in `HttpIntegrationTest::cleanupUpstreamAndDownstream()`, the +tear-down sequence between upstream, Envoy, and client is somewhat sensitive to +ordering. If a given unit test does not use the provided member variables, for +example opens multiple client or upstream connections, the test author should be +aware of test best practices for clean-up which boil down to "Clean up upstream +first". + +Upstream connections run in their own threads, so if the client disconnects with +open streams, there's a race where Envoy detects the disconnect, and kills the +corresponding upstream stream, which is indistinguishable from an unexpected +disconnect and triggers test failure. Because the client is run from the main +thread, if upstream is closed first, the client will not detect the inverse +close, so no test failure will occur. + +## Unparented upstream connections + +The most common failure mode here is if the test adds additional fake +upstreams for *DS connections (ADS, EDS etc) which are not properly shut down +(for a very sensitive test framework) + +The failure mode here is that during test teardown one closes the DS connection +and then shuts down Envoy. Unfortunately as Envoy is running in its own thread, +it will try to re-establish the *DS connection, sometimes creating a connection +which is then "unparented". The solution here is to explicitly allow Envoy +reconnects before closing the connection, using + +`my_ds_upstream_->set_allow_unexpected_disconnects(true);` + diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index d9259044feef..87bc4cfe022f 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -211,7 +211,10 @@ class SharedConnectionWrapper : public Network::ConnectionCallbacks { } else { RELEASE_ASSERT( allow_unexpected_disconnects_, - "The connection disconnected unexpectedly, and allow_unexpected_disconnects_ is false"); + "The connection disconnected unexpectedly, and allow_unexpected_disconnects_ is false." + "\n See " + "https://github.com/envoyproxy/envoy/blob/master/test/integration/README.md#" + "unexpected-disconnects"); } callback_ready_event.notifyOne(); }); @@ -251,7 +254,9 @@ class QueuedConnectionWrapper : public LinkedObject { "An queued upstream connection was torn down without being associated " "with a fake connection. Either manage the connection via " "waitForRawConnection() or waitForHttpConnection(), or " - "set_allow_unexpected_disconnects(true)."); + "set_allow_unexpected_disconnects(true).\n See " + "https://github.com/envoyproxy/envoy/blob/master/test/integration/README.md#" + "unparented-upstream-connections"); }); } From 0a43ae8d94d4dccafff4fc44bae6d3037d86eefb Mon Sep 17 00:00:00 2001 From: cmluciano Date: Mon, 23 Jul 2018 21:47:53 -0400 Subject: [PATCH 25/46] Update network utility getoriginaldst for IPv6 (#3933) This PR adds IP6T_SO_ORIGINAL_DST to the network utility for retaining the original_dest. Risk Level: Medium: Because we have to hardcode this with a define, there is a risk that upstream this gets changed and we would have to adjust. The define is necessary without the unmerged patch from Oct 2016. The TLDR is that the C code does not compile well with our compiler as shown here. Testing: Current tests exist Docs Changes: In progress Release Notes: Support IPv6 for original_dest in network utility Fixes #1094 Signed-off-by: Christopher M. Luciano --- source/common/network/BUILD | 1 + source/common/network/utility.cc | 37 +++++- test/server/listener_manager_impl_test.cc | 137 ++++++++++++++++++++++ 3 files changed, 170 insertions(+), 5 deletions(-) diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 04354a75e6c8..8fcb9d779375 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -226,6 +226,7 @@ envoy_cc_library( ":address_lib", "//include/envoy/network:connection_interface", "//include/envoy/stats:stats_interface", + "//source/common/api:os_sys_calls_lib", "//source/common/common:assert_lib", "//source/common/common:utility_lib", "//source/common/protobuf", diff --git a/source/common/network/utility.cc b/source/common/network/utility.cc index a887af86ea95..c0e40917ed36 100644 --- a/source/common/network/utility.cc +++ b/source/common/network/utility.cc @@ -7,6 +7,11 @@ #include #endif +#ifndef IP6T_SO_ORIGINAL_DST +// From linux/netfilter_ipv6/ip6_tables.h +#define IP6T_SO_ORIGINAL_DST 80 +#endif + #include #include @@ -20,6 +25,7 @@ #include "envoy/network/connection.h" #include "envoy/stats/stats.h" +#include "common/api/os_sys_calls_impl.h" #include "common/common/assert.h" #include "common/common/utility.h" #include "common/network/address_impl.h" @@ -292,14 +298,35 @@ Address::InstanceConstSharedPtr Utility::getOriginalDst(int fd) { #ifdef SOL_IP sockaddr_storage orig_addr; socklen_t addr_len = sizeof(sockaddr_storage); - int status = getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &orig_addr, &addr_len); + int socket_domain; + socklen_t domain_len = sizeof(socket_domain); + auto& os_syscalls = Api::OsSysCallsSingleton::get(); + int status = os_syscalls.getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &socket_domain, &domain_len); + + if (status != 0) { + return nullptr; + } - if (status == 0) { - // TODO(mattklein123): IPv6 support. See github issue #1094. - ASSERT(orig_addr.ss_family == AF_INET); + if (socket_domain == AF_INET) { + status = os_syscalls.getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, &orig_addr, &addr_len); + } else if (socket_domain == AF_INET6) { + status = os_syscalls.getsockopt(fd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, &orig_addr, &addr_len); + } else { + return nullptr; + } + + if (status != 0) { + return nullptr; + } + + switch (orig_addr.ss_family) { + case AF_INET: return Address::InstanceConstSharedPtr{ new Address::Ipv4Instance(reinterpret_cast(&orig_addr))}; - } else { + case AF_INET6: + return Address::InstanceConstSharedPtr{ + new Address::Ipv6Instance(reinterpret_cast(orig_addr))}; + default: return nullptr; } #else diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 94698c3d76d3..956b8f288450 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -2269,6 +2269,143 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, OriginalDstTestFilterOptionFail) EXPECT_EQ(0U, manager_->listeners().size()); } +class OriginalDstTestFilterIPv6 + : public Extensions::ListenerFilters::OriginalDst::OriginalDstFilter { + Network::Address::InstanceConstSharedPtr getOriginalDst(int) override { + return Network::Address::InstanceConstSharedPtr{ + new Network::Address::Ipv6Instance("1::2", 2345)}; + } +}; + +TEST_F(ListenerManagerImplWithRealFiltersTest, OriginalDstTestFilterIPv6) { + static int fd; + fd = -1; + EXPECT_CALL(*listener_factory_.socket_, fd()).WillOnce(Return(0)); + + class OriginalDstTestConfigFactory : public Configuration::NamedListenerFilterConfigFactory { + public: + // NamedListenerFilterConfigFactory + Network::ListenerFilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message&, + Configuration::ListenerFactoryContext& context) override { + auto option = std::make_unique(); + EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_PREBIND)) + .WillOnce(Return(true)); + EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_BOUND)) + .WillOnce(Invoke( + [](Network::Socket& socket, envoy::api::v2::core::SocketOption::SocketState) -> bool { + fd = socket.fd(); + return true; + })); + context.addListenSocketOption(std::move(option)); + return [](Network::ListenerFilterManager& filter_manager) -> void { + filter_manager.addAcceptFilter(std::make_unique()); + }; + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() override { return "test.listener.original_dstipv6"; } + }; + + /** + * Static registration for the original dst filter. @see RegisterFactory. + */ + static Registry::RegisterFactory + registered_; + + const std::string yaml = TestEnvironment::substitute(R"EOF( + address: + socket_address: { address: ::0001, port_value: 1111 } + filter_chains: {} + listener_filters: + - name: "test.listener.original_dstipv6" + config: {} + )EOF", + Network::Address::IpVersion::v6); + + EXPECT_CALL(server_.random_, uuid()); + EXPECT_CALL(listener_factory_, createListenSocket(_, _, true)); + manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true); + EXPECT_EQ(1U, manager_->listeners().size()); + + Network::ListenerConfig& listener = manager_->listeners().back().get(); + + Network::FilterChainFactory& filterChainFactory = listener.filterChainFactory(); + Network::MockListenerFilterManager manager; + + NiceMock callbacks; + Network::AcceptedSocketImpl socket( + -1, std::make_unique("::0001", 1234), + std::make_unique("::0001", 5678)); + + EXPECT_CALL(callbacks, socket()).WillOnce(Invoke([&]() -> Network::ConnectionSocket& { + return socket; + })); + + EXPECT_CALL(manager, addAcceptFilter_(_)) + .WillOnce(Invoke([&](Network::ListenerFilterPtr& filter) -> void { + EXPECT_EQ(Network::FilterStatus::Continue, filter->onAccept(callbacks)); + })); + + EXPECT_TRUE(filterChainFactory.createListenerFilterChain(manager)); + EXPECT_TRUE(socket.localAddressRestored()); + EXPECT_EQ("[1::2]:2345", socket.localAddress()->asString()); + EXPECT_NE(fd, -1); +} + +TEST_F(ListenerManagerImplWithRealFiltersTest, OriginalDstTestFilterOptionFailIPv6) { + class OriginalDstTestConfigFactory : public Configuration::NamedListenerFilterConfigFactory { + public: + // NamedListenerFilterConfigFactory + Network::ListenerFilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message&, + Configuration::ListenerFactoryContext& context) override { + auto option = std::make_unique(); + EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_PREBIND)) + .WillOnce(Return(false)); + context.addListenSocketOption(std::move(option)); + return [](Network::ListenerFilterManager& filter_manager) -> void { + filter_manager.addAcceptFilter(std::make_unique()); + }; + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() override { return "testfail.listener.original_dstipv6"; } + }; + + /** + * Static registration for the original dst filter. @see RegisterFactory. + */ + static Registry::RegisterFactory + registered_; + + const std::string yaml = TestEnvironment::substitute(R"EOF( + name: "socketOptionFailListener" + address: + socket_address: { address: ::0001, port_value: 1111 } + filter_chains: {} + listener_filters: + - name: "testfail.listener.original_dstipv6" + config: {} + )EOF", + Network::Address::IpVersion::v6); + + EXPECT_CALL(listener_factory_, createListenSocket(_, _, true)); + + EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true), + EnvoyException, + "MockListenerComponentFactory: Setting socket options failed"); + EXPECT_EQ(0U, manager_->listeners().size()); +} + // Validate that when neither transparent nor freebind is not set in the // Listener, we see no socket option set. TEST_F(ListenerManagerImplWithRealFiltersTest, TransparentFreebindListenerDisabled) { From eefd06db40bda87e549f0e8c2a7acc7b6433ce66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Guti=C3=A9rrez=20Segal=C3=A9s?= Date: Mon, 23 Jul 2018 19:47:17 -0700 Subject: [PATCH 26/46] Add new field to access log (#3907) This new field, RESPONSE_TX_DURATION, represents the duration between the first upstream byte received and the last downstream byte sent. *Risk*: low *Testing*: unit tests *Doc changes*: added access_log config docs for RESPONSE_TX_DURATION and RESPONSE_DURATION *Release Notes*: noted addition of RESPONSE_TX_DURATION and RESPONSE_DURATION Signed-off-by: Raul Gutierrez Segales --- docs/root/configuration/access_log.rst | 16 ++++++++++++++ docs/root/intro/version_history.rst | 1 + .../common/access_log/access_log_formatter.cc | 20 ++++++++++++++--- .../common/access_log/access_log_formatter.h | 1 + .../access_log/access_log_formatter_test.cc | 22 +++++++++++++++++++ 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/docs/root/configuration/access_log.rst b/docs/root/configuration/access_log.rst index c85bd554aa10..94392f404c69 100644 --- a/docs/root/configuration/access_log.rst +++ b/docs/root/configuration/access_log.rst @@ -102,6 +102,14 @@ The following command operators are supported: TCP Total duration in milliseconds of the downstream connection. +%RESPONSE_DURATION% + HTTP + Total duration in milliseconds of the request from the start time to the first byte read from the + upstream host. + + TCP + Not implemented ("-"). + .. _config_access_log_format_response_flags: %RESPONSE_FLAGS% @@ -123,6 +131,14 @@ The following command operators are supported: * **FI**: The request was aborted with a response code specified via :ref:`fault injection `. * **RL**: The request was ratelimited locally by the :ref:`HTTP rate limit filter ` in addition to 429 response code. +%RESPONSE_TX_DURATION% + HTTP + Total duration in milliseconds of the request from the first byte read from the upstream host to the last + byte sent downstream. + + TCP + Not implemented ("-"). + %UPSTREAM_HOST% Upstream host URL (e.g., tcp://ip:port for TCP connections). diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index a4090a47eea4..8d1c1e6fba4a 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -47,6 +47,7 @@ Version history `. * upstream: added configuration option to the subset load balancer to take locality weights into account when selecting a host from a subset. +* access log: added RESPONSE_DURATION and RESPONSE_TX_DURATION. 1.7.0 =============== diff --git a/source/common/access_log/access_log_formatter.cc b/source/common/access_log/access_log_formatter.cc index 933a1cc65a78..88ec956d9d55 100644 --- a/source/common/access_log/access_log_formatter.cc +++ b/source/common/access_log/access_log_formatter.cc @@ -35,14 +35,16 @@ FormatterPtr AccessLogFormatUtils::defaultAccessLogFormatter() { std::string AccessLogFormatUtils::durationToString(const absl::optional& time) { if (time) { - return fmt::FormatInt( - std::chrono::duration_cast(time.value()).count()) - .str(); + return durationToString(time.value()); } else { return UnspecifiedValueString; } } +std::string AccessLogFormatUtils::durationToString(const std::chrono::nanoseconds& time) { + return fmt::FormatInt(std::chrono::duration_cast(time).count()).str(); +} + const std::string& AccessLogFormatUtils::protocolToString(const absl::optional& protocol) { if (protocol) { @@ -221,6 +223,18 @@ RequestInfoFormatter::RequestInfoFormatter(const std::string& field_name) { field_extractor_ = [](const RequestInfo::RequestInfo& request_info) { return AccessLogFormatUtils::durationToString(request_info.firstUpstreamRxByteReceived()); }; + } else if (field_name == "RESPONSE_TX_DURATION") { + field_extractor_ = [](const RequestInfo::RequestInfo& request_info) { + auto downstream = request_info.lastDownstreamTxByteSent(); + auto upstream = request_info.firstUpstreamRxByteReceived(); + + if (downstream && upstream) { + auto val = downstream.value() - upstream.value(); + return AccessLogFormatUtils::durationToString(val); + } + + return UnspecifiedValueString; + }; } else if (field_name == "BYTES_RECEIVED") { field_extractor_ = [](const RequestInfo::RequestInfo& request_info) { return fmt::FormatInt(request_info.bytesReceived()).str(); diff --git a/source/common/access_log/access_log_formatter.h b/source/common/access_log/access_log_formatter.h index b0a1246345c0..b6cb0a8a2775 100644 --- a/source/common/access_log/access_log_formatter.h +++ b/source/common/access_log/access_log_formatter.h @@ -73,6 +73,7 @@ class AccessLogFormatUtils { static FormatterPtr defaultAccessLogFormatter(); static const std::string& protocolToString(const absl::optional& protocol); static std::string durationToString(const absl::optional& time); + static std::string durationToString(const std::chrono::nanoseconds& time); private: AccessLogFormatUtils(); diff --git a/test/common/access_log/access_log_formatter_test.cc b/test/common/access_log/access_log_formatter_test.cc index fe711bf4f2b1..44c0ddf9eda8 100644 --- a/test/common/access_log/access_log_formatter_test.cc +++ b/test/common/access_log/access_log_formatter_test.cc @@ -73,6 +73,28 @@ TEST(AccessLogFormatterTest, requestInfoFormatter) { EXPECT_EQ("-", response_duration_format.format(header, header, header, request_info)); } + { + RequestInfoFormatter ttlb_duration_format("RESPONSE_TX_DURATION"); + + absl::optional dur_upstream = std::chrono::nanoseconds(10000000); + EXPECT_CALL(request_info, firstUpstreamRxByteReceived()).WillRepeatedly(Return(dur_upstream)); + absl::optional dur_downstream = std::chrono::nanoseconds(25000000); + EXPECT_CALL(request_info, lastDownstreamTxByteSent()).WillRepeatedly(Return(dur_downstream)); + + EXPECT_EQ("15", ttlb_duration_format.format(header, header, header, request_info)); + } + + { + RequestInfoFormatter ttlb_duration_format("RESPONSE_TX_DURATION"); + + absl::optional dur_upstream; + EXPECT_CALL(request_info, firstUpstreamRxByteReceived()).WillRepeatedly(Return(dur_upstream)); + absl::optional dur_downstream; + EXPECT_CALL(request_info, lastDownstreamTxByteSent()).WillRepeatedly(Return(dur_downstream)); + + EXPECT_EQ("-", ttlb_duration_format.format(header, header, header, request_info)); + } + { RequestInfoFormatter bytes_received_format("BYTES_RECEIVED"); EXPECT_CALL(request_info, bytesReceived()).WillOnce(Return(1)); From 8b3aae8c6c7c68eb0f05c43a729a7e415cca3e55 Mon Sep 17 00:00:00 2001 From: Matt Rice Date: Tue, 24 Jul 2018 12:33:46 -0400 Subject: [PATCH 27/46] health_check: remove deprecated endpoint field (#3891) Removes the deprecated endpoint field for 1.8 release. Risk Level: Low Testing: N/A Docs Changes: N/A Release Notes: Already listed under 1.7. Fixes #3717 Signed-off-by: Matt Rice --- .../http/health_check/v2/health_check.proto | 11 ++--- docs/root/intro/version_history.rst | 3 +- source/common/config/filter_json.cc | 5 +- .../filters/http/health_check/config.cc | 15 +----- .../filters/http/health_check/config_test.cc | 48 ++++--------------- 5 files changed, 18 insertions(+), 64 deletions(-) diff --git a/api/envoy/config/filter/http/health_check/v2/health_check.proto b/api/envoy/config/filter/http/health_check/v2/health_check.proto index 88106e93136c..0f584b451f68 100644 --- a/api/envoy/config/filter/http/health_check/v2/health_check.proto +++ b/api/envoy/config/filter/http/health_check/v2/health_check.proto @@ -19,11 +19,8 @@ message HealthCheck { // Specifies whether the filter operates in pass through mode or not. google.protobuf.BoolValue pass_through_mode = 1 [(validate.rules).message.required = true]; - // Specifies the incoming HTTP endpoint that should be considered the - // health check endpoint. For example */healthcheck*. - // Note that this field is deprecated in favor of - // :ref:`headers `. - string endpoint = 2 [deprecated = true]; + reserved 2; + reserved "endpoint"; // If operating in pass through mode, the amount of time in milliseconds // that the filter should cache the upstream response. @@ -36,8 +33,6 @@ message HealthCheck { // Specifies a set of health check request headers to match on. The health check filter will // check a request’s headers against all the specified headers. To specify the health check - // endpoint, set the ``:path`` header to match on. Note that if the - // :ref:`endpoint ` - // field is set, it will overwrite any ``:path`` header to match. + // endpoint, set the ``:path`` header to match on. repeated envoy.api.v2.route.HeaderMatcher headers = 5; } diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 8d1c1e6fba4a..121c40993e0f 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -101,8 +101,7 @@ Version history * health check: health check connections can now be configured to use http/2. * health check http filter: added :ref:`generic header matching ` - to trigger health check response. Deprecated the - :ref:`endpoint option `. + to trigger health check response. Deprecated the endpoint option. * http: filters can now optionally support :ref:`virtual host `, :ref:`route `, and diff --git a/source/common/config/filter_json.cc b/source/common/config/filter_json.cc index 1b912766e9c6..8069fea03b71 100644 --- a/source/common/config/filter_json.cc +++ b/source/common/config/filter_json.cc @@ -302,7 +302,10 @@ void FilterJson::translateHealthCheckFilter( JSON_UTIL_SET_BOOL(json_config, proto_config, pass_through_mode); JSON_UTIL_SET_DURATION(json_config, proto_config, cache_time); - JSON_UTIL_SET_STRING(json_config, proto_config, endpoint); + std::string endpoint = json_config.getString("endpoint"); + auto& header = *proto_config.add_headers(); + header.set_name(":path"); + header.set_exact_match(endpoint); } void FilterJson::translateGrpcJsonTranscoder( diff --git a/source/extensions/filters/http/health_check/config.cc b/source/extensions/filters/http/health_check/config.cc index 04e28992198f..56b682dafa38 100644 --- a/source/extensions/filters/http/health_check/config.cc +++ b/source/extensions/filters/http/health_check/config.cc @@ -20,25 +20,12 @@ Http::FilterFactoryCb HealthCheckFilterConfig::createFilterFactoryFromProtoTyped const bool pass_through_mode = proto_config.pass_through_mode().value(); const int64_t cache_time_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config, cache_time, 0); - const std::string hc_endpoint = proto_config.endpoint(); auto header_match_data = std::make_shared>(); - // TODO(mrice32): remove endpoint field at the end of the 1.7.0 deprecation cycle. - const bool endpoint_set = !proto_config.endpoint().empty(); - if (endpoint_set) { - envoy::api::v2::route::HeaderMatcher matcher; - matcher.set_name(Http::Headers::get().Path.get()); - matcher.set_exact_match(proto_config.endpoint()); - header_match_data->emplace_back(matcher); - } - for (const envoy::api::v2::route::HeaderMatcher& matcher : proto_config.headers()) { Http::HeaderUtility::HeaderData single_header_match(matcher); - // Ignore any path header matchers if the endpoint field has been set. - if (!(endpoint_set && single_header_match.name_ == Http::Headers::get().Path)) { - header_match_data->push_back(std::move(single_header_match)); - } + header_match_data->push_back(std::move(single_header_match)); } if (!pass_through_mode && cache_time_ms) { diff --git a/test/extensions/filters/http/health_check/config_test.cc b/test/extensions/filters/http/health_check/config_test.cc index dd743334991f..a966cae896c8 100644 --- a/test/extensions/filters/http/health_check/config_test.cc +++ b/test/extensions/filters/http/health_check/config_test.cc @@ -71,8 +71,10 @@ TEST(HealthCheckFilterConfig, FailsWhenNotPassThroughButTimeoutSetProto) { NiceMock context; config.mutable_pass_through_mode()->set_value(false); - config.set_endpoint("foo"); config.mutable_cache_time()->set_seconds(10); + envoy::api::v2::route::HeaderMatcher& header = *config.add_headers(); + header.set_name(":path"); + header.set_exact_match("foo"); EXPECT_THROW( healthCheckFilterConfig.createFilterFactoryFromProto(config, "dummy_stats_prefix", context), @@ -85,7 +87,9 @@ TEST(HealthCheckFilterConfig, NotFailingWhenNotPassThroughAndTimeoutNotSetProto) NiceMock context; config.mutable_pass_through_mode()->set_value(false); - config.set_endpoint("foo"); + envoy::api::v2::route::HeaderMatcher& header = *config.add_headers(); + header.set_name(":path"); + header.set_exact_match("foo"); healthCheckFilterConfig.createFilterFactoryFromProto(config, "dummy_stats_prefix", context); } @@ -97,7 +101,9 @@ TEST(HealthCheckFilterConfig, HealthCheckFilterWithEmptyProto) { healthCheckFilterConfig.createEmptyConfigProto().get()); config.mutable_pass_through_mode()->set_value(false); - config.set_endpoint("foo"); + envoy::api::v2::route::HeaderMatcher& header = *config.add_headers(); + header.set_name(":path"); + header.set_exact_match("foo"); healthCheckFilterConfig.createFilterFactoryFromProto(config, "dummy_stats_prefix", context); } @@ -195,42 +201,6 @@ TEST(HealthCheckFilterConfig, HealthCheckFilterHeaderMatchMissingHeader) { testHealthCheckHeaderMatch(config, headers, false); } -// If an endpoint is specified and the path matches, it should match regardless of any :path -// conditions given in the headers field. -TEST(HealthCheckFilterConfig, HealthCheckFilterEndpoint) { - envoy::config::filter::http::health_check::v2::HealthCheck config; - - config.mutable_pass_through_mode()->set_value(false); - - config.set_endpoint("foo"); - - envoy::api::v2::route::HeaderMatcher& header = *config.add_headers(); - header.set_name(Http::Headers::get().Path.get()); - header.set_exact_match("bar"); - - Http::TestHeaderMapImpl headers{{Http::Headers::get().Path.get(), "foo"}}; - - testHealthCheckHeaderMatch(config, headers, true); -} - -// If an endpoint is specified and the path does not match, the filter should not match regardless -// of any :path conditions given in the headers field. -TEST(HealthCheckFilterConfig, HealthCheckFilterEndpointOverride) { - envoy::config::filter::http::health_check::v2::HealthCheck config; - - config.mutable_pass_through_mode()->set_value(false); - - config.set_endpoint("foo"); - - envoy::api::v2::route::HeaderMatcher& header = *config.add_headers(); - header.set_name(Http::Headers::get().Path.get()); - header.set_exact_match("bar"); - - Http::TestHeaderMapImpl headers{{Http::Headers::get().Path.get(), "bar"}}; - - testHealthCheckHeaderMatch(config, headers, false); -} - // Conditions for the same header should match if they are both satisfied. TEST(HealthCheckFilterConfig, HealthCheckFilterDuplicateMatch) { envoy::config::filter::http::health_check::v2::HealthCheck config; From b32eabfc141760ec14622a4a2a2f0ab0a741cd6c Mon Sep 17 00:00:00 2001 From: Dhi Aurrahman Date: Wed, 25 Jul 2018 00:01:23 +0700 Subject: [PATCH 28/46] upstream: implement Cluster's load_assignment field (#3864) This patch implements load_assigment field in CDS' Cluster. This change specifically adds the implementation of the new load_assigment field for clusters with discovery-type: STATIC, STRICT_DNS and LOGICAL_DNS. While adding this load_assigment field implementation to Cluster, this patch also allows specifying optional (active) health check config per specified upstream host. Risk Level: medium Testing: unit tests Docs Changes: This unhides docs for endpoint health check config Release Notes: N/A Fixes #439 Signed-off-by: Dhi Aurrahman --- DEPRECATED.md | 3 +- api/envoy/api/v2/cds.proto | 9 +- api/envoy/api/v2/endpoint/endpoint.proto | 6 +- .../configuration/overview/v2_overview.rst | 35 +- .../intro/arch_overview/health_checking.rst | 29 + source/common/config/utility.cc | 15 + source/common/config/utility.h | 8 + source/common/upstream/eds.cc | 2 +- source/common/upstream/logical_dns_cluster.cc | 45 +- source/common/upstream/logical_dns_cluster.h | 42 +- source/common/upstream/upstream_impl.cc | 173 +++--- source/common/upstream/upstream_impl.h | 38 +- test/common/upstream/BUILD | 2 + .../upstream/cluster_manager_impl_test.cc | 2 +- .../upstream/logical_dns_cluster_test.cc | 318 ++++++++--- test/common/upstream/upstream_impl_test.cc | 536 +++++++++++++++++- 16 files changed, 1018 insertions(+), 245 deletions(-) diff --git a/DEPRECATED.md b/DEPRECATED.md index 33eff1bc489a..e5308f46e43e 100644 --- a/DEPRECATED.md +++ b/DEPRECATED.md @@ -10,7 +10,7 @@ A logged warning is expected for each deprecated item that is in deprecation win * Use of the v1 API is deprecated. See envoy-announce [email](https://groups.google.com/forum/#!topic/envoy-announce/oPnYMZw8H4U). -* Use of the legacy +* Use of the legacy [ratelimit.proto](https://github.com/envoyproxy/envoy/blob/b0a518d064c8255e0e20557a8f909b6ff457558f/source/common/ratelimit/ratelimit.proto) is deprecated, in favor of the proto defined in [date-plane-api](https://github.com/envoyproxy/envoy/blob/master/api/envoy/service/ratelimit/v2/rls.proto) @@ -23,6 +23,7 @@ A logged warning is expected for each deprecated item that is in deprecation win is deprecated. Please use the new `upgrade_configs` in the [HttpConnectionManager](https://github.com/envoyproxy/envoy/blob/master/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto) instead. +* Setting hosts via `hosts` field in `Cluster` is deprecated. Use `load_assignment` instead. ## Version 1.7.0 diff --git a/api/envoy/api/v2/cds.proto b/api/envoy/api/v2/cds.proto index f649dafe0fb6..ea19212772e9 100644 --- a/api/envoy/api/v2/cds.proto +++ b/api/envoy/api/v2/cds.proto @@ -160,7 +160,13 @@ message Cluster { // :ref:`STRICT_DNS` // or :ref:`LOGICAL_DNS`, // then hosts is required. - repeated core.Address hosts = 7; + // + // .. attention:: + // + // **This field is deprecated**. Set the + // :ref:`load_assignment` field instead. + // + repeated core.Address hosts = 7 [deprecated = true]; // Setting this is required for specifying members of // :ref:`STATIC`, @@ -176,7 +182,6 @@ message Cluster { // :ref:`endpoint assignments`. // Setting this overrides :ref:`hosts` values. // - // [#not-implemented-hide:] ClusterLoadAssignment load_assignment = 33; // Optional :ref:`active health checking ` diff --git a/api/envoy/api/v2/endpoint/endpoint.proto b/api/envoy/api/v2/endpoint/endpoint.proto index 6f4cad1ce66e..bfdc6bcffe1a 100644 --- a/api/envoy/api/v2/endpoint/endpoint.proto +++ b/api/envoy/api/v2/endpoint/endpoint.proto @@ -29,7 +29,7 @@ message Endpoint { // and will be resolved via DNS. core.Address address = 1; - // [#not-implemented-hide:] The optional health check configuration. + // The optional health check configuration. message HealthCheckConfig { // Optional alternative health check port value. // @@ -40,8 +40,8 @@ message Endpoint { uint32 port_value = 1; } - // [#not-implemented-hide:] The optional health check configuration is used as - // configuration for the health checker to contact the health checked host. + // The optional health check configuration is used as configuration for the + // health checker to contact the health checked host. // // .. attention:: // diff --git a/docs/root/configuration/overview/v2_overview.rst b/docs/root/configuration/overview/v2_overview.rst index feb1032a4d4a..3c8e0aff06c9 100644 --- a/docs/root/configuration/overview/v2_overview.rst +++ b/docs/root/configuration/overview/v2_overview.rst @@ -95,7 +95,14 @@ A minimal fully static bootstrap config is provided below: connect_timeout: 0.25s type: STATIC lb_policy: ROUND_ROBIN - hosts: [{ socket_address: { address: 127.0.0.2, port_value: 1234 }}] + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 1234 Mostly static with dynamic EDS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -150,7 +157,14 @@ on 127.0.0.3:5678 is provided below: type: STATIC lb_policy: ROUND_ROBIN http2_protocol_options: {} - hosts: [{ socket_address: { address: 127.0.0.3, port_value: 5678 }}] + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 5678 Notice above that *xds_cluster* is defined to point Envoy at the management server. Even in an otherwise completely dynamic configurations, some static resources need to @@ -214,7 +228,14 @@ below: type: STATIC lb_policy: ROUND_ROBIN http2_protocol_options: {} - hosts: [{ socket_address: { address: 127.0.0.3, port_value: 5678 }}] + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 5678 The management server could respond to LDS requests with: @@ -543,11 +564,11 @@ the shared ADS channel. Management Server Unreachability -------------------------------- -When Envoy instance looses connectivity with the management server, Envoy will latch on to -the previous configuration while actively retrying in the background to reestablish the -connection with the management server. +When an Envoy instance loses connectivity with the management server, Envoy will latch on to +the previous configuration while actively retrying in the background to reestablish the +connection with the management server. -Envoy debug logs the fact that it is not able to establish a connection with the management server +Envoy debug logs the fact that it is not able to establish a connection with the management server every time it attempts a connection. :ref:`upstream_cx_connect_fail ` a cluster level statistic diff --git a/docs/root/intro/arch_overview/health_checking.rst b/docs/root/intro/arch_overview/health_checking.rst index 6928ac94d669..08812c0b62e5 100644 --- a/docs/root/intro/arch_overview/health_checking.rst +++ b/docs/root/intro/arch_overview/health_checking.rst @@ -24,6 +24,35 @@ unhealthy, successes required before marking a host healthy, etc.): maintenance by setting the specified key to any value and waiting for traffic to drain. See :ref:`redis_key `. +.. _arch_overview_per_cluster_health_check_config: + +Per cluster member health check config +-------------------------------------- + +If active health checking is configured for an upstream cluster, a specific additional configuration +for each registered member can be specified by setting the +:ref:`HealthCheckConfig` +in the :ref:`Endpoint` of an :ref:`LbEndpoint` +of each defined :ref:`LocalityLbEndpoints` in a +:ref:`ClusterLoadAssignment`. + +An example of setting up :ref:`health check config` +to set a :ref:`cluster member`'s alternative health check +:ref:`port` is: + +.. code-block:: yaml + + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + health_check_config: + port_value: 8080 + address: + socket_address: + address: localhost + port_value: 80 + .. _arch_overview_health_check_logging: Health check event logging diff --git a/source/common/config/utility.cc b/source/common/config/utility.cc index 007204761b8a..37fac934f2ad 100644 --- a/source/common/config/utility.cc +++ b/source/common/config/utility.cc @@ -225,5 +225,20 @@ Grpc::AsyncClientFactoryPtr Utility::factoryForGrpcApiConfigSource( return async_client_manager.factoryForGrpcService(grpc_service, scope, false); } +envoy::api::v2::ClusterLoadAssignment Utility::translateClusterHosts( + const Protobuf::RepeatedPtrField& hosts) { + envoy::api::v2::ClusterLoadAssignment load_assignment; + envoy::api::v2::endpoint::LocalityLbEndpoints* locality_lb_endpoints = + load_assignment.add_endpoints(); + // Since this LocalityLbEndpoints is built from hosts list, set the default weight to 1. + locality_lb_endpoints->mutable_load_balancing_weight()->set_value(1); + for (const envoy::api::v2::core::Address& host : hosts) { + envoy::api::v2::endpoint::LbEndpoint* lb_endpoint = locality_lb_endpoints->add_lb_endpoints(); + lb_endpoint->mutable_endpoint()->mutable_address()->MergeFrom(host); + lb_endpoint->mutable_load_balancing_weight()->set_value(1); + } + return load_assignment; +} + } // namespace Config } // namespace Envoy diff --git a/source/common/config/utility.h b/source/common/config/utility.h index 32797c4f627b..79086504ad94 100644 --- a/source/common/config/utility.h +++ b/source/common/config/utility.h @@ -288,6 +288,14 @@ class Utility { factoryForGrpcApiConfigSource(Grpc::AsyncClientManager& async_client_manager, const envoy::api::v2::core::ApiConfigSource& api_config_source, Stats::Scope& scope); + + /** + * Translate a set of cluster's hosts into a load assignment configuration. + * @param hosts cluster's list of hosts. + * @return envoy::api::v2::ClusterLoadAssignment a load assignment configuration. + */ + static envoy::api::v2::ClusterLoadAssignment + translateClusterHosts(const Protobuf::RepeatedPtrField& hosts); }; } // namespace Config diff --git a/source/common/upstream/eds.cc b/source/common/upstream/eds.cc index 4fb00c0d2e40..46435a6de4d3 100644 --- a/source/common/upstream/eds.cc +++ b/source/common/upstream/eds.cc @@ -140,7 +140,7 @@ bool EdsClusterImpl::updateHostsPerLocality(const uint32_t priority, const HostV info_->name(), host_set.hosts().size(), host_set.priority()); priority_state_manager.updateClusterPrioritySet(priority, std::move(current_hosts_copy), - hosts_added, hosts_removed); + hosts_added, hosts_removed, absl::nullopt); return true; } return false; diff --git a/source/common/upstream/logical_dns_cluster.cc b/source/common/upstream/logical_dns_cluster.cc index 75284b9c31ad..5b3709041b02 100644 --- a/source/common/upstream/logical_dns_cluster.cc +++ b/source/common/upstream/logical_dns_cluster.cc @@ -18,6 +18,7 @@ namespace Upstream { LogicalDnsCluster::LogicalDnsCluster(const envoy::api::v2::Cluster& cluster, Runtime::Loader& runtime, Stats::Store& stats, Ssl::ContextManager& ssl_context_manager, + const LocalInfo::LocalInfo& local_info, Network::DnsResolverSharedPtr dns_resolver, ThreadLocal::SlotAllocator& tls, ClusterManager& cm, Event::Dispatcher& dispatcher, bool added_via_api) @@ -27,10 +28,19 @@ LogicalDnsCluster::LogicalDnsCluster(const envoy::api::v2::Cluster& cluster, dns_refresh_rate_ms_( std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(cluster, dns_refresh_rate, 5000))), tls_(tls.allocateSlot()), - resolve_timer_(dispatcher.createTimer([this]() -> void { startResolve(); })) { - const auto& hosts = cluster.hosts(); - if (hosts.size() != 1) { - throw EnvoyException("logical_dns clusters must have a single host"); + resolve_timer_(dispatcher.createTimer([this]() -> void { startResolve(); })), + local_info_(local_info), + load_assignment_(cluster.has_load_assignment() + ? cluster.load_assignment() + : Config::Utility::translateClusterHosts(cluster.hosts())) { + const auto& locality_lb_endpoints = load_assignment_.endpoints(); + if (locality_lb_endpoints.size() != 1 || locality_lb_endpoints[0].lb_endpoints().size() != 1) { + if (cluster.has_load_assignment()) { + throw EnvoyException( + "LOGICAL_DNS clusters must have a single locality_lb_endpoint and a single lb_endpoint"); + } else { + throw EnvoyException("LOGICAL_DNS clusters must have a single host"); + } } switch (cluster.dns_lookup_family()) { @@ -47,7 +57,8 @@ LogicalDnsCluster::LogicalDnsCluster(const envoy::api::v2::Cluster& cluster, NOT_REACHED_GCOVR_EXCL_LINE; } - const auto& socket_address = hosts[0].socket_address(); + const envoy::api::v2::core::SocketAddress& socket_address = + lbEndpoint().endpoint().address().socket_address(); dns_url_ = fmt::format("tcp://{}:{}", socket_address.address(), socket_address.port_value()); hostname_ = Network::Utility::hostFromTcpUrl(dns_url_); Network::Utility::portFromTcpUrl(dns_url_); @@ -88,7 +99,8 @@ void LogicalDnsCluster::startResolve() { current_resolved_address_ = new_address; // Capture URL to avoid a race with another update. tls_->runOnAllThreads([this, new_address]() -> void { - tls_->getTyped().current_resolved_address_ = new_address; + PerThreadCurrentHostData& data = tls_->getTyped(); + data.current_resolved_address_ = new_address; }); } @@ -107,14 +119,16 @@ void LogicalDnsCluster::startResolve() { new LogicalHost(info_, hostname_, Network::Utility::getIpv6AnyAddress(), *this)); break; } - HostVectorSharedPtr new_hosts(new HostVector()); - new_hosts->emplace_back(logical_host_); - // Given the current config, only EDS clusters support multiple priorities. - ASSERT(priority_set_.hostSetsPerPriority().size() == 1); - auto& first_host_set = priority_set_.getOrCreateHostSet(0); - first_host_set.updateHosts(new_hosts, createHealthyHostList(*new_hosts), - HostsPerLocalityImpl::empty(), HostsPerLocalityImpl::empty(), - {}, *new_hosts, {}); + const auto& locality_lb_endpoint = localityLbEndpoint(); + PriorityStateManager priority_state_manager(*this, local_info_); + priority_state_manager.initializePriorityFor(locality_lb_endpoint); + priority_state_manager.registerHostForPriority(logical_host_, locality_lb_endpoint, + lbEndpoint(), absl::nullopt); + + const uint32_t priority = locality_lb_endpoint.priority(); + priority_state_manager.updateClusterPrioritySet( + priority, std::move(priority_state_manager.priorityState()[priority].first), + absl::nullopt, absl::nullopt, absl::nullopt); } } @@ -131,7 +145,8 @@ Upstream::Host::CreateConnectionData LogicalDnsCluster::LogicalHost::createConne return {HostImpl::createConnection(dispatcher, *parent_.info_, data.current_resolved_address_, options), HostDescriptionConstSharedPtr{ - new RealHostDescription(data.current_resolved_address_, shared_from_this())}}; + new RealHostDescription(data.current_resolved_address_, parent_.localityLbEndpoint(), + parent_.lbEndpoint(), shared_from_this())}}; } } // namespace Upstream diff --git a/source/common/upstream/logical_dns_cluster.h b/source/common/upstream/logical_dns_cluster.h index fa0a94955472..2feec15579b7 100644 --- a/source/common/upstream/logical_dns_cluster.h +++ b/source/common/upstream/logical_dns_cluster.h @@ -30,6 +30,7 @@ class LogicalDnsCluster : public ClusterImplBase { public: LogicalDnsCluster(const envoy::api::v2::Cluster& cluster, Runtime::Loader& runtime, Stats::Store& stats, Ssl::ContextManager& ssl_context_manager, + const LocalInfo::LocalInfo& local_info, Network::DnsResolverSharedPtr dns_resolver, ThreadLocal::SlotAllocator& tls, ClusterManager& cm, Event::Dispatcher& dispatcher, bool added_via_api); @@ -42,9 +43,10 @@ class LogicalDnsCluster : public ClusterImplBase { struct LogicalHost : public HostImpl { LogicalHost(ClusterInfoConstSharedPtr cluster, const std::string& hostname, Network::Address::InstanceConstSharedPtr address, LogicalDnsCluster& parent) - : HostImpl(cluster, hostname, address, envoy::api::v2::core::Metadata::default_instance(), - 1, envoy::api::v2::core::Locality().default_instance(), - envoy::api::v2::endpoint::Endpoint::HealthCheckConfig().default_instance()), + : HostImpl(cluster, hostname, address, parent.lbEndpoint().metadata(), + parent.lbEndpoint().load_balancing_weight().value(), + parent.localityLbEndpoint().locality(), + parent.lbEndpoint().endpoint().health_check_config()), parent_(parent) {} // Upstream::Host @@ -57,10 +59,17 @@ class LogicalDnsCluster : public ClusterImplBase { struct RealHostDescription : public HostDescription { RealHostDescription(Network::Address::InstanceConstSharedPtr address, + const envoy::api::v2::endpoint::LocalityLbEndpoints& locality_lb_endpoint, + const envoy::api::v2::endpoint::LbEndpoint& lb_endpoint, HostConstSharedPtr logical_host) : address_(address), logical_host_(logical_host), - metadata_(std::make_shared( - envoy::api::v2::core::Metadata::default_instance())) {} + metadata_(std::make_shared(lb_endpoint.metadata())), + health_check_address_( + lb_endpoint.endpoint().health_check_config().port_value() == 0 + ? address + : Network::Utility::getAddressWithPort( + *address, lb_endpoint.endpoint().health_check_config().port_value())), + locality_lb_endpoint_(locality_lb_endpoint), lb_endpoint_(lb_endpoint) {} // Upstream:HostDescription bool canary() const override { return false; } @@ -81,21 +90,36 @@ class LogicalDnsCluster : public ClusterImplBase { const std::string& hostname() const override { return logical_host_->hostname(); } Network::Address::InstanceConstSharedPtr address() const override { return address_; } const envoy::api::v2::core::Locality& locality() const override { - return envoy::api::v2::core::Locality().default_instance(); + return locality_lb_endpoint_.locality(); } - // TODO(dio): To support different address port. Network::Address::InstanceConstSharedPtr healthCheckAddress() const override { - return address_; + return health_check_address_; } + uint32_t priority() const { return locality_lb_endpoint_.priority(); } Network::Address::InstanceConstSharedPtr address_; HostConstSharedPtr logical_host_; const std::shared_ptr metadata_; + Network::Address::InstanceConstSharedPtr health_check_address_; + const envoy::api::v2::endpoint::LocalityLbEndpoints& locality_lb_endpoint_; + const envoy::api::v2::endpoint::LbEndpoint& lb_endpoint_; }; struct PerThreadCurrentHostData : public ThreadLocal::ThreadLocalObject { Network::Address::InstanceConstSharedPtr current_resolved_address_; }; + const envoy::api::v2::endpoint::LocalityLbEndpoints& localityLbEndpoint() const { + // This is checked in the constructor, i.e. at config load time. + ASSERT(load_assignment_.endpoints().size() == 1); + return load_assignment_.endpoints()[0]; + } + + const envoy::api::v2::endpoint::LbEndpoint& lbEndpoint() const { + // This is checked in the constructor, i.e. at config load time. + ASSERT(localityLbEndpoint().lb_endpoints().size() == 1); + return localityLbEndpoint().lb_endpoints()[0]; + } + void startResolve(); // ClusterImplBase @@ -111,6 +135,8 @@ class LogicalDnsCluster : public ClusterImplBase { Network::Address::InstanceConstSharedPtr current_resolved_address_; HostSharedPtr logical_host_; Network::ActiveDnsQuery* active_dns_query_{}; + const LocalInfo::LocalInfo& local_info_; + const envoy::api::v2::ClusterLoadAssignment load_assignment_; }; } // namespace Upstream diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 014c9a6c9400..a2f7d8bc85ce 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -382,17 +382,17 @@ ClusterSharedPtr ClusterImplBase::create( switch (cluster.type()) { case envoy::api::v2::Cluster::STATIC: - new_cluster.reset( - new StaticClusterImpl(cluster, runtime, stats, ssl_context_manager, cm, added_via_api)); + new_cluster.reset(new StaticClusterImpl(cluster, runtime, stats, ssl_context_manager, + local_info, cm, added_via_api)); break; case envoy::api::v2::Cluster::STRICT_DNS: new_cluster.reset(new StrictDnsClusterImpl(cluster, runtime, stats, ssl_context_manager, - selected_dns_resolver, cm, dispatcher, + local_info, selected_dns_resolver, cm, dispatcher, added_via_api)); break; case envoy::api::v2::Cluster::LOGICAL_DNS: new_cluster.reset(new LogicalDnsCluster(cluster, runtime, stats, ssl_context_manager, - selected_dns_resolver, tls, cm, dispatcher, + local_info, selected_dns_resolver, tls, cm, dispatcher, added_via_api)); break; case envoy::api::v2::Cluster::ORIGINAL_DST: @@ -666,27 +666,37 @@ void PriorityStateManager::registerHostForPriority( const std::string& hostname, Network::Address::InstanceConstSharedPtr address, const envoy::api::v2::endpoint::LocalityLbEndpoints& locality_lb_endpoint, const envoy::api::v2::endpoint::LbEndpoint& lb_endpoint, - const Upstream::Host::HealthFlag health_checker_flag) { + const absl::optional health_checker_flag) { + const HostSharedPtr host(new HostImpl(parent_.info(), hostname, address, lb_endpoint.metadata(), + lb_endpoint.load_balancing_weight().value(), + locality_lb_endpoint.locality(), + lb_endpoint.endpoint().health_check_config())); + registerHostForPriority(host, locality_lb_endpoint, lb_endpoint, health_checker_flag); +} + +void PriorityStateManager::registerHostForPriority( + const HostSharedPtr& host, + const envoy::api::v2::endpoint::LocalityLbEndpoints& locality_lb_endpoint, + const envoy::api::v2::endpoint::LbEndpoint& lb_endpoint, + const absl::optional health_checker_flag) { const uint32_t priority = locality_lb_endpoint.priority(); // Should be called after initializePriorityFor. ASSERT(priority_state_[priority].first); - priority_state_[priority].first->emplace_back( - new HostImpl(parent_.info(), hostname, address, lb_endpoint.metadata(), - lb_endpoint.load_balancing_weight().value(), locality_lb_endpoint.locality(), - lb_endpoint.endpoint().health_check_config())); - - const auto& health_status = lb_endpoint.health_status(); - if (health_status == envoy::api::v2::core::HealthStatus::UNHEALTHY || - health_status == envoy::api::v2::core::HealthStatus::DRAINING || - health_status == envoy::api::v2::core::HealthStatus::TIMEOUT) { - priority_state_[priority].first->back()->healthFlagSet(health_checker_flag); + priority_state_[priority].first->emplace_back(host); + if (health_checker_flag.has_value()) { + const auto& health_status = lb_endpoint.health_status(); + if (health_status == envoy::api::v2::core::HealthStatus::UNHEALTHY || + health_status == envoy::api::v2::core::HealthStatus::DRAINING || + health_status == envoy::api::v2::core::HealthStatus::TIMEOUT) { + priority_state_[priority].first->back()->healthFlagSet(health_checker_flag.value()); + } } } void PriorityStateManager::updateClusterPrioritySet( const uint32_t priority, HostVectorSharedPtr&& current_hosts, - const absl::optional& hosts_added, - const absl::optional& hosts_removed) { + const absl::optional& hosts_added, const absl::optional& hosts_removed, + const absl::optional health_checker_flag) { // If local locality is not defined then skip populating per locality hosts. const auto& local_locality = local_info_node_.locality(); ENVOY_LOG(trace, "Local locality: {}", local_locality.DebugString()); @@ -710,9 +720,12 @@ void PriorityStateManager::updateClusterPrioritySet( std::map hosts_per_locality; for (const HostSharedPtr& host : *hosts) { - // TODO(dio): Take into consideration when a non-EDS cluster has active health checking, i.e. to - // mark all the hosts unhealthy (host->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC)) and - // then fire update callbacks to start the health checking process. + // Take into consideration when a non-EDS cluster has active health checking, i.e. to mark all + // the hosts unhealthy (host->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC)) and then fire + // update callbacks to start the health checking process. + if (health_checker_flag.has_value()) { + host->healthFlagSet(health_checker_flag.value()); + } hosts_per_locality[host->locality()].push_back(host); } @@ -754,36 +767,41 @@ void PriorityStateManager::updateClusterPrioritySet( StaticClusterImpl::StaticClusterImpl(const envoy::api::v2::Cluster& cluster, Runtime::Loader& runtime, Stats::Store& stats, - Ssl::ContextManager& ssl_context_manager, ClusterManager& cm, + Ssl::ContextManager& ssl_context_manager, + const LocalInfo::LocalInfo& local_info, ClusterManager& cm, bool added_via_api) : ClusterImplBase(cluster, cm.bindConfig(), runtime, stats, ssl_context_manager, cm.clusterManagerFactory().secretManager(), added_via_api), - initial_hosts_(new HostVector()) { - - for (const auto& host : cluster.hosts()) { - initial_hosts_->emplace_back(HostSharedPtr{new HostImpl( - info_, "", resolveProtoAddress(host), envoy::api::v2::core::Metadata::default_instance(), 1, - envoy::api::v2::core::Locality().default_instance(), - envoy::api::v2::endpoint::Endpoint::HealthCheckConfig().default_instance())}); + priority_state_manager_(new PriorityStateManager(*this, local_info)) { + // TODO(dio): Use by-reference when cluster.hosts() is removed. + const envoy::api::v2::ClusterLoadAssignment cluster_load_assignment( + cluster.has_load_assignment() ? cluster.load_assignment() + : Config::Utility::translateClusterHosts(cluster.hosts())); + + for (const auto& locality_lb_endpoint : cluster_load_assignment.endpoints()) { + priority_state_manager_->initializePriorityFor(locality_lb_endpoint); + for (const auto& lb_endpoint : locality_lb_endpoint.lb_endpoints()) { + priority_state_manager_->registerHostForPriority( + "", resolveProtoAddress(lb_endpoint.endpoint().address()), locality_lb_endpoint, + lb_endpoint, absl::nullopt); + } } } void StaticClusterImpl::startPreInit() { - // At this point see if we have a health checker. If so, mark all the hosts unhealthy and then - // fire update callbacks to start the health checking process. - if (health_checker_) { - for (const auto& host : *initial_hosts_) { - host->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC); - } + // At this point see if we have a health checker. If so, mark all the hosts unhealthy and + // then fire update callbacks to start the health checking process. + const auto& health_checker_flag = + health_checker_ != nullptr + ? absl::optional(Host::HealthFlag::FAILED_ACTIVE_HC) + : absl::nullopt; + + auto& priority_state = priority_state_manager_->priorityState(); + for (size_t i = 0; i < priority_state.size(); ++i) { + priority_state_manager_->updateClusterPrioritySet( + i, std::move(priority_state[i].first), absl::nullopt, absl::nullopt, health_checker_flag); } - - // Given the current config, only EDS clusters support multiple priorities. - ASSERT(priority_set_.hostSetsPerPriority().size() == 1); - auto& first_host_set = priority_set_.getOrCreateHostSet(0); - first_host_set.updateHosts(initial_hosts_, createHealthyHostList(*initial_hosts_), - HostsPerLocalityImpl::empty(), HostsPerLocalityImpl::empty(), {}, - *initial_hosts_, {}); - initial_hosts_ = nullptr; + priority_state_manager_.reset(); onPreInitComplete(); } @@ -934,12 +952,13 @@ bool BaseDynamicClusterImpl::updateDynamicHostList(const HostVector& new_hosts, StrictDnsClusterImpl::StrictDnsClusterImpl(const envoy::api::v2::Cluster& cluster, Runtime::Loader& runtime, Stats::Store& stats, Ssl::ContextManager& ssl_context_manager, + const LocalInfo::LocalInfo& local_info, Network::DnsResolverSharedPtr dns_resolver, ClusterManager& cm, Event::Dispatcher& dispatcher, bool added_via_api) : BaseDynamicClusterImpl(cluster, cm.bindConfig(), runtime, stats, ssl_context_manager, cm.clusterManagerFactory().secretManager(), added_via_api), - dns_resolver_(dns_resolver), + local_info_(local_info), dns_resolver_(dns_resolver), dns_refresh_rate_ms_( std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(cluster, dns_refresh_rate, 5000))) { switch (cluster.dns_lookup_family()) { @@ -956,11 +975,18 @@ StrictDnsClusterImpl::StrictDnsClusterImpl(const envoy::api::v2::Cluster& cluste NOT_REACHED_GCOVR_EXCL_LINE; } - for (const auto& host : cluster.hosts()) { - resolve_targets_.emplace_back( - new ResolveTarget(*this, dispatcher, - fmt::format("tcp://{}:{}", host.socket_address().address(), - host.socket_address().port_value()))); + const envoy::api::v2::ClusterLoadAssignment load_assignment( + cluster.has_load_assignment() ? cluster.load_assignment() + : Config::Utility::translateClusterHosts(cluster.hosts())); + const auto& locality_lb_endpoints = load_assignment.endpoints(); + for (const auto& locality_lb_endpoint : locality_lb_endpoints) { + for (const auto& lb_endpoint : locality_lb_endpoint.lb_endpoints()) { + const auto& host = lb_endpoint.endpoint().address(); + const std::string& url = fmt::format("tcp://{}:{}", host.socket_address().address(), + host.socket_address().port_value()); + resolve_targets_.emplace_back( + new ResolveTarget(*this, dispatcher, url, locality_lb_endpoint, lb_endpoint)); + } } } @@ -971,29 +997,34 @@ void StrictDnsClusterImpl::startPreInit() { } void StrictDnsClusterImpl::updateAllHosts(const HostVector& hosts_added, - const HostVector& hosts_removed) { + const HostVector& hosts_removed, + uint32_t current_priority) { + PriorityStateManager priority_state_manager(*this, local_info_); // At this point we know that we are different so make a new host list and notify. - HostVectorSharedPtr new_hosts(new HostVector()); for (const ResolveTargetPtr& target : resolve_targets_) { + priority_state_manager.initializePriorityFor(target->locality_lb_endpoint_); for (const HostSharedPtr& host : target->hosts_) { - new_hosts->emplace_back(host); + if (target->locality_lb_endpoint_.priority() == current_priority) { + priority_state_manager.registerHostForPriority(host, target->locality_lb_endpoint_, + target->lb_endpoint_, absl::nullopt); + } } } - // Given the current config, only EDS clusters support multiple priorities. - ASSERT(priority_set_.hostSetsPerPriority().size() == 1); - auto& first_host_set = priority_set_.getOrCreateHostSet(0); - first_host_set.updateHosts(new_hosts, createHealthyHostList(*new_hosts), - HostsPerLocalityImpl::empty(), HostsPerLocalityImpl::empty(), {}, - hosts_added, hosts_removed); + // TODO(dio): Add assertion in here. + priority_state_manager.updateClusterPrioritySet( + current_priority, std::move(priority_state_manager.priorityState()[current_priority].first), + hosts_added, hosts_removed, absl::nullopt); } -StrictDnsClusterImpl::ResolveTarget::ResolveTarget(StrictDnsClusterImpl& parent, - Event::Dispatcher& dispatcher, - const std::string& url) +StrictDnsClusterImpl::ResolveTarget::ResolveTarget( + StrictDnsClusterImpl& parent, Event::Dispatcher& dispatcher, const std::string& url, + const envoy::api::v2::endpoint::LocalityLbEndpoints& locality_lb_endpoint, + const envoy::api::v2::endpoint::LbEndpoint& lb_endpoint) : parent_(parent), dns_address_(Network::Utility::hostFromTcpUrl(url)), port_(Network::Utility::portFromTcpUrl(url)), - resolve_timer_(dispatcher.createTimer([this]() -> void { startResolve(); })) {} + resolve_timer_(dispatcher.createTimer([this]() -> void { startResolve(); })), + locality_lb_endpoint_(locality_lb_endpoint), lb_endpoint_(lb_endpoint) {} StrictDnsClusterImpl::ResolveTarget::~ResolveTarget() { if (active_query_) { @@ -1014,28 +1045,28 @@ void StrictDnsClusterImpl::ResolveTarget::startResolve() { HostVector new_hosts; for (const Network::Address::InstanceConstSharedPtr& address : address_list) { - // TODO(mattklein123): Currently the DNS interface does not consider port. We need to make - // a new address that has port in it. We need to both support IPv6 as well as potentially - // move port handling into the DNS interface itself, which would work better for SRV. + // TODO(mattklein123): Currently the DNS interface does not consider port. We need to + // make a new address that has port in it. We need to both support IPv6 as well as + // potentially move port handling into the DNS interface itself, which would work better + // for SRV. ASSERT(address != nullptr); new_hosts.emplace_back(new HostImpl( parent_.info_, dns_address_, Network::Utility::getAddressWithPort(*address, port_), - envoy::api::v2::core::Metadata::default_instance(), 1, - envoy::api::v2::core::Locality().default_instance(), - envoy::api::v2::endpoint::Endpoint::HealthCheckConfig().default_instance())); + lb_endpoint_.metadata(), lb_endpoint_.load_balancing_weight().value(), + locality_lb_endpoint_.locality(), lb_endpoint_.endpoint().health_check_config())); } HostVector hosts_added; HostVector hosts_removed; if (parent_.updateDynamicHostList(new_hosts, hosts_, hosts_added, hosts_removed)) { ENVOY_LOG(debug, "DNS hosts have changed for {}", dns_address_); - parent_.updateAllHosts(hosts_added, hosts_removed); + parent_.updateAllHosts(hosts_added, hosts_removed, locality_lb_endpoint_.priority()); } // If there is an initialize callback, fire it now. Note that if the cluster refers to - // multiple DNS names, this will return initialized after a single DNS resolution completes. - // This is not perfect but is easier to code and unclear if the extra complexity is needed - // so will start with this. + // multiple DNS names, this will return initialized after a single DNS resolution + // completes. This is not perfect but is easier to code and unclear if the extra + // complexity is needed so will start with this. parent_.onPreInitComplete(); resolve_timer_->enableTimer(parent_.dns_refresh_rate_ms_); }); diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index f5ba7e83794c..90b7935d55d4 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -541,18 +541,19 @@ class PriorityStateManager : protected Logger::Loggable { Network::Address::InstanceConstSharedPtr address, const envoy::api::v2::endpoint::LocalityLbEndpoints& locality_lb_endpoint, const envoy::api::v2::endpoint::LbEndpoint& lb_endpoint, - const Upstream::Host::HealthFlag health_checker_flag); + const absl::optional health_checker_flag); - // TODO(dio): Add an override of registerHostForPriority to register a host to the PriorityState - // based on a specified priority. This will be useful for non-EDS cluster hosts setup. - // - // void registerHostForPriority(const HostSharedPtr& host, const uint32_t priority); + void + registerHostForPriority(const HostSharedPtr& host, + const envoy::api::v2::endpoint::LocalityLbEndpoints& locality_lb_endpoint, + const envoy::api::v2::endpoint::LbEndpoint& lb_endpoint, + const absl::optional health_checker_flag); - // Updates the cluster priority set. This should be called after the PriorityStateManager is - // initialized. - void updateClusterPrioritySet(const uint32_t priority, HostVectorSharedPtr&& current_hosts, - const absl::optional& hosts_added, - const absl::optional& hosts_removed); + void + updateClusterPrioritySet(const uint32_t priority, HostVectorSharedPtr&& current_hosts, + const absl::optional& hosts_added, + const absl::optional& hosts_removed, + const absl::optional health_checker_flag); // Returns the size of the current cluster priority state. size_t size() const { return priority_state_.size(); } @@ -566,6 +567,8 @@ class PriorityStateManager : protected Logger::Loggable { const envoy::api::v2::core::Node& local_info_node_; }; +typedef std::unique_ptr PriorityStateManagerPtr; + /** * Implementation of Upstream::Cluster for static clusters (clusters that have a fixed number of * hosts with resolved IP addresses). @@ -574,7 +577,7 @@ class StaticClusterImpl : public ClusterImplBase { public: StaticClusterImpl(const envoy::api::v2::Cluster& cluster, Runtime::Loader& runtime, Stats::Store& stats, Ssl::ContextManager& ssl_context_manager, - ClusterManager& cm, bool added_via_api); + const LocalInfo::LocalInfo& local_info, ClusterManager& cm, bool added_via_api); // Upstream::Cluster InitializePhase initializePhase() const override { return InitializePhase::Primary; } @@ -583,7 +586,7 @@ class StaticClusterImpl : public ClusterImplBase { // ClusterImplBase void startPreInit() override; - HostVectorSharedPtr initial_hosts_; + PriorityStateManagerPtr priority_state_manager_; }; /** @@ -605,6 +608,7 @@ class StrictDnsClusterImpl : public BaseDynamicClusterImpl { public: StrictDnsClusterImpl(const envoy::api::v2::Cluster& cluster, Runtime::Loader& runtime, Stats::Store& stats, Ssl::ContextManager& ssl_context_manager, + const LocalInfo::LocalInfo& local_info, Network::DnsResolverSharedPtr dns_resolver, ClusterManager& cm, Event::Dispatcher& dispatcher, bool added_via_api); @@ -614,7 +618,9 @@ class StrictDnsClusterImpl : public BaseDynamicClusterImpl { private: struct ResolveTarget { ResolveTarget(StrictDnsClusterImpl& parent, Event::Dispatcher& dispatcher, - const std::string& url); + const std::string& url, + const envoy::api::v2::endpoint::LocalityLbEndpoints& locality_lb_endpoint, + const envoy::api::v2::endpoint::LbEndpoint& lb_endpoint); ~ResolveTarget(); void startResolve(); @@ -624,15 +630,19 @@ class StrictDnsClusterImpl : public BaseDynamicClusterImpl { uint32_t port_; Event::TimerPtr resolve_timer_; HostVector hosts_; + const envoy::api::v2::endpoint::LocalityLbEndpoints locality_lb_endpoint_; + const envoy::api::v2::endpoint::LbEndpoint lb_endpoint_; }; typedef std::unique_ptr ResolveTargetPtr; - void updateAllHosts(const HostVector& hosts_added, const HostVector& hosts_removed); + void updateAllHosts(const HostVector& hosts_added, const HostVector& hosts_removed, + uint32_t priority); // ClusterImplBase void startPreInit() override; + const LocalInfo::LocalInfo& local_info_; Network::DnsResolverSharedPtr dns_resolver_; std::list resolve_targets_; const std::chrono::milliseconds dns_refresh_rate_ms_; diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 067ad5734c4b..441ce2437613 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -172,6 +172,7 @@ envoy_cc_test( "//source/common/upstream:upstream_lib", "//source/extensions/transport_sockets/raw_buffer:config", "//test/mocks:common_lib", + "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/ssl:ssl_mocks", @@ -326,6 +327,7 @@ envoy_cc_test( "//source/common/upstream:upstream_lib", "//source/extensions/transport_sockets/raw_buffer:config", "//test/mocks:common_lib", + "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/ssl:ssl_mocks", diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 5afbfac84911..8082c0c090c0 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -122,7 +122,7 @@ class TestClusterManagerFactory : public ClusterManagerFactory { NiceMock random_; Ssl::ContextManagerImpl ssl_context_manager_{runtime_}; NiceMock dispatcher_; - LocalInfo::MockLocalInfo local_info_; + NiceMock local_info_; Secret::MockSecretManager secret_manager_; }; diff --git a/test/common/upstream/logical_dns_cluster_test.cc b/test/common/upstream/logical_dns_cluster_test.cc index df74b6f1aba4..80a5c7b705fc 100644 --- a/test/common/upstream/logical_dns_cluster_test.cc +++ b/test/common/upstream/logical_dns_cluster_test.cc @@ -9,6 +9,7 @@ #include "test/common/upstream/utility.h" #include "test/mocks/common.h" +#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/ssl/mocks.h" @@ -26,14 +27,16 @@ using testing::_; namespace Envoy { namespace Upstream { +enum class ConfigType { V2_YAML, V1_JSON }; + class LogicalDnsClusterTest : public testing::Test { public: - void setup(const std::string& json) { + void setupFromV1Json(const std::string& json) { resolve_timer_ = new Event::MockTimer(&dispatcher_); NiceMock cm; cluster_.reset(new LogicalDnsCluster(parseClusterFromJson(json), runtime_, stats_store_, - ssl_context_manager_, dns_resolver_, tls_, cm, dispatcher_, - false)); + ssl_context_manager_, local_info_, dns_resolver_, tls_, cm, + dispatcher_, false)); cluster_->prioritySet().addMemberUpdateCb( [&](uint32_t, const HostVector&, const HostVector&) -> void { membership_updated_.ready(); @@ -41,8 +44,22 @@ class LogicalDnsClusterTest : public testing::Test { cluster_->initialize([&]() -> void { initialized_.ready(); }); } - void expectResolve(Network::DnsLookupFamily dns_lookup_family) { - EXPECT_CALL(*dns_resolver_, resolve("foo.bar.com", dns_lookup_family, _)) + void setupFromV2Yaml(const std::string& yaml) { + resolve_timer_ = new Event::MockTimer(&dispatcher_); + NiceMock cm; + cluster_.reset(new LogicalDnsCluster(parseClusterFromV2Yaml(yaml), runtime_, stats_store_, + ssl_context_manager_, local_info_, dns_resolver_, tls_, cm, + dispatcher_, false)); + cluster_->prioritySet().addMemberUpdateCb( + [&](uint32_t, const HostVector&, const HostVector&) -> void { + membership_updated_.ready(); + }); + cluster_->initialize([&]() -> void { initialized_.ready(); }); + } + + void expectResolve(Network::DnsLookupFamily dns_lookup_family, + const std::string& expected_address) { + EXPECT_CALL(*dns_resolver_, resolve(expected_address, dns_lookup_family, _)) .WillOnce(Invoke([&](const std::string&, Network::DnsLookupFamily, Network::DnsResolver::ResolveCb cb) -> Network::ActiveDnsQuery* { dns_callback_ = cb; @@ -50,6 +67,102 @@ class LogicalDnsClusterTest : public testing::Test { })); } + void testBasicSetup(const std::string& config, const std::string& expected_address, + ConfigType config_type = ConfigType::V2_YAML) { + expectResolve(Network::DnsLookupFamily::V4Only, expected_address); + if (config_type == ConfigType::V1_JSON) { + setupFromV1Json(config); + } else { + setupFromV2Yaml(config); + } + + EXPECT_CALL(membership_updated_, ready()); + EXPECT_CALL(initialized_, ready()); + EXPECT_CALL(*resolve_timer_, enableTimer(std::chrono::milliseconds(4000))); + dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); + + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); + EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, + cluster_->prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); + EXPECT_EQ( + 1UL, + cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); + EXPECT_EQ(cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0], + cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts()[0]); + HostSharedPtr logical_host = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]; + + EXPECT_CALL(dispatcher_, + createClientConnection_( + PointeesEq(Network::Utility::resolveUrl("tcp://127.0.0.1:443")), _, _, _)) + .WillOnce(Return(new NiceMock())); + logical_host->createConnection(dispatcher_, nullptr); + logical_host->outlierDetector().putHttpResponseCode(200); + + expectResolve(Network::DnsLookupFamily::V4Only, expected_address); + resolve_timer_->callback_(); + + // Should not cause any changes. + EXPECT_CALL(*resolve_timer_, enableTimer(_)); + dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2", "127.0.0.3"})); + + EXPECT_EQ(logical_host, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]); + EXPECT_CALL(dispatcher_, + createClientConnection_( + PointeesEq(Network::Utility::resolveUrl("tcp://127.0.0.1:443")), _, _, _)) + .WillOnce(Return(new NiceMock())); + Host::CreateConnectionData data = logical_host->createConnection(dispatcher_, nullptr); + EXPECT_FALSE(data.host_description_->canary()); + EXPECT_EQ(&cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->cluster(), + &data.host_description_->cluster()); + EXPECT_EQ(&cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->stats(), + &data.host_description_->stats()); + EXPECT_EQ("127.0.0.1:443", data.host_description_->address()->asString()); + EXPECT_EQ("", data.host_description_->locality().region()); + EXPECT_EQ("", data.host_description_->locality().zone()); + EXPECT_EQ("", data.host_description_->locality().sub_zone()); + EXPECT_EQ("foo.bar.com", data.host_description_->hostname()); + EXPECT_TRUE(TestUtility::protoEqual(envoy::api::v2::core::Metadata::default_instance(), + *data.host_description_->metadata())); + data.host_description_->outlierDetector().putHttpResponseCode(200); + data.host_description_->healthChecker().setUnhealthy(); + + expectResolve(Network::DnsLookupFamily::V4Only, expected_address); + resolve_timer_->callback_(); + + // Should cause a change. + EXPECT_CALL(*resolve_timer_, enableTimer(_)); + dns_callback_(TestUtility::makeDnsResponse({"127.0.0.3", "127.0.0.1", "127.0.0.2"})); + + EXPECT_EQ(logical_host, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]); + EXPECT_CALL(dispatcher_, + createClientConnection_( + PointeesEq(Network::Utility::resolveUrl("tcp://127.0.0.3:443")), _, _, _)) + .WillOnce(Return(new NiceMock())); + logical_host->createConnection(dispatcher_, nullptr); + + expectResolve(Network::DnsLookupFamily::V4Only, expected_address); + resolve_timer_->callback_(); + + // Empty should not cause any change. + EXPECT_CALL(*resolve_timer_, enableTimer(_)); + dns_callback_({}); + + EXPECT_EQ(logical_host, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]); + EXPECT_CALL(dispatcher_, + createClientConnection_( + PointeesEq(Network::Utility::resolveUrl("tcp://127.0.0.3:443")), _, _, _)) + .WillOnce(Return(new NiceMock())); + logical_host->createConnection(dispatcher_, nullptr); + + // Make sure we cancel. + EXPECT_CALL(active_dns_query_, cancel()); + expectResolve(Network::DnsLookupFamily::V4Only, expected_address); + resolve_timer_->callback_(); + + tls_.shutdownThread(); + } + Stats::IsolatedStoreImpl stats_store_; Ssl::MockContextManager ssl_context_manager_; std::shared_ptr> dns_resolver_{ @@ -63,6 +176,7 @@ class LogicalDnsClusterTest : public testing::Test { ReadyWatcher initialized_; NiceMock runtime_; NiceMock dispatcher_; + NiceMock local_info_; }; typedef std::tuple> @@ -127,7 +241,7 @@ TEST_P(LogicalDnsParamTest, ImmediateResolve) { cb(TestUtility::makeDnsResponse(std::get<2>(GetParam()))); return nullptr; })); - setup(json); + setupFromV1Json(json); EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); EXPECT_EQ("foo.bar.com", @@ -137,7 +251,7 @@ TEST_P(LogicalDnsParamTest, ImmediateResolve) { } TEST_F(LogicalDnsClusterTest, BadConfig) { - const std::string json = R"EOF( + const std::string multiple_hosts_json = R"EOF( { "name": "name", "connect_timeout_ms": 250, @@ -147,7 +261,72 @@ TEST_F(LogicalDnsClusterTest, BadConfig) { } )EOF"; - EXPECT_THROW(setup(json), EnvoyException); + EXPECT_THROW_WITH_MESSAGE(setupFromV1Json(multiple_hosts_json), EnvoyException, + "LOGICAL_DNS clusters must have a single host"); + + const std::string multiple_lb_endpoints_yaml = R"EOF( + name: name + type: LOGICAL_DNS + dns_refresh_rate: 4s + connect_timeout: 0.25s + lb_policy: ROUND_ROBIN + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: name + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.bar.com + port_value: 443 + health_check_config: + port_value: 8000 + - endpoint: + address: + socket_address: + address: hello.world.com + port_value: 443 + health_check_config: + port_value: 8000 + )EOF"; + + EXPECT_THROW_WITH_MESSAGE( + setupFromV2Yaml(multiple_lb_endpoints_yaml), EnvoyException, + "LOGICAL_DNS clusters must have a single locality_lb_endpoint and a single lb_endpoint"); + + const std::string multiple_endpoints_yaml = R"EOF( + name: name + type: LOGICAL_DNS + dns_refresh_rate: 4s + connect_timeout: 0.25s + lb_policy: ROUND_ROBIN + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: name + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.bar.com + port_value: 443 + health_check_config: + port_value: 8000 + + - lb_endpoints: + - endpoint: + address: + socket_address: + address: hello.world.com + port_value: 443 + health_check_config: + port_value: 8000 + )EOF"; + + EXPECT_THROW_WITH_MESSAGE( + setupFromV2Yaml(multiple_endpoints_yaml), EnvoyException, + "LOGICAL_DNS clusters must have a single locality_lb_endpoint and a single lb_endpoint"); } TEST_F(LogicalDnsClusterTest, Basic) { @@ -162,93 +341,46 @@ TEST_F(LogicalDnsClusterTest, Basic) { } )EOF"; - expectResolve(Network::DnsLookupFamily::V4Only); - setup(json); - - EXPECT_CALL(membership_updated_, ready()); - EXPECT_CALL(initialized_, ready()); - EXPECT_CALL(*resolve_timer_, enableTimer(std::chrono::milliseconds(4000))); - dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); + const std::string basic_yaml_hosts = R"EOF( + name: name + type: LOGICAL_DNS + dns_refresh_rate: 4s + connect_timeout: 0.25s + lb_policy: ROUND_ROBIN + # Since the following expectResolve() requires Network::DnsLookupFamily::V4Only we need to set + # dns_lookup_family to V4_ONLY explicitly for v2 .yaml config. + dns_lookup_family: V4_ONLY + hosts: + - socket_address: + address: foo.bar.com + port_value: 443 + )EOF"; - EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); - EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); - EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); - EXPECT_EQ( - 0UL, - cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); - EXPECT_EQ(cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0], - cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts()[0]); - HostSharedPtr logical_host = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]; - - EXPECT_CALL(dispatcher_, - createClientConnection_( - PointeesEq(Network::Utility::resolveUrl("tcp://127.0.0.1:443")), _, _, _)) - .WillOnce(Return(new NiceMock())); - logical_host->createConnection(dispatcher_, nullptr); - logical_host->outlierDetector().putHttpResponseCode(200); - - expectResolve(Network::DnsLookupFamily::V4Only); - resolve_timer_->callback_(); - - // Should not cause any changes. - EXPECT_CALL(*resolve_timer_, enableTimer(_)); - dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2", "127.0.0.3"})); - - EXPECT_EQ(logical_host, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]); - EXPECT_CALL(dispatcher_, - createClientConnection_( - PointeesEq(Network::Utility::resolveUrl("tcp://127.0.0.1:443")), _, _, _)) - .WillOnce(Return(new NiceMock())); - Host::CreateConnectionData data = logical_host->createConnection(dispatcher_, nullptr); - EXPECT_FALSE(data.host_description_->canary()); - EXPECT_EQ(&cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->cluster(), - &data.host_description_->cluster()); - EXPECT_EQ(&cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->stats(), - &data.host_description_->stats()); - EXPECT_EQ("127.0.0.1:443", data.host_description_->address()->asString()); - EXPECT_EQ("", data.host_description_->locality().region()); - EXPECT_EQ("", data.host_description_->locality().zone()); - EXPECT_EQ("", data.host_description_->locality().sub_zone()); - EXPECT_EQ("foo.bar.com", data.host_description_->hostname()); - EXPECT_TRUE(TestUtility::protoEqual(envoy::api::v2::core::Metadata::default_instance(), - *data.host_description_->metadata())); - data.host_description_->outlierDetector().putHttpResponseCode(200); - data.host_description_->healthChecker().setUnhealthy(); - - expectResolve(Network::DnsLookupFamily::V4Only); - resolve_timer_->callback_(); - - // Should cause a change. - EXPECT_CALL(*resolve_timer_, enableTimer(_)); - dns_callback_(TestUtility::makeDnsResponse({"127.0.0.3", "127.0.0.1", "127.0.0.2"})); - - EXPECT_EQ(logical_host, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]); - EXPECT_CALL(dispatcher_, - createClientConnection_( - PointeesEq(Network::Utility::resolveUrl("tcp://127.0.0.3:443")), _, _, _)) - .WillOnce(Return(new NiceMock())); - logical_host->createConnection(dispatcher_, nullptr); - - expectResolve(Network::DnsLookupFamily::V4Only); - resolve_timer_->callback_(); - - // Empty should not cause any change. - EXPECT_CALL(*resolve_timer_, enableTimer(_)); - dns_callback_({}); - - EXPECT_EQ(logical_host, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]); - EXPECT_CALL(dispatcher_, - createClientConnection_( - PointeesEq(Network::Utility::resolveUrl("tcp://127.0.0.3:443")), _, _, _)) - .WillOnce(Return(new NiceMock())); - logical_host->createConnection(dispatcher_, nullptr); - - // Make sure we cancel. - EXPECT_CALL(active_dns_query_, cancel()); - expectResolve(Network::DnsLookupFamily::V4Only); - resolve_timer_->callback_(); + const std::string basic_yaml_load_assignment = R"EOF( + name: name + type: LOGICAL_DNS + dns_refresh_rate: 4s + connect_timeout: 0.25s + lb_policy: ROUND_ROBIN + # Since the following expectResolve() requires Network::DnsLookupFamily::V4Only we need to set + # dns_lookup_family to V4_ONLY explicitly for v2 .yaml config. + dns_lookup_family: V4_ONLY + load_assignment: + cluster_name: name + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.bar.com + port_value: 443 + health_check_config: + port_value: 8000 + )EOF"; - tls_.shutdownThread(); + testBasicSetup(json, "foo.bar.com", ConfigType::V1_JSON); + testBasicSetup(basic_yaml_hosts, "foo.bar.com"); + testBasicSetup(basic_yaml_load_assignment, "foo.bar.com"); } } // namespace Upstream diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index 17888a900630..f4b43c393d2d 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -17,6 +17,7 @@ #include "test/common/upstream/utility.h" #include "test/mocks/common.h" +#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/ssl/mocks.h" @@ -119,6 +120,7 @@ TEST_P(StrictDnsParamTest, ImmediateResolve) { auto dns_resolver = std::make_shared>(); NiceMock dispatcher; NiceMock runtime; + NiceMock local_info; ReadyWatcher initialized; const std::string json = R"EOF( @@ -141,7 +143,7 @@ TEST_P(StrictDnsParamTest, ImmediateResolve) { })); NiceMock cm; StrictDnsClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, - dns_resolver, cm, dispatcher, false); + local_info, dns_resolver, cm, dispatcher, false); cluster.initialize([&]() -> void { initialized.ready(); }); EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->hosts().size()); EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); @@ -155,6 +157,7 @@ TEST(StrictDnsClusterImplTest, ZeroHostsHealthChecker) { NiceMock dispatcher; NiceMock runtime; NiceMock cm; + NiceMock local_info; ReadyWatcher initialized; const std::string yaml = R"EOF( @@ -167,7 +170,7 @@ TEST(StrictDnsClusterImplTest, ZeroHostsHealthChecker) { ResolverData resolver(*dns_resolver, dispatcher); StrictDnsClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, - dns_resolver, cm, dispatcher, false); + local_info, dns_resolver, cm, dispatcher, false); std::shared_ptr health_checker(new MockHealthChecker()); EXPECT_CALL(*health_checker, start()); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); @@ -188,6 +191,7 @@ TEST(StrictDnsClusterImplTest, Basic) { auto dns_resolver = std::make_shared>(); NiceMock dispatcher; NiceMock runtime; + NiceMock local_info; // gmock matches in LIFO order which is why these are swapped. ResolverData resolver2(*dns_resolver, dispatcher); @@ -225,7 +229,7 @@ TEST(StrictDnsClusterImplTest, Basic) { NiceMock cm; StrictDnsClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, - dns_resolver, cm, dispatcher, false); + local_info, dns_resolver, cm, dispatcher, false); EXPECT_CALL(runtime.snapshot_, getInteger("circuit_breakers.name.default.max_connections", 43)); EXPECT_EQ(43U, cluster.info()->resourceManager(ResourcePriority::Default).connections().max()); EXPECT_CALL(runtime.snapshot_, @@ -302,8 +306,8 @@ TEST(StrictDnsClusterImplTest, Basic) { ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); - EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); - EXPECT_EQ(0UL, + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); for (const HostSharedPtr& host : cluster.prioritySet().hostSetsPerPriority()[0]->hosts()) { @@ -329,6 +333,7 @@ TEST(StrictDnsClusterImplTest, HostRemovalActiveHealthSkipped) { NiceMock dispatcher; NiceMock runtime; NiceMock cm; + NiceMock local_info; const std::string yaml = R"EOF( name: name @@ -341,7 +346,7 @@ TEST(StrictDnsClusterImplTest, HostRemovalActiveHealthSkipped) { ResolverData resolver(*dns_resolver, dispatcher); StrictDnsClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, - dns_resolver, cm, dispatcher, false); + local_info, dns_resolver, cm, dispatcher, false); std::shared_ptr health_checker(new MockHealthChecker()); EXPECT_CALL(*health_checker, start()); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); @@ -372,6 +377,298 @@ TEST(StrictDnsClusterImplTest, HostRemovalActiveHealthSkipped) { EXPECT_EQ(1UL, hosts.size()); } +TEST(StrictDnsClusterImplTest, LoadAssignmentBasic) { + Stats::IsolatedStoreImpl stats; + Ssl::MockContextManager ssl_context_manager; + auto dns_resolver = std::make_shared>(); + NiceMock dispatcher; + NiceMock runtime; + NiceMock local_info; + + // gmock matches in LIFO order which is why these are swapped. + ResolverData resolver2(*dns_resolver, dispatcher); + ResolverData resolver1(*dns_resolver, dispatcher); + + const std::string yaml = R"EOF( + name: name + type: STRICT_DNS + + dns_lookup_family: V4_ONLY + connect_timeout: 0.25s + dns_refresh_rate: 4s + + lb_policy: ROUND_ROBIN + + circuit_breakers: + thresholds: + - priority: DEFAULT + max_connections: 43 + max_pending_requests: 57 + max_requests: 50 + max_retries: 10 + - priority: HIGH + max_connections: 1 + max_pending_requests: 2 + max_requests: 3 + max_retries: 4 + + max_requests_per_connection: 3 + + http2_protocol_options: + hpack_table_size: 0 + + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: localhost1 + port_value: 11001 + health_check_config: + port_value: 8000 + - endpoint: + address: + socket_address: + address: localhost2 + port_value: 11002 + health_check_config: + port_value: 8000 + )EOF"; + + NiceMock cm; + StrictDnsClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, + local_info, dns_resolver, cm, dispatcher, false); + EXPECT_CALL(runtime.snapshot_, getInteger("circuit_breakers.name.default.max_connections", 43)); + EXPECT_EQ(43U, cluster.info()->resourceManager(ResourcePriority::Default).connections().max()); + EXPECT_CALL(runtime.snapshot_, + getInteger("circuit_breakers.name.default.max_pending_requests", 57)); + EXPECT_EQ(57U, + cluster.info()->resourceManager(ResourcePriority::Default).pendingRequests().max()); + EXPECT_CALL(runtime.snapshot_, getInteger("circuit_breakers.name.default.max_requests", 50)); + EXPECT_EQ(50U, cluster.info()->resourceManager(ResourcePriority::Default).requests().max()); + EXPECT_CALL(runtime.snapshot_, getInteger("circuit_breakers.name.default.max_retries", 10)); + EXPECT_EQ(10U, cluster.info()->resourceManager(ResourcePriority::Default).retries().max()); + EXPECT_CALL(runtime.snapshot_, getInteger("circuit_breakers.name.high.max_connections", 1)); + EXPECT_EQ(1U, cluster.info()->resourceManager(ResourcePriority::High).connections().max()); + EXPECT_CALL(runtime.snapshot_, getInteger("circuit_breakers.name.high.max_pending_requests", 2)); + EXPECT_EQ(2U, cluster.info()->resourceManager(ResourcePriority::High).pendingRequests().max()); + EXPECT_CALL(runtime.snapshot_, getInteger("circuit_breakers.name.high.max_requests", 3)); + EXPECT_EQ(3U, cluster.info()->resourceManager(ResourcePriority::High).requests().max()); + EXPECT_CALL(runtime.snapshot_, getInteger("circuit_breakers.name.high.max_retries", 4)); + EXPECT_EQ(4U, cluster.info()->resourceManager(ResourcePriority::High).retries().max()); + EXPECT_EQ(3U, cluster.info()->maxRequestsPerConnection()); + EXPECT_EQ(0U, cluster.info()->http2Settings().hpack_table_size_); + + cluster.info()->stats().upstream_rq_total_.inc(); + EXPECT_EQ(1UL, stats.counter("cluster.name.upstream_rq_total").value()); + + EXPECT_CALL(runtime.snapshot_, featureEnabled("upstream.maintenance_mode.name", 0)); + EXPECT_FALSE(cluster.info()->maintenanceMode()); + + ReadyWatcher membership_updated; + cluster.prioritySet().addMemberUpdateCb( + [&](uint32_t, const HostVector&, const HostVector&) -> void { membership_updated.ready(); }); + + cluster.initialize([] {}); + + resolver1.expectResolve(*dns_resolver); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(membership_updated, ready()); + resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); + EXPECT_THAT( + std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); + EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->hostname()); + + resolver1.expectResolve(*dns_resolver); + resolver1.timer_->callback_(); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); + EXPECT_THAT( + std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + + resolver1.expectResolve(*dns_resolver); + resolver1.timer_->callback_(); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); + EXPECT_THAT( + std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + + resolver1.timer_->callback_(); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(membership_updated, ready()); + resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.3"})); + EXPECT_THAT( + std::list({"127.0.0.3:11001"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + + // Make sure we de-dup the same address. + EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(membership_updated, ready()); + resolver2.dns_callback_(TestUtility::makeDnsResponse({"10.0.0.1", "10.0.0.1"})); + EXPECT_THAT( + std::list({"127.0.0.3:11001", "10.0.0.1:11002"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + + EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); + EXPECT_EQ(1UL, + cluster.prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); + + for (const HostSharedPtr& host : cluster.prioritySet().hostSetsPerPriority()[0]->hosts()) { + EXPECT_EQ(cluster.info().get(), &host->cluster()); + } + + // Make sure we cancel. + resolver1.expectResolve(*dns_resolver); + resolver1.timer_->callback_(); + resolver2.expectResolve(*dns_resolver); + resolver2.timer_->callback_(); + + EXPECT_CALL(resolver1.active_dns_query_, cancel()); + EXPECT_CALL(resolver2.active_dns_query_, cancel()); +} + +TEST(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { + Stats::IsolatedStoreImpl stats; + Ssl::MockContextManager ssl_context_manager; + auto dns_resolver = std::make_shared>(); + NiceMock dispatcher; + NiceMock runtime; + NiceMock local_info; + + // gmock matches in LIFO order which is why these are swapped. + ResolverData resolver3(*dns_resolver, dispatcher); + ResolverData resolver2(*dns_resolver, dispatcher); + ResolverData resolver1(*dns_resolver, dispatcher); + + const std::string yaml = R"EOF( + name: name + type: STRICT_DNS + + dns_lookup_family: V4_ONLY + connect_timeout: 0.25s + dns_refresh_rate: 4s + + lb_policy: ROUND_ROBIN + + load_assignment: + endpoints: + - priority: 0 + lb_endpoints: + - endpoint: + address: + socket_address: + address: localhost1 + port_value: 11001 + health_check_config: + port_value: 8000 + - endpoint: + address: + socket_address: + address: localhost2 + port_value: 11002 + health_check_config: + port_value: 8000 + + - priority: 1 + lb_endpoints: + - endpoint: + address: + socket_address: + address: localhost3 + port_value: 11003 + health_check_config: + port_value: 8000 + )EOF"; + + NiceMock cm; + StrictDnsClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, + local_info, dns_resolver, cm, dispatcher, false); + + ReadyWatcher membership_updated; + cluster.prioritySet().addMemberUpdateCb( + [&](uint32_t, const HostVector&, const HostVector&) -> void { membership_updated.ready(); }); + + cluster.initialize([] {}); + + resolver1.expectResolve(*dns_resolver); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(membership_updated, ready()); + resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); + EXPECT_THAT( + std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); + EXPECT_EQ("localhost1", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[1]->hostname()); + + resolver1.expectResolve(*dns_resolver); + resolver1.timer_->callback_(); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); + EXPECT_THAT( + std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + + resolver1.expectResolve(*dns_resolver); + resolver1.timer_->callback_(); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); + EXPECT_THAT( + std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + + resolver1.timer_->callback_(); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(membership_updated, ready()); + resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.3"})); + EXPECT_THAT( + std::list({"127.0.0.3:11001"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + + // Make sure we de-dup the same address. + EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(membership_updated, ready()); + resolver2.dns_callback_(TestUtility::makeDnsResponse({"10.0.0.1", "10.0.0.1"})); + EXPECT_THAT( + std::list({"127.0.0.3:11001", "10.0.0.1:11002"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); + + EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); + EXPECT_EQ(1UL, + cluster.prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); + + for (const HostSharedPtr& host : cluster.prioritySet().hostSetsPerPriority()[0]->hosts()) { + EXPECT_EQ(cluster.info().get(), &host->cluster()); + } + + EXPECT_CALL(*resolver3.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(membership_updated, ready()); + resolver3.dns_callback_(TestUtility::makeDnsResponse({"192.168.1.1", "192.168.1.2"})); + + // Make sure we have multiple priorities. + EXPECT_THAT( + std::list({"192.168.1.1:11003", "192.168.1.2:11003"}), + ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[1]->hosts()))); + + // Make sure we cancel. + resolver1.expectResolve(*dns_resolver); + resolver1.timer_->callback_(); + resolver2.expectResolve(*dns_resolver); + resolver2.timer_->callback_(); + resolver3.expectResolve(*dns_resolver); + resolver3.timer_->callback_(); + + EXPECT_CALL(resolver1.active_dns_query_, cancel()); + EXPECT_CALL(resolver2.active_dns_query_, cancel()); + EXPECT_CALL(resolver3.active_dns_query_, cancel()); +} + TEST(HostImplTest, HostCluster) { MockCluster cluster; HostSharedPtr host = makeTestHost(cluster.info_, "tcp://10.0.0.1:1234", 1); @@ -419,10 +716,38 @@ TEST(HostImplTest, HostnameCanaryAndLocality) { EXPECT_EQ("world", host.locality().sub_zone()); } +TEST(StaticClusterImplTest, InitialHosts) { + Stats::IsolatedStoreImpl stats; + Ssl::MockContextManager ssl_context_manager; + NiceMock runtime; + NiceMock local_info; + const std::string yaml = R"EOF( + name: staticcluster + connect_timeout: 0.25s + type: STATIC + lb_policy: ROUND_ROBIN + hosts: + - socket_address: + address: 10.0.0.1 + port_value: 443 + )EOF"; + + NiceMock cm; + StaticClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, + local_info, cm, false); + cluster.initialize([] {}); + + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ("", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); + EXPECT_FALSE(cluster.info()->addedViaApi()); +} + TEST(StaticClusterImplTest, EmptyHostname) { Stats::IsolatedStoreImpl stats; Ssl::MockContextManager ssl_context_manager; NiceMock runtime; + NiceMock local_info; + const std::string json = R"EOF( { "name": "staticcluster", @@ -434,8 +759,41 @@ TEST(StaticClusterImplTest, EmptyHostname) { )EOF"; NiceMock cm; - StaticClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, cm, - false); + StaticClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, + local_info, cm, false); + cluster.initialize([] {}); + + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ("", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); + EXPECT_FALSE(cluster.info()->addedViaApi()); +} + +TEST(StaticClusterImplTest, LoadAssignmentEmptyHostname) { + Stats::IsolatedStoreImpl stats; + Ssl::MockContextManager ssl_context_manager; + NiceMock runtime; + NiceMock local_info; + + const std::string yaml = R"EOF( + name: staticcluster + connect_timeout: 0.25s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 10.0.0.1 + port_value: 443 + health_check_config: + port_value: 8000 + )EOF"; + + NiceMock cm; + StaticClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, + local_info, cm, false); cluster.initialize([] {}); EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); @@ -443,10 +801,114 @@ TEST(StaticClusterImplTest, EmptyHostname) { EXPECT_FALSE(cluster.info()->addedViaApi()); } +TEST(StaticClusterImplTest, LoadAssignmentMultiplePriorities) { + Stats::IsolatedStoreImpl stats; + Ssl::MockContextManager ssl_context_manager; + NiceMock runtime; + NiceMock local_info; + + const std::string yaml = R"EOF( + name: staticcluster + connect_timeout: 0.25s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + endpoints: + - priority: 0 + lb_endpoints: + - endpoint: + address: + socket_address: + address: 10.0.0.1 + port_value: 443 + health_check_config: + port_value: 8000 + - endpoint: + address: + socket_address: + address: 10.0.0.2 + port_value: 443 + health_check_config: + port_value: 8000 + + - priority: 1 + lb_endpoints: + - endpoint: + address: + socket_address: + address: 10.0.0.3 + port_value: 443 + health_check_config: + port_value: 8000 + )EOF"; + + NiceMock cm; + StaticClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, + local_info, cm, false); + cluster.initialize([] {}); + + EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[1]->healthyHosts().size()); + EXPECT_EQ("", cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->hostname()); + EXPECT_FALSE(cluster.info()->addedViaApi()); +} + +TEST(StaticClusterImplTest, LoadAssignmentLocality) { + Stats::IsolatedStoreImpl stats; + Ssl::MockContextManager ssl_context_manager; + NiceMock runtime; + NiceMock local_info; + + const std::string yaml = R"EOF( + name: staticcluster + connect_timeout: 0.25s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + endpoints: + - locality: + region: oceania + zone: hello + sub_zone: world + lb_endpoints: + - endpoint: + address: + socket_address: + address: 10.0.0.1 + port_value: 443 + health_check_config: + port_value: 8000 + - endpoint: + address: + socket_address: + address: 10.0.0.2 + port_value: 443 + health_check_config: + port_value: 8000 + )EOF"; + + NiceMock cm; + StaticClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, + local_info, cm, false); + cluster.initialize([] {}); + + auto& hosts = cluster.prioritySet().hostSetsPerPriority()[0]->hosts(); + EXPECT_EQ(hosts.size(), 2); + for (int i = 0; i < 2; ++i) { + const auto& locality = hosts[i]->locality(); + EXPECT_EQ("oceania", locality.region()); + EXPECT_EQ("hello", locality.zone()); + EXPECT_EQ("world", locality.sub_zone()); + } + EXPECT_EQ(nullptr, cluster.prioritySet().hostSetsPerPriority()[0]->localityWeights()); + EXPECT_FALSE(cluster.info()->addedViaApi()); +} + TEST(StaticClusterImplTest, AltStatName) { Stats::IsolatedStoreImpl stats; Ssl::MockContextManager ssl_context_manager; NiceMock runtime; + NiceMock local_info; const std::string yaml = R"EOF( name: staticcluster @@ -458,8 +920,8 @@ TEST(StaticClusterImplTest, AltStatName) { )EOF"; NiceMock cm; - StaticClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, cm, - false); + StaticClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, + local_info, cm, false); cluster.initialize([] {}); // Increment a stat and verify it is emitted with alt_stat_name cluster.info()->stats().upstream_rq_total_.inc(); @@ -470,6 +932,8 @@ TEST(StaticClusterImplTest, RingHash) { Stats::IsolatedStoreImpl stats; Ssl::MockContextManager ssl_context_manager; NiceMock runtime; + NiceMock local_info; + const std::string json = R"EOF( { "name": "staticcluster", @@ -481,8 +945,8 @@ TEST(StaticClusterImplTest, RingHash) { )EOF"; NiceMock cm; - StaticClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, cm, - true); + StaticClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, + local_info, cm, true); cluster.initialize([] {}); EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); @@ -494,6 +958,8 @@ TEST(StaticClusterImplTest, OutlierDetector) { Stats::IsolatedStoreImpl stats; Ssl::MockContextManager ssl_context_manager; NiceMock runtime; + NiceMock local_info; + const std::string json = R"EOF( { "name": "addressportconfig", @@ -506,8 +972,8 @@ TEST(StaticClusterImplTest, OutlierDetector) { )EOF"; NiceMock cm; - StaticClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, cm, - false); + StaticClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, + local_info, cm, false); Outlier::MockDetector* detector = new Outlier::MockDetector(); EXPECT_CALL(*detector, addChangedStateCb(_)); @@ -541,6 +1007,8 @@ TEST(StaticClusterImplTest, HealthyStat) { Stats::IsolatedStoreImpl stats; Ssl::MockContextManager ssl_context_manager; NiceMock runtime; + NiceMock local_info; + const std::string json = R"EOF( { "name": "addressportconfig", @@ -553,8 +1021,8 @@ TEST(StaticClusterImplTest, HealthyStat) { )EOF"; NiceMock cm; - StaticClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, cm, - false); + StaticClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, + local_info, cm, false); Outlier::MockDetector* outlier_detector = new NiceMock(); cluster.setOutlierDetector(Outlier::DetectorSharedPtr{outlier_detector}); @@ -623,6 +1091,8 @@ TEST(StaticClusterImplTest, UrlConfig) { Stats::IsolatedStoreImpl stats; Ssl::MockContextManager ssl_context_manager; NiceMock runtime; + NiceMock local_info; + const std::string json = R"EOF( { "name": "addressportconfig", @@ -635,8 +1105,8 @@ TEST(StaticClusterImplTest, UrlConfig) { )EOF"; NiceMock cm; - StaticClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, cm, - false); + StaticClusterImpl cluster(parseClusterFromJson(json), runtime, stats, ssl_context_manager, + local_info, cm, false); cluster.initialize([] {}); EXPECT_EQ(1024U, cluster.info()->resourceManager(ResourcePriority::Default).connections().max()); @@ -656,8 +1126,8 @@ TEST(StaticClusterImplTest, UrlConfig) { std::list({"10.0.0.1:11001", "10.0.0.2:11002"}), ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); EXPECT_EQ(2UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); - EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); - EXPECT_EQ(0UL, + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->hostsPerLocality().get().size()); + EXPECT_EQ(1UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); cluster.prioritySet().hostSetsPerPriority()[0]->hosts()[0]->healthChecker().setUnhealthy(); } @@ -667,6 +1137,8 @@ TEST(StaticClusterImplTest, UnsupportedLBType) { Ssl::MockContextManager ssl_context_manager; NiceMock runtime; NiceMock cm; + NiceMock local_info; + const std::string json = R"EOF( { "name": "addressportconfig", @@ -678,15 +1150,17 @@ TEST(StaticClusterImplTest, UnsupportedLBType) { } )EOF"; - EXPECT_THROW( - StaticClusterImpl(parseClusterFromJson(json), runtime, stats, ssl_context_manager, cm, false), - EnvoyException); + EXPECT_THROW(StaticClusterImpl(parseClusterFromJson(json), runtime, stats, ssl_context_manager, + local_info, cm, false), + EnvoyException); } TEST(StaticClusterImplTest, MalformedHostIP) { Stats::IsolatedStoreImpl stats; Ssl::MockContextManager ssl_context_manager; NiceMock runtime; + NiceMock local_info; + const std::string yaml = R"EOF( name: name connect_timeout: 0.25s @@ -697,7 +1171,7 @@ TEST(StaticClusterImplTest, MalformedHostIP) { NiceMock cm; EXPECT_THROW_WITH_MESSAGE(StaticClusterImpl(parseClusterFromV2Yaml(yaml), runtime, stats, - ssl_context_manager, cm, false), + ssl_context_manager, local_info, cm, false), EnvoyException, "malformed IP address: foo.bar.com. Consider setting resolver_name or " "setting cluster type to 'STRICT_DNS' or 'LOGICAL_DNS'"); @@ -739,6 +1213,8 @@ TEST(StaticClusterImplTest, SourceAddressPriority) { Stats::IsolatedStoreImpl stats; Ssl::MockContextManager ssl_context_manager; NiceMock runtime; + NiceMock local_info; + envoy::api::v2::Cluster config; config.set_name("staticcluster"); config.mutable_connect_timeout(); @@ -747,7 +1223,7 @@ TEST(StaticClusterImplTest, SourceAddressPriority) { // If the cluster manager gets a source address from the bootstrap proto, use it. NiceMock cm; cm.bind_config_.mutable_source_address()->set_address("1.2.3.5"); - StaticClusterImpl cluster(config, runtime, stats, ssl_context_manager, cm, false); + StaticClusterImpl cluster(config, runtime, stats, ssl_context_manager, local_info, cm, false); EXPECT_EQ("1.2.3.5:0", cluster.info()->sourceAddress()->asString()); } @@ -756,7 +1232,7 @@ TEST(StaticClusterImplTest, SourceAddressPriority) { { // Verify source address from cluster config is used when present. NiceMock cm; - StaticClusterImpl cluster(config, runtime, stats, ssl_context_manager, cm, false); + StaticClusterImpl cluster(config, runtime, stats, ssl_context_manager, local_info, cm, false); EXPECT_EQ(cluster_address, cluster.info()->sourceAddress()->ip()->addressAsString()); } @@ -764,7 +1240,7 @@ TEST(StaticClusterImplTest, SourceAddressPriority) { // The source address from cluster config takes precedence over one from the bootstrap proto. NiceMock cm; cm.bind_config_.mutable_source_address()->set_address("1.2.3.5"); - StaticClusterImpl cluster(config, runtime, stats, ssl_context_manager, cm, false); + StaticClusterImpl cluster(config, runtime, stats, ssl_context_manager, local_info, cm, false); EXPECT_EQ(cluster_address, cluster.info()->sourceAddress()->ip()->addressAsString()); } } @@ -778,6 +1254,7 @@ TEST(ClusterImplTest, CloseConnectionsOnHostHealthFailure) { NiceMock dispatcher; NiceMock runtime; NiceMock cm; + NiceMock local_info; ReadyWatcher initialized; const std::string yaml = R"EOF( @@ -789,7 +1266,7 @@ TEST(ClusterImplTest, CloseConnectionsOnHostHealthFailure) { hosts: [{ socket_address: { address: foo.bar.com, port_value: 443 }}] )EOF"; StrictDnsClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, - dns_resolver, cm, dispatcher, false); + local_info, dns_resolver, cm, dispatcher, false); EXPECT_TRUE(cluster.info()->features() & ClusterInfo::Features::CLOSE_CONNECTIONS_ON_HOST_HEALTH_FAILURE); } @@ -851,6 +1328,7 @@ TEST(ClusterMetadataTest, Metadata) { NiceMock dispatcher; NiceMock runtime; NiceMock cm; + NiceMock local_info; ReadyWatcher initialized; const std::string yaml = R"EOF( @@ -866,7 +1344,7 @@ TEST(ClusterMetadataTest, Metadata) { )EOF"; StrictDnsClusterImpl cluster(parseClusterFromV2Yaml(yaml), runtime, stats, ssl_context_manager, - dns_resolver, cm, dispatcher, false); + local_info, dns_resolver, cm, dispatcher, false); EXPECT_EQ("test_value", Config::Metadata::metadataValue(cluster.info()->metadata(), "com.bar.foo", "baz") .string_value()); From e89c9d6f23371971120e4c91119cdafe65d3a8df Mon Sep 17 00:00:00 2001 From: Sam Smith Date: Tue, 24 Jul 2018 13:11:30 -0400 Subject: [PATCH 29/46] build: Initial bazel build file changes for Windows (#3884) This is the first step in breaking up #3786 into smaller chunks. This contains the Bazel config / compiler options to allow Envoy to build on Windows. Future PRs will address the external deps build scripts and PGV. Risk Level: Low Testing: Ran bazel build //source/exe:envoy-static and bazel test //test/... on Linux Docs Changes: None Release Notes: None Signed-off-by: Sam Smith --- .gitignore | 1 + bazel/BUILD | 29 ++++++ bazel/envoy_build_system.bzl | 59 ++++++++++-- bazel/external/libcircllhist.BUILD | 4 + bazel/repositories.bat | 4 + bazel/repositories.bzl | 27 +++++- bazel/repository_locations.bzl | 2 +- ci/build_setup.ps1 | 22 +++++ ci/do_ci.ps1 | 20 ++++ ci/prebuilt/BUILD | 40 ++++++-- source/common/event/BUILD | 10 +- source/common/filesystem/BUILD | 2 +- source/exe/BUILD | 6 +- source/extensions/all_extensions.bzl | 16 +++- source/extensions/extensions_build_config.bzl | 93 +++++++++++++++++++ windows/setup/workstation_setup.ps1 | 54 +++++++++++ windows/tools/bazel.rc | 7 ++ 17 files changed, 364 insertions(+), 32 deletions(-) create mode 100644 bazel/repositories.bat create mode 100755 ci/build_setup.ps1 create mode 100755 ci/do_ci.ps1 create mode 100644 windows/setup/workstation_setup.ps1 create mode 100644 windows/tools/bazel.rc diff --git a/.gitignore b/.gitignore index db2be52a8b12..7f4c28b37948 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ SOURCE_VERSION .cache .vimrc .vscode +.vs diff --git a/bazel/BUILD b/bazel/BUILD index 6a5258d5440b..223d2d0b7ee1 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -35,6 +35,35 @@ genrule( stamp = 1, ) +config_setting( + name = "windows_x86_64", + values = {"cpu": "x64_windows"}, +) + +config_setting( + name = "windows_opt_build", + values = { + "cpu": "x64_windows", + "compilation_mode": "opt", + }, +) + +config_setting( + name = "windows_dbg_build", + values = { + "cpu": "x64_windows", + "compilation_mode": "dbg", + }, +) + +config_setting( + name = "windows_fastbuild_build", + values = { + "cpu": "x64_windows", + "compilation_mode": "fastbuild", + }, +) + config_setting( name = "opt_build", values = {"compilation_mode": "opt"}, diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl index 374ecb4612d3..a545a293ddd1 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -5,19 +5,39 @@ def envoy_package(): # Compute the final copts based on various options. def envoy_copts(repository, test = False): - return [ - "-Wall", - "-Wextra", - "-Werror", - "-Wnon-virtual-dtor", - "-Woverloaded-virtual", - "-Wold-style-cast", - "-std=c++14", - ] + select({ + posix_options = [ + "-Wall", + "-Wextra", + "-Werror", + "-Wnon-virtual-dtor", + "-Woverloaded-virtual", + "-Wold-style-cast", + "-std=c++14", + ] + + msvc_options = [ + "-WX", + "-DWIN32", + "-DWIN32_LEAN_AND_MEAN", + # need win8 for ntohll + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa383745(v=vs.85).aspx + "-D_WIN32_WINNT=0x0602", + "-DNTDDI_VERSION=0x06020000", + "-DCARES_STATICLIB", + "-DNGHTTP2_STATICLIB", + ] + + return select({ + "//bazel:windows_x86_64": msvc_options, + "//conditions:default": posix_options, + }) + select({ # Bazel adds an implicit -DNDEBUG for opt. repository + "//bazel:opt_build": [] if test else ["-ggdb3"], repository + "//bazel:fastbuild_build": [], repository + "//bazel:dbg_build": ["-ggdb3"], + repository + "//bazel:windows_opt_build": [], + repository + "//bazel:windows_fastbuild_build": [], + repository + "//bazel:windows_dbg_build": [], }) + select({ repository + "//bazel:disable_tcmalloc": ["-DABSL_MALLOC_HOOK_MMAP_DISABLE"], "//conditions:default": ["-DTCMALLOC"], @@ -47,6 +67,9 @@ def envoy_linkopts(): "-pagezero_size 10000", "-image_base 100000000", ], + "//bazel:windows_x86_64": [ + "-DEFAULTLIB:advapi32.lib", + ], "//conditions:default": [ "-pthread", "-lrt", @@ -62,6 +85,7 @@ def _envoy_stamped_linkopts(): # # /usr/bin/ld.gold: internal error in write_build_id, at ../../gold/layout.cc:5419 "@envoy//bazel:coverage_build": [], + "//bazel:windows_x86_64": [], # MacOS doesn't have an official equivalent to the `.note.gnu.build-id` # ELF section, so just stuff the raw ID into a new text section. @@ -120,6 +144,13 @@ def tcmalloc_external_deps(repository): "//conditions:default": [envoy_external_dep_path("tcmalloc_and_profiler")], }) +# Dependencies on libevent should be wrapped with this function. +def libevent_external_deps(repository): + return [envoy_external_dep_path("event")] + select({ + repository + "//bazel:windows_x86_64": [], + "//conditions:default": [envoy_external_dep_path("event_pthreads")], + }) + # Transform the package path (e.g. include/envoy/common) into a path for # exporting the package headers at (e.g. envoy/common). Source files can then # include using this path scheme (e.g. #include "envoy/common/time.h"). @@ -144,6 +175,7 @@ def envoy_cc_library( visibility = None, external_deps = [], tcmalloc_dep = None, + libevent_dep = None, repository = "", linkstamp = None, tags = [], @@ -151,6 +183,9 @@ def envoy_cc_library( strip_include_prefix = None): if tcmalloc_dep: deps += tcmalloc_external_deps(repository) + if libevent_dep: + deps += libevent_external_deps(repository) + native.cc_library( name = name, srcs = srcs, @@ -168,7 +203,10 @@ def envoy_cc_library( include_prefix = envoy_include_prefix(PACKAGE_NAME), alwayslink = 1, linkstatic = 1, - linkstamp = linkstamp, + linkstamp = select({ + repository + "//bazel:windows_x86_64": None, + "//conditions:default": linkstamp, + }), strip_include_prefix = strip_include_prefix, ) @@ -481,5 +519,6 @@ def envoy_select_force_libcpp(if_libcpp, default = None): return select({ "@envoy//bazel:force_libcpp": if_libcpp, "@bazel_tools//tools/osx:darwin": [], + "//bazel:windows_x86_64": [], "//conditions:default": default or [], }) diff --git a/bazel/external/libcircllhist.BUILD b/bazel/external/libcircllhist.BUILD index 4e109f0b38d4..a937b65a382c 100644 --- a/bazel/external/libcircllhist.BUILD +++ b/bazel/external/libcircllhist.BUILD @@ -6,4 +6,8 @@ cc_library( ], includes = ["src"], visibility = ["//visibility:public"], + copts = select({ + "@envoy//bazel:windows_x86_64": ["-DWIN32"], + "//conditions:default": [], + }), ) diff --git a/bazel/repositories.bat b/bazel/repositories.bat new file mode 100644 index 000000000000..7b6695710593 --- /dev/null +++ b/bazel/repositories.bat @@ -0,0 +1,4 @@ +echo "Start" +@ECHO OFF +%BAZEL_SH% -c "./repositories.sh %*" +exit %ERRORLEVEL% diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index c85aaf358c8f..3d231c259ff0 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -7,6 +7,12 @@ load(":genrule_repository.bzl", "genrule_repository") load(":patched_http_archive.bzl", "patched_http_archive") load(":repository_locations.bzl", "REPOSITORY_LOCATIONS") load(":target_recipes.bzl", "TARGET_RECIPES") +load( + "@bazel_tools//tools/cpp:windows_cc_configure.bzl", + "find_vc_path", + "setup_vc_env_vars", +) +load("@bazel_tools//tools/cpp:lib_cc_configure.bzl", "get_env_var") def _repository_impl(name, **kwargs): # `existing_rule_keys` contains the names of repositories that have already @@ -67,6 +73,7 @@ def _repository_impl(name, **kwargs): def _build_recipe_repository_impl(ctxt): # Setup the build directory with links to the relevant files. ctxt.symlink(Label("//bazel:repositories.sh"), "repositories.sh") + ctxt.symlink(Label("//bazel:repositories.bat"), "repositories.bat") ctxt.symlink( Label("//ci/build_container:build_and_install_deps.sh"), "build_and_install_deps.sh", @@ -81,11 +88,25 @@ def _build_recipe_repository_impl(ctxt): ctxt.symlink(Label("//ci/prebuilt:BUILD"), "BUILD") # Run the build script. - environment = {} + command = [] + env = {} + if ctxt.os.name.upper().startswith("WINDOWS"): + vc_path = find_vc_path(ctxt) + current_path = get_env_var(ctxt, "PATH", None, False) + env = setup_vc_env_vars(ctxt, vc_path) + env["PATH"] += (";%s" % current_path) + env["CC"] = "cl" + env["CXX"] = "cl" + env["CXXFLAGS"] = "-DNDEBUG" + env["CFLAGS"] = "-DNDEBUG" + command = ["./repositories.bat"] + ctxt.attr.recipes + else: + command = ["./repositories.sh"] + ctxt.attr.recipes + print("Fetching external dependencies...") result = ctxt.execute( - ["./repositories.sh"] + ctxt.attr.recipes, - environment = environment, + command, + environment = env, quiet = False, ) print(result.stdout) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index d952af941cf5..c68f8cbe5f2e 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -18,7 +18,7 @@ REPOSITORY_LOCATIONS = dict( remote = "https://github.com/bombela/backward-cpp", ), com_github_circonus_labs_libcircllhist = dict( - commit = "476687ac9cc636fc92ac3070246d757ae6854547", # 2018-05-08 + commit = "050da53a44dede7bda136b93a9aeef47bd91fa12", # 2018-07-02 remote = "https://github.com/circonus-labs/libcircllhist", ), com_github_cyan4973_xxhash = dict( diff --git a/ci/build_setup.ps1 b/ci/build_setup.ps1 new file mode 100755 index 000000000000..12a3aeff987f --- /dev/null +++ b/ci/build_setup.ps1 @@ -0,0 +1,22 @@ +$ErrorActionPreference = "Stop"; +trap { $host.SetShouldExit(1) } + +if ("$env:NUM_CPUS" -eq "") { + $env:NUM_CPUS = (Get-WmiObject -class Win32_computersystem).NumberOfLogicalProcessors +} + +if ("$env:ENVOY_BAZEL_ROOT" -eq "") { + Write-Host "ENVOY_BAZEL_ROOT must be set!" + throw +} + +mkdir -force "$env:ENVOY_BAZEL_ROOT" > $nul + +$env:ENVOY_SRCDIR = [System.IO.Path]::GetFullPath("$PSScriptRoot\..") + +echo "ENVOY_BAZEL_ROOT: $env:ENVOY_BAZEL_ROOT" +echo "ENVOY_SRCDIR: $env:ENVOY_SRCDIR" + +$env:BAZEL_BASE_OPTIONS="--nomaster_bazelrc --output_base=$env:ENVOY_BAZEL_ROOT --bazelrc=$env:ENVOY_SRCDIR\windows\tools\bazel.rc" +$env:BAZEL_BUILD_OPTIONS="--strategy=Genrule=standalone --spawn_strategy=standalone --verbose_failures --jobs=$env:NUM_CPUS --show_task_finish $env:BAZEL_BUILD_EXTRA_OPTIONS" +$env:BAZEL_TEST_OPTIONS="$env:BAZEL_BUILD_OPTIONS --cache_test_results=no --test_output=all $env:BAZEL_EXTRA_TEST_OPTIONS" diff --git a/ci/do_ci.ps1 b/ci/do_ci.ps1 new file mode 100755 index 000000000000..fa0aa691c1a7 --- /dev/null +++ b/ci/do_ci.ps1 @@ -0,0 +1,20 @@ +$ErrorActionPreference = "Stop"; +trap { $host.SetShouldExit(1) } + +. "$PSScriptRoot\build_setup.ps1" +Write-Host "building using $env:NUM_CPUS CPUs" + +function bazel_debug_binary_build() { + echo "Building..." + pushd "$env:ENVOY_SRCDIR" + bazel $env:BAZEL_BASE_OPTIONS.Split(" ") build $env:BAZEL_BUILD_OPTIONS.Split(" ") -c dbg "//source/exe:envoy-static" + $exit = $LASTEXITCODE + if ($exit -ne 0) { + popd + exit $exit + } + popd +} + +echo "bazel debug build..." +bazel_debug_binary_build diff --git a/ci/prebuilt/BUILD b/ci/prebuilt/BUILD index 691644568905..3e2e5bebe2ac 100644 --- a/ci/prebuilt/BUILD +++ b/ci/prebuilt/BUILD @@ -2,23 +2,37 @@ licenses(["notice"]) # Apache 2 package(default_visibility = ["//visibility:public"]) +config_setting( + name = "windows_x86_64", + values = {"cpu": "x64_windows"}, +) + cc_library( name = "ares", - srcs = ["thirdparty_build/lib/libcares.a"], + srcs = select({ + ":windows_x86_64": ["thirdparty_build/lib/cares.lib"], + "//conditions:default": ["thirdparty_build/lib/libcares.a"], + }), hdrs = glob(["thirdparty_build/include/ares*.h"]), includes = ["thirdparty_build/include"], ) cc_library( name = "benchmark", - srcs = ["thirdparty_build/lib/libbenchmark.a"], + srcs = select({ + ":windows_x86_64": ["thirdparty_build/lib/benchmark.lib"], + "//conditions:default": ["thirdparty_build/lib/libbenchmark.a"], + }), hdrs = ["thirdparty_build/include/testing/base/public/benchmark.h"], includes = ["thirdparty_build/include"], ) cc_library( name = "event", - srcs = ["thirdparty_build/lib/libevent.a"], + srcs = select({ + ":windows_x86_64": ["thirdparty_build/lib/event.lib"], + "//conditions:default": ["thirdparty_build/lib/libevent.a"], + }), hdrs = glob(["thirdparty_build/include/event2/**/*.h"]), includes = ["thirdparty_build/include"], ) @@ -31,7 +45,10 @@ cc_library( cc_library( name = "luajit", - srcs = ["thirdparty_build/lib/libluajit-5.1.a"], + srcs = select({ + ":windows_x86_64": ["thirdparty_build/lib/luajit.lib"], + "//conditions:default": ["thirdparty_build/lib/libluajit-5.1.a"], + }), hdrs = glob(["thirdparty_build/include/luajit-2.0/*"]), includes = ["thirdparty_build/include"], # TODO(mattklein123): We should strip luajit-2.0 here for consumers. However, if we do that @@ -40,7 +57,10 @@ cc_library( cc_library( name = "nghttp2", - srcs = ["thirdparty_build/lib/libnghttp2.a"], + srcs = select({ + ":windows_x86_64": ["thirdparty_build/lib/nghttp2.lib"], + "//conditions:default": ["thirdparty_build/lib/libnghttp2.a"], + }), hdrs = glob(["thirdparty_build/include/nghttp2/**/*.h"]), includes = ["thirdparty_build/include"], ) @@ -54,14 +74,20 @@ cc_library( cc_library( name = "yaml_cpp", - srcs = ["thirdparty_build/lib/libyaml-cpp.a"], + srcs = select({ + ":windows_x86_64": glob(["thirdparty_build/lib/libyaml-cpp*.lib"]), + "//conditions:default": ["thirdparty_build/lib/libyaml-cpp.a"], + }), hdrs = glob(["thirdparty_build/include/yaml-cpp/**/*.h"]), includes = ["thirdparty_build/include"], ) cc_library( name = "zlib", - srcs = ["thirdparty_build/lib/libz.a"], + srcs = select({ + ":windows_x86_64": glob(["thirdparty_build/lib/zlibstaticd.lib"]), + "//conditions:default": ["thirdparty_build/lib/libz.a"], + }), hdrs = [ "thirdparty_build/include/zconf.h", "thirdparty_build/include/zlib.h", diff --git a/source/common/event/BUILD b/source/common/event/BUILD index 68420b3312b5..85c0bc7d7327 100644 --- a/source/common/event/BUILD +++ b/source/common/event/BUILD @@ -57,10 +57,7 @@ envoy_cc_library( name = "libevent_lib", srcs = ["libevent.cc"], hdrs = ["libevent.h"], - external_deps = [ - "event", - "event_pthreads", - ], + libevent_dep = 1, deps = [ "//source/common/common:assert_lib", "//source/common/common:c_smart_ptr_lib", @@ -71,10 +68,7 @@ envoy_cc_library( name = "dispatched_thread_lib", srcs = ["dispatched_thread.cc"], hdrs = ["dispatched_thread.h"], - external_deps = [ - "event", - "event_pthreads", - ], + libevent_dep = 1, deps = [ ":dispatcher_lib", "//include/envoy/event:dispatcher_interface", diff --git a/source/common/filesystem/BUILD b/source/common/filesystem/BUILD index b548a0321326..1c2b286e07b5 100644 --- a/source/common/filesystem/BUILD +++ b/source/common/filesystem/BUILD @@ -40,7 +40,7 @@ envoy_cc_library( "inotify/watcher_impl.h", ], }), - external_deps = ["event"], + libevent_dep = 1, strip_include_prefix = select({ "@bazel_tools//tools/osx:darwin": "kqueue", "//conditions:default": "inotify", diff --git a/source/exe/BUILD b/source/exe/BUILD index a6b91acfdf82..8d1e932de1d1 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -9,6 +9,7 @@ load( load( "//source/extensions:all_extensions.bzl", "envoy_all_extensions", + "envoy_windows_extensions", ) envoy_package() @@ -37,7 +38,10 @@ envoy_cc_library( "//source/server:options_lib", "//source/server:server_lib", "//source/server:test_hooks_lib", - ] + envoy_all_extensions(), + ] + select({ + "//bazel:windows_x86_64": envoy_windows_extensions(), + "//conditions:default": envoy_all_extensions(), + }), ) envoy_cc_library( diff --git a/source/extensions/all_extensions.bzl b/source/extensions/all_extensions.bzl index f5613b67d55b..1dc1a34aad6b 100644 --- a/source/extensions/all_extensions.bzl +++ b/source/extensions/all_extensions.bzl @@ -1,4 +1,4 @@ -load("@envoy_build_config//:extensions_build_config.bzl", "EXTENSIONS") +load("@envoy_build_config//:extensions_build_config.bzl", "EXTENSIONS", "WINDOWS_EXTENSIONS") # Return all extensions to be compiled into Envoy. def envoy_all_extensions(): @@ -14,3 +14,17 @@ def envoy_all_extensions(): all_extensions.append(path) return all_extensions + +def envoy_windows_extensions(): + # These extensions are registered using the extension system but are required for the core + # Envoy build. + windows_extensions = [ + "//source/extensions/transport_sockets/raw_buffer:config", + "//source/extensions/transport_sockets/ssl:config", + ] + + # These extensions can be removed on a site specific basis. + for path in WINDOWS_EXTENSIONS.values(): + windows_extensions.append(path) + + return windows_extensions diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index dad0243f89dc..bafaaad92579 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -101,3 +101,96 @@ EXTENSIONS = { "envoy.transport_sockets.alts": "//source/extensions/transport_sockets/alts:tsi_handshaker", "envoy.transport_sockets.capture": "//source/extensions/transport_sockets/capture:config", } + +WINDOWS_EXTENSIONS = { + # + # Access loggers + # + + "envoy.access_loggers.file": "//source/extensions/access_loggers/file:config", + #"envoy.access_loggers.http_grpc": "//source/extensions/access_loggers/http_grpc:config", + + # + # gRPC Credentials Plugins + # + + #"envoy.grpc_credentials.file_based_metadata": "//source/extensions/grpc_credentials/file_based_metadata:config", + + # + # Health checkers + # + + #"envoy.health_checkers.redis": "//source/extensions/health_checkers/redis:config", + + # + # HTTP filters + # + + #"envoy.filters.http.buffer": "//source/extensions/filters/http/buffer:config", + #"envoy.filters.http.cors": "//source/extensions/filters/http/cors:config", + #"envoy.filters.http.dynamo": "//source/extensions/filters/http/dynamo:config", + #"envoy.filters.http.ext_authz": "//source/extensions/filters/http/ext_authz:config", + #"envoy.filters.http.fault": "//source/extensions/filters/http/fault:config", + #"envoy.filters.http.grpc_http1_bridge": "//source/extensions/filters/http/grpc_http1_bridge:config", + #"envoy.filters.http.grpc_json_transcoder": "//source/extensions/filters/http/grpc_json_transcoder:config", + #"envoy.filters.http.grpc_web": "//source/extensions/filters/http/grpc_web:config", + #"envoy.filters.http.gzip": "//source/extensions/filters/http/gzip:config", + #"envoy.filters.http.health_check": "//source/extensions/filters/http/health_check:config", + #"envoy.filters.http.ip_tagging": "//source/extensions/filters/http/ip_tagging:config", + #"envoy.filters.http.lua": "//source/extensions/filters/http/lua:config", + #"envoy.filters.http.ratelimit": "//source/extensions/filters/http/ratelimit:config", + #"envoy.filters.http.rbac": "//source/extensions/filters/http/rbac:config", + #"envoy.filters.http.router": "//source/extensions/filters/http/router:config", + #"envoy.filters.http.squash": "//source/extensions/filters/http/squash:config", + + # + # Listener filters + # + + # NOTE: The proxy_protocol filter is implicitly loaded if proxy_protocol functionality is + # configured on the listener. Do not remove it in that case or configs will fail to load. + "envoy.filters.listener.proxy_protocol": "//source/extensions/filters/listener/proxy_protocol:config", + + # NOTE: The original_dst filter is implicitly loaded if original_dst functionality is + # configured on the listener. Do not remove it in that case or configs will fail to load. + #"envoy.filters.listener.original_dst": "//source/extensions/filters/listener/original_dst:config", + + "envoy.filters.listener.tls_inspector": "//source/extensions/filters/listener/tls_inspector:config", + + # + # Network filters + # + + "envoy.filters.network.client_ssl_auth": "//source/extensions/filters/network/client_ssl_auth:config", + #"envoy.filters.network.echo": "//source/extensions/filters/network/echo:config", + #"envoy.filters.network.ext_authz": "//source/extensions/filters/network/ext_authz:config", + #"envoy.filters.network.http_connection_manager": "//source/extensions/filters/network/http_connection_manager:config", + #"envoy.filters.network.mongo_proxy": "//source/extensions/filters/network/mongo_proxy:config", + #"envoy.filters.network.redis_proxy": "//source/extensions/filters/network/redis_proxy:config", + #"envoy.filters.network.ratelimit": "//source/extensions/filters/network/ratelimit:config", + "envoy.filters.network.tcp_proxy": "//source/extensions/filters/network/tcp_proxy:config", + # TODO(zuercher): switch to config target once a filter exists + #"envoy.filters.network.thrift_proxy": "//source/extensions/filters/network/thrift_proxy:transport_lib", + + # + # Stat sinks + # + + #"envoy.stat_sinks.dog_statsd": "//source/extensions/stat_sinks/dog_statsd:config", + #"envoy.stat_sinks.metrics_service": "//source/extensions/stat_sinks/metrics_service:config", + #"envoy.stat_sinks.statsd": "//source/extensions/stat_sinks/statsd:config", + + # + # Tracers + # + + #"envoy.tracers.dynamic_ot": "//source/extensions/tracers/dynamic_ot:config", + #"envoy.tracers.lightstep": "//source/extensions/tracers/lightstep:config", + #"envoy.tracers.zipkin": "//source/extensions/tracers/zipkin:config", + + # + # Transport sockets + # + + #"envoy.transport_sockets.capture": "//source/extensions/transport_sockets/capture:config", +} diff --git a/windows/setup/workstation_setup.ps1 b/windows/setup/workstation_setup.ps1 new file mode 100644 index 000000000000..bf253519602e --- /dev/null +++ b/windows/setup/workstation_setup.ps1 @@ -0,0 +1,54 @@ +$ErrorActionPreference = "Stop"; +$ProgressPreference="SilentlyContinue" + +trap { $host.SetShouldExit(1) } + +Start-BitsTransfer "https://aka.ms/vs/15/release/vs_buildtools.exe" "$env:TEMP\vs_buildtools.exe" + +# Install VS Build Tools in a directory without spaces to work around: https://github.com/bazelbuild/bazel/issues/4496 +# otherwise none of the go code will build (c++ is fine) + +$vsInstallDir="c:\VSBuildTools\2017" +echo "Installing VS Build Tools..." +cmd.exe /s /c "$env:TEMP\vs_buildtools.exe --installPath $vsInstallDir --passive --wait --norestart --nocache --add Microsoft.VisualStudio.Component.VC.CoreBuildTools --add Microsoft.VisualStudio.Component.VC.Redist.14.Latest --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows10SDK --add Microsoft.VisualStudio.Component.Windows10SDK.17134" + +if ($LASTEXITCODE -ne 0) { + echo "VS Build Tools install failed: $LASTEXITCODE" + exit $LASTEXITCODE +} +Remove-Item "$env:TEMP\vs_buildtools.exe" +echo "Done" + +Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) + +choco install make bazel cmake ninja git -y +if ($LASTEXITCODE -ne 0) { + echo "choco install failed: $LASTEXITCODE" + exit $LASTEXITCODE +} + +$envoyBazelRootDir = "c:\_eb" + +$env:ENVOY_BAZEL_ROOT=$envoyBazelRootDir +setx ENVOY_BAZEL_ROOT $envoyBazelRootDir > $nul +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + +$env:PATH ="$env:PATH;c:\tools\msys64\usr\bin;c:\make\bin;c:\Program Files\CMake\bin;C:\Python27;c:\programdata\chocolatey\bin;C:\Program Files\Git\bin" +setx PATH $env:PATH > $nul +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + +$env:BAZEL_VC="$vsInstallDir\VC" +setx BAZEL_VC $env:BAZEL_VC > $nul +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} + +$env:BAZEL_SH="C:\tools\msys64\usr\bin\bash.exe" +setx BAZEL_SH $env:BAZEL_SH > $nul +if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE +} diff --git a/windows/tools/bazel.rc b/windows/tools/bazel.rc new file mode 100644 index 000000000000..234d5bc4a85a --- /dev/null +++ b/windows/tools/bazel.rc @@ -0,0 +1,7 @@ +# Windows/Envoy specific Bazel build/test options. + +// TODO: remove experimental_shortened_obj_file_path for Bazel 0.16.0 +build --experimental_shortened_obj_file_path +build --define signal_trace=disabled +build --define hot_restart=disabled +build --define tcmalloc=disabled From a03c3435579b1f438a6ad8cbf2b63bba91108071 Mon Sep 17 00:00:00 2001 From: Sam Smith Date: Tue, 24 Jul 2018 19:24:18 -0400 Subject: [PATCH 30/46] Fix envoy-filter-example build (#3947) Some of the references to "//bazel:windows_x86_64" were not properly prefixed with the repostory name, causing the build of envoy-filter-example to fail. Risk Level: Low Testing: bazel test //test/... From envoy-filter-example, ran bazel build //:envoy Docs Changes: Release Notes: Signed-off-by: Sam Smith --- bazel/envoy_build_system.bzl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl index a545a293ddd1..e43460ccf202 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -28,7 +28,7 @@ def envoy_copts(repository, test = False): ] return select({ - "//bazel:windows_x86_64": msvc_options, + repository + "//bazel:windows_x86_64": msvc_options, "//conditions:default": posix_options, }) + select({ # Bazel adds an implicit -DNDEBUG for opt. @@ -67,7 +67,7 @@ def envoy_linkopts(): "-pagezero_size 10000", "-image_base 100000000", ], - "//bazel:windows_x86_64": [ + "@envoy//bazel:windows_x86_64": [ "-DEFAULTLIB:advapi32.lib", ], "//conditions:default": [ @@ -85,7 +85,7 @@ def _envoy_stamped_linkopts(): # # /usr/bin/ld.gold: internal error in write_build_id, at ../../gold/layout.cc:5419 "@envoy//bazel:coverage_build": [], - "//bazel:windows_x86_64": [], + "@envoy//bazel:windows_x86_64": [], # MacOS doesn't have an official equivalent to the `.note.gnu.build-id` # ELF section, so just stuff the raw ID into a new text section. @@ -519,6 +519,6 @@ def envoy_select_force_libcpp(if_libcpp, default = None): return select({ "@envoy//bazel:force_libcpp": if_libcpp, "@bazel_tools//tools/osx:darwin": [], - "//bazel:windows_x86_64": [], + "@envoy//bazel:windows_x86_64": [], "//conditions:default": default or [], }) From 1f445bdb14c6bdcb97698b1d93ca1fa0828ffbdc Mon Sep 17 00:00:00 2001 From: Sam Smith Date: Tue, 24 Jul 2018 19:25:30 -0400 Subject: [PATCH 31/46] build: external deps build on Windows (#3892) This PR is a continuation of breaking up #3786 into smaller chunks. It: 1. Converts all external deps (excluding gperftools and luajit which do not support cmake) to use Ninja instead of make 2. Ensures these deps build on Windows. This involves using curl instead of wget and copying *.pdb files to the appropriate location In the process of switching from make to Ninja, libevent.sh now outputs just a libevent.a archive on Linux - a separate libevent_pthreads.a archive is no longer created or necessary. Risk Level: After discussion here: #3892 (comment) changing risk level to medium (potential for performance regression in c-ares) Testing: Ran bazel build //source/exe:envoy-static and bazel test //test/... on Linux Signed-off-by: Sam Smith --- bazel/README.md | 8 ++-- ci/build_container/build_container_centos.sh | 2 +- ci/build_container/build_container_ubuntu.sh | 2 +- ci/build_container/build_recipes/benchmark.sh | 12 ++++-- ci/build_container/build_recipes/cares.sh | 31 +++++++++++--- .../build_recipes/gperftools.sh | 6 ++- ci/build_container/build_recipes/libevent.sh | 35 +++++++++++++--- ci/build_container/build_recipes/luajit.sh | 21 +++++++--- ci/build_container/build_recipes/nghttp2.sh | 42 ++++++++++++++++--- ci/build_container/build_recipes/yaml-cpp.sh | 28 +++++++++++-- ci/build_container/build_recipes/zlib.sh | 13 ++++-- ci/mac_ci_setup.sh | 2 +- ci/prebuilt/BUILD | 6 ++- 13 files changed, 167 insertions(+), 41 deletions(-) diff --git a/bazel/README.md b/bazel/README.md index 4a1b9ff55432..0b68e7ec16e8 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -27,16 +27,15 @@ up-to-date with the latest security patches. See for how to update or override dependencies. 1. Install the latest version of [Bazel](https://bazel.build/versions/master/docs/install.html) in your environment. -2. Install external dependencies libtool, cmake, and realpath libraries separately. +2. Install external dependencies libtool, cmake, ninja, and realpath libraries separately. On Ubuntu, run the following commands: ``` apt-get install libtool apt-get install cmake apt-get install realpath apt-get install clang-format-5.0 - apt-get install autoconf apt-get install automake - apt-get install pkg-config + apt-get install ninja-build ``` On Fedora (maybe also other red hat distros), run the following: @@ -52,9 +51,8 @@ brew install cmake brew install libtool brew install go brew install bazel -brew install autoconf brew install automake -brew install pkg-config +brew install ninja ``` Envoy compiles and passes tests with the version of clang installed by XCode 9.3.0: diff --git a/ci/build_container/build_container_centos.sh b/ci/build_container/build_container_centos.sh index f26971230c3d..d416fddea6f7 100755 --- a/ci/build_container/build_container_centos.sh +++ b/ci/build_container/build_container_centos.sh @@ -9,7 +9,7 @@ curl -L -o /etc/yum.repos.d/alonid-llvm-5.0.0-epel-7.repo \ # dependencies for bazel and build_recipes yum install -y java-1.8.0-openjdk-devel unzip which openssl rpm-build \ - cmake3 devtoolset-4-gcc-c++ git golang libtool make patch rsync wget \ + cmake3 devtoolset-4-gcc-c++ git golang libtool make ninja-build patch rsync wget \ clang-5.0.0 devtoolset-4-libatomic-devel llvm-5.0.0 python-virtualenv bc yum clean all diff --git a/ci/build_container/build_container_ubuntu.sh b/ci/build_container/build_container_ubuntu.sh index 4561a79168d2..e107bd1d2deb 100755 --- a/ci/build_container/build_container_ubuntu.sh +++ b/ci/build_container/build_container_ubuntu.sh @@ -6,7 +6,7 @@ set -e apt-get update export DEBIAN_FRONTEND=noninteractive apt-get install -y wget software-properties-common make cmake git python python-pip \ - bc libtool autoconf automake zip time golang g++ gdb strace wireshark tshark + bc libtool ninja-build automake zip time golang g++ gdb strace wireshark tshark # clang head (currently 5.0) wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main" diff --git a/ci/build_container/build_recipes/benchmark.sh b/ci/build_container/build_recipes/benchmark.sh index 5e8f2f41ab2c..6817ea42a291 100644 --- a/ci/build_container/build_recipes/benchmark.sh +++ b/ci/build_container/build_recipes/benchmark.sh @@ -8,11 +8,17 @@ git clone https://github.com/google/benchmark.git mkdir build cd build -cmake -G "Unix Makefiles" ../benchmark \ +cmake -G "Ninja" ../benchmark \ -DCMAKE_BUILD_TYPE=RELEASE \ -DBENCHMARK_ENABLE_GTEST_TESTS=OFF -make -cp src/libbenchmark.a "$THIRDPARTY_BUILD"/lib +ninja + +benchmark_lib="libbenchmark.a" +if [[ "${OS}" == "Windows_NT" ]]; then + benchmark_lib="benchmark.lib" +fi + +cp "src/$benchmark_lib" "$THIRDPARTY_BUILD"/lib cd ../benchmark INCLUDE_DIR="$THIRDPARTY_BUILD/include/testing/base/public" diff --git a/ci/build_container/build_recipes/cares.sh b/ci/build_container/build_recipes/cares.sh index b3797f432e99..d4191ae7fadd 100755 --- a/ci/build_container/build_recipes/cares.sh +++ b/ci/build_container/build_recipes/cares.sh @@ -10,10 +10,31 @@ VERSION=cares-1_14_0 CPPFLAGS="$(for f in $CXXFLAGS; do if [[ $f =~ -D.* ]]; then echo $f; fi; done | tr '\n' ' ')" CFLAGS="$(for f in $CXXFLAGS; do if [[ ! $f =~ -D.* ]]; then echo $f; fi; done | tr '\n' ' ')" -wget -O c-ares-"$VERSION".tar.gz https://github.com/c-ares/c-ares/archive/"$VERSION".tar.gz +curl https://github.com/c-ares/c-ares/archive/"$VERSION".tar.gz -sLo c-ares-"$VERSION".tar.gz tar xf c-ares-"$VERSION".tar.gz cd c-ares-"$VERSION" -./buildconf -./configure --prefix="$THIRDPARTY_BUILD" --enable-shared=no --enable-lib-only \ - --enable-debug --enable-optimize -make V=1 install + +mkdir build +cd build + +build_type=RelWithDebInfo +if [[ "${OS}" == "Windows_NT" ]]; then + # On Windows, every object file in the final executable needs to be compiled to use the + # same version of the C Runtime Library. If Envoy is built with '-c dbg', then it will + # use the Debug C Runtime Library. Setting CMAKE_BUILD_TYPE to Debug will cause c-ares + # to use the debug version as well + # TODO: when '-c fastbuild' and '-c opt' work for Windows builds, set this appropriately + build_type=Debug +fi + +cmake -G "Ninja" -DCMAKE_INSTALL_PREFIX="$THIRDPARTY_BUILD" \ + -DCARES_SHARED=no \ + -DCARES_STATIC=on \ + -DCMAKE_BUILD_TYPE="$build_type" \ + .. +ninja +ninja install + +if [[ "${OS}" == "Windows_NT" ]]; then + cp "CMakeFiles/c-ares.dir/c-ares.pdb" "$THIRDPARTY_BUILD/lib/c-ares.pdb" +fi diff --git a/ci/build_container/build_recipes/gperftools.sh b/ci/build_container/build_recipes/gperftools.sh index 7c0c72d9c6e6..de18e91a526d 100755 --- a/ci/build_container/build_recipes/gperftools.sh +++ b/ci/build_container/build_recipes/gperftools.sh @@ -2,9 +2,13 @@ set -e +if [[ "${OS}" == "Windows_NT" ]]; then + exit 0 +fi + VERSION=2.7 -wget -O gperftools-"$VERSION".tar.gz https://github.com/gperftools/gperftools/releases/download/gperftools-"$VERSION"/gperftools-"$VERSION".tar.gz +curl https://github.com/gperftools/gperftools/releases/download/gperftools-"$VERSION"/gperftools-"$VERSION".tar.gz -sLo gperftools-"$VERSION".tar.gz tar xf gperftools-"$VERSION".tar.gz cd gperftools-"$VERSION" diff --git a/ci/build_container/build_recipes/libevent.sh b/ci/build_container/build_recipes/libevent.sh index c88d5bb3a2ed..0bd783cf4bdd 100755 --- a/ci/build_container/build_recipes/libevent.sh +++ b/ci/build_container/build_recipes/libevent.sh @@ -4,8 +4,33 @@ set -e VERSION=2.1.8-stable -wget -O libevent-"$VERSION".tar.gz https://github.com/libevent/libevent/releases/download/release-"$VERSION"/libevent-"$VERSION".tar.gz -tar xf libevent-"$VERSION".tar.gz -cd libevent-"$VERSION" -./configure --prefix="$THIRDPARTY_BUILD" --enable-shared=no --disable-libevent-regress --disable-openssl -make V=1 install +curl https://github.com/libevent/libevent/archive/release-"$VERSION".tar.gz -sLo libevent-release-"$VERSION".tar.gz +tar xf libevent-release-"$VERSION".tar.gz +cd libevent-release-"$VERSION" + +mkdir build +cd build + +# libevent defaults CMAKE_BUILD_TYPE to Release +build_type=Release +if [[ "${OS}" == "Windows_NT" ]]; then + # On Windows, every object file in the final executable needs to be compiled to use the + # same version of the C Runtime Library. If Envoy is built with '-c dbg', then it will + # use the Debug C Runtime Library. Setting CMAKE_BUILD_TYPE to Debug will cause libevent + # to use the debug version as well + # TODO: when '-c fastbuild' and '-c opt' work for Windows builds, set this appropriately + build_type=Debug +fi + +cmake -G "Ninja" \ + -DCMAKE_INSTALL_PREFIX="$THIRDPARTY_BUILD" \ + -DEVENT__DISABLE_OPENSSL:BOOL=on \ + -DEVENT__DISABLE_REGRESS:BOOL=on \ + -DCMAKE_BUILD_TYPE="$build_type" \ + .. +ninja +ninja install + +if [[ "${OS}" == "Windows_NT" ]]; then + cp "CMakeFiles/event.dir/event.pdb" "$THIRDPARTY_BUILD/lib/event.pdb" +fi diff --git a/ci/build_container/build_recipes/luajit.sh b/ci/build_container/build_recipes/luajit.sh index 4deba13a51d4..3b02133d34dc 100644 --- a/ci/build_container/build_recipes/luajit.sh +++ b/ci/build_container/build_recipes/luajit.sh @@ -4,7 +4,7 @@ set -e VERSION=2.0.5 -wget -O LuaJIT-"$VERSION".tar.gz https://github.com/LuaJIT/LuaJIT/archive/v"$VERSION".tar.gz +curl https://github.com/LuaJIT/LuaJIT/archive/v"$VERSION".tar.gz -sLo LuaJIT-"$VERSION".tar.gz tar xf LuaJIT-"$VERSION".tar.gz cd LuaJIT-"$VERSION" @@ -46,15 +46,26 @@ index f7f81a4..e698517 100644 # Disable the JIT compiler, i.e. turn LuaJIT into a pure interpreter. #XCFLAGS+= -DLUAJIT_DISABLE_JIT @@ -564,7 +564,7 @@ endif - + Q= @ E= @echo -#Q= +Q= #E= @: - + ############################################################################## EOF -patch -p1 < ../luajit_make.diff -DEFAULT_CC=${CC} TARGET_CFLAGS=${CFLAGS} TARGET_LDFLAGS=${CFLAGS} CFLAGS="" make V=1 PREFIX="$THIRDPARTY_BUILD" install +if [[ "${OS}" == "Windows_NT" ]]; then + cd src + ./msvcbuild.bat debug + + mkdir -p "$THIRDPARTY_BUILD/include/luajit-2.0" + cp *.h* "$THIRDPARTY_BUILD/include/luajit-2.0" + cp luajit.lib "$THIRDPARTY_BUILD/lib" + cp *.pdb "$THIRDPARTY_BUILD/lib" +else + patch -p1 < ../luajit_make.diff + + DEFAULT_CC=${CC} TARGET_CFLAGS=${CFLAGS} TARGET_LDFLAGS=${CFLAGS} CFLAGS="" make V=1 PREFIX="$THIRDPARTY_BUILD" install +fi diff --git a/ci/build_container/build_recipes/nghttp2.sh b/ci/build_container/build_recipes/nghttp2.sh index 2890b10860be..f4abbc558ab5 100755 --- a/ci/build_container/build_recipes/nghttp2.sh +++ b/ci/build_container/build_recipes/nghttp2.sh @@ -7,11 +7,41 @@ set -e # TODO(PiotrSikora): switch back to releases once v1.33.0 is out. VERSION=e5b3f9addd49bca27e2f99c5c65a564eb5c0cf6d # 2018-06-09 -wget -O nghttp2-"$VERSION".tar.gz https://github.com/nghttp2/nghttp2/archive/"$VERSION".tar.gz +curl https://github.com/nghttp2/nghttp2/archive/"$VERSION".tar.gz -sLo nghttp2-"$VERSION".tar.gz tar xf nghttp2-"$VERSION".tar.gz cd nghttp2-"$VERSION" -autoreconf -i -automake -autoconf -./configure --prefix="$THIRDPARTY_BUILD" --enable-shared=no --enable-lib-only -make V=1 install + +# Allow nghttp2 to build as static lib on Windows +# TODO: remove once https://github.com/nghttp2/nghttp2/pull/1198 is merged +cat > nghttp2_cmakelists.diff << 'EOF' +diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt +index 17e422b2..e58070f5 100644 +--- a/lib/CMakeLists.txt ++++ b/lib/CMakeLists.txt +@@ -56,6 +56,7 @@ if(HAVE_CUNIT OR ENABLE_STATIC_LIB) + COMPILE_FLAGS "${WARNCFLAGS}" + VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION} + ARCHIVE_OUTPUT_NAME nghttp2 ++ ARCHIVE_OUTPUT_DIRECTORY static + ) + target_compile_definitions(nghttp2_static PUBLIC "-DNGHTTP2_STATICLIB") + if(ENABLE_STATIC_LIB) +EOF + +if [[ "${OS}" == "Windows_NT" ]]; then + git apply nghttp2_cmakelists.diff +fi + +mkdir build +cd build + +cmake -G "Ninja" -DCMAKE_INSTALL_PREFIX="$THIRDPARTY_BUILD" \ + -DENABLE_STATIC_LIB=on \ + -DENABLE_LIB_ONLY=on \ + .. +ninja +ninja install + +if [[ "${OS}" == "Windows_NT" ]]; then + cp "lib/CMakeFiles/nghttp2_static.dir/nghttp2_static.pdb" "$THIRDPARTY_BUILD/lib/nghttp2_static.pdb" +fi diff --git a/ci/build_container/build_recipes/yaml-cpp.sh b/ci/build_container/build_recipes/yaml-cpp.sh index db63dcb3a11e..2c565cfd1bf6 100755 --- a/ci/build_container/build_recipes/yaml-cpp.sh +++ b/ci/build_container/build_recipes/yaml-cpp.sh @@ -4,11 +4,31 @@ set -e VERSION=0.6.2 -wget -O yaml-cpp-"$VERSION".tar.gz https://github.com/jbeder/yaml-cpp/archive/yaml-cpp-"$VERSION".tar.gz +curl https://github.com/jbeder/yaml-cpp/archive/yaml-cpp-"$VERSION".tar.gz -sLo yaml-cpp-"$VERSION".tar.gz tar xf yaml-cpp-"$VERSION".tar.gz cd yaml-cpp-yaml-cpp-"$VERSION" -cmake -DCMAKE_INSTALL_PREFIX:PATH="$THIRDPARTY_BUILD" \ + +mkdir build +cd build + +build_type=RelWithDebInfo +if [[ "${OS}" == "Windows_NT" ]]; then + # On Windows, every object file in the final executable needs to be compiled to use the + # same version of the C Runtime Library. If Envoy is built with '-c dbg', then it will + # use the Debug C Runtime Library. Setting CMAKE_BUILD_TYPE to Debug will cause yaml-cpp + # to use the debug version as well + # TODO: when '-c fastbuild' and '-c opt' work for Windows builds, set this appropriately + build_type=Debug +fi + +cmake -G "Ninja" -DCMAKE_INSTALL_PREFIX:PATH="$THIRDPARTY_BUILD" \ -DCMAKE_CXX_FLAGS:STRING="${CXXFLAGS} ${CPPFLAGS}" \ -DCMAKE_C_FLAGS:STRING="${CFLAGS} ${CPPFLAGS}" \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo . -make VERBOSE=1 install + -DYAML_CPP_BUILD_TESTS=off \ + -DCMAKE_BUILD_TYPE="$build_type" \ + .. +ninja install + +if [[ "${OS}" == "Windows_NT" ]]; then + cp "CMakeFiles/yaml-cpp.dir/yaml-cpp.pdb" "$THIRDPARTY_BUILD/lib/yaml-cpp.pdb" +fi diff --git a/ci/build_container/build_recipes/zlib.sh b/ci/build_container/build_recipes/zlib.sh index fd22ea67f0af..62997062f149 100644 --- a/ci/build_container/build_recipes/zlib.sh +++ b/ci/build_container/build_recipes/zlib.sh @@ -4,8 +4,15 @@ set -e VERSION=1.2.11 -wget -O zlib-"$VERSION".tar.gz https://github.com/madler/zlib/archive/v"$VERSION".tar.gz +curl https://github.com/madler/zlib/archive/v"$VERSION".tar.gz -sLo zlib-"$VERSION".tar.gz tar xf zlib-"$VERSION".tar.gz cd zlib-"$VERSION" -./configure --prefix="$THIRDPARTY_BUILD" -make V=1 install +mkdir build +cd build +cmake -G "Ninja" -DCMAKE_INSTALL_PREFIX:PATH="$THIRDPARTY_BUILD" .. +ninja +ninja install + +if [[ "${OS}" == "Windows_NT" ]]; then + cp "CMakeFiles/zlibstatic.dir/zlibstatic.pdb" "$THIRDPARTY_BUILD/lib/zlibstatic.pdb" +fi diff --git a/ci/mac_ci_setup.sh b/ci/mac_ci_setup.sh index e44bd7d5430f..decdfd75f4d9 100755 --- a/ci/mac_ci_setup.sh +++ b/ci/mac_ci_setup.sh @@ -21,7 +21,7 @@ if ! brew update; then exit 1 fi -DEPS="automake bazel cmake coreutils go libtool wget" +DEPS="automake bazel cmake coreutils go libtool wget ninja" for DEP in ${DEPS} do is_installed "${DEP}" || install "${DEP}" diff --git a/ci/prebuilt/BUILD b/ci/prebuilt/BUILD index 3e2e5bebe2ac..4f85e1a6b21c 100644 --- a/ci/prebuilt/BUILD +++ b/ci/prebuilt/BUILD @@ -38,8 +38,12 @@ cc_library( ) cc_library( + # TODO: Remove 'event_pthreads' once no BUIlD files use it + # glob here is due to the fact that ninja doesn't produce a libevent_pthreads.a file - + # all the symbols are included in libevent.a. This is a hack so that CI can pass before + # https://github.com/envoyproxy/envoy/pull/3909 is merged as well name = "event_pthreads", - srcs = ["thirdparty_build/lib/libevent_pthreads.a"], + srcs = glob(["thirdparty_build/lib/libevent_pthreads.a"]), deps = [":event"], ) From 0621763bd00016f91e530412a9a307fca474ad69 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Tue, 24 Jul 2018 20:22:53 -0700 Subject: [PATCH 32/46] listener: deprecate sni_domains. (#3948) *Risk Level*: Low *Testing*: bazel test //test/... *Docs Changes*: Removed docs for "sni_domains" *Release Notes*: n/a Fixes #3718. Signed-off-by: Piotr Sikora --- api/envoy/api/v2/listener/listener.proto | 16 +---- docs/root/intro/version_history.rst | 3 +- source/server/listener_manager_impl.cc | 20 +----- test/server/listener_manager_impl_test.cc | 65 +------------------ ...testcase-server_fuzz_test-6610050496856064 | 2 +- 5 files changed, 8 insertions(+), 98 deletions(-) diff --git a/api/envoy/api/v2/listener/listener.proto b/api/envoy/api/v2/listener/listener.proto index c4274733335a..1e8015dbb244 100644 --- a/api/envoy/api/v2/listener/listener.proto +++ b/api/envoy/api/v2/listener/listener.proto @@ -148,20 +148,8 @@ message FilterChainMatch { // unless all connecting clients are known to use ALPN. repeated string application_protocols = 10; - // If non-empty, a list of server names (e.g. SNI for TLS protocol) to consider when determining - // a filter chain match. Those values will be compared against the server names of a new - // connection, when detected by one of the listener filters. - // - // The server name will be matched against all wildcard domains, i.e. ``www.example.com`` - // will be first matched against ``www.example.com``, then ``*.example.com``, then ``*.com``. - // - // Note that partial wildcards are not supported, and values like ``*w.example.com`` are invalid. - // - // .. attention:: - // - // Deprecated. Use :ref:`server_names ` - // instead. - repeated string sni_domains = 1 [deprecated = true]; + reserved 1; + reserved "sni_domains"; } // A filter chain wraps a set of match criteria, an option TLS context, a set of filters, and diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 121c40993e0f..da45f64bc612 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -124,8 +124,7 @@ Version history * listeners: added the ability to match :ref:`FilterChain ` using :ref:`application_protocols ` (e.g. ALPN for TLS protocol). -* listeners: :ref:`sni_domains ` has been deprecated/renamed to - :ref:`server_names `. +* listeners: `sni_domains` has been deprecated/renamed to :ref:`server_names `. * listeners: removed restriction on all filter chains having identical filters. * load balancer: added :ref:`weighted round robin ` support. The round robin diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index e69bebe4ba3f..3e3d25bb4f18 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -209,29 +209,15 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st destination_ips.push_back(cidr_range.asString()); } - std::vector server_names; - if (!filter_chain_match.server_names().empty()) { - if (!filter_chain_match.sni_domains().empty()) { - throw EnvoyException( - fmt::format("error adding listener '{}': both \"server_names\" and the deprecated " - "\"sni_domains\" are used, please merge the list of expected server names " - "into \"server_names\" and remove \"sni_domains\"", - address_->asString())); - } - - server_names.assign(filter_chain_match.server_names().begin(), - filter_chain_match.server_names().end()); - } else if (!filter_chain_match.sni_domains().empty()) { - server_names.assign(filter_chain_match.sni_domains().begin(), - filter_chain_match.sni_domains().end()); - } + std::vector server_names(filter_chain_match.server_names().begin(), + filter_chain_match.server_names().end()); // Reject partial wildcards, we don't match on them. for (const auto& server_name : server_names) { if (server_name.find('*') != std::string::npos && !isWildcardServerName(server_name)) { throw EnvoyException( fmt::format("error adding listener '{}': partial wildcards are not supported in " - "\"server_names\" (or the deprecated \"sni_domains\")", + "\"server_names\"", address_->asString())); } } diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 956b8f288450..8558e85e10dc 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -1743,7 +1743,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithInvalidServe EXPECT_THROW_WITH_MESSAGE(manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true), EnvoyException, "error adding listener '127.0.0.1:1234': partial wildcards are not " - "supported in \"server_names\" (or the deprecated \"sni_domains\")"); + "supported in \"server_names\""); } TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithSameMatch) { @@ -1874,69 +1874,6 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, CustomTransportProtocolWithSniWit EXPECT_TRUE(filterChainFactory.createListenerFilterChain(manager)); } -// Copy of the SingleFilterChainWithServerNamesMatch to make sure it behaves the same. -TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithDeprecatedSniDomainsMatch) { - const std::string yaml = TestEnvironment::substitute(R"EOF( - address: - socket_address: { address: 127.0.0.1, port_value: 1234 } - listener_filters: - - name: "envoy.listener.tls_inspector" - config: {} - filter_chains: - - filter_chain_match: - sni_domains: "server1.example.com" - tls_context: - common_tls_context: - tls_certificates: - - certificate_chain: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_cert.pem" } - private_key: { filename: "{{ test_rundir }}/test/common/ssl/test_data/san_dns_key.pem" } - )EOF", - Network::Address::IpVersion::v4); - - EXPECT_CALL(server_.random_, uuid()); - EXPECT_CALL(listener_factory_, createListenSocket(_, _, true)); - manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true); - EXPECT_EQ(1U, manager_->listeners().size()); - - // TLS client without SNI - no match. - auto filter_chain = findFilterChain(1234, true, "127.0.0.1", true, "", false, "tls", false, {}); - EXPECT_EQ(filter_chain, nullptr); - - // TLS client without matching SNI - no match. - filter_chain = - findFilterChain(1234, true, "127.0.0.1", true, "www.example.com", false, "tls", false, {}); - EXPECT_EQ(filter_chain, nullptr); - - // TLS client with matching SNI - using 1st filter chain. - filter_chain = - findFilterChain(1234, true, "127.0.0.1", true, "server1.example.com", true, "tls", true, {}); - ASSERT_NE(filter_chain, nullptr); - EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); - auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(); - auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); - EXPECT_EQ(server_names.size(), 1); - EXPECT_EQ(server_names.front(), "server1.example.com"); -} - -TEST_F(ListenerManagerImplWithRealFiltersTest, DeprecatedSniDomainsAndServerNamesUsedTogether) { - const std::string yaml = TestEnvironment::substitute(R"EOF( - address: - socket_address: { address: 127.0.0.1, port_value: 1234 } - filter_chains: - - filter_chain_match: - server_names: "example.com" - sni_domains: "www.example.com" - )EOF", - Network::Address::IpVersion::v4); - - EXPECT_THROW_WITH_MESSAGE( - manager_->addOrUpdateListener(parseListenerFromV2Yaml(yaml), "", true), EnvoyException, - "error adding listener '127.0.0.1:1234': both \"server_names\" and the deprecated " - "\"sni_domains\" are used, please merge the list of expected server names into " - "\"server_names\" and remove \"sni_domains\""); -} - TEST_F(ListenerManagerImplWithRealFiltersTest, TlsCertificateInline) { const std::string yaml = R"EOF( address: diff --git a/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6610050496856064 b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6610050496856064 index 64ceff65aa68..3314510bb3fd 100644 --- a/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6610050496856064 +++ b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6610050496856064 @@ -28,7 +28,7 @@ static_resources { } filter_chains { filter_chain_match { - sni_domains: "6e702f1f66d415068aabbc60377ad67a326b6b2b" + server_names: "6e702f1f66d415068aabbc60377ad67a326b6b2b" } } filter_chains { From 1ef23d481a4701ad4a414d1ef98036bd2ed322e7 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Tue, 24 Jul 2018 21:28:25 -0700 Subject: [PATCH 33/46] tls: update BoringSSL to 372daf70 (3440). (#3946) Signed-off-by: Piotr Sikora --- bazel/repository_locations.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index c68f8cbe5f2e..7a65f4c956f3 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1,7 +1,7 @@ REPOSITORY_LOCATIONS = dict( boringssl = dict( # Use commits from branch "chromium-stable-with-bazel" - commit = "2a52ce799382c87cd3119f3b44fbbebf97061ab6", # chromium-67.0.3396.62 + commit = "372daf7042ffe3da1335743e7c93d78f1399aba7", # chromium-68.0.3440.75 remote = "https://github.com/google/boringssl", ), com_google_absl = dict( From ec0179a78b6928f2ae96a43811c27b059d9186d7 Mon Sep 17 00:00:00 2001 From: Derek Argueta Date: Wed, 25 Jul 2018 10:51:04 -0700 Subject: [PATCH 34/46] router: add response/request header options at route level (#3838) This adds the ability to specify response_headers_to_* and request_headers_to_add at the route level, for #3520 Risk Level: low Testing: updated unit tests Docs Changes: added Fixes Issue: #3520 Signed-off-by: Derek Argueta --- DEPRECATED.md | 2 + api/envoy/api/v2/route/route.proto | 58 +++++++++++++----------- docs/root/intro/version_history.rst | 1 + source/common/router/config_impl.cc | 20 ++++++--- source/common/router/config_impl.h | 4 +- test/common/router/config_impl_test.cc | 62 +++++++++++++++++--------- 6 files changed, 95 insertions(+), 52 deletions(-) diff --git a/DEPRECATED.md b/DEPRECATED.md index e5308f46e43e..955494c23b6e 100644 --- a/DEPRECATED.md +++ b/DEPRECATED.md @@ -24,6 +24,8 @@ A logged warning is expected for each deprecated item that is in deprecation win [HttpConnectionManager](https://github.com/envoyproxy/envoy/blob/master/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto) instead. * Setting hosts via `hosts` field in `Cluster` is deprecated. Use `load_assignment` instead. +* Use of `response_headers_to_*` and `request_headers_to_add` are deprecated at the `RouteAction` + level. Please use the configuration options at the `Route` level. ## Version 1.7.0 diff --git a/api/envoy/api/v2/route/route.proto b/api/envoy/api/v2/route/route.proto index 0a3bb20ee749..a497a1871f9e 100644 --- a/api/envoy/api/v2/route/route.proto +++ b/api/envoy/api/v2/route/route.proto @@ -75,7 +75,7 @@ message VirtualHost { // Specifies a list of HTTP headers that should be added to each request // handled by this virtual host. Headers specified at this level are applied - // after headers from enclosed :ref:`envoy_api_msg_route.RouteAction` and before headers from the + // after headers from enclosed :ref:`envoy_api_msg_route.Route` and before headers from the // enclosing :ref:`envoy_api_msg_RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on :ref:`custom request headers // `. @@ -83,7 +83,7 @@ message VirtualHost { // Specifies a list of HTTP headers that should be added to each response // handled by this virtual host. Headers specified at this level are applied - // after headers from enclosed :ref:`envoy_api_msg_route.RouteAction` and before headers from the + // after headers from enclosed :ref:`envoy_api_msg_route.Route` and before headers from the // enclosing :ref:`envoy_api_msg_RouteConfiguration`. For more information, including // details on header value syntax, see the documentation on :ref:`custom request headers // `. @@ -148,6 +148,26 @@ message Route { // specific; see the :ref:`HTTP filter documentation ` for // if and how it is utilized. map per_filter_config = 8; + + // Specifies a set of headers that will be added to requests matching this + // route. Headers specified at this level are applied before headers from the + // enclosing :ref:`envoy_api_msg_route.VirtualHost` and + // :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on + // header value syntax, see the documentation on :ref:`custom request headers + // `. + repeated core.HeaderValueOption request_headers_to_add = 9; + + // Specifies a set of headers that will be added to responses to requests + // matching this route. Headers specified at this level are applied before + // headers from the enclosing :ref:`envoy_api_msg_route.VirtualHost` and + // :ref:`envoy_api_msg_RouteConfiguration`. For more information, including + // details on header value syntax, see the documentation on + // :ref:`custom request headers `. + repeated core.HeaderValueOption response_headers_to_add = 10; + + // Specifies a list of HTTP headers that should be removed from each response + // to requests matching this route. + repeated string response_headers_to_remove = 11; } // Compared to the :ref:`cluster ` field that specifies a @@ -176,8 +196,7 @@ message WeightedCluster { // Specifies a list of headers to be added to requests when this cluster is selected // through the enclosing :ref:`envoy_api_msg_route.RouteAction`. // Headers specified at this level are applied before headers from the enclosing - // :ref:`envoy_api_msg_route.RouteAction`, - // :ref:`envoy_api_msg_route.VirtualHost`, and + // :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_route.VirtualHost`, and // :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. @@ -186,8 +205,7 @@ message WeightedCluster { // Specifies a list of headers to be added to responses when this cluster is selected // through the enclosing :ref:`envoy_api_msg_route.RouteAction`. // Headers specified at this level are applied before headers from the enclosing - // :ref:`envoy_api_msg_route.RouteAction`, - // :ref:`envoy_api_msg_route.VirtualHost`, and + // :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_route.VirtualHost`, and // :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on // header value syntax, see the documentation on :ref:`custom request headers // `. @@ -492,25 +510,14 @@ message RouteAction { // https://github.com/lyft/protoc-gen-validate/issues/42 is resolved.] core.RoutingPriority priority = 11; - // Specifies a set of headers that will be added to requests matching this - // route. Headers specified at this level are applied before headers from the - // enclosing :ref:`envoy_api_msg_route.VirtualHost` and - // :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on - // header value syntax, see the documentation on :ref:`custom request headers - // `. - repeated core.HeaderValueOption request_headers_to_add = 12; + // [#not-implemented-hide:] + repeated core.HeaderValueOption request_headers_to_add = 12 [deprecated = true]; - // Specifies a set of headers that will be added to responses to requests - // matching this route. Headers specified at this level are applied before - // headers from the enclosing :ref:`envoy_api_msg_route.VirtualHost` and - // :ref:`envoy_api_msg_RouteConfiguration`. For more information, including - // details on header value syntax, see the documentation on - // :ref:`custom request headers `. - repeated core.HeaderValueOption response_headers_to_add = 18; + // [#not-implemented-hide:] + repeated core.HeaderValueOption response_headers_to_add = 18 [deprecated = true]; - // Specifies a list of HTTP headers that should be removed from each response - // to requests matching this route. - repeated string response_headers_to_remove = 19; + // [#not-implemented-hide:] + repeated string response_headers_to_remove = 19 [deprecated = true]; // Specifies a set of rate limit configurations that could be applied to the // route. @@ -711,8 +718,9 @@ message DirectResponseAction { // // .. note:: // - // Headers can be specified using *response_headers_to_add* in - // :ref:`envoy_api_msg_RouteConfiguration`. + // Headers can be specified using *response_headers_to_add* in the enclosing + // :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_RouteConfiguration` or + // :ref:`envoy_api_msg_route.VirtualHost`. core.DataSource body = 2; } diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index da45f64bc612..c1629a469788 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -43,6 +43,7 @@ Version history :ref:`use_data_plane_proto` boolean flag in the ratelimit configuration. Support for the legacy proto :repo:`source/common/ratelimit/ratelimit.proto` is deprecated and will be removed at the start of the 1.9.0 release cycle. +* router: added ability to set request/response headers at the :ref:`envoy_api_msg_route.Route` level. * tracing: added support for configuration of :ref:`tracing sampling `. * upstream: added configuration option to the subset load balancer to take locality weights into account when diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index 5288a8143640..d5301073fd1a 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -270,9 +270,13 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost, priority_(ConfigUtility::parsePriority(route.route().priority())), total_cluster_weight_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(route.route().weighted_clusters(), total_weight, 100UL)), - request_headers_parser_(HeaderParser::configure(route.route().request_headers_to_add())), - response_headers_parser_(HeaderParser::configure(route.route().response_headers_to_add(), - route.route().response_headers_to_remove())), + route_action_request_headers_parser_( + HeaderParser::configure(route.route().request_headers_to_add())), + route_action_response_headers_parser_(HeaderParser::configure( + route.route().response_headers_to_add(), route.route().response_headers_to_remove())), + request_headers_parser_(HeaderParser::configure(route.request_headers_to_add())), + response_headers_parser_(HeaderParser::configure(route.response_headers_to_add(), + route.response_headers_to_remove())), opaque_config_(parseOpaqueConfig(route)), decorator_(parseDecorator(route)), direct_response_code_(ConfigUtility::parseDirectResponseCode(route)), direct_response_body_(ConfigUtility::parseDirectResponseBody(route)), @@ -369,8 +373,10 @@ Http::WebSocketProxyPtr RouteEntryImplBase::createWebSocketProxy( void RouteEntryImplBase::finalizeRequestHeaders(Http::HeaderMap& headers, const RequestInfo::RequestInfo& request_info, bool insert_envoy_original_path) const { - // Append user-specified request headers in the following order: route-level headers, - // virtual host level headers and finally global connection manager level headers. + // Append user-specified request headers in the following order: route-action-level headers, + // route-level headers, virtual host level headers and finally global connection manager level + // headers. + route_action_request_headers_parser_->evaluateHeaders(headers, request_info); request_headers_parser_->evaluateHeaders(headers, request_info); vhost_.requestHeaderParser().evaluateHeaders(headers, request_info); vhost_.globalRouteConfig().requestHeaderParser().evaluateHeaders(headers, request_info); @@ -386,6 +392,10 @@ void RouteEntryImplBase::finalizeRequestHeaders(Http::HeaderMap& headers, void RouteEntryImplBase::finalizeResponseHeaders( Http::HeaderMap& headers, const RequestInfo::RequestInfo& request_info) const { + // Append user-specified response headers in the following order: route-action-level headers, + // route-level headers, virtual host level headers and finally global connection manager level + // headers. + route_action_response_headers_parser_->evaluateHeaders(headers, request_info); response_headers_parser_->evaluateHeaders(headers, request_info); vhost_.responseHeaderParser().evaluateHeaders(headers, request_info); vhost_.globalRouteConfig().responseHeaderParser().evaluateHeaders(headers, request_info); diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index e5c79a6c6222..238f54b99d51 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -359,8 +359,6 @@ class RouteEntryImplBase : public RouteEntry, void finalizePathHeader(Http::HeaderMap& headers, const std::string& matched_path, bool insert_envoy_original_path) const; - const HeaderParser& requestHeaderParser() const { return *request_headers_parser_; }; - const HeaderParser& responseHeaderParser() const { return *response_headers_parser_; }; private: struct RuntimeData { @@ -535,6 +533,8 @@ class RouteEntryImplBase : public RouteEntry, const uint64_t total_cluster_weight_; std::unique_ptr hash_policy_; MetadataMatchCriteriaConstPtr metadata_match_criteria_; + HeaderParserPtr route_action_request_headers_parser_; + HeaderParserPtr route_action_response_headers_parser_; HeaderParserPtr request_headers_parser_; HeaderParserPtr response_headers_parser_; envoy::api::v2::core::Metadata metadata_; diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 63c942be9dc6..eed552b9bc55 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -532,7 +532,7 @@ TEST(RouteMatcherTest, TestRoutesWithInvalidRegex) { EnvoyException, "Invalid regex '\\^/\\(\\+invalid\\)':"); } -// Validates behavior of request_headers_to_add at router, vhost, and route levels. +// Validates behavior of request_headers_to_add at router, vhost, and route action levels. TEST(RouteMatcherTest, TestAddRemoveRequestHeaders) { std::string json = R"EOF( { @@ -552,14 +552,14 @@ TEST(RouteMatcherTest, TestAddRemoveRequestHeaders) { "request_headers_to_add": [ {"key": "x-global-header1", "value": "route-override"}, {"key": "x-vhost-header1", "value": "route-override"}, - {"key": "x-route-header", "value": "route-new_endpoint"} + {"key": "x-route-action-header", "value": "route-new_endpoint"} ] }, { "path": "/", "cluster": "root_www2", "request_headers_to_add": [ - {"key": "x-route-header", "value": "route-allpath"} + {"key": "x-route-action-header", "value": "route-allpath"} ] }, { @@ -579,7 +579,7 @@ TEST(RouteMatcherTest, TestAddRemoveRequestHeaders) { "prefix": "/", "cluster": "www2_staging", "request_headers_to_add": [ - {"key": "x-route-header", "value": "route-allprefix"} + {"key": "x-route-action-header", "value": "route-allprefix"} ] } ] @@ -629,7 +629,7 @@ TEST(RouteMatcherTest, TestAddRemoveRequestHeaders) { route->finalizeRequestHeaders(headers, request_info, true); EXPECT_EQ("route-override", headers.get_("x-global-header1")); EXPECT_EQ("route-override", headers.get_("x-vhost-header1")); - EXPECT_EQ("route-new_endpoint", headers.get_("x-route-header")); + EXPECT_EQ("route-new_endpoint", headers.get_("x-route-action-header")); } // Multiple routes can have same route-level headers with different values. @@ -639,7 +639,7 @@ TEST(RouteMatcherTest, TestAddRemoveRequestHeaders) { route->finalizeRequestHeaders(headers, request_info, true); EXPECT_EQ("vhost-override", headers.get_("x-global-header1")); EXPECT_EQ("vhost1-www2", headers.get_("x-vhost-header1")); - EXPECT_EQ("route-allpath", headers.get_("x-route-header")); + EXPECT_EQ("route-allpath", headers.get_("x-route-action-header")); } // Multiple virtual hosts can have same virtual host level headers with different values. @@ -649,7 +649,7 @@ TEST(RouteMatcherTest, TestAddRemoveRequestHeaders) { route->finalizeRequestHeaders(headers, request_info, true); EXPECT_EQ("global1", headers.get_("x-global-header1")); EXPECT_EQ("vhost1-www2_staging", headers.get_("x-vhost-header1")); - EXPECT_EQ("route-allprefix", headers.get_("x-route-header")); + EXPECT_EQ("route-allprefix", headers.get_("x-route-action-header")); } // Global headers. @@ -662,8 +662,8 @@ TEST(RouteMatcherTest, TestAddRemoveRequestHeaders) { } } -// Validates behavior of request_headers_to_add at router, vhost, and route levels when append -// is disabled. +// Validates behavior of request_headers_to_add at router, vhost, route, and route action levels +// when append is disabled. TEST(RouteMatcherTest, TestRequestHeadersToAddWithAppendFalse) { std::string yaml = R"EOF( name: foo @@ -681,20 +681,36 @@ name: foo append: false routes: - match: { prefix: "/endpoint" } + request_headers_to_add: + - header: + key: x-global-header + value: route-endpoint + append: false + - header: + key: x-vhost-header + value: route-endpoint + append: false + - header: + key: x-route-header + value: route-endpoint + append: false route: cluster: www2 request_headers_to_add: - header: key: x-global-header - value: route-endpoint + value: route-action-endpoint append: false - header: key: x-vhost-header - value: route-endpoint + value: route-action-endpoint append: false - header: key: x-route-header - value: route-endpoint + value: route-action-endpoint + - header: + key: x-route-action-header + value: route-action-endpoint append: false - match: { prefix: "/" } route: { cluster: www2 } @@ -714,7 +730,7 @@ name: foo // Request header manipulation testing. { - // Global and virtual host override route. + // Global and virtual host override route, route overrides route action. { Http::TestHeaderMapImpl headers = genHeaders("www.lyft.com", "/endpoint", "GET"); const RouteEntry* route = config.route(headers, 0)->routeEntry(); @@ -722,6 +738,7 @@ name: foo EXPECT_EQ("global", headers.get_("x-global-header")); EXPECT_EQ("vhost-www2", headers.get_("x-vhost-header")); EXPECT_EQ("route-endpoint", headers.get_("x-route-header")); + EXPECT_EQ("route-action-endpoint", headers.get_("x-route-action-header")); } // Global overrides virtual host. @@ -736,7 +753,7 @@ name: foo } // Validates behavior of response_headers_to_add and response_headers_to_remove at router, vhost, -// and route levels. +// route, and route action levels. TEST(RouteMatcherTest, TestAddRemoveResponseHeaders) { std::string yaml = R"EOF( name: foo @@ -753,6 +770,10 @@ name: foo response_headers_to_remove: ["x-vhost-remove"] routes: - match: { prefix: "/new_endpoint" } + response_headers_to_add: + - header: + key: x-route-header + value: route-override route: prefix_rewrite: "/api/new_endpoint" cluster: www2 @@ -764,14 +785,14 @@ name: foo key: x-vhost-header1 value: route-override - header: - key: x-route-header + key: x-route-action-header value: route-new_endpoint - match: { path: "/" } route: cluster: root_www2 response_headers_to_add: - header: - key: x-route-header + key: x-route-action-header value: route-allpath response_headers_to_remove: ["x-route-remove"] - match: { prefix: "/" } @@ -788,7 +809,7 @@ name: foo cluster: www2_staging response_headers_to_add: - header: - key: x-route-header + key: x-route-action-header value: route-allprefix - name: default domains: ["*"] @@ -817,7 +838,8 @@ response_headers_to_remove: ["x-global-remove"] route->finalizeResponseHeaders(headers, request_info); EXPECT_EQ("route-override", headers.get_("x-global-header1")); EXPECT_EQ("route-override", headers.get_("x-vhost-header1")); - EXPECT_EQ("route-new_endpoint", headers.get_("x-route-header")); + EXPECT_EQ("route-new_endpoint", headers.get_("x-route-action-header")); + EXPECT_EQ("route-override", headers.get_("x-route-header")); } // Multiple routes can have same route-level headers with different values. @@ -828,7 +850,7 @@ response_headers_to_remove: ["x-global-remove"] route->finalizeResponseHeaders(headers, request_info); EXPECT_EQ("vhost-override", headers.get_("x-global-header1")); EXPECT_EQ("vhost1-www2", headers.get_("x-vhost-header1")); - EXPECT_EQ("route-allpath", headers.get_("x-route-header")); + EXPECT_EQ("route-allpath", headers.get_("x-route-action-header")); } // Multiple virtual hosts can have same virtual host level headers with different values. @@ -839,7 +861,7 @@ response_headers_to_remove: ["x-global-remove"] route->finalizeResponseHeaders(headers, request_info); EXPECT_EQ("global1", headers.get_("x-global-header1")); EXPECT_EQ("vhost1-www2_staging", headers.get_("x-vhost-header1")); - EXPECT_EQ("route-allprefix", headers.get_("x-route-header")); + EXPECT_EQ("route-allprefix", headers.get_("x-route-action-header")); } // Global headers. From 845923714078e5a3528e85d4b58f2cc95850a1c6 Mon Sep 17 00:00:00 2001 From: Anirudh Date: Wed, 25 Jul 2018 23:28:44 +0530 Subject: [PATCH 35/46] fuzz: fixes oss-fuzz: 8363 (#3905) oss-fuzz issue (8363): https://oss-fuzz.com/v2/testcase-detail/5988544525893632 The crash was because of passing nan to Envoy::ProtobufPercentHelper::convertPercent, it asserts since it is not in the numeric range. Instead of adding a check in this function, have added a check in the preprocessor so that it goes to checkAndReturnDefault and the default value is used. Have also added the crashing testcase to the corpus. Risk Level: Low Testing: Tested unit tests (bazel test //server:server_fuzz_test), built and ran fuzzers with oss-fuzz. Signed-off-by: Anirudh M --- source/common/protobuf/utility.h | 11 +++++-- test/common/protobuf/utility_test.cc | 9 ++++++ ...testcase-server_fuzz_test-5988544525893632 | 29 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5988544525893632 diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 9229d6eebcce..93d098c75d73 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -71,11 +71,16 @@ uint64_t fractionalPercentDenominatorToInt(const envoy::type::FractionalPercent& // @param field_name supplies the field name in the message. // @param max_value supplies the maximum allowed integral value (e.g., 100, 10000, etc.). // @param default_value supplies the default if the field is not present. +// +// TODO(anirudhmurali): Recommended to capture and validate NaN values in PGV +// Issue: https://github.com/lyft/protoc-gen-validate/issues/85 #define PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(message, field_name, max_value, \ default_value) \ - ((message).has_##field_name() \ - ? ProtobufPercentHelper::convertPercent((message).field_name().value(), max_value) \ - : ProtobufPercentHelper::checkAndReturnDefault(default_value, max_value)) + (!std::isnan((message).field_name().value()) \ + ? (message).has_##field_name() \ + ? ProtobufPercentHelper::convertPercent((message).field_name().value(), max_value) \ + : ProtobufPercentHelper::checkAndReturnDefault(default_value, max_value) \ + : throw EnvoyException(fmt::format("Value not in the range of 0..100 range."))) namespace Envoy { diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index e7f5ee34fd40..15dbfa1a55f5 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -13,6 +13,15 @@ namespace Envoy { +TEST(UtilityTest, convertPercentNaN) { + envoy::api::v2::Cluster::CommonLbConfig common_config_; + common_config_.mutable_healthy_panic_threshold()->set_value( + std::numeric_limits::quiet_NaN()); + EXPECT_THROW(PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config_, + healthy_panic_threshold, 100, 50), + EnvoyException); +} + TEST(UtilityTest, RepeatedPtrUtilDebugString) { Protobuf::RepeatedPtrField repeated; EXPECT_EQ("[]", RepeatedPtrUtil::debugString(repeated)); diff --git a/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5988544525893632 b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5988544525893632 new file mode 100644 index 000000000000..5c8b2ec2c49e --- /dev/null +++ b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5988544525893632 @@ -0,0 +1,29 @@ +static_resources { + clusters { + name: "-2353373969551157135775236" + connect_timeout { + seconds: 12884901890 + } + hosts { + pipe { + path: "@" + } + } + outlier_detection { + } + common_lb_config { + healthy_panic_threshold { + value: nan + } + } + } +} +admin { + access_log_path: "@r" + address { + pipe { + path: "W" + } + } +} + From f3b110091c0a0e1ae3abd4ce245d57296e1e868d Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 25 Jul 2018 15:58:59 -0400 Subject: [PATCH 36/46] websocket: fixing websocket to consistently not send connection: close when draining (#3952) Using the isUpgrade utility for consistent handling of upgrade strings w.r.t. case sensitivity. Risk Level: Low (should only affect WebSocket, only when draining) Testing: new regression unit test Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- source/common/http/conn_manager_impl.cc | 2 +- test/common/http/conn_manager_impl_test.cc | 59 ++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index ab0f7bbf935d..6663b29c5479 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1006,7 +1006,7 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ActiveStreamEncoderFilte // If the connection manager is draining send "Connection: Close" on HTTP/1.1 connections. // Do not do this for H2 (which drains via GOAWA) or Upgrade (as the upgrade // payload is no longer HTTP/1.1) - if (headers.Connection() == nullptr || headers.Connection()->value() != "Upgrade") { + if (!Utility::isUpgrade(headers)) { headers.insertConnection().value().setReference(Headers::get().ConnectionValues.Close); } } diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 66a564a7a98e..19219f748ec9 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -1856,6 +1856,65 @@ TEST_F(HttpConnectionManagerImplTest, WebSocketEarlyEndStream) { conn_manager_.reset(); } +// Make sure for upgrades, we do not append Connection: Close when draining. +TEST_F(HttpConnectionManagerImplTest, FooUpgradeDrainClose) { + setup(false, "envoy-custom-server", false); + + // Store the basic request encoder during filter chain setup. + MockStreamFilter* filter = new MockStreamFilter(); + EXPECT_CALL(drain_close_, drainClose()).WillOnce(Return(true)); + + EXPECT_CALL(*filter, decodeHeaders(_, false)) + .WillRepeatedly(Invoke([&](HeaderMap&, bool) -> FilterHeadersStatus { + return FilterHeadersStatus::StopIteration; + })); + + EXPECT_CALL(*filter, encodeHeaders(_, false)) + .WillRepeatedly(Invoke( + [&](HeaderMap&, bool) -> FilterHeadersStatus { return FilterHeadersStatus::Continue; })); + + NiceMock encoder; + EXPECT_CALL(encoder, encodeHeaders(_, false)) + .WillOnce(Invoke([&](const HeaderMap& headers, bool) -> void { + EXPECT_NE(nullptr, headers.Connection()); + EXPECT_STREQ("upgrade", headers.Connection()->value().c_str()); + })); + + EXPECT_CALL(*filter, setDecoderFilterCallbacks(_)).Times(1); + EXPECT_CALL(*filter, setEncoderFilterCallbacks(_)).Times(1); + + EXPECT_CALL(filter_factory_, createUpgradeFilterChain(_, _)) + .WillRepeatedly( + Invoke([&](absl::string_view, FilterChainFactoryCallbacks& callbacks) -> bool { + callbacks.addStreamFilter(StreamFilterSharedPtr{filter}); + return true; + })); + + // When dispatch is called on the codec, we pretend to get a new stream and then fire a headers + // only request into it. Then we respond into the filter. + StreamDecoder* decoder = nullptr; + EXPECT_CALL(*codec_, dispatch(_)).WillRepeatedly(Invoke([&](Buffer::Instance& data) -> void { + decoder = &conn_manager_->newStream(encoder); + + HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, + {":method", "GET"}, + {":path", "/"}, + {"connection", "Upgrade"}, + {"upgrade", "foo"}}}; + decoder->decodeHeaders(std::move(headers), false); + + HeaderMapPtr response_headers{ + new TestHeaderMapImpl{{":status", "101"}, {"Connection", "upgrade"}, {"upgrade", "foo"}}}; + filter->decoder_callbacks_->encodeHeaders(std::move(response_headers), false); + + data.drain(4); + })); + + // Kick off the incoming data. Use extra data which should cause a redispatch. + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); +} + TEST_F(HttpConnectionManagerImplTest, DrainClose) { setup(true, ""); From 3ee3aa34d826fa1783c201fbf99dfec3a21eed53 Mon Sep 17 00:00:00 2001 From: vishalpowar Date: Wed, 25 Jul 2018 15:26:48 -0700 Subject: [PATCH 37/46] Add support for drop category policy and reporting (#3894) This PR contains changes to implement feature requested in issue #3823 - Adding DropOverload in eds policy which can be used to specify drop_percentage per category. - Adding DroppedRequests in load_report which can report deliberately dropped requests for each category. Signed-off-by: vishalpowar --- api/envoy/api/v2/BUILD | 2 ++ api/envoy/api/v2/eds.proto | 36 +++++++++++++++++---- api/envoy/api/v2/endpoint/load_report.proto | 10 ++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/api/envoy/api/v2/BUILD b/api/envoy/api/v2/BUILD index 09bdf6ddbf2a..261d14081998 100644 --- a/api/envoy/api/v2/BUILD +++ b/api/envoy/api/v2/BUILD @@ -40,6 +40,7 @@ api_proto_library_internal( "//envoy/api/v2/core:base", "//envoy/api/v2/core:health_check", "//envoy/api/v2/endpoint", + "//envoy/type:percent", ], ) @@ -52,6 +53,7 @@ api_go_grpc_library( "//envoy/api/v2/core:base_go_proto", "//envoy/api/v2/core:health_check_go_proto", "//envoy/api/v2/endpoint:endpoint_go_proto", + "//envoy/type:percent_go_proto", ], ) diff --git a/api/envoy/api/v2/eds.proto b/api/envoy/api/v2/eds.proto index 0c63fbaa5848..d4bdc93e16a2 100644 --- a/api/envoy/api/v2/eds.proto +++ b/api/envoy/api/v2/eds.proto @@ -6,6 +6,7 @@ option java_generic_services = true; import "envoy/api/v2/discovery.proto"; import "envoy/api/v2/endpoint/endpoint.proto"; +import "envoy/type/percent.proto"; import "google/api/annotations.proto"; @@ -50,12 +51,35 @@ message ClusterLoadAssignment { // Load balancing policy settings. message Policy { - // Percentage of traffic (0-100) that should be dropped. This - // action allows protection of upstream hosts should they unable to - // recover from an outage or should they be unable to autoscale and hence - // overall incoming traffic volume need to be trimmed to protect them. - // [#v2-api-diff: This is known as maintenance mode in v1.] - double drop_overload = 1 [(validate.rules).double = {gte: 0, lte: 100}]; + reserved 1; + + message DropOverload { + // Identifier for the policy specifying the drop. + string category = 1 [(validate.rules).string.min_bytes = 1]; + + // Percentage of traffic that should be dropped for the category. + envoy.type.Percent drop_percentage = 2; + } + // Action to trim the overall incoming traffic to protect the upstream + // hosts. This action allows protection in case the hosts are unable to + // recover from an outage, or unable to autoscale or unable to handle + // incoming traffic volume for any reason. + // + // At the client each category is applied one after the other to generate + // the 'actual' drop percentage on all outgoing traffic. For example: + // + // .. code-block:: json + // + // { "drop_overloads": [ + // { "category": "throttle", "drop_percentage": 60 } + // { "category": "lb", "drop_percentage": 50 } + // ]} + // + // The actual drop percentages applied to the traffic at the clients will be + // "throttle"_drop = 60% + // "lb"_drop = 20% // 50% of the remaining 'actual' load, which is 40%. + // actual_outgoing_load = 20% // remaining after applying all categories. + repeated DropOverload drop_overloads = 2; } // Load balancing policy settings. diff --git a/api/envoy/api/v2/endpoint/load_report.proto b/api/envoy/api/v2/endpoint/load_report.proto index 80d8b669d494..45ca3a168bba 100644 --- a/api/envoy/api/v2/endpoint/load_report.proto +++ b/api/envoy/api/v2/endpoint/load_report.proto @@ -96,6 +96,16 @@ message ClusterStats { // deliberately dropped by the drop_overload policy and circuit breaking. uint64 total_dropped_requests = 3; + message DroppedRequests { + // Identifier for the policy specifying the drop. + string category = 1 [(validate.rules).string.min_bytes = 1]; + // Total number of deliberately dropped requests for the category. + uint64 dropped_count = 2; + } + // Information about deliberately dropped requests for each category specified + // in the DropOverload policy. + repeated DroppedRequests dropped_requests = 5; + // Period over which the actual load report occurred. This will be guaranteed to include every // request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy // and the *LoadStatsResponse* message sent from the management server, this may be longer than From 9b64f4bfc1ccaaf44169a2b1ddf97afca959d469 Mon Sep 17 00:00:00 2001 From: Sam Smith Date: Wed, 25 Jul 2018 20:04:37 -0400 Subject: [PATCH 38/46] build: BUILD file changes necessary for #3892 (#3909) As requested in #3892, breaking the BUILD file changes out into a separate PR Risk Level: Low Testing: bazel build //source/exe:envoy-static and bazel test //test/... on Linux Docs Changes: None Release Notes: None Signed-off-by: Sam Smith --- .circleci/config.yml | 2 +- bazel/envoy_build_system.bzl | 10 ---------- bazel/target_recipes.bzl | 1 - ci/prebuilt/BUILD | 10 ---------- source/common/event/BUILD | 8 ++++++-- source/common/filesystem/BUILD | 4 +++- 6 files changed, 10 insertions(+), 25 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 31cb68053a67..5abbabeff266 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ references: envoy-build-image: &envoy-build-image - envoyproxy/envoy-build:18cdf0f806b66bef60ee64248352a8b3bc4ace7d + envoyproxy/envoy-build:1ef23d481a4701ad4a414d1ef98036bd2ed322e7 version: 2 jobs: diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl index e43460ccf202..9f79cdf88565 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -144,13 +144,6 @@ def tcmalloc_external_deps(repository): "//conditions:default": [envoy_external_dep_path("tcmalloc_and_profiler")], }) -# Dependencies on libevent should be wrapped with this function. -def libevent_external_deps(repository): - return [envoy_external_dep_path("event")] + select({ - repository + "//bazel:windows_x86_64": [], - "//conditions:default": [envoy_external_dep_path("event_pthreads")], - }) - # Transform the package path (e.g. include/envoy/common) into a path for # exporting the package headers at (e.g. envoy/common). Source files can then # include using this path scheme (e.g. #include "envoy/common/time.h"). @@ -175,7 +168,6 @@ def envoy_cc_library( visibility = None, external_deps = [], tcmalloc_dep = None, - libevent_dep = None, repository = "", linkstamp = None, tags = [], @@ -183,8 +175,6 @@ def envoy_cc_library( strip_include_prefix = None): if tcmalloc_dep: deps += tcmalloc_external_deps(repository) - if libevent_dep: - deps += libevent_external_deps(repository) native.cc_library( name = name, diff --git a/bazel/target_recipes.bzl b/bazel/target_recipes.bzl index 002780148a4e..626033688792 100644 --- a/bazel/target_recipes.bzl +++ b/bazel/target_recipes.bzl @@ -5,7 +5,6 @@ TARGET_RECIPES = { "ares": "cares", "benchmark": "benchmark", "event": "libevent", - "event_pthreads": "libevent", "tcmalloc_and_profiler": "gperftools", "luajit": "luajit", "nghttp2": "nghttp2", diff --git a/ci/prebuilt/BUILD b/ci/prebuilt/BUILD index 4f85e1a6b21c..8997736ea30a 100644 --- a/ci/prebuilt/BUILD +++ b/ci/prebuilt/BUILD @@ -37,16 +37,6 @@ cc_library( includes = ["thirdparty_build/include"], ) -cc_library( - # TODO: Remove 'event_pthreads' once no BUIlD files use it - # glob here is due to the fact that ninja doesn't produce a libevent_pthreads.a file - - # all the symbols are included in libevent.a. This is a hack so that CI can pass before - # https://github.com/envoyproxy/envoy/pull/3909 is merged as well - name = "event_pthreads", - srcs = glob(["thirdparty_build/lib/libevent_pthreads.a"]), - deps = [":event"], -) - cc_library( name = "luajit", srcs = select({ diff --git a/source/common/event/BUILD b/source/common/event/BUILD index 85c0bc7d7327..fe1eb52e3109 100644 --- a/source/common/event/BUILD +++ b/source/common/event/BUILD @@ -57,7 +57,9 @@ envoy_cc_library( name = "libevent_lib", srcs = ["libevent.cc"], hdrs = ["libevent.h"], - libevent_dep = 1, + external_deps = [ + "event", + ], deps = [ "//source/common/common:assert_lib", "//source/common/common:c_smart_ptr_lib", @@ -68,7 +70,9 @@ envoy_cc_library( name = "dispatched_thread_lib", srcs = ["dispatched_thread.cc"], hdrs = ["dispatched_thread.h"], - libevent_dep = 1, + external_deps = [ + "event", + ], deps = [ ":dispatcher_lib", "//include/envoy/event:dispatcher_interface", diff --git a/source/common/filesystem/BUILD b/source/common/filesystem/BUILD index 1c2b286e07b5..f357a1295769 100644 --- a/source/common/filesystem/BUILD +++ b/source/common/filesystem/BUILD @@ -40,7 +40,9 @@ envoy_cc_library( "inotify/watcher_impl.h", ], }), - libevent_dep = 1, + external_deps = [ + "event", + ], strip_include_prefix = select({ "@bazel_tools//tools/osx:darwin": "kqueue", "//conditions:default": "inotify", From 5beff991a5f525c51f68d10e83b44637253d40f6 Mon Sep 17 00:00:00 2001 From: KeZhang Date: Thu, 26 Jul 2018 12:30:41 +0800 Subject: [PATCH 39/46] Fix run_envoy_docker.sh 'do_ci.sh ' does not work behind proxy (#3956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 张可10140699 --- ci/run_envoy_docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index d52172671f73..3fd04a01e283 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -18,7 +18,7 @@ USER_GROUP=root mkdir -p "${ENVOY_DOCKER_BUILD_DIR}" # Since we specify an explicit hash, docker-run will pull from the remote repo if missing. -docker run --rm -t -i -e http_proxy=${http_proxy} -e https_proxy=${https_proxy} \ +docker run --rm -t -i -e HTTP_PROXY=${http_proxy} -e HTTPS_PROXY=${https_proxy} \ -u "${USER}":"${USER_GROUP}" -v "${ENVOY_DOCKER_BUILD_DIR}":/build \ -v "$PWD":/source -e NUM_CPUS --cap-add SYS_PTRACE "${IMAGE_NAME}":"${IMAGE_ID}" \ /bin/bash -lc "groupadd --gid $(id -g) -f envoygroup && useradd -o --uid $(id -u) --gid $(id -g) \ From 72a964c7b6f52746f4f7c357dcf667e533470567 Mon Sep 17 00:00:00 2001 From: Stephan Zuercher Date: Thu, 26 Jul 2018 08:57:53 -0700 Subject: [PATCH 40/46] thrift_proxy: simple thrift router (#3863) Provides a very basic thrift router that can route to clusters based on method name only. A Thrift DecoderFilter interface is introduced, but the only available filter is the Router. The Network filter and router are capable of translating transports and protocols but presently cannot be configured to do so. Relates to #2247. *Risk Level*: low *Testing*: unit and integration testing *Docs Changes*: protobuf documentation updated *Release Notes*: introduced a basic thrift_proxy routing extension Signed-off-by: Stephan Zuercher --- .../network/thrift_proxy/v2alpha1/BUILD | 5 +- .../network/thrift_proxy/v2alpha1/route.proto | 41 + .../thrift_proxy/v2alpha1/router/BUILD | 8 + .../thrift_proxy/v2alpha1/router/router.proto | 9 + .../thrift_proxy/v2alpha1/thrift_proxy.proto | 44 + source/extensions/extensions_build_config.bzl | 6 + .../filters/network/thrift_proxy/BUILD | 84 +- .../thrift_proxy/app_exception_impl.cc | 34 + .../network/thrift_proxy/app_exception_impl.h | 44 + .../thrift_proxy/binary_protocol_impl.cc | 27 +- .../thrift_proxy/binary_protocol_impl.h | 7 +- .../network/thrift_proxy/buffer_helper.h | 51 -- .../thrift_proxy/compact_protocol_impl.cc | 19 +- .../thrift_proxy/compact_protocol_impl.h | 5 +- .../filters/network/thrift_proxy/config.cc | 109 ++- .../filters/network/thrift_proxy/config.h | 41 + .../network/thrift_proxy/conn_manager.cc | 290 +++++++ .../network/thrift_proxy/conn_manager.h | 256 ++++++ .../filters/network/thrift_proxy/decoder.cc | 266 +++--- .../filters/network/thrift_proxy/decoder.h | 92 ++- .../filters/network/thrift_proxy/filter.cc | 310 ------- .../filters/network/thrift_proxy/filter.h | 173 ---- .../network/thrift_proxy/filters/BUILD | 50 ++ .../thrift_proxy/filters/factory_base.h | 45 ++ .../network/thrift_proxy/filters/filter.h | 306 +++++++ .../thrift_proxy/filters/filter_config.h | 55 ++ .../thrift_proxy/filters/well_known_names.h | 25 + .../thrift_proxy/framed_transport_impl.cc | 29 +- .../thrift_proxy/framed_transport_impl.h | 7 +- .../filters/network/thrift_proxy/protocol.h | 127 +-- .../network/thrift_proxy/protocol_converter.h | 144 ++++ .../network/thrift_proxy/protocol_impl.cc | 14 +- .../network/thrift_proxy/protocol_impl.h | 33 +- .../filters/network/thrift_proxy/router/BUILD | 51 ++ .../network/thrift_proxy/router/config.cc | 34 + .../network/thrift_proxy/router/config.h | 32 + .../network/thrift_proxy/router/router.h | 62 ++ .../thrift_proxy/router/router_impl.cc | 305 +++++++ .../network/thrift_proxy/router/router_impl.h | 160 ++++ .../filters/network/thrift_proxy/stats.h | 52 ++ .../filters/network/thrift_proxy/transport.h | 112 ++- .../network/thrift_proxy/transport_impl.cc | 18 +- .../network/thrift_proxy/transport_impl.h | 28 +- .../thrift_proxy/unframed_transport_impl.cc | 22 + .../thrift_proxy/unframed_transport_impl.h | 14 +- .../filters/network/thrift_proxy/BUILD | 56 +- .../thrift_proxy/binary_protocol_impl_test.cc | 134 +-- .../thrift_proxy/buffer_helper_test.cc | 44 - .../compact_protocol_impl_test.cc | 157 ++-- .../network/thrift_proxy/config_test.cc | 4 +- .../network/thrift_proxy/conn_manager_test.cc | 760 ++++++++++++++++++ .../network/thrift_proxy/decoder_test.cc | 396 +++++++-- .../network/thrift_proxy/filter_test.cc | 559 ------------- .../framed_transport_impl_test.cc | 54 +- ...ntegration_test.cc => integration_test.cc} | 41 +- .../filters/network/thrift_proxy/mocks.cc | 66 +- .../filters/network/thrift_proxy/mocks.h | 132 ++- .../thrift_proxy/protocol_impl_test.cc | 35 +- .../network/thrift_proxy/router_test.cc | 631 +++++++++++++++ .../thrift_proxy/transport_impl_test.cc | 83 +- .../unframed_transport_impl_test.cc | 32 +- test/mocks/tcp/mocks.cc | 3 +- 62 files changed, 4955 insertions(+), 1878 deletions(-) create mode 100644 api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.proto create mode 100644 api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/BUILD create mode 100644 api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.proto create mode 100644 source/extensions/filters/network/thrift_proxy/app_exception_impl.cc create mode 100644 source/extensions/filters/network/thrift_proxy/app_exception_impl.h create mode 100644 source/extensions/filters/network/thrift_proxy/conn_manager.cc create mode 100644 source/extensions/filters/network/thrift_proxy/conn_manager.h delete mode 100644 source/extensions/filters/network/thrift_proxy/filter.cc delete mode 100644 source/extensions/filters/network/thrift_proxy/filter.h create mode 100644 source/extensions/filters/network/thrift_proxy/filters/BUILD create mode 100644 source/extensions/filters/network/thrift_proxy/filters/factory_base.h create mode 100644 source/extensions/filters/network/thrift_proxy/filters/filter.h create mode 100644 source/extensions/filters/network/thrift_proxy/filters/filter_config.h create mode 100644 source/extensions/filters/network/thrift_proxy/filters/well_known_names.h create mode 100644 source/extensions/filters/network/thrift_proxy/protocol_converter.h create mode 100644 source/extensions/filters/network/thrift_proxy/router/BUILD create mode 100644 source/extensions/filters/network/thrift_proxy/router/config.cc create mode 100644 source/extensions/filters/network/thrift_proxy/router/config.h create mode 100644 source/extensions/filters/network/thrift_proxy/router/router.h create mode 100644 source/extensions/filters/network/thrift_proxy/router/router_impl.cc create mode 100644 source/extensions/filters/network/thrift_proxy/router/router_impl.h create mode 100644 source/extensions/filters/network/thrift_proxy/stats.h create mode 100644 source/extensions/filters/network/thrift_proxy/unframed_transport_impl.cc create mode 100644 test/extensions/filters/network/thrift_proxy/conn_manager_test.cc delete mode 100644 test/extensions/filters/network/thrift_proxy/filter_test.cc rename test/extensions/filters/network/thrift_proxy/{filter_integration_test.cc => integration_test.cc} (88%) create mode 100644 test/extensions/filters/network/thrift_proxy/router_test.cc diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/BUILD b/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/BUILD index 60540cd8079a..da39334babd1 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/BUILD +++ b/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/BUILD @@ -4,5 +4,8 @@ licenses(["notice"]) # Apache 2 api_proto_library_internal( name = "thrift_proxy", - srcs = ["thrift_proxy.proto"], + srcs = [ + "route.proto", + "thrift_proxy.proto", + ], ) diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.proto b/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.proto new file mode 100644 index 000000000000..5c9af1c48755 --- /dev/null +++ b/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package envoy.extensions.filters.network.thrift_proxy.v2alpha1; +option go_package = "v2"; + +import "validate/validate.proto"; +import "gogoproto/gogo.proto"; + +// [#protodoc-title: Thrift route configuration] + +// [#comment:next free field: 3] +message RouteConfiguration { + // The name of the route configuration. Reserved for future use in asynchronous route discovery. + string name = 1; + + // The list of routes that will be matched, in order, against incoming requests. The first route + // that matches will be used. + repeated Route routes = 2 [(gogoproto.nullable) = false]; +} + +// [#comment:next free field: 3] +message Route { + // Route matching prarameters. + RouteMatch match = 1 [(validate.rules).message.required = true, (gogoproto.nullable) = false]; + + // Route request to some upstream cluster. + RouteAction route = 2 [(validate.rules).message.required = true, (gogoproto.nullable) = false]; +} + +// [#comment:next free field: 2] +message RouteMatch { + // If specified, the route must exactly match the request method name. As a special case, an + // empty string matches any request method name. + string method = 1; +} + +// [#comment:next free field: 2] +message RouteAction { + // Indicates the upstream cluster to which the request should be routed. + string cluster = 1 [(validate.rules).string.min_bytes = 1]; +} diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/BUILD b/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/BUILD new file mode 100644 index 000000000000..ce0ad0e254f0 --- /dev/null +++ b/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/BUILD @@ -0,0 +1,8 @@ +load("//bazel:api_build_system.bzl", "api_proto_library_internal") + +licenses(["notice"]) # Apache 2 + +api_proto_library_internal( + name = "router", + srcs = ["router.proto"], +) diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.proto b/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.proto new file mode 100644 index 000000000000..2818c044e7b0 --- /dev/null +++ b/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package envoy.extensions.filters.network.thrift_proxy.v2alpha1.router; +option go_package = "router"; + +// [#protodoc-title: Thrift Router] +// Thrift Router configuration. +message Router { +} diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.proto b/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.proto index e2d6bd02cb26..889729227320 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.proto +++ b/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.proto @@ -3,11 +3,55 @@ syntax = "proto3"; package envoy.extensions.filters.network.thrift_proxy.v2alpha1; option go_package = "v2"; +import "envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.proto"; + import "validate/validate.proto"; +import "gogoproto/gogo.proto"; // [#protodoc-title: Extensions Thrift Proxy] // Thrift Proxy filter configuration. +// [#comment:next free field: 5] message ThriftProxy { + enum TransportType { + option (gogoproto.goproto_enum_prefix) = false; + + // For every new connection, the Thrift proxy will determine which transport to use. + AUTO_TRANSPORT = 0; + + // The Thrift proxy will assume the client is using the Thrift framed transport. + FRAMED = 1; + + // The Thrift proxy will assume the client is using the Thrift unframed transport. + UNFRAMED = 2; + } + + // Supplies the type of transport that the Thrift proxy should use. Defaults to `AUTO_TRANSPORT`. + TransportType transport = 2 [(validate.rules).enum.defined_only = true]; + + enum ProtocolType { + option (gogoproto.goproto_enum_prefix) = false; + + // For every new connection, the Thrift proxy will determine which protocol to use. + // N.B. The older, non-strict binary protocol is not included in automatic protocol + // detection. + AUTO_PROTOCOL = 0; + + // The Thrift proxy will assume the client is using the Thrift binary protocol. + BINARY = 1; + + // The Thrift proxy will assume the client is using the Thrift non-strict binary protocol. + LAX_BINARY = 2; + + // The Thrift proxy will assume the client is using the Thrift compact protocol. + COMPACT = 3; + } + + // Supplies the type of protocol that the Thrift proxy should use. Defaults to `AUTO_PROTOCOL`. + ProtocolType protocol = 3 [(validate.rules).enum.defined_only = true]; + // The human readable prefix to use when emitting statistics. string stat_prefix = 1 [(validate.rules).string.min_bytes = 1]; + + // The route table for the connection manager is static and is specified in this property. + RouteConfiguration route_config = 4; } diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index bafaaad92579..736f8c319c72 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -85,6 +85,12 @@ EXTENSIONS = { "envoy.stat_sinks.metrics_service": "//source/extensions/stat_sinks/metrics_service:config", "envoy.stat_sinks.statsd": "//source/extensions/stat_sinks/statsd:config", + # + # Thrift filters + # + + "envoy.filters.thrift.router": "//source/extensions/filters/network/thrift_proxy/router:config", + # # Tracers # diff --git a/source/extensions/filters/network/thrift_proxy/BUILD b/source/extensions/filters/network/thrift_proxy/BUILD index 48555347641b..c69373d53e37 100644 --- a/source/extensions/filters/network/thrift_proxy/BUILD +++ b/source/extensions/filters/network/thrift_proxy/BUILD @@ -8,6 +8,17 @@ load( envoy_package() +envoy_cc_library( + name = "app_exception_lib", + srcs = ["app_exception_impl.cc"], + hdrs = ["app_exception_impl.h"], + deps = [ + ":protocol_interface", + "//include/envoy/buffer:buffer_interface", + "//source/extensions/filters/network/thrift_proxy/filters:filter_interface", + ], +) + envoy_cc_library( name = "buffer_helper_lib", srcs = ["buffer_helper.cc"], @@ -24,41 +35,68 @@ envoy_cc_library( srcs = ["config.cc"], hdrs = ["config.h"], deps = [ - ":filter_lib", + ":conn_manager_lib", + ":decoder_lib", + ":protocol_lib", "//include/envoy/registry", - "//source/common/config:filter_json_lib", + "//source/common/config:utility_lib", "//source/extensions/filters/network:well_known_names", "//source/extensions/filters/network/common:factory_base_lib", + "//source/extensions/filters/network/thrift_proxy/filters:filter_config_interface", + "//source/extensions/filters/network/thrift_proxy/filters:well_known_names", + "//source/extensions/filters/network/thrift_proxy/router:router_lib", "@envoy_api//envoy/extensions/filters/network/thrift_proxy/v2alpha1:thrift_proxy_cc", ], ) +envoy_cc_library( + name = "conn_manager_lib", + srcs = ["conn_manager.cc"], + hdrs = ["conn_manager.h"], + deps = [ + ":app_exception_lib", + ":decoder_lib", + ":protocol_converter_lib", + ":protocol_lib", + ":stats_lib", + ":transport_lib", + "//include/envoy/event:deferred_deletable", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/network:connection_interface", + "//include/envoy/network:filter_interface", + "//include/envoy/stats:stats_interface", + "//include/envoy/stats:timespan", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/common:linked_object", + "//source/common/common:logger_lib", + "//source/common/network:filter_lib", + "//source/extensions/filters/network/thrift_proxy/router:router_interface", + ], +) + envoy_cc_library( name = "decoder_lib", srcs = ["decoder.cc"], hdrs = ["decoder.h"], deps = [ ":protocol_lib", + ":stats_lib", ":transport_lib", "//source/common/buffer:buffer_lib", + "//source/extensions/filters/network/thrift_proxy/filters:filter_interface", ], ) envoy_cc_library( - name = "filter_lib", - srcs = ["filter.cc"], - hdrs = ["filter.h"], + name = "protocol_converter_lib", + hdrs = [ + "protocol_converter.h", + ], deps = [ - ":decoder_lib", - "//include/envoy/network:connection_interface", - "//include/envoy/network:filter_interface", - "//include/envoy/stats:stats_interface", - "//include/envoy/stats:stats_macros", - "//include/envoy/stats:timespan", - "//source/common/buffer:buffer_lib", - "//source/common/common:assert_lib", - "//source/common/common:logger_lib", - "//source/common/network:filter_lib", + ":protocol_interface", + "//include/envoy/buffer:buffer_interface", + "//source/extensions/filters/network/thrift_proxy/filters:filter_interface", ], ) @@ -70,6 +108,9 @@ envoy_cc_library( external_deps = ["abseil_optional"], deps = [ "//include/envoy/buffer:buffer_interface", + "//include/envoy/registry", + "//source/common/common:assert_lib", + "//source/common/config:utility_lib", "//source/common/singleton:const_singleton", ], ) @@ -94,12 +135,24 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "stats_lib", + hdrs = ["stats.h"], + deps = [ + "//include/envoy/stats:stats_interface", + "//include/envoy/stats:stats_macros", + ], +) + envoy_cc_library( name = "transport_interface", hdrs = ["transport.h"], external_deps = ["abseil_optional"], deps = [ "//include/envoy/buffer:buffer_interface", + "//include/envoy/registry", + "//source/common/common:assert_lib", + "//source/common/config:utility_lib", "//source/common/singleton:const_singleton", ], ) @@ -109,6 +162,7 @@ envoy_cc_library( srcs = [ "framed_transport_impl.cc", "transport_impl.cc", + "unframed_transport_impl.cc", ], hdrs = [ "framed_transport_impl.h", diff --git a/source/extensions/filters/network/thrift_proxy/app_exception_impl.cc b/source/extensions/filters/network/thrift_proxy/app_exception_impl.cc new file mode 100644 index 000000000000..65455c12b360 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/app_exception_impl.cc @@ -0,0 +1,34 @@ +#include "extensions/filters/network/thrift_proxy/app_exception_impl.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { + +static const std::string TApplicationException = "TApplicationException"; +static const std::string MessageField = "message"; +static const std::string TypeField = "type"; +static const std::string StopField = ""; + +void AppException::encode(ThriftProxy::Protocol& proto, Buffer::Instance& buffer) { + proto.writeMessageBegin(buffer, method_name_, ThriftProxy::MessageType::Exception, seq_id_); + proto.writeStructBegin(buffer, TApplicationException); + + proto.writeFieldBegin(buffer, MessageField, ThriftProxy::FieldType::String, 1); + proto.writeString(buffer, error_message_); + proto.writeFieldEnd(buffer); + + proto.writeFieldBegin(buffer, TypeField, ThriftProxy::FieldType::I32, 2); + proto.writeInt32(buffer, static_cast(type_)); + proto.writeFieldEnd(buffer); + + proto.writeFieldBegin(buffer, StopField, ThriftProxy::FieldType::Stop, 0); + + proto.writeStructEnd(buffer); + proto.writeMessageEnd(buffer); +} + +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/app_exception_impl.h b/source/extensions/filters/network/thrift_proxy/app_exception_impl.h new file mode 100644 index 000000000000..4a0335704100 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/app_exception_impl.h @@ -0,0 +1,44 @@ +#pragma once + +#include "extensions/filters/network/thrift_proxy/filters/filter.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { + +/** + * Thrift Application Exception types. + * See https://github.com/apache/thrift/blob/master/doc/specs/thrift-rpc.md + */ +enum class AppExceptionType { + Unknown = 0, + UnknownMethod = 1, + InvalidMessageType = 2, + WrongMethodName = 3, + BadSequenceId = 4, + MissingResult = 5, + InternalError = 6, + ProtocolError = 7, + InvalidTransform = 8, + InvalidProtocol = 9, + UnsupportedClientType = 10, +}; + +struct AppException : public ThriftFilters::DirectResponse { + AppException(const absl::string_view method_name, int32_t seq_id, AppExceptionType type, + const std::string& error_message) + : method_name_(method_name), seq_id_(seq_id), type_(type), error_message_(error_message) {} + + void encode(ThriftProxy::Protocol& proto, Buffer::Instance& buffer) override; + + const std::string method_name_; + const int32_t seq_id_; + const AppExceptionType type_; + const std::string error_message_; +}; + +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.cc b/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.cc index 3fc1038962e8..ee5a734eda20 100644 --- a/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.cc @@ -60,26 +60,22 @@ bool BinaryProtocolImpl::readMessageBegin(Buffer::Instance& buffer, std::string& msg_type = type; seq_id = BufferHelper::drainI32(buffer); - onMessageStart(absl::string_view(name), msg_type, seq_id); return true; } bool BinaryProtocolImpl::readMessageEnd(Buffer::Instance& buffer) { UNREFERENCED_PARAMETER(buffer); - onMessageComplete(); return true; } bool BinaryProtocolImpl::readStructBegin(Buffer::Instance& buffer, std::string& name) { UNREFERENCED_PARAMETER(buffer); name.clear(); // binary protocol does not transmit struct names - onStructBegin(absl::string_view(name)); return true; } bool BinaryProtocolImpl::readStructEnd(Buffer::Instance& buffer) { UNREFERENCED_PARAMETER(buffer); - onStructEnd(); return true; } @@ -110,7 +106,6 @@ bool BinaryProtocolImpl::readFieldBegin(Buffer::Instance& buffer, std::string& n name.clear(); // binary protocol does not transmit field names field_type = type; - onStructField(absl::string_view(name), field_type, field_id); return true; } @@ -402,7 +397,6 @@ bool LaxBinaryProtocolImpl::readMessageBegin(Buffer::Instance& buffer, std::stri seq_id = BufferHelper::peekI32(buffer, 1); buffer.drain(5); - onMessageStart(absl::string_view(name), msg_type, seq_id); return true; } @@ -413,6 +407,27 @@ void LaxBinaryProtocolImpl::writeMessageBegin(Buffer::Instance& buffer, const st BufferHelper::writeI32(buffer, seq_id); } +class BinaryProtocolConfigFactory : public ProtocolFactoryBase { +public: + BinaryProtocolConfigFactory() : ProtocolFactoryBase(ProtocolNames::get().BINARY) {} +}; + +/** + * Static registration for the binary protocol. @see RegisterFactory. + */ +static Registry::RegisterFactory register_; + +class LaxBinaryProtocolConfigFactory : public ProtocolFactoryBase { +public: + LaxBinaryProtocolConfigFactory() : ProtocolFactoryBase(ProtocolNames::get().LAX_BINARY) {} +}; + +/** + * Static registration for the auto protocol. @see RegisterFactory. + */ +static Registry::RegisterFactory + register_lax_; + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.h b/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.h index fac781ea563b..e292d6cd036b 100644 --- a/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.h +++ b/source/extensions/filters/network/thrift_proxy/binary_protocol_impl.h @@ -16,12 +16,13 @@ namespace ThriftProxy { * BinaryProtocolImpl implements the Thrift Binary protocol with strict message encoding. * See https://github.com/apache/thrift/blob/master/doc/specs/thrift-binary-protocol.md */ -class BinaryProtocolImpl : public ProtocolImplBase { +class BinaryProtocolImpl : public Protocol { public: - BinaryProtocolImpl(ProtocolCallbacks& callbacks) : ProtocolImplBase(callbacks) {} + BinaryProtocolImpl() {} // Protocol const std::string& name() const override { return ProtocolNames::get().BINARY; } + ProtocolType type() const override { return ProtocolType::Binary; } bool readMessageBegin(Buffer::Instance& buffer, std::string& name, MessageType& msg_type, int32_t& seq_id) override; bool readMessageEnd(Buffer::Instance& buffer) override; @@ -81,7 +82,7 @@ class BinaryProtocolImpl : public ProtocolImplBase { */ class LaxBinaryProtocolImpl : public BinaryProtocolImpl { public: - LaxBinaryProtocolImpl(ProtocolCallbacks& callbacks) : BinaryProtocolImpl(callbacks) {} + LaxBinaryProtocolImpl() {} const std::string& name() const override { return ProtocolNames::get().LAX_BINARY; } diff --git a/source/extensions/filters/network/thrift_proxy/buffer_helper.h b/source/extensions/filters/network/thrift_proxy/buffer_helper.h index 8eb633a0140a..c4945cd5da6b 100644 --- a/source/extensions/filters/network/thrift_proxy/buffer_helper.h +++ b/source/extensions/filters/network/thrift_proxy/buffer_helper.h @@ -10,57 +10,6 @@ namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { -/** - * BufferWrapper provides a partial implementation of Buffer::Instance that is sufficient for - * BufferHelper to read Thrift protocol data without draining the buffer's contents. - */ -class BufferWrapper : public Buffer::Instance { -public: - BufferWrapper(Buffer::Instance& underlying) : underlying_(underlying) {} - - uint64_t position() { return position_; } - - // Buffer::Instance - void copyOut(size_t start, uint64_t size, void* data) const override { - ASSERT(position_ + start + size <= underlying_.length()); - underlying_.copyOut(start + position_, size, data); - } - void drain(uint64_t size) override { - ASSERT(position_ + size <= underlying_.length()); - position_ += size; - } - uint64_t length() const override { - ASSERT(underlying_.length() >= position_); - return underlying_.length() - position_; - } - void* linearize(uint32_t size) override { - ASSERT(position_ + size <= underlying_.length()); - uint8_t* p = static_cast(underlying_.linearize(position_ + size)); - return p + position_; - } - void add(const void*, uint64_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - void addBufferFragment(Buffer::BufferFragment&) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - void add(const std::string&) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - void add(const Buffer::Instance&) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - void commit(Buffer::RawSlice*, uint64_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - uint64_t getRawSlices(Buffer::RawSlice*, uint64_t) const override { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; - } - void move(Buffer::Instance&) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - void move(Buffer::Instance&, uint64_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - std::tuple read(int, uint64_t) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - uint64_t reserve(uint64_t, Buffer::RawSlice*, uint64_t) override { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; - } - ssize_t search(const void*, uint64_t, size_t) const override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - std::tuple write(int) override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - std::string toString() const override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } - -private: - Buffer::Instance& underlying_; - uint64_t position_{0}; -}; - /** * BufferHelper provides buffer operations for reading bytes and numbers in the various encodings * used by Thrift protocols. diff --git a/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.cc b/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.cc index 5ea3a3cafb48..417a80d8b619 100644 --- a/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.cc @@ -72,13 +72,11 @@ bool CompactProtocolImpl::readMessageBegin(Buffer::Instance& buffer, std::string msg_type = type; seq_id = id; - onMessageStart(absl::string_view(name), msg_type, seq_id); return true; } bool CompactProtocolImpl::readMessageEnd(Buffer::Instance& buffer) { UNREFERENCED_PARAMETER(buffer); - onMessageComplete(); return true; } @@ -91,7 +89,6 @@ bool CompactProtocolImpl::readStructBegin(Buffer::Instance& buffer, std::string& last_field_id_stack_.push(last_field_id_); last_field_id_ = 0; - onStructBegin(absl::string_view(name)); return true; } @@ -105,7 +102,6 @@ bool CompactProtocolImpl::readStructEnd(Buffer::Instance& buffer) { last_field_id_ = last_field_id_stack_.top(); last_field_id_stack_.pop(); - onStructEnd(); return true; } @@ -124,7 +120,6 @@ bool CompactProtocolImpl::readFieldBegin(Buffer::Instance& buffer, std::string& field_type = FieldType::Stop; buffer.drain(1); - onStructField(absl::string_view(name), field_type, field_id); return true; } @@ -166,7 +161,6 @@ bool CompactProtocolImpl::readFieldBegin(Buffer::Instance& buffer, std::string& buffer.drain(id_size + 1); - onStructField(absl::string_view(name), field_type, field_id); return true; } @@ -459,7 +453,7 @@ void CompactProtocolImpl::writeFieldBeginInternal( static_cast(compact_field_type)); } else { BufferHelper::writeI8(buffer, static_cast(compact_field_type)); - BufferHelper::writeI16(buffer, field_id); + BufferHelper::writeZigZagI32(buffer, static_cast(field_id)); } last_field_id_ = field_id; @@ -623,6 +617,17 @@ CompactProtocolImpl::CompactFieldType CompactProtocolImpl::convertFieldType(Fiel } } +class CompactProtocolConfigFactory : public ProtocolFactoryBase { +public: + CompactProtocolConfigFactory() : ProtocolFactoryBase(ProtocolNames::get().COMPACT) {} +}; + +/** + * Static registration for the binary protocol. @see RegisterFactory. + */ +static Registry::RegisterFactory + register_; + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h b/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h index 72bad6dee448..322d03a3a83d 100644 --- a/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h +++ b/source/extensions/filters/network/thrift_proxy/compact_protocol_impl.h @@ -19,12 +19,13 @@ namespace ThriftProxy { * CompactProtocolImpl implements the Thrift Compact protocol. * See https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md */ -class CompactProtocolImpl : public ProtocolImplBase { +class CompactProtocolImpl : public Protocol { public: - CompactProtocolImpl(ProtocolCallbacks& callbacks) : ProtocolImplBase(callbacks) {} + CompactProtocolImpl() {} // Protocol const std::string& name() const override { return ProtocolNames::get().COMPACT; } + ProtocolType type() const override { return ProtocolType::Compact; } bool readMessageBegin(Buffer::Instance& buffer, std::string& name, MessageType& msg_type, int32_t& seq_id) override; bool readMessageEnd(Buffer::Instance& buffer) override; diff --git a/source/extensions/filters/network/thrift_proxy/config.cc b/source/extensions/filters/network/thrift_proxy/config.cc index 49f169eee481..ffab3d37f53c 100644 --- a/source/extensions/filters/network/thrift_proxy/config.cc +++ b/source/extensions/filters/network/thrift_proxy/config.cc @@ -1,26 +1,81 @@ #include "extensions/filters/network/thrift_proxy/config.h" +#include #include #include "envoy/network/connection.h" #include "envoy/registry/registry.h" -#include "extensions/filters/network/thrift_proxy/filter.h" +#include "common/config/utility.h" + +#include "extensions/filters/network/thrift_proxy/binary_protocol_impl.h" +#include "extensions/filters/network/thrift_proxy/compact_protocol_impl.h" +#include "extensions/filters/network/thrift_proxy/decoder.h" +#include "extensions/filters/network/thrift_proxy/filters/filter_config.h" +#include "extensions/filters/network/thrift_proxy/filters/well_known_names.h" +#include "extensions/filters/network/thrift_proxy/framed_transport_impl.h" +#include "extensions/filters/network/thrift_proxy/protocol_impl.h" +#include "extensions/filters/network/thrift_proxy/stats.h" +#include "extensions/filters/network/thrift_proxy/transport_impl.h" +#include "extensions/filters/network/thrift_proxy/unframed_transport_impl.h" namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { +namespace { + +typedef std::map< + envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy_TransportType, + TransportType> + TransportTypeMap; + +static const TransportTypeMap& transportTypeMap() { + CONSTRUCT_ON_FIRST_USE(TransportTypeMap, + { + {envoy::extensions::filters::network::thrift_proxy::v2alpha1:: + ThriftProxy_TransportType_AUTO_TRANSPORT, + TransportType::Auto}, + {envoy::extensions::filters::network::thrift_proxy::v2alpha1:: + ThriftProxy_TransportType_FRAMED, + TransportType::Framed}, + {envoy::extensions::filters::network::thrift_proxy::v2alpha1:: + ThriftProxy_TransportType_UNFRAMED, + TransportType::Unframed}, + }); +} + +typedef std::map< + envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy_ProtocolType, + ProtocolType> + ProtocolTypeMap; + +static const ProtocolTypeMap& protocolTypeMap() { + CONSTRUCT_ON_FIRST_USE(ProtocolTypeMap, { + {envoy::extensions::filters::network::thrift_proxy:: + v2alpha1::ThriftProxy_ProtocolType_AUTO_PROTOCOL, + ProtocolType::Auto}, + {envoy::extensions::filters::network::thrift_proxy:: + v2alpha1::ThriftProxy_ProtocolType_BINARY, + ProtocolType::Binary}, + {envoy::extensions::filters::network::thrift_proxy:: + v2alpha1::ThriftProxy_ProtocolType_LAX_BINARY, + ProtocolType::LaxBinary}, + {envoy::extensions::filters::network::thrift_proxy:: + v2alpha1::ThriftProxy_ProtocolType_COMPACT, + ProtocolType::Compact}, + }); +} + +} // namespace Network::FilterFactoryCb ThriftProxyFilterConfigFactory::createFilterFactoryFromProtoTyped( const envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy& proto_config, Server::Configuration::FactoryContext& context) { - ASSERT(!proto_config.stat_prefix().empty()); - - const std::string stat_prefix = fmt::format("thrift.{}.", proto_config.stat_prefix()); + std::shared_ptr filter_config(new ConfigImpl(proto_config, context)); - return [stat_prefix, &context](Network::FilterManager& filter_manager) -> void { - filter_manager.addFilter(std::make_shared(stat_prefix, context.scope())); + return [filter_config](Network::FilterManager& filter_manager) -> void { + filter_manager.addReadFilter(std::make_shared(*filter_config)); }; } @@ -31,6 +86,48 @@ static Registry::RegisterFactory registered_; +ConfigImpl::ConfigImpl( + const envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy& config, + Server::Configuration::FactoryContext& context) + : context_(context), stats_prefix_(fmt::format("thrift.{}.", config.stat_prefix())), + stats_(ThriftFilterStats::generateStats(stats_prefix_, context_.scope())), + transport_(config.transport()), proto_(config.protocol()), + route_matcher_(new Router::RouteMatcher(config.route_config())) { + + // Construct the only Thrift DecoderFilter: the Router + auto& factory = + Envoy::Config::Utility::getAndCheckFactory( + ThriftFilters::ThriftFilterNames::get().ROUTER); + ThriftFilters::FilterFactoryCb callback; + + auto empty_config = factory.createEmptyConfigProto(); + callback = factory.createFilterFactoryFromProto(*empty_config, stats_prefix_, context_); + filter_factories_.push_back(callback); +} + +void ConfigImpl::createFilterChain(ThriftFilters::FilterChainFactoryCallbacks& callbacks) { + for (const ThriftFilters::FilterFactoryCb& factory : filter_factories_) { + factory(callbacks); + } +} + +DecoderPtr ConfigImpl::createDecoder(DecoderCallbacks& callbacks) { + return std::make_unique(createTransport(), createProtocol(), callbacks); +} + +TransportPtr ConfigImpl::createTransport() { + TransportTypeMap::const_iterator i = transportTypeMap().find(transport_); + RELEASE_ASSERT(i != transportTypeMap().end(), "invalid transport type"); + + return NamedTransportConfigFactory::getFactory(i->second).createTransport(); +} + +ProtocolPtr ConfigImpl::createProtocol() { + ProtocolTypeMap::const_iterator i = protocolTypeMap().find(proto_); + RELEASE_ASSERT(i != protocolTypeMap().end(), "invalid protocol type"); + return NamedProtocolConfigFactory::getFactory(i->second).createProtocol(); +} + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/source/extensions/filters/network/thrift_proxy/config.h b/source/extensions/filters/network/thrift_proxy/config.h index 40aa4b418774..7dcc4e094353 100644 --- a/source/extensions/filters/network/thrift_proxy/config.h +++ b/source/extensions/filters/network/thrift_proxy/config.h @@ -1,11 +1,16 @@ #pragma once +#include #include #include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" #include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.validate.h" +#include "envoy/stats/stats.h" #include "extensions/filters/network/common/factory_base.h" +#include "extensions/filters/network/thrift_proxy/conn_manager.h" +#include "extensions/filters/network/thrift_proxy/filters/filter.h" +#include "extensions/filters/network/thrift_proxy/router/router_impl.h" #include "extensions/filters/network/well_known_names.h" namespace Envoy { @@ -28,6 +33,42 @@ class ThriftProxyFilterConfigFactory Server::Configuration::FactoryContext& context) override; }; +class ConfigImpl : public Config, + public Router::Config, + public ThriftFilters::FilterChainFactory, + Logger::Loggable { +public: + ConfigImpl(const envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy& config, + Server::Configuration::FactoryContext& context); + + // ThriftFilters::FilterChainFactory + void createFilterChain(ThriftFilters::FilterChainFactoryCallbacks& callbacks) override; + + // Router::Config + Router::RouteConstSharedPtr route(const std::string& method_name) const override { + return route_matcher_->route(method_name); + } + + // Config + ThriftFilterStats& stats() override { return stats_; } + ThriftFilters::FilterChainFactory& filterFactory() override { return *this; } + DecoderPtr createDecoder(DecoderCallbacks& callbacks) override; + Router::Config& routerConfig() override { return *this; } + +private: + TransportPtr createTransport(); + ProtocolPtr createProtocol(); + + Server::Configuration::FactoryContext& context_; + const std::string stats_prefix_; + ThriftFilterStats stats_; + envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy_TransportType transport_; + envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy_ProtocolType proto_; + std::unique_ptr route_matcher_; + + std::list filter_factories_; +}; + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/source/extensions/filters/network/thrift_proxy/conn_manager.cc b/source/extensions/filters/network/thrift_proxy/conn_manager.cc new file mode 100644 index 000000000000..c94bbeefcec3 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/conn_manager.cc @@ -0,0 +1,290 @@ +#include "extensions/filters/network/thrift_proxy/conn_manager.h" + +#include "envoy/common/exception.h" +#include "envoy/event/dispatcher.h" + +#include "extensions/filters/network/thrift_proxy/app_exception_impl.h" +#include "extensions/filters/network/thrift_proxy/binary_protocol_impl.h" +#include "extensions/filters/network/thrift_proxy/compact_protocol_impl.h" +#include "extensions/filters/network/thrift_proxy/framed_transport_impl.h" +#include "extensions/filters/network/thrift_proxy/unframed_transport_impl.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { + +ConnectionManager::ConnectionManager(Config& config) + : config_(config), stats_(config_.stats()), decoder_(config_.createDecoder(*this)) {} + +ConnectionManager::~ConnectionManager() {} + +Network::FilterStatus ConnectionManager::onData(Buffer::Instance& data, bool end_stream) { + UNREFERENCED_PARAMETER(end_stream); + + request_buffer_.move(data); + dispatch(); + + return Network::FilterStatus::StopIteration; +} + +void ConnectionManager::dispatch() { + if (stopped_) { + ENVOY_LOG(error, "thrift filter stopped"); + return; + } + + try { + bool underflow = false; + while (!underflow) { + ThriftFilters::FilterStatus status = decoder_->onData(request_buffer_, underflow); + if (status == ThriftFilters::FilterStatus::StopIteration) { + stopped_ = true; + break; + } + } + } catch (const EnvoyException& ex) { + ENVOY_LOG(error, "thrift error: {}", ex.what()); + stats_.request_decoding_error_.inc(); + + // Use the current rpc to send an error downstream, if possible. + rpcs_.front()->onError(ex.what()); + + resetAllRpcs(); + read_callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite); + } +} + +void ConnectionManager::continueDecoding() { + stopped_ = false; + dispatch(); +} + +void ConnectionManager::doDeferredRpcDestroy(ConnectionManager::ActiveRpc& rpc) { + read_callbacks_->connection().dispatcher().deferredDelete(rpc.removeFromList(rpcs_)); +} + +void ConnectionManager::resetAllRpcs() { + while (!rpcs_.empty()) { + rpcs_.front()->onReset(); + } +} + +void ConnectionManager::initializeReadFilterCallbacks(Network::ReadFilterCallbacks& callbacks) { + read_callbacks_ = &callbacks; +} + +void ConnectionManager::onEvent(Network::ConnectionEvent event) { + if (!rpcs_.empty()) { + if (event == Network::ConnectionEvent::RemoteClose) { + stats_.cx_destroy_remote_with_active_rq_.inc(); + } else if (event == Network::ConnectionEvent::LocalClose) { + stats_.cx_destroy_local_with_active_rq_.inc(); + } + + resetAllRpcs(); + } +} + +ThriftFilters::DecoderFilter& ConnectionManager::newDecoderFilter() { + ENVOY_LOG(debug, "new decoder filter"); + + ActiveRpcPtr new_rpc(new ActiveRpc(*this)); + new_rpc->createFilterChain(); + new_rpc->moveIntoList(std::move(new_rpc), rpcs_); + + return **rpcs_.begin(); +} + +bool ConnectionManager::ResponseDecoder::onData(Buffer::Instance& data) { + upstream_buffer_.move(data); + + bool underflow = false; + decoder_->onData(upstream_buffer_, underflow); + ASSERT(complete_ || underflow); + return complete_; +} + +ThriftFilters::FilterStatus ConnectionManager::ResponseDecoder::messageBegin(absl::string_view name, + MessageType msg_type, + int32_t seq_id) { + reply_.emplace(std::string(name), msg_type, seq_id); + first_reply_field_ = (msg_type == MessageType::Reply); + return ProtocolConverter::messageBegin(name, msg_type, seq_id); +} + +ThriftFilters::FilterStatus ConnectionManager::ResponseDecoder::fieldBegin(absl::string_view name, + FieldType field_type, + int16_t field_id) { + if (first_reply_field_) { + // Reply messages contain a struct where field 0 is the call result and fields 1+ are + // exceptions, if defined. At most one field may be set. Therefore, the very first field we + // encounter in a reply is either field 0 (success) or not (IDL exception returned). + ASSERT(reply_.has_value()); + reply_.value().success_ = field_id == 0 && field_type != FieldType::Stop; + first_reply_field_ = false; + } + + return ProtocolConverter::fieldBegin(name, field_type, field_id); +} + +ThriftFilters::FilterStatus ConnectionManager::ResponseDecoder::transportEnd() { + ConnectionManager& cm = parent_.parent_; + + Buffer::OwnedImpl buffer; + + // Use the factory to get the concrete transport from the decoder transport (as opposed to + // potentially pre-detection auto transport). + TransportPtr transport = + NamedTransportConfigFactory::getFactory(parent_.parent_.decoder_->transportType()) + .createTransport(); + transport->encodeFrame(buffer, parent_.response_buffer_); + complete_ = true; + + cm.read_callbacks_->connection().write(buffer, false); + + cm.stats_.response_.inc(); + + ASSERT(reply_.has_value()); + switch (reply_.value().msg_type_) { + case MessageType::Reply: + cm.stats_.response_reply_.inc(); + if (reply_.value().success_.value_or(false)) { + cm.stats_.response_success_.inc(); + } else { + cm.stats_.response_error_.inc(); + } + + break; + + case MessageType::Exception: + cm.stats_.response_exception_.inc(); + break; + + default: + cm.stats_.response_invalid_type_.inc(); + break; + } + + return ThriftFilters::FilterStatus::Continue; +} + +ThriftFilters::FilterStatus ConnectionManager::ActiveRpc::transportEnd() { + ASSERT(call_.has_value()); + + parent_.stats_.request_.inc(); + + switch (call_.value().msg_type_) { + case MessageType::Call: + parent_.stats_.request_call_.inc(); + break; + + case MessageType::Oneway: + parent_.stats_.request_oneway_.inc(); + + // No response forthcoming, we're done. + parent_.doDeferredRpcDestroy(*this); + break; + + default: + parent_.stats_.request_invalid_type_.inc(); + break; + } + + return decoder_filter_->transportEnd(); +} + +void ConnectionManager::ActiveRpc::createFilterChain() { + parent_.config_.filterFactory().createFilterChain(*this); +} + +void ConnectionManager::ActiveRpc::onReset() { + // TODO(zuercher): e.g., parent_.stats_.named_.downstream_rq_rx_reset_.inc(); + parent_.doDeferredRpcDestroy(*this); +} + +void ConnectionManager::ActiveRpc::onError(const std::string& what) { + if (call_.has_value()) { + const Message& msg = call_.value(); + sendLocalReply(std::make_unique(msg.method_name_, msg.seq_id_, + AppExceptionType::ProtocolError, what)); + return; + } + + // Transport or protocol error happened before (or during message begin) parsing. It's not + // possible to provide a valid response, so don't try. +} + +const Network::Connection* ConnectionManager::ActiveRpc::connection() const { + return &parent_.read_callbacks_->connection(); +} + +void ConnectionManager::ActiveRpc::continueDecoding() { parent_.continueDecoding(); } + +Router::RouteConstSharedPtr ConnectionManager::ActiveRpc::route() { + if (!cached_route_) { + if (call_.has_value()) { + Router::RouteConstSharedPtr route = + parent_.config_.routerConfig().route(call_.value().method_name_); + cached_route_ = std::move(route); + } else { + cached_route_ = nullptr; + } + } + + return cached_route_.value(); +} + +void ConnectionManager::ActiveRpc::sendLocalReply(ThriftFilters::DirectResponsePtr&& response) { + // Use the factory to get the concrete protocol from the decoder protocol (as opposed to + // potentially pre-detection auto protocol). + ProtocolPtr proto = + NamedProtocolConfigFactory::getFactory(parent_.decoder_->protocolType()).createProtocol(); + Buffer::OwnedImpl buffer; + + response->encode(*proto, buffer); + + // Same logic as protocol above. + TransportPtr transport = + NamedTransportConfigFactory::getFactory(parent_.decoder_->transportType()).createTransport(); + transport->encodeFrame(response_buffer_, buffer); + + parent_.read_callbacks_->connection().write(response_buffer_, false); + parent_.doDeferredRpcDestroy(*this); +} + +void ConnectionManager::ActiveRpc::startUpstreamResponse(TransportType transport_type, + ProtocolType protocol_type) { + ASSERT(response_decoder_ == nullptr); + + response_decoder_ = std::make_unique(*this, transport_type, protocol_type); +} + +bool ConnectionManager::ActiveRpc::upstreamData(Buffer::Instance& buffer) { + ASSERT(response_decoder_ != nullptr); + + try { + bool complete = response_decoder_->onData(buffer); + if (complete) { + parent_.doDeferredRpcDestroy(*this); + } + return complete; + } catch (const EnvoyException& ex) { + ENVOY_LOG(error, "thrift response error: {}", ex.what()); + parent_.stats_.response_decoding_error_.inc(); + + onError(ex.what()); + decoder_filter_->resetUpstreamConnection(); + return true; + } +} + +void ConnectionManager::ActiveRpc::resetDownstreamConnection() { + parent_.read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + parent_.doDeferredRpcDestroy(*this); +} + +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/conn_manager.h b/source/extensions/filters/network/thrift_proxy/conn_manager.h new file mode 100644 index 000000000000..c366a40c0f2a --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/conn_manager.h @@ -0,0 +1,256 @@ +#pragma once + +#include "envoy/common/pure.h" +#include "envoy/event/deferred_deletable.h" +#include "envoy/network/connection.h" +#include "envoy/network/filter.h" +#include "envoy/stats/stats.h" +#include "envoy/stats/timespan.h" + +#include "common/buffer/buffer_impl.h" +#include "common/common/linked_object.h" +#include "common/common/logger.h" + +#include "extensions/filters/network/thrift_proxy/decoder.h" +#include "extensions/filters/network/thrift_proxy/filters/filter.h" +#include "extensions/filters/network/thrift_proxy/protocol.h" +#include "extensions/filters/network/thrift_proxy/protocol_converter.h" +#include "extensions/filters/network/thrift_proxy/stats.h" +#include "extensions/filters/network/thrift_proxy/transport.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { + +/** + * Config is a configuration interface for ConnectionManager. + */ +class Config { +public: + virtual ~Config() {} + + virtual ThriftFilters::FilterChainFactory& filterFactory() PURE; + virtual ThriftFilterStats& stats() PURE; + virtual DecoderPtr createDecoder(DecoderCallbacks& callbacks) PURE; + virtual Router::Config& routerConfig() PURE; +}; + +/** + * ConnectionManager is a Network::Filter that will perform Thrift request handling on a connection. + */ +class ConnectionManager : public Network::ReadFilter, + public Network::ConnectionCallbacks, + public DecoderCallbacks, + Logger::Loggable { +public: + ConnectionManager(Config& config); + ~ConnectionManager(); + + // Network::ReadFilter + Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; + Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } + void initializeReadFilterCallbacks(Network::ReadFilterCallbacks&) override; + + // Network::ConnectionCallbacks + void onEvent(Network::ConnectionEvent) override; + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + + // DecoderCallbacks + ThriftFilters::DecoderFilter& newDecoderFilter() override; + +private: + class Message { + public: + Message(const std::string& method_name, MessageType msg_type, int32_t seq_id) + : method_name_(method_name), msg_type_(msg_type), seq_id_(seq_id) {} + + const std::string method_name_; + const MessageType msg_type_; + const int32_t seq_id_; + absl::optional success_; + }; + + struct ActiveRpc; + + struct ResponseDecoder : public DecoderCallbacks, public ProtocolConverter { + ResponseDecoder(ActiveRpc& parent, TransportType transport_type, ProtocolType protocol_type) + : parent_(parent), + decoder_(std::make_unique( + NamedTransportConfigFactory::getFactory(transport_type).createTransport(), + NamedProtocolConfigFactory::getFactory(protocol_type).createProtocol(), *this)), + complete_(false), first_reply_field_(false) { + // Use the factory to get the concrete protocol from the decoder protocol (as opposed to + // potentially pre-detection auto protocol). + initProtocolConverter( + NamedProtocolConfigFactory::getFactory(parent_.parent_.decoder_->protocolType()) + .createProtocol(), + parent_.response_buffer_); + } + + bool onData(Buffer::Instance& data); + + // ProtocolConverter + ThriftFilters::FilterStatus messageBegin(absl::string_view name, MessageType msg_type, + int32_t seq_id) override; + ThriftFilters::FilterStatus fieldBegin(absl::string_view name, FieldType field_type, + int16_t field_id) override; + ThriftFilters::FilterStatus transportBegin(absl::optional size) override { + UNREFERENCED_PARAMETER(size); + return ThriftFilters::FilterStatus::Continue; + } + ThriftFilters::FilterStatus transportEnd() override; + + // DecoderCallbacks + ThriftFilters::DecoderFilter& newDecoderFilter() override { return *this; } + + ActiveRpc& parent_; + DecoderPtr decoder_; + Buffer::OwnedImpl upstream_buffer_; + absl::optional reply_; + bool complete_ : 1; + bool first_reply_field_ : 1; + }; + typedef std::unique_ptr ResponseDecoderPtr; + + // ActiveRpc tracks request/response pairs. + struct ActiveRpc : LinkedObject, + public Event::DeferredDeletable, + public ThriftFilters::DecoderFilter, + public ThriftFilters::DecoderFilterCallbacks, + public ThriftFilters::FilterChainFactoryCallbacks { + ActiveRpc(ConnectionManager& parent) + : parent_(parent), request_timer_(new Stats::Timespan(parent_.stats_.request_time_ms_)), + stream_id_(parent_.stream_id_++) { + parent_.stats_.request_active_.inc(); + } + ~ActiveRpc() { + request_timer_->complete(); + parent_.stats_.request_active_.dec(); + + if (decoder_filter_ != nullptr) { + decoder_filter_->onDestroy(); + } + } + + // ThriftFilters::DecoderFilter + void onDestroy() override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + void setDecoderFilterCallbacks(ThriftFilters::DecoderFilterCallbacks&) override { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } + void resetUpstreamConnection() override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + ThriftFilters::FilterStatus transportBegin(absl::optional size) override { + return decoder_filter_->transportBegin(size); + } + ThriftFilters::FilterStatus transportEnd() override; + ThriftFilters::FilterStatus messageBegin(absl::string_view name, MessageType msg_type, + int32_t seq_id) override { + call_.emplace(std::string(name), msg_type, seq_id); + return decoder_filter_->messageBegin(name, msg_type, seq_id); + } + ThriftFilters::FilterStatus messageEnd() override { return decoder_filter_->messageEnd(); } + ThriftFilters::FilterStatus structBegin(absl::string_view name) override { + return decoder_filter_->structBegin(name); + } + ThriftFilters::FilterStatus structEnd() override { return decoder_filter_->structEnd(); } + ThriftFilters::FilterStatus fieldBegin(absl::string_view name, FieldType field_type, + int16_t field_id) override { + return decoder_filter_->fieldBegin(name, field_type, field_id); + } + ThriftFilters::FilterStatus fieldEnd() override { return decoder_filter_->fieldEnd(); } + ThriftFilters::FilterStatus boolValue(bool value) override { + return decoder_filter_->boolValue(value); + } + ThriftFilters::FilterStatus byteValue(uint8_t value) override { + return decoder_filter_->byteValue(value); + } + ThriftFilters::FilterStatus int16Value(int16_t value) override { + return decoder_filter_->int16Value(value); + } + ThriftFilters::FilterStatus int32Value(int32_t value) override { + return decoder_filter_->int32Value(value); + } + ThriftFilters::FilterStatus int64Value(int64_t value) override { + return decoder_filter_->int64Value(value); + } + ThriftFilters::FilterStatus doubleValue(double value) override { + return decoder_filter_->doubleValue(value); + } + ThriftFilters::FilterStatus stringValue(absl::string_view value) override { + return decoder_filter_->stringValue(value); + } + ThriftFilters::FilterStatus mapBegin(FieldType key_type, FieldType value_type, + uint32_t size) override { + return decoder_filter_->mapBegin(key_type, value_type, size); + } + ThriftFilters::FilterStatus mapEnd() override { return decoder_filter_->mapEnd(); } + ThriftFilters::FilterStatus listBegin(FieldType elem_type, uint32_t size) override { + return decoder_filter_->listBegin(elem_type, size); + } + ThriftFilters::FilterStatus listEnd() override { return decoder_filter_->listEnd(); } + ThriftFilters::FilterStatus setBegin(FieldType elem_type, uint32_t size) override { + return decoder_filter_->setBegin(elem_type, size); + } + ThriftFilters::FilterStatus setEnd() override { return decoder_filter_->setEnd(); } + + // ThriftFilters::DecoderFilterCallbacks + uint64_t streamId() const override { return stream_id_; } + const Network::Connection* connection() const override; + void continueDecoding() override; + Router::RouteConstSharedPtr route() override; + TransportType downstreamTransportType() const override { + return parent_.decoder_->transportType(); + } + ProtocolType downstreamProtocolType() const override { + return parent_.decoder_->protocolType(); + } + void sendLocalReply(ThriftFilters::DirectResponsePtr&& response) override; + void startUpstreamResponse(TransportType transport_type, ProtocolType protocol_type) override; + bool upstreamData(Buffer::Instance& buffer) override; + void resetDownstreamConnection() override; + + // Thrift::FilterChainFactoryCallbacks + void addDecoderFilter(ThriftFilters::DecoderFilterSharedPtr filter) override { + // TODO(zuercher): support multiple filters + filter->setDecoderFilterCallbacks(*this); + decoder_filter_ = filter; + } + + void createFilterChain(); + void onReset(); + void onError(const std::string& what); + + ConnectionManager& parent_; + Stats::TimespanPtr request_timer_; + uint64_t stream_id_; + ThriftFilters::DecoderFilterSharedPtr decoder_filter_; + ResponseDecoderPtr response_decoder_; + absl::optional cached_route_; + absl::optional call_; + Buffer::OwnedImpl response_buffer_; + }; + + typedef std::unique_ptr ActiveRpcPtr; + + void continueDecoding(); + void dispatch(); + void doDeferredRpcDestroy(ActiveRpc& rpc); + void resetAllRpcs(); + + Config& config_; + ThriftFilterStats& stats_; + + Network::ReadFilterCallbacks* read_callbacks_{}; + + DecoderPtr decoder_; + std::list rpcs_; + Buffer::OwnedImpl request_buffer_; + uint64_t stream_id_{1}; + bool stopped_{false}; +}; + +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/decoder.cc b/source/extensions/filters/network/thrift_proxy/decoder.cc index 84b9df721faa..39aa8af6a283 100644 --- a/source/extensions/filters/network/thrift_proxy/decoder.cc +++ b/source/extensions/filters/network/thrift_proxy/decoder.cc @@ -13,69 +13,72 @@ namespace NetworkFilters { namespace ThriftProxy { // MessageBegin -> StructBegin -ProtocolState DecoderStateMachine::messageBegin(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::messageBegin(Buffer::Instance& buffer) { std::string message_name; MessageType msg_type; int32_t seq_id; if (!proto_.readMessageBegin(buffer, message_name, msg_type, seq_id)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } stack_.clear(); stack_.emplace_back(Frame(ProtocolState::MessageEnd)); - return ProtocolState::StructBegin; + return DecoderStatus(ProtocolState::StructBegin, + filter_.messageBegin(absl::string_view(message_name), msg_type, seq_id)); } // MessageEnd -> Done -ProtocolState DecoderStateMachine::messageEnd(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::messageEnd(Buffer::Instance& buffer) { if (!proto_.readMessageEnd(buffer)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } - return ProtocolState::Done; + return DecoderStatus(ProtocolState::Done, filter_.messageEnd()); } // StructBegin -> FieldBegin -ProtocolState DecoderStateMachine::structBegin(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::structBegin(Buffer::Instance& buffer) { std::string name; if (!proto_.readStructBegin(buffer, name)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } - return ProtocolState::FieldBegin; + return DecoderStatus(ProtocolState::FieldBegin, filter_.structBegin(absl::string_view(name))); } // StructEnd -> stack's return state -ProtocolState DecoderStateMachine::structEnd(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::structEnd(Buffer::Instance& buffer) { if (!proto_.readStructEnd(buffer)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } - return popReturnState(); + ProtocolState next_state = popReturnState(); + return DecoderStatus(next_state, filter_.structEnd()); } // FieldBegin -> FieldValue, or // FieldBegin -> StructEnd (stop field) -ProtocolState DecoderStateMachine::fieldBegin(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::fieldBegin(Buffer::Instance& buffer) { std::string name; FieldType field_type; int16_t field_id; if (!proto_.readFieldBegin(buffer, name, field_type, field_id)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } if (field_type == FieldType::Stop) { - return ProtocolState::StructEnd; + return DecoderStatus(ProtocolState::StructEnd, ThriftFilters::FilterStatus::Continue); } stack_.emplace_back(Frame(ProtocolState::FieldEnd, field_type)); - return ProtocolState::FieldValue; + return DecoderStatus(ProtocolState::FieldValue, + filter_.fieldBegin(absl::string_view(name), field_type, field_id)); } // FieldValue -> FieldEnd (via stack return state) -ProtocolState DecoderStateMachine::fieldValue(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::fieldValue(Buffer::Instance& buffer) { ASSERT(!stack_.empty()); Frame& frame = stack_.back(); @@ -83,36 +86,36 @@ ProtocolState DecoderStateMachine::fieldValue(Buffer::Instance& buffer) { } // FieldEnd -> FieldBegin -ProtocolState DecoderStateMachine::fieldEnd(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::fieldEnd(Buffer::Instance& buffer) { if (!proto_.readFieldEnd(buffer)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } popReturnState(); - return ProtocolState::FieldBegin; + return DecoderStatus(ProtocolState::FieldBegin, filter_.fieldEnd()); } // ListBegin -> ListValue -ProtocolState DecoderStateMachine::listBegin(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::listBegin(Buffer::Instance& buffer) { FieldType elem_type; uint32_t size; if (!proto_.readListBegin(buffer, elem_type, size)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } stack_.emplace_back(Frame(ProtocolState::ListEnd, elem_type, size)); - return ProtocolState::ListValue; + return DecoderStatus(ProtocolState::ListValue, filter_.listBegin(elem_type, size)); } // ListValue -> ListValue, ListBegin, MapBegin, SetBegin, StructBegin (depending on value type), or // ListValue -> ListEnd -ProtocolState DecoderStateMachine::listValue(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::listValue(Buffer::Instance& buffer) { ASSERT(!stack_.empty()); Frame& frame = stack_.back(); if (frame.remaining_ == 0) { - return popReturnState(); + return DecoderStatus(popReturnState(), ThriftFilters::FilterStatus::Continue); } frame.remaining_--; @@ -120,34 +123,35 @@ ProtocolState DecoderStateMachine::listValue(Buffer::Instance& buffer) { } // ListEnd -> stack's return state -ProtocolState DecoderStateMachine::listEnd(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::listEnd(Buffer::Instance& buffer) { if (!proto_.readListEnd(buffer)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } - return popReturnState(); + ProtocolState next_state = popReturnState(); + return DecoderStatus(next_state, filter_.listEnd()); } // MapBegin -> MapKey -ProtocolState DecoderStateMachine::mapBegin(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::mapBegin(Buffer::Instance& buffer) { FieldType key_type, value_type; uint32_t size; if (!proto_.readMapBegin(buffer, key_type, value_type, size)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } stack_.emplace_back(Frame(ProtocolState::MapEnd, key_type, value_type, size)); - return ProtocolState::MapKey; + return DecoderStatus(ProtocolState::MapKey, filter_.mapBegin(key_type, value_type, size)); } // MapKey -> MapValue, ListBegin, MapBegin, SetBegin, StructBegin (depending on key type), or // MapKey -> MapEnd -ProtocolState DecoderStateMachine::mapKey(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::mapKey(Buffer::Instance& buffer) { ASSERT(!stack_.empty()); Frame& frame = stack_.back(); if (frame.remaining_ == 0) { - return popReturnState(); + return DecoderStatus(popReturnState(), ThriftFilters::FilterStatus::Continue); } return handleValue(buffer, frame.elem_type_, ProtocolState::MapValue); @@ -155,7 +159,7 @@ ProtocolState DecoderStateMachine::mapKey(Buffer::Instance& buffer) { // MapValue -> MapKey, ListBegin, MapBegin, SetBegin, StructBegin (depending on value type), or // MapValue -> MapKey -ProtocolState DecoderStateMachine::mapValue(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::mapValue(Buffer::Instance& buffer) { ASSERT(!stack_.empty()); Frame& frame = stack_.back(); ASSERT(frame.remaining_ != 0); @@ -165,34 +169,35 @@ ProtocolState DecoderStateMachine::mapValue(Buffer::Instance& buffer) { } // MapEnd -> stack's return state -ProtocolState DecoderStateMachine::mapEnd(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::mapEnd(Buffer::Instance& buffer) { if (!proto_.readMapEnd(buffer)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } - return popReturnState(); + ProtocolState next_state = popReturnState(); + return DecoderStatus(next_state, filter_.mapEnd()); } // SetBegin -> SetValue -ProtocolState DecoderStateMachine::setBegin(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::setBegin(Buffer::Instance& buffer) { FieldType elem_type; uint32_t size; if (!proto_.readSetBegin(buffer, elem_type, size)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } stack_.emplace_back(Frame(ProtocolState::SetEnd, elem_type, size)); - return ProtocolState::SetValue; + return DecoderStatus(ProtocolState::SetValue, filter_.setBegin(elem_type, size)); } // SetValue -> SetValue, ListBegin, MapBegin, SetBegin, StructBegin (depending on value type), or // SetValue -> SetEnd -ProtocolState DecoderStateMachine::setValue(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::setValue(Buffer::Instance& buffer) { ASSERT(!stack_.empty()); Frame& frame = stack_.back(); if (frame.remaining_ == 0) { - return popReturnState(); + return DecoderStatus(popReturnState(), ThriftFilters::FilterStatus::Continue); } frame.remaining_--; @@ -200,85 +205,88 @@ ProtocolState DecoderStateMachine::setValue(Buffer::Instance& buffer) { } // SetEnd -> stack's return state -ProtocolState DecoderStateMachine::setEnd(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::setEnd(Buffer::Instance& buffer) { if (!proto_.readSetEnd(buffer)) { - return ProtocolState::WaitForData; + return DecoderStatus(ProtocolState::WaitForData); } - return popReturnState(); + ProtocolState next_state = popReturnState(); + return DecoderStatus(next_state, filter_.setEnd()); } -ProtocolState DecoderStateMachine::handleValue(Buffer::Instance& buffer, FieldType elem_type, - ProtocolState return_state) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::handleValue(Buffer::Instance& buffer, + FieldType elem_type, + ProtocolState return_state) { switch (elem_type) { - case FieldType::Bool: - bool value; - if (!proto_.readBool(buffer, value)) { - return ProtocolState::WaitForData; + case FieldType::Bool: { + bool value{}; + if (proto_.readBool(buffer, value)) { + return DecoderStatus(return_state, filter_.boolValue(value)); } break; + } case FieldType::Byte: { - uint8_t value; - if (!proto_.readByte(buffer, value)) { - return ProtocolState::WaitForData; + uint8_t value{}; + if (proto_.readByte(buffer, value)) { + return DecoderStatus(return_state, filter_.byteValue(value)); } break; } case FieldType::I16: { - int16_t value; - if (!proto_.readInt16(buffer, value)) { - return ProtocolState::WaitForData; + int16_t value{}; + if (proto_.readInt16(buffer, value)) { + return DecoderStatus(return_state, filter_.int16Value(value)); } break; } case FieldType::I32: { - int32_t value; - if (!proto_.readInt32(buffer, value)) { - return ProtocolState::WaitForData; + int32_t value{}; + if (proto_.readInt32(buffer, value)) { + return DecoderStatus(return_state, filter_.int32Value(value)); } break; } case FieldType::I64: { - int64_t value; - if (!proto_.readInt64(buffer, value)) { - return ProtocolState::WaitForData; + int64_t value{}; + if (proto_.readInt64(buffer, value)) { + return DecoderStatus(return_state, filter_.int64Value(value)); } break; } case FieldType::Double: { - double value; - if (!proto_.readDouble(buffer, value)) { - return ProtocolState::WaitForData; + double value{}; + if (proto_.readDouble(buffer, value)) { + return DecoderStatus(return_state, filter_.doubleValue(value)); } break; } case FieldType::String: { std::string value; - if (!proto_.readString(buffer, value)) { - return ProtocolState::WaitForData; + if (proto_.readString(buffer, value)) { + return DecoderStatus(return_state, filter_.stringValue(value)); } break; } case FieldType::Struct: stack_.emplace_back(Frame(return_state)); - return ProtocolState::StructBegin; + return DecoderStatus(ProtocolState::StructBegin, ThriftFilters::FilterStatus::Continue); case FieldType::Map: stack_.emplace_back(Frame(return_state)); - return ProtocolState::MapBegin; + return DecoderStatus(ProtocolState::MapBegin, ThriftFilters::FilterStatus::Continue); case FieldType::List: stack_.emplace_back(Frame(return_state)); - return ProtocolState::ListBegin; + return DecoderStatus(ProtocolState::ListBegin, ThriftFilters::FilterStatus::Continue); case FieldType::Set: stack_.emplace_back(Frame(return_state)); - return ProtocolState::SetBegin; + return DecoderStatus(ProtocolState::SetBegin, ThriftFilters::FilterStatus::Continue); default: throw EnvoyException(fmt::format("unknown field type {}", static_cast(elem_type))); } - return return_state; + return DecoderStatus(ProtocolState::WaitForData); } -ProtocolState DecoderStateMachine::handleState(Buffer::Instance& buffer) { +DecoderStateMachine::DecoderStatus DecoderStateMachine::handleState(Buffer::Instance& buffer) { switch (state_) { case ProtocolState::MessageBegin: return messageBegin(buffer); @@ -328,61 +336,97 @@ ProtocolState DecoderStateMachine::popReturnState() { ProtocolState DecoderStateMachine::run(Buffer::Instance& buffer) { while (state_ != ProtocolState::Done) { - ProtocolState s = handleState(buffer); - if (s == ProtocolState::WaitForData) { - return s; + DecoderStatus s = handleState(buffer); + if (s.next_state_ == ProtocolState::WaitForData) { + return ProtocolState::WaitForData; } - state_ = s; + state_ = s.next_state_; + + ASSERT(s.filter_status_.has_value()); + if (s.filter_status_.value() == ThriftFilters::FilterStatus::StopIteration) { + return ProtocolState::StopIteration; + } } return state_; } -Decoder::Decoder(TransportPtr&& transport, ProtocolPtr&& protocol) - : transport_(std::move(transport)), protocol_(std::move(protocol)), state_machine_{}, - frame_started_(false) {} +Decoder::Decoder(TransportPtr&& transport, ProtocolPtr&& protocol, DecoderCallbacks& callbacks) + : transport_(std::move(transport)), protocol_(std::move(protocol)), callbacks_(callbacks) {} + +void Decoder::complete() { + request_.reset(); + state_machine_ = nullptr; + frame_started_ = false; + frame_ended_ = false; +} -void Decoder::onData(Buffer::Instance& data) { +ThriftFilters::FilterStatus Decoder::onData(Buffer::Instance& data, bool& buffer_underflow) { ENVOY_LOG(debug, "thrift: {} bytes available", data.length()); + buffer_underflow = false; - while (true) { - if (!frame_started_) { - // Look for start of next frame. - if (!transport_->decodeFrameStart(data)) { - ENVOY_LOG(debug, "thrift: need more data for {} transport start", transport_->name()); - return; - } - ENVOY_LOG(debug, "thrift: {} transport started", transport_->name()); - - frame_started_ = true; - state_machine_ = std::make_unique(*protocol_); - } + if (frame_ended_) { + // Continuation after filter stopped iteration on transportComplete callback. + complete(); + buffer_underflow = (data.length() == 0); + return ThriftFilters::FilterStatus::Continue; + } - ASSERT(state_machine_ != nullptr); + if (!frame_started_) { + // Look for start of next frame. + absl::optional size{}; + if (!transport_->decodeFrameStart(data, size)) { + ENVOY_LOG(debug, "thrift: need more data for {} transport start", transport_->name()); + buffer_underflow = true; + return ThriftFilters::FilterStatus::Continue; + } + ENVOY_LOG(debug, "thrift: {} transport started", transport_->name()); - ENVOY_LOG(debug, "thrift: protocol {}, state {}, {} bytes available", protocol_->name(), - ProtocolStateNameValues::name(state_machine_->currentState()), data.length()); + request_ = std::make_unique(callbacks_.newDecoderFilter()); + frame_started_ = true; + state_machine_ = std::make_unique(*protocol_, request_->filter_); - ProtocolState rv = state_machine_->run(data); - if (rv == ProtocolState::WaitForData) { - ENVOY_LOG(debug, "thrift: wait for data"); - return; + if (request_->filter_.transportBegin(size) == ThriftFilters::FilterStatus::StopIteration) { + return ThriftFilters::FilterStatus::StopIteration; } + } - ASSERT(rv == ProtocolState::Done); + ASSERT(state_machine_ != nullptr); - // Message complete, get decode end of frame. - if (!transport_->decodeFrameEnd(data)) { - ENVOY_LOG(debug, "thrift: need more data for {} transport end", transport_->name()); - return; - } - ENVOY_LOG(debug, "thrift: {} transport ended", transport_->name()); + ENVOY_LOG(debug, "thrift: protocol {}, state {}, {} bytes available", protocol_->name(), + ProtocolStateNameValues::name(state_machine_->currentState()), data.length()); - // Reset for next frame. - state_machine_ = nullptr; - frame_started_ = false; + ProtocolState rv = state_machine_->run(data); + if (rv == ProtocolState::WaitForData) { + ENVOY_LOG(debug, "thrift: wait for data"); + buffer_underflow = true; + return ThriftFilters::FilterStatus::Continue; + } else if (rv == ProtocolState::StopIteration) { + ENVOY_LOG(debug, "thrift: wait for continuation"); + return ThriftFilters::FilterStatus::StopIteration; } + + ASSERT(rv == ProtocolState::Done); + + // Message complete, decode end of frame. + if (!transport_->decodeFrameEnd(data)) { + ENVOY_LOG(debug, "thrift: need more data for {} transport end", transport_->name()); + buffer_underflow = true; + return ThriftFilters::FilterStatus::Continue; + } + + frame_ended_ = true; + + ENVOY_LOG(debug, "thrift: {} transport ended", transport_->name()); + if (request_->filter_.transportEnd() == ThriftFilters::FilterStatus::StopIteration) { + return ThriftFilters::FilterStatus::StopIteration; + } + + // Reset for next frame. + complete(); + buffer_underflow = (data.length() == 0); + return ThriftFilters::FilterStatus::Continue; } } // namespace ThriftProxy diff --git a/source/extensions/filters/network/thrift_proxy/decoder.h b/source/extensions/filters/network/thrift_proxy/decoder.h index a05d70cee30c..26068858beb6 100644 --- a/source/extensions/filters/network/thrift_proxy/decoder.h +++ b/source/extensions/filters/network/thrift_proxy/decoder.h @@ -6,6 +6,7 @@ #include "common/common/assert.h" #include "common/common/logger.h" +#include "extensions/filters/network/thrift_proxy/filters/filter.h" #include "extensions/filters/network/thrift_proxy/protocol.h" #include "extensions/filters/network/thrift_proxy/transport.h" @@ -15,6 +16,7 @@ namespace NetworkFilters { namespace ThriftProxy { #define ALL_PROTOCOL_STATES(FUNCTION) \ + FUNCTION(StopIteration) \ FUNCTION(WaitForData) \ FUNCTION(MessageBegin) \ FUNCTION(MessageEnd) \ @@ -61,7 +63,8 @@ class ProtocolStateNameValues { */ class DecoderStateMachine { public: - DecoderStateMachine(Protocol& proto) : proto_(proto), state_(ProtocolState::MessageBegin) {} + DecoderStateMachine(Protocol& proto, ThriftFilters::DecoderFilter& filter) + : proto_(proto), filter_(filter), state_(ProtocolState::MessageBegin) {} /** * Consumes as much data from the configured Buffer as possible and executes the decoding state @@ -114,72 +117,107 @@ class DecoderStateMachine { uint32_t remaining_; }; + struct DecoderStatus { + DecoderStatus(ProtocolState next_state) : next_state_(next_state), filter_status_{} {}; + DecoderStatus(ProtocolState next_state, ThriftFilters::FilterStatus filter_status) + : next_state_(next_state), filter_status_(filter_status){}; + + ProtocolState next_state_; + absl::optional filter_status_; + }; + // These functions map directly to the matching ProtocolState values. Each returns the next state // or ProtocolState::WaitForData if more data is required. - ProtocolState messageBegin(Buffer::Instance& buffer); - ProtocolState messageEnd(Buffer::Instance& buffer); - ProtocolState structBegin(Buffer::Instance& buffer); - ProtocolState structEnd(Buffer::Instance& buffer); - ProtocolState fieldBegin(Buffer::Instance& buffer); - ProtocolState fieldValue(Buffer::Instance& buffer); - ProtocolState fieldEnd(Buffer::Instance& buffer); - ProtocolState listBegin(Buffer::Instance& buffer); - ProtocolState listValue(Buffer::Instance& buffer); - ProtocolState listEnd(Buffer::Instance& buffer); - ProtocolState mapBegin(Buffer::Instance& buffer); - ProtocolState mapKey(Buffer::Instance& buffer); - ProtocolState mapValue(Buffer::Instance& buffer); - ProtocolState mapEnd(Buffer::Instance& buffer); - ProtocolState setBegin(Buffer::Instance& buffer); - ProtocolState setValue(Buffer::Instance& buffer); - ProtocolState setEnd(Buffer::Instance& buffer); + DecoderStatus messageBegin(Buffer::Instance& buffer); + DecoderStatus messageEnd(Buffer::Instance& buffer); + DecoderStatus structBegin(Buffer::Instance& buffer); + DecoderStatus structEnd(Buffer::Instance& buffer); + DecoderStatus fieldBegin(Buffer::Instance& buffer); + DecoderStatus fieldValue(Buffer::Instance& buffer); + DecoderStatus fieldEnd(Buffer::Instance& buffer); + DecoderStatus listBegin(Buffer::Instance& buffer); + DecoderStatus listValue(Buffer::Instance& buffer); + DecoderStatus listEnd(Buffer::Instance& buffer); + DecoderStatus mapBegin(Buffer::Instance& buffer); + DecoderStatus mapKey(Buffer::Instance& buffer); + DecoderStatus mapValue(Buffer::Instance& buffer); + DecoderStatus mapEnd(Buffer::Instance& buffer); + DecoderStatus setBegin(Buffer::Instance& buffer); + DecoderStatus setValue(Buffer::Instance& buffer); + DecoderStatus setEnd(Buffer::Instance& buffer); // handleValue represents the generic Value state from the state machine documentation. It // returns either ProtocolState::WaitForData if more data is required or the next state. For // structs, lists, maps, or sets the return_state is pushed onto the stack and the next state is // based on elem_type. For primitive value types, return_state is returned as the next state // (unless WaitForData is returned). - ProtocolState handleValue(Buffer::Instance& buffer, FieldType elem_type, + DecoderStatus handleValue(Buffer::Instance& buffer, FieldType elem_type, ProtocolState return_state); // handleState delegates to the appropriate method based on state_. - ProtocolState handleState(Buffer::Instance& buffer); + DecoderStatus handleState(Buffer::Instance& buffer); // Helper method to retrieve the current frame's return state and remove the frame from the // stack. ProtocolState popReturnState(); Protocol& proto_; + ThriftFilters::DecoderFilter& filter_; ProtocolState state_; std::vector stack_; }; typedef std::unique_ptr DecoderStateMachinePtr; +class DecoderCallbacks { +public: + virtual ~DecoderCallbacks() {} + + /** + * @return DecoderFilter& a new DecoderFilter for a message. + */ + virtual ThriftFilters::DecoderFilter& newDecoderFilter() PURE; +}; + /** * Decoder encapsulates a configured TransportPtr and ProtocolPtr. */ class Decoder : public Logger::Loggable { public: - Decoder(TransportPtr&& transport, ProtocolPtr&& protocol); + Decoder(TransportPtr&& transport, ProtocolPtr&& protocol, DecoderCallbacks& callbacks); + Decoder(TransportType transport_type, ProtocolType protocol_type, DecoderCallbacks& callbacks); /** - * Drains data from the given buffer while executing a DecoderStateMachine over the data. A new - * DecoderStateMachine is instantiated for each message. + * Drains data from the given buffer while executing a DecoderStateMachine over the data. * * @param data a Buffer containing Thrift protocol data + * @param buffer_underflow bool set to true if more data is required to continue decoding + * @return ThriftFilters::FilterStatus::StopIteration when waiting for filter continuation, + * Continue otherwise. * @throw EnvoyException on Thrift protocol errors */ - void onData(Buffer::Instance& data); + ThriftFilters::FilterStatus onData(Buffer::Instance& data, bool& buffer_underflow); - const Transport& transport() { return *transport_; } - const Protocol& protocol() { return *protocol_; } + TransportType transportType() { return transport_->type(); } + ProtocolType protocolType() { return protocol_->type(); } private: + struct ActiveRequest { + ActiveRequest(ThriftFilters::DecoderFilter& filter) : filter_(filter) {} + + ThriftFilters::DecoderFilter& filter_; + }; + typedef std::unique_ptr ActiveRequestPtr; + + void complete(); + TransportPtr transport_; ProtocolPtr protocol_; + DecoderCallbacks& callbacks_; + ActiveRequestPtr request_; DecoderStateMachinePtr state_machine_; - bool frame_started_; + bool frame_started_{false}; + bool frame_ended_{false}; }; typedef std::unique_ptr DecoderPtr; diff --git a/source/extensions/filters/network/thrift_proxy/filter.cc b/source/extensions/filters/network/thrift_proxy/filter.cc deleted file mode 100644 index 3244c153d961..000000000000 --- a/source/extensions/filters/network/thrift_proxy/filter.cc +++ /dev/null @@ -1,310 +0,0 @@ -#include "extensions/filters/network/thrift_proxy/filter.h" - -#include "envoy/common/exception.h" - -#include "common/common/assert.h" - -#include "extensions/filters/network/thrift_proxy/buffer_helper.h" -#include "extensions/filters/network/thrift_proxy/protocol_impl.h" -#include "extensions/filters/network/thrift_proxy/transport_impl.h" - -namespace Envoy { -namespace Extensions { -namespace NetworkFilters { -namespace ThriftProxy { - -Filter::Filter(const std::string& stat_prefix, Stats::Scope& scope) - : req_callbacks_(*this), resp_callbacks_(*this), stats_(generateStats(stat_prefix, scope)) {} - -Filter::~Filter() {} - -void Filter::onEvent(Network::ConnectionEvent event) { - if (active_call_map_.empty() && req_ == nullptr && resp_ == nullptr) { - return; - } - - if (event == Network::ConnectionEvent::RemoteClose) { - stats_.cx_destroy_local_with_active_rq_.inc(); - } - - if (event == Network::ConnectionEvent::LocalClose) { - stats_.cx_destroy_remote_with_active_rq_.inc(); - } -} - -Network::FilterStatus Filter::onData(Buffer::Instance& data, bool) { - if (!sniffing_) { - if (req_buffer_.length() > 0) { - // Stopped sniffing during response (in onWrite). Make sure leftover req_buffer_ contents are - // at the start of data or the upstream will see a corrupted request. - req_buffer_.move(data); - data.move(req_buffer_); - ASSERT(req_buffer_.length() == 0); - } - - return Network::FilterStatus::Continue; - } - - if (req_decoder_ == nullptr) { - req_decoder_ = std::make_unique(std::make_unique(req_callbacks_), - std::make_unique(req_callbacks_)); - } - - ENVOY_LOG(trace, "thrift: read {} bytes", data.length()); - req_buffer_.move(data); - - try { - BufferWrapper wrapped(req_buffer_); - - req_decoder_->onData(wrapped); - - // Move consumed portion of request back to data for the upstream to consume. - uint64_t pos = wrapped.position(); - if (pos > 0) { - data.move(req_buffer_, pos); - } - } catch (const EnvoyException& ex) { - ENVOY_LOG(error, "thrift error: {}", ex.what()); - req_decoder_.reset(); - data.move(req_buffer_); - stats_.request_decoding_error_.inc(); - sniffing_ = false; - } - - return Network::FilterStatus::Continue; -} - -Network::FilterStatus Filter::onWrite(Buffer::Instance& data, bool) { - if (!sniffing_) { - if (resp_buffer_.length() > 0) { - // Stopped sniffing during request (in onData). Make sure resp_buffer_ contents are at the - // start of data or the downstream will see a corrupted response. - resp_buffer_.move(data); - data.move(resp_buffer_); - ASSERT(resp_buffer_.length() == 0); - } - - return Network::FilterStatus::Continue; - } - - if (resp_decoder_ == nullptr) { - resp_decoder_ = std::make_unique(std::make_unique(resp_callbacks_), - std::make_unique(resp_callbacks_)); - } - - ENVOY_LOG(trace, "thrift wrote {} bytes", data.length()); - resp_buffer_.move(data); - - try { - BufferWrapper wrapped(resp_buffer_); - - resp_decoder_->onData(wrapped); - - // Move consumed portion of response back to data for the downstream to consume. - uint64_t pos = wrapped.position(); - if (pos > 0) { - data.move(resp_buffer_, pos); - } - } catch (const EnvoyException& ex) { - ENVOY_LOG(error, "thrift error: {}", ex.what()); - resp_decoder_.reset(); - data.move(resp_buffer_); - - stats_.response_decoding_error_.inc(); - sniffing_ = false; - } - - return Network::FilterStatus::Continue; -} - -void Filter::chargeDownstreamRequestStart(MessageType msg_type, int32_t seq_id) { - if (req_ != nullptr) { - throw EnvoyException("unexpected request messageStart callback"); - } - - if (active_call_map_.size() >= 64) { - throw EnvoyException("too many pending calls (64), disabling sniffing"); - } - - req_ = std::make_unique(*this, msg_type, seq_id); - - stats_.request_.inc(); - switch (msg_type) { - case MessageType::Call: - stats_.request_call_.inc(); - break; - case MessageType::Oneway: - stats_.request_oneway_.inc(); - break; - default: - stats_.request_invalid_type_.inc(); - break; - } -} - -void Filter::chargeDownstreamRequestComplete() { - if (req_ == nullptr) { - throw EnvoyException("unexpected request messageComplete callback"); - } - - // One-way messages do not receive responses. - if (req_->msg_type_ == MessageType::Oneway) { - req_.reset(); - return; - } - - int32_t seq_id = req_->seq_id_; - active_call_map_.emplace(seq_id, std::move(req_)); -} - -void Filter::chargeUpstreamResponseStart(MessageType msg_type, int32_t seq_id) { - if (resp_ != nullptr) { - throw EnvoyException("unexpected response messageStart callback"); - } - - auto i = active_call_map_.find(seq_id); - if (i == active_call_map_.end()) { - throw EnvoyException(fmt::format("unknown reply seq_id {}", seq_id)); - } - - resp_ = std::move(i->second); - resp_->response_msg_type_ = msg_type; - active_call_map_.erase(i); -} - -void Filter::chargeUpstreamResponseField(FieldType field_type, int16_t field_id) { - if (resp_ == nullptr) { - throw EnvoyException("unexpected response messageField callback"); - } - - if (resp_->response_msg_type_ != MessageType::Reply) { - // If this is not a reply, we'll count an exception instead of an error, so leave - // resp_->success_ unset. - return; - } - - if (resp_->success_.has_value()) { - // If resp->success_ is already set, leave the existing value. - return; - } - - // Successful replies have a single field, with field_id 0 that contains the response value. - // IDL-level exceptions are encoded as a single field with field_id >= 1. - resp_->success_ = field_id == 0 && field_type != FieldType::Stop; -} - -void Filter::chargeUpstreamResponseComplete() { - if (resp_ == nullptr) { - throw EnvoyException("unexpected response messageComplete callback"); - } - - stats_.response_.inc(); - switch (resp_->response_msg_type_) { - case MessageType::Reply: - stats_.response_reply_.inc(); - break; - case MessageType::Exception: - stats_.response_exception_.inc(); - break; - default: - stats_.response_invalid_type_.inc(); - break; - } - - if (resp_->success_.has_value()) { - if (resp_->success_.value()) { - stats_.response_success_.inc(); - } else { - stats_.response_error_.inc(); - } - } - - resp_.reset(); -} - -void Filter::RequestCallbacks::transportFrameStart(absl::optional size) { - UNREFERENCED_PARAMETER(size); - ENVOY_LOG(debug, "thrift request: started {} frame", parent_.req_decoder_->transport().name()); -} - -void Filter::RequestCallbacks::transportFrameComplete() { - ENVOY_LOG(debug, "thrift request: ended {} frame", parent_.req_decoder_->transport().name()); -} - -void Filter::RequestCallbacks::messageStart(const absl::string_view name, MessageType msg_type, - int32_t seq_id) { - ENVOY_LOG(debug, "thrift request: started {} message {}: {}", - parent_.req_decoder_->protocol().name(), name, seq_id); - parent_.chargeDownstreamRequestStart(msg_type, seq_id); -} - -void Filter::RequestCallbacks::structBegin(const absl::string_view name) { - UNREFERENCED_PARAMETER(name); - ENVOY_LOG(debug, "thrift request: started {} struct", parent_.req_decoder_->protocol().name()); -} - -void Filter::RequestCallbacks::structField(const absl::string_view name, FieldType field_type, - int16_t field_id) { - UNREFERENCED_PARAMETER(name); - ENVOY_LOG(debug, "thrift request: started {} field {}, type {}", - parent_.req_decoder_->protocol().name(), field_id, static_cast(field_type)); -} - -void Filter::RequestCallbacks::structEnd() { - ENVOY_LOG(debug, "thrift request: ended {} struct", parent_.req_decoder_->protocol().name()); -} - -void Filter::RequestCallbacks::messageComplete() { - ENVOY_LOG(debug, "thrift request: ended {} message", parent_.req_decoder_->protocol().name()); - parent_.chargeDownstreamRequestComplete(); -} - -void Filter::ResponseCallbacks::transportFrameStart(absl::optional size) { - UNREFERENCED_PARAMETER(size); - ENVOY_LOG(debug, "thrift response: started {} frame", parent_.resp_decoder_->transport().name()); -} - -void Filter::ResponseCallbacks::transportFrameComplete() { - ENVOY_LOG(debug, "thrift response: ended {} frame", parent_.resp_decoder_->transport().name()); -} - -void Filter::ResponseCallbacks::messageStart(const absl::string_view name, MessageType msg_type, - int32_t seq_id) { - ENVOY_LOG(debug, "thrift response: started {} message {}: {}", - parent_.resp_decoder_->protocol().name(), name, seq_id); - parent_.chargeUpstreamResponseStart(msg_type, seq_id); -} - -void Filter::ResponseCallbacks::structBegin(const absl::string_view name) { - UNREFERENCED_PARAMETER(name); - ENVOY_LOG(debug, "thrift response: started {} struct", parent_.req_decoder_->protocol().name()); - depth_++; -} - -void Filter::ResponseCallbacks::structField(const absl::string_view name, FieldType field_type, - int16_t field_id) { - UNREFERENCED_PARAMETER(name); - ENVOY_LOG(debug, "thrift response: started {} field {}, type {}", - parent_.req_decoder_->protocol().name(), field_id, static_cast(field_type)); - - if (depth_ == 1) { - // Only care about the outermost struct, which corresponds to the success or failure of the - // request. - parent_.chargeUpstreamResponseField(field_type, field_id); - } -} - -void Filter::ResponseCallbacks::structEnd() { - ENVOY_LOG(debug, "thrift request: ended {} struct", parent_.req_decoder_->protocol().name()); - depth_--; -} - -void Filter::ResponseCallbacks::messageComplete() { - ENVOY_LOG(debug, "thrift response: ended {} message", parent_.resp_decoder_->protocol().name()); - parent_.chargeUpstreamResponseComplete(); -} - -} // namespace ThriftProxy -} // namespace NetworkFilters -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/filter.h b/source/extensions/filters/network/thrift_proxy/filter.h deleted file mode 100644 index a0f028ed0221..000000000000 --- a/source/extensions/filters/network/thrift_proxy/filter.h +++ /dev/null @@ -1,173 +0,0 @@ -#pragma once - -#include - -#include "envoy/network/connection.h" -#include "envoy/network/filter.h" -#include "envoy/stats/stats.h" -#include "envoy/stats/stats_macros.h" -#include "envoy/stats/timespan.h" - -#include "common/buffer/buffer_impl.h" -#include "common/common/logger.h" - -#include "extensions/filters/network/thrift_proxy/decoder.h" - -namespace Envoy { -namespace Extensions { -namespace NetworkFilters { -namespace ThriftProxy { - -/** - * All thrift filter stats. @see stats_macros.h - */ -// clang-format off -#define ALL_THRIFT_FILTER_STATS(COUNTER, GAUGE, HISTOGRAM) \ - COUNTER(request) \ - COUNTER(request_call) \ - COUNTER(request_oneway) \ - COUNTER(request_invalid_type) \ - GAUGE(request_active) \ - COUNTER(request_decoding_error) \ - HISTOGRAM(request_time_ms) \ - COUNTER(response) \ - COUNTER(response_reply) \ - COUNTER(response_success) \ - COUNTER(response_error) \ - COUNTER(response_exception) \ - COUNTER(response_invalid_type) \ - COUNTER(response_decoding_error) \ - COUNTER(cx_destroy_local_with_active_rq) \ - COUNTER(cx_destroy_remote_with_active_rq) -// clang-format on - -/** - * Struct definition for all mongo proxy stats. @see stats_macros.h - */ -struct ThriftFilterStats { - ALL_THRIFT_FILTER_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) -}; - -/** - * A sniffing filter for thrift traffic. - */ -class Filter : public Network::Filter, - public Network::ConnectionCallbacks, - Logger::Loggable { -public: - Filter(const std::string& stat_prefix, Stats::Scope& scope); - ~Filter(); - - // Network::ReadFilter - Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override; - Network::FilterStatus onNewConnection() override { return Network::FilterStatus::Continue; } - void initializeReadFilterCallbacks(Network::ReadFilterCallbacks&) override {} - - // Network::WriteFilter - Network::FilterStatus onWrite(Buffer::Instance& data, bool end_stream) override; - - // Network::ConnectionCallbacks - void onEvent(Network::ConnectionEvent) override; - void onAboveWriteBufferHighWatermark() override {} - void onBelowWriteBufferLowWatermark() override {} - -private: - // RequestCallbacks handles callbacks related to decoding downstream requests. - class RequestCallbacks : public virtual ProtocolCallbacks, public virtual TransportCallbacks { - public: - RequestCallbacks(Filter& parent) : parent_(parent) {} - - // TransportCallbacks - void transportFrameStart(absl::optional size) override; - void transportFrameComplete() override; - - // ProtocolCallbacks - void messageStart(const absl::string_view name, MessageType msg_type, int32_t seq_id) override; - void structBegin(const absl::string_view name) override; - void structField(const absl::string_view name, FieldType field_type, int16_t field_id) override; - void structEnd() override; - void messageComplete() override; - - private: - Filter& parent_; - }; - - // ResponseCallbacks handles callbacks related to decoding upstream responses. - class ResponseCallbacks : public virtual ProtocolCallbacks, public virtual TransportCallbacks { - public: - ResponseCallbacks(Filter& parent) : parent_(parent) {} - - // TransportCallbacks - void transportFrameStart(absl::optional size) override; - void transportFrameComplete() override; - - // ProtocolCallbacks - void messageStart(const absl::string_view name, MessageType msg_type, int32_t seq_id) override; - void structBegin(const absl::string_view name) override; - void structField(const absl::string_view name, FieldType field_type, int16_t field_id) override; - void structEnd() override; - void messageComplete() override; - - private: - Filter& parent_; - int depth_{0}; - }; - - // ActiveMessage tracks downstream requests for which no response has been received. - struct ActiveMessage { - ActiveMessage(Filter& parent, MessageType msg_type, int32_t seq_id) - : parent_(parent), request_timer_(new Stats::Timespan(parent_.stats_.request_time_ms_)), - msg_type_(msg_type), seq_id_(seq_id) { - parent_.stats_.request_active_.inc(); - } - ~ActiveMessage() { - request_timer_->complete(); - parent_.stats_.request_active_.dec(); - } - - Filter& parent_; - Stats::TimespanPtr request_timer_; - const MessageType msg_type_; - const int32_t seq_id_; - MessageType response_msg_type_{}; - absl::optional success_{}; - }; - typedef std::unique_ptr ActiveMessagePtr; - - ThriftFilterStats generateStats(const std::string& prefix, Stats::Scope& scope) { - return ThriftFilterStats{ALL_THRIFT_FILTER_STATS(POOL_COUNTER_PREFIX(scope, prefix), - POOL_GAUGE_PREFIX(scope, prefix), - POOL_HISTOGRAM_PREFIX(scope, prefix))}; - } - - void chargeDownstreamRequestStart(MessageType msg_type, int32_t seq_id); - void chargeDownstreamRequestComplete(); - void chargeUpstreamResponseStart(MessageType msg_type, int32_t seq_id); - void chargeUpstreamResponseField(FieldType field_type, int16_t field_id); - void chargeUpstreamResponseComplete(); - - // Downstream request decoder, callbacks, and buffer. - DecoderPtr req_decoder_{}; - RequestCallbacks req_callbacks_; - Buffer::OwnedImpl req_buffer_; - // Request currently being decoded. - ActiveMessagePtr req_; - - // Upstream response decoder, callbacks, and buffer. - DecoderPtr resp_decoder_{}; - ResponseCallbacks resp_callbacks_; - Buffer::OwnedImpl resp_buffer_; - // Response currently being decoded. - ActiveMessagePtr resp_; - - // List of active request messages. - std::unordered_map active_call_map_; - - bool sniffing_{true}; - ThriftFilterStats stats_; -}; - -} // namespace ThriftProxy -} // namespace NetworkFilters -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/filters/BUILD b/source/extensions/filters/network/thrift_proxy/filters/BUILD new file mode 100644 index 000000000000..7f374b1fe5fd --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/filters/BUILD @@ -0,0 +1,50 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "filter_config_interface", + hdrs = ["filter_config.h"], + deps = [ + ":filter_interface", + "//include/envoy/server:filter_config_interface", + "//source/common/common:macros", + "//source/common/protobuf:cc_wkt_protos", + ], +) + +envoy_cc_library( + name = "factory_base_lib", + hdrs = ["factory_base.h"], + deps = [ + ":filter_config_interface", + "//source/common/protobuf:utility_lib", + ], +) + +envoy_cc_library( + name = "filter_interface", + hdrs = ["filter.h"], + external_deps = ["abseil_optional"], + deps = [ + "//include/envoy/buffer:buffer_interface", + "//include/envoy/network:connection_interface", + "//source/extensions/filters/network/thrift_proxy:protocol_interface", + "//source/extensions/filters/network/thrift_proxy:transport_interface", + "//source/extensions/filters/network/thrift_proxy/router:router_interface", + ], +) + +envoy_cc_library( + name = "well_known_names", + hdrs = ["well_known_names.h"], + deps = [ + "//source/common/singleton:const_singleton", + ], +) diff --git a/source/extensions/filters/network/thrift_proxy/filters/factory_base.h b/source/extensions/filters/network/thrift_proxy/filters/factory_base.h new file mode 100644 index 000000000000..bf2bb292f043 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/filters/factory_base.h @@ -0,0 +1,45 @@ +#pragma once + +#include "common/protobuf/utility.h" + +#include "extensions/filters/network/thrift_proxy/filters/filter_config.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { +namespace ThriftFilters { + +template class FactoryBase : public NamedThriftFilterConfigFactory { +public: + FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message& proto_config, + const std::string& stats_prefix, + Server::Configuration::FactoryContext& context) override { + return createFilterFactoryFromProtoTyped( + MessageUtil::downcastAndValidate(proto_config), stats_prefix, context); + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() override { return name_; } + +protected: + FactoryBase(const std::string& name) : name_(name) {} + +private: + virtual FilterFactoryCb + createFilterFactoryFromProtoTyped(const ConfigProto& proto_config, + const std::string& stats_prefix, + Server::Configuration::FactoryContext& context) PURE; + + const std::string name_; +}; + +} // namespace ThriftFilters +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/filters/filter.h b/source/extensions/filters/network/thrift_proxy/filters/filter.h new file mode 100644 index 000000000000..969ffcadfc46 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/filters/filter.h @@ -0,0 +1,306 @@ +#pragma once + +#include +#include +#include + +#include "envoy/buffer/buffer.h" +#include "envoy/network/connection.h" + +#include "extensions/filters/network/thrift_proxy/protocol.h" +#include "extensions/filters/network/thrift_proxy/router/router.h" +#include "extensions/filters/network/thrift_proxy/transport.h" + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { +namespace ThriftFilters { + +class DirectResponse { +public: + virtual ~DirectResponse() {} + + /** + * Encodes the response via the given Protocol. + * @param proto the Protocol to be used for message encoding + * @param buffer the Buffer into which the message should be encoded + */ + virtual void encode(ThriftProxy::Protocol& proto, Buffer::Instance& buffer) PURE; +}; + +typedef std::unique_ptr DirectResponsePtr; + +/** + * Decoder filter callbacks add additional callbacks. + */ +class DecoderFilterCallbacks { +public: + virtual ~DecoderFilterCallbacks() {} + + /** + * @return uint64_t the ID of the originating stream for logging purposes. + */ + virtual uint64_t streamId() const PURE; + + /** + * @return const Network::Connection* the originating connection, or nullptr if there is none. + */ + virtual const Network::Connection* connection() const PURE; + + /** + * Continue iterating through the filter chain with buffered data. This routine can only be + * called if the filter has previously returned StopIteration from one of the DecoderFilter + * methods. The connection manager will callbacks to the next filter in the chain. Further note + * that if the request is not complete, the calling filter may receive further callbacks and must + * return an appropriate status code depending on what the filter needs to do. + */ + virtual void continueDecoding() PURE; + + /** + * @return RouteConstSharedPtr the route for the current request. + */ + virtual Router::RouteConstSharedPtr route() PURE; + + /** + * @return TransportType the originating transport. + */ + virtual TransportType downstreamTransportType() const PURE; + + /** + * @return ProtocolType the originating protocol. + */ + virtual ProtocolType downstreamProtocolType() const PURE; + + /** + * Create a locally generated response using the provided response object. + * @param response DirectResponsePtr the response to send to the downstream client + */ + virtual void sendLocalReply(DirectResponsePtr&& response) PURE; + + /** + * Indicates the start of an upstream response. May only be called once. + * @param transport_type TransportType the upstream is using + * @param protocol_type ProtocolType the upstream is using + */ + virtual void startUpstreamResponse(TransportType transport_type, ProtocolType protocol_type) PURE; + + /** + * Called with upstream response data. + * @param data supplies the upstream's data + * @return true if the upstream response is complete; false if more data is expected + */ + virtual bool upstreamData(Buffer::Instance& data) PURE; + + /** + * Reset the downstream connection. + */ + virtual void resetDownstreamConnection() PURE; +}; + +enum class FilterStatus { + // Continue filter chain iteration. + Continue, + + // Stop iterating over filters in the filter chain. Iteration must be explicitly restarted via + // continueDecoding(). + StopIteration +}; + +/** + * Decoder filter interface. + */ +class DecoderFilter { +public: + virtual ~DecoderFilter() {} + + /** + * This routine is called prior to a filter being destroyed. This may happen after normal stream + * finish (both downstream and upstream) or due to reset. Every filter is responsible for making + * sure that any async events are cleaned up in the context of this routine. This includes timers, + * network calls, etc. The reason there is an onDestroy() method vs. doing this type of cleanup + * in the destructor is due to the deferred deletion model that Envoy uses to avoid stack unwind + * complications. Filters must not invoke either encoder or decoder filter callbacks after having + * onDestroy() invoked. + */ + virtual void onDestroy() PURE; + + /** + * Called by the connection manager once to initialize the filter decoder callbacks that the + * filter should use. Callbacks will not be invoked by the filter after onDestroy() is called. + */ + virtual void setDecoderFilterCallbacks(DecoderFilterCallbacks& callbacks) PURE; + + /** + * Resets the upstream connection. + */ + virtual void resetUpstreamConnection() PURE; + + /** + * Indicates the start of a Thrift transport frame was detected. Unframed transports generate + * simulated start messages. + * @param size the size of the message, if available to the transport + */ + virtual FilterStatus transportBegin(absl::optional size) PURE; + + /** + * Indicates the end of a Thrift transport frame was detected. Unframed transport generate + * simulated complete messages. + */ + virtual FilterStatus transportEnd() PURE; + + /** + * Indicates that the start of a Thrift protocol message was detected. + * @param name the name of the message, if available + * @param msg_type the type of the message + * @param seq_id the message sequence id + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus messageBegin(absl::string_view name, MessageType msg_type, + int32_t seq_id) PURE; + + /** + * Indicates that the end of a Thrift protocol message was detected. + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus messageEnd() PURE; + + /** + * Indicates that the start of a Thrift protocol struct was detected. + * @param name the name of the struct, if available + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus structBegin(absl::string_view name) PURE; + + /** + * Indicates that the end of a Thrift protocol struct was detected. + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus structEnd() PURE; + + /** + * Indicates that the start of Thrift protocol struct field was detected. + * @param name the name of the field, if available + * @param field_type the type of the field + * @param field_id the field id + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus fieldBegin(absl::string_view name, FieldType field_type, + int16_t field_id) PURE; + + /** + * Indicates that the end of a Thrift protocol struct field was detected. + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus fieldEnd() PURE; + + /** + * A struct field, map key, map value, list element or set element was detected. + * @param value type value of the field + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus boolValue(bool value) PURE; + virtual FilterStatus byteValue(uint8_t value) PURE; + virtual FilterStatus int16Value(int16_t value) PURE; + virtual FilterStatus int32Value(int32_t value) PURE; + virtual FilterStatus int64Value(int64_t value) PURE; + virtual FilterStatus doubleValue(double value) PURE; + virtual FilterStatus stringValue(absl::string_view value) PURE; + + /** + * Indicates the start of a Thrift protocol map was detected. + * @param key_type the map key type + * @param value_type the map value type + * @param size the number of key-value pairs + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus mapBegin(FieldType key_type, FieldType value_type, uint32_t size) PURE; + + /** + * Indicates that the end of a Thrift protocol map was detected. + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus mapEnd() PURE; + + /** + * Indicates the start of a Thrift protocol list was detected. + * @param elem_type the list value type + * @param size the number of values in the list + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus listBegin(FieldType elem_type, uint32_t size) PURE; + + /** + * Indicates that the end of a Thrift protocol list was detected. + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus listEnd() PURE; + + /** + * Indicates the start of a Thrift protocol set was detected. + * @param elem_type the set value type + * @param size the number of values in the set + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus setBegin(FieldType elem_type, uint32_t size) PURE; + + /** + * Indicates that the end of a Thrift protocol set was detected. + * @return FilterStatus to indicate if filter chain iteration should continue + */ + virtual FilterStatus setEnd() PURE; +}; + +typedef std::shared_ptr DecoderFilterSharedPtr; + +/** + * These callbacks are provided by the connection manager to the factory so that the factory can + * build the filter chain in an application specific way. + */ +class FilterChainFactoryCallbacks { +public: + virtual ~FilterChainFactoryCallbacks() {} + + /** + * Add a decoder filter that is used when reading connection data. + * @param filter supplies the filter to add. + */ + virtual void addDecoderFilter(DecoderFilterSharedPtr filter) PURE; +}; + +/** + * This function is used to wrap the creation of a Thrift filter chain for new connections as they + * come in. Filter factories create the function at configuration initialization time, and then + * they are used at runtime. + * @param callbacks supplies the callbacks for the stream to install filters to. Typically the + * function will install a single filter, but it's technically possibly to install more than one + * if desired. + */ +typedef std::function FilterFactoryCb; + +/** + * A FilterChainFactory is used by a connection manager to create a Thrift level filter chain when + * a new connection is created. Typically it would be implemented by a configuration engine that + * would install a set of filters that are able to process an application scenario on top of a + * stream of Thrift requests. + */ +class FilterChainFactory { +public: + virtual ~FilterChainFactory() {} + + /** + * Called when a new Thrift stream is created on the connection. + * @param callbacks supplies the "sink" that is used for actually creating the filter chain. @see + * FilterChainFactoryCallbacks. + */ + virtual void createFilterChain(FilterChainFactoryCallbacks& callbacks) PURE; +}; + +} // namespace ThriftFilters +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/filters/filter_config.h b/source/extensions/filters/network/thrift_proxy/filters/filter_config.h new file mode 100644 index 000000000000..86f4b7730517 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/filters/filter_config.h @@ -0,0 +1,55 @@ +#pragma once + +#include "envoy/server/filter_config.h" + +#include "common/common/macros.h" +#include "common/protobuf/protobuf.h" + +#include "extensions/filters/network/thrift_proxy/filters/filter.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { +namespace ThriftFilters { + +/** + * Implemented by each Thrift filter and registered via Registry::registerFactory or the + * convenience class RegisterFactory. + */ +class NamedThriftFilterConfigFactory { +public: + virtual ~NamedThriftFilterConfigFactory() {} + + /** + * Create a particular thrift filter factory implementation. If the implementation is unable to + * produce a factory with the provided parameters, it should throw an EnvoyException in the case + * of general error. The returned callback should always be initialized. + * @param config supplies the configuration for the filter + * @param stat_prefix prefix for stat logging + * @param context supplies the filter's context. + * @return FilterFactoryCb the factory creation function. + */ + virtual FilterFactoryCb + createFilterFactoryFromProto(const Protobuf::Message& config, const std::string& stat_prefix, + Server::Configuration::FactoryContext& context) PURE; + + /** + * @return ProtobufTypes::MessagePtr create empty config proto message for v2. The filter + * config, which arrives in an opaque google.protobuf.Struct message, will be converted to + * JSON and then parsed into this empty proto. + */ + virtual ProtobufTypes::MessagePtr createEmptyConfigProto() PURE; + + /** + * @return std::string the identifying name for a particular implementation of a thrift filter + * produced by the factory. + */ + virtual std::string name() PURE; +}; + +} // namespace ThriftFilters +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/filters/well_known_names.h b/source/extensions/filters/network/thrift_proxy/filters/well_known_names.h new file mode 100644 index 000000000000..41abac8d5475 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/filters/well_known_names.h @@ -0,0 +1,25 @@ +#pragma once + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { +namespace ThriftFilters { + +/** + * Well-known http filter names. + * NOTE: New filters should use the well known name: envoy.filters.thrift.name. + */ +class ThriftFilterNameValues { +public: + // Router filter + const std::string ROUTER = "envoy.filters.thrift.router"; +}; + +typedef ConstSingleton ThriftFilterNames; + +} // namespace ThriftFilters +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/framed_transport_impl.cc b/source/extensions/filters/network/thrift_proxy/framed_transport_impl.cc index b66863e9bfbf..a45861a349e4 100644 --- a/source/extensions/filters/network/thrift_proxy/framed_transport_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/framed_transport_impl.cc @@ -10,28 +10,26 @@ namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { -bool FramedTransportImpl::decodeFrameStart(Buffer::Instance& buffer) { +bool FramedTransportImpl::decodeFrameStart(Buffer::Instance& buffer, + absl::optional& size) { if (buffer.length() < 4) { return false; } - int32_t size = BufferHelper::peekI32(buffer); + int32_t thrift_size = BufferHelper::peekI32(buffer); - if (size <= 0 || size > MaxFrameSize) { - throw EnvoyException(fmt::format("invalid thrift framed transport frame size {}", size)); + if (thrift_size <= 0 || thrift_size > MaxFrameSize) { + throw EnvoyException(fmt::format("invalid thrift framed transport frame size {}", thrift_size)); } - onFrameStart(absl::optional(static_cast(size))); - buffer.drain(4); - return true; -} -bool FramedTransportImpl::decodeFrameEnd(Buffer::Instance&) { - onFrameComplete(); + size = static_cast(thrift_size); return true; } +bool FramedTransportImpl::decodeFrameEnd(Buffer::Instance&) { return true; } + void FramedTransportImpl::encodeFrame(Buffer::Instance& buffer, Buffer::Instance& message) { uint64_t size = message.length(); if (size == 0 || size > MaxFrameSize) { @@ -44,6 +42,17 @@ void FramedTransportImpl::encodeFrame(Buffer::Instance& buffer, Buffer::Instance buffer.move(message); } +class FramedTransportConfigFactory : public TransportFactoryBase { +public: + FramedTransportConfigFactory() : TransportFactoryBase(TransportNames::get().FRAMED) {} +}; + +/** + * Static registration for the framed transport. @see RegisterFactory. + */ +static Registry::RegisterFactory + register_; + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/source/extensions/filters/network/thrift_proxy/framed_transport_impl.h b/source/extensions/filters/network/thrift_proxy/framed_transport_impl.h index b51b5b3819cd..4c7569487ea3 100644 --- a/source/extensions/filters/network/thrift_proxy/framed_transport_impl.h +++ b/source/extensions/filters/network/thrift_proxy/framed_transport_impl.h @@ -17,13 +17,14 @@ namespace ThriftProxy { * FramedTransportImpl implements the Thrift Framed transport. * See https://github.com/apache/thrift/blob/master/doc/specs/thrift-rpc.md */ -class FramedTransportImpl : public TransportImplBase { +class FramedTransportImpl : public Transport { public: - FramedTransportImpl(TransportCallbacks& callbacks) : TransportImplBase(callbacks) {} + FramedTransportImpl() {} // Transport const std::string& name() const override { return TransportNames::get().FRAMED; } - bool decodeFrameStart(Buffer::Instance& buffer) override; + TransportType type() const override { return TransportType::Framed; } + bool decodeFrameStart(Buffer::Instance& buffer, absl::optional& size) override; bool decodeFrameEnd(Buffer::Instance& buffer) override; void encodeFrame(Buffer::Instance& buffer, Buffer::Instance& message) override; diff --git a/source/extensions/filters/network/thrift_proxy/protocol.h b/source/extensions/filters/network/thrift_proxy/protocol.h index 472f2d1852d5..02f2808427e0 100644 --- a/source/extensions/filters/network/thrift_proxy/protocol.h +++ b/source/extensions/filters/network/thrift_proxy/protocol.h @@ -5,7 +5,10 @@ #include "envoy/buffer/buffer.h" #include "envoy/common/pure.h" +#include "envoy/registry/registry.h" +#include "common/common/assert.h" +#include "common/config/utility.h" #include "common/singleton/const_singleton.h" #include "absl/strings/string_view.h" @@ -15,6 +18,16 @@ namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { +enum class ProtocolType { + Binary, + LaxBinary, + Compact, + Auto, + + // ATTENTION: MAKE SURE THIS REMAINS EQUAL TO THE LAST PROTOCOL TYPE + LastProtocolType = Auto, +}; + /** * Names of available Protocol implementations. */ @@ -29,11 +42,23 @@ class ProtocolNameValues { // Compact protocol const std::string COMPACT = "compact"; - // JSON protocol - const std::string JSON = "json"; - // Auto-detection protocol const std::string AUTO = "auto"; + + const std::string& fromType(ProtocolType type) const { + switch (type) { + case ProtocolType::Binary: + return BINARY; + case ProtocolType::LaxBinary: + return LAX_BINARY; + case ProtocolType::Compact: + return COMPACT; + case ProtocolType::Auto: + return AUTO; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + } }; typedef ConstSingleton ProtocolNames; @@ -75,48 +100,6 @@ enum class FieldType { LastFieldType = List, }; -/** - * ProtocolCallbacks are Thrift protocol-level callbacks. - */ -class ProtocolCallbacks { -public: - virtual ~ProtocolCallbacks() {} - - /** - * Indicates that the start of a Thrift protocol message was detected. - * @param name the name of the message, if available - * @param msg_type the type of the message - * @param seq_id the message sequence id - */ - virtual void messageStart(const absl::string_view name, MessageType msg_type, - int32_t seq_id) PURE; - - /** - * Indicates that the start of a Thrift protocol struct was detected. - * @param name the name of the struct, if available - */ - virtual void structBegin(const absl::string_view name) PURE; - - /** - * Indicates that the start of Thrift protocol struct field was detected. - * @param name the name of the field, if available - * @param field_type the type of the field - * @param field_id the field id - */ - virtual void structField(const absl::string_view name, FieldType field_type, - int16_t field_id) PURE; - - /** - * Indicates that the end of a Thrift protocol struct was detected. - */ - virtual void structEnd() PURE; - - /** - * Indicates that the end of a Thrift protocol message was detected. - */ - virtual void messageComplete() PURE; -}; - /** * Protocol represents the operations necessary to implement the a generic Thrift protocol. * See https://github.com/apache/thrift/blob/master/doc/specs/thrift-protocol-spec.md @@ -125,8 +108,16 @@ class Protocol { public: virtual ~Protocol() {} + /** + * @return const std::string& the human-readable name of the protocol + */ virtual const std::string& name() const PURE; + /** + * @return ProtocolType the protocol type + */ + virtual ProtocolType type() const PURE; + /** * Reads the start of a Thrift protocol message from the buffer and updates the name, msg_type, * and seq_id parameters with values from the message header. If successful, the message header @@ -485,6 +476,52 @@ class Protocol { typedef std::unique_ptr ProtocolPtr; +/** + * Implemented by each Thrift protocol and registered via Registry::registerFactory or the + * convenience class RegisterFactory. + */ +class NamedProtocolConfigFactory { +public: + virtual ~NamedProtocolConfigFactory() {} + + /** + * Create a particular Thrift protocol + * @return ProtocolFactoryCb the protocol + */ + virtual ProtocolPtr createProtocol() PURE; + + /** + * @return std::string the identifying name for a particular implementation of thrift protocol + * produced by the factory. + */ + virtual std::string name() PURE; + + /** + * Convenience method to lookup a factory by type. + * @param ProtocolType the protocol type + * @return NamedProtocolConfigFactory& for the ProtocolType + */ + static NamedProtocolConfigFactory& getFactory(ProtocolType type) { + const std::string& name = ProtocolNames::get().fromType(type); + return Envoy::Config::Utility::getAndCheckFactory(name); + } +}; + +/** + * ProtocolFactoryBase provides a template for a trivial NamedProtocolConfigFactory. + */ +template class ProtocolFactoryBase : public NamedProtocolConfigFactory { + ProtocolPtr createProtocol() override { return std::move(std::make_unique()); } + + std::string name() override { return name_; } + +protected: + ProtocolFactoryBase(const std::string& name) : name_(name) {} + +private: + const std::string name_; +}; + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/source/extensions/filters/network/thrift_proxy/protocol_converter.h b/source/extensions/filters/network/thrift_proxy/protocol_converter.h new file mode 100644 index 000000000000..af7b6dfa2af3 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/protocol_converter.h @@ -0,0 +1,144 @@ +#pragma once + +#include "envoy/buffer/buffer.h" + +#include "extensions/filters/network/thrift_proxy/filters/filter.h" +#include "extensions/filters/network/thrift_proxy/protocol.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { + +/** + * ProtocolConverter is an abstract class that implements protocol-related methods on + * ThriftFilters::DecoderFilter in terms of converting the decoded messages into a different + * protocol. + */ +class ProtocolConverter : public ThriftFilters::DecoderFilter { +public: + ProtocolConverter() {} + ~ProtocolConverter() {} + + void initProtocolConverter(ProtocolPtr&& proto, Buffer::Instance& buffer) { + proto_ = std::move(proto); + buffer_ = &buffer; + } + + // ThiftFilters::DecoderFilter + void onDestroy() override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + void setDecoderFilterCallbacks(ThriftFilters::DecoderFilterCallbacks&) override { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } + void resetUpstreamConnection() override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + ThriftFilters::FilterStatus messageBegin(absl::string_view name, MessageType msg_type, + int32_t seq_id) override { + proto_->writeMessageBegin(*buffer_, std::string(name), msg_type, seq_id); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus messageEnd() override { + proto_->writeMessageEnd(*buffer_); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus structBegin(absl::string_view name) override { + proto_->writeStructBegin(*buffer_, std::string(name)); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus structEnd() override { + proto_->writeFieldBegin(*buffer_, "", FieldType::Stop, 0); + proto_->writeStructEnd(*buffer_); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus fieldBegin(absl::string_view name, FieldType field_type, + int16_t field_id) override { + proto_->writeFieldBegin(*buffer_, std::string(name), field_type, field_id); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus fieldEnd() override { + proto_->writeFieldEnd(*buffer_); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus boolValue(bool value) override { + proto_->writeBool(*buffer_, value); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus byteValue(uint8_t value) override { + proto_->writeByte(*buffer_, value); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus int16Value(int16_t value) override { + proto_->writeInt16(*buffer_, value); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus int32Value(int32_t value) override { + proto_->writeInt32(*buffer_, value); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus int64Value(int64_t value) override { + proto_->writeInt64(*buffer_, value); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus doubleValue(double value) override { + proto_->writeDouble(*buffer_, value); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus stringValue(absl::string_view value) override { + proto_->writeString(*buffer_, std::string(value)); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus mapBegin(FieldType key_type, FieldType value_type, + uint32_t size) override { + proto_->writeMapBegin(*buffer_, key_type, value_type, size); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus mapEnd() override { + proto_->writeMapEnd(*buffer_); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus listBegin(FieldType elem_type, uint32_t size) override { + proto_->writeListBegin(*buffer_, elem_type, size); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus listEnd() override { + proto_->writeListEnd(*buffer_); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus setBegin(FieldType elem_type, uint32_t size) override { + proto_->writeSetBegin(*buffer_, elem_type, size); + return ThriftFilters::FilterStatus::Continue; + } + + ThriftFilters::FilterStatus setEnd() override { + proto_->writeSetEnd(*buffer_); + return ThriftFilters::FilterStatus::Continue; + } + +protected: + ProtocolType protocolType() const { return proto_->type(); } + +private: + ProtocolPtr proto_; + Buffer::Instance* buffer_{}; +}; + +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/protocol_impl.cc b/source/extensions/filters/network/thrift_proxy/protocol_impl.cc index f2cac638d292..46636099f543 100644 --- a/source/extensions/filters/network/thrift_proxy/protocol_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/protocol_impl.cc @@ -26,9 +26,9 @@ bool AutoProtocolImpl::readMessageBegin(Buffer::Instance& buffer, std::string& n uint16_t version = BufferHelper::peekU16(buffer); if (BinaryProtocolImpl::isMagic(version)) { - setProtocol(std::make_unique(callbacks_)); + setProtocol(std::make_unique()); } else if (CompactProtocolImpl::isMagic(version)) { - setProtocol(std::make_unique(callbacks_)); + setProtocol(std::make_unique()); } else { throw EnvoyException( fmt::format("unknown thrift auto protocol message start {:04x}", version)); @@ -45,6 +45,16 @@ bool AutoProtocolImpl::readMessageEnd(Buffer::Instance& buffer) { return protocol_->readMessageEnd(buffer); } +class AutoProtocolConfigFactory : public ProtocolFactoryBase { +public: + AutoProtocolConfigFactory() : ProtocolFactoryBase(ProtocolNames::get().AUTO) {} +}; + +/** + * Static registration for the auto protocol. @see RegisterFactory. + */ +static Registry::RegisterFactory register_; + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/source/extensions/filters/network/thrift_proxy/protocol_impl.h b/source/extensions/filters/network/thrift_proxy/protocol_impl.h index def5061fa8e9..0eb374e9f986 100644 --- a/source/extensions/filters/network/thrift_proxy/protocol_impl.h +++ b/source/extensions/filters/network/thrift_proxy/protocol_impl.h @@ -13,39 +13,24 @@ namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { -/* - * ProtocolImplBase provides a base class for Protocol implementations. - */ -class ProtocolImplBase : public virtual Protocol { -public: - ProtocolImplBase(ProtocolCallbacks& callbacks) : callbacks_(callbacks) {} - -protected: - void onMessageStart(const absl::string_view name, MessageType msg_type, int32_t seq_id) const { - callbacks_.messageStart(name, msg_type, seq_id); - } - void onStructBegin(const absl::string_view name) const { callbacks_.structBegin(name); } - void onStructField(const absl::string_view name, FieldType field_type, int16_t field_id) const { - callbacks_.structField(name, field_type, field_id); - } - void onStructEnd() const { callbacks_.structEnd(); } - void onMessageComplete() const { callbacks_.messageComplete(); } - - ProtocolCallbacks& callbacks_; -}; - /** * AutoProtocolImpl attempts to distinguish between the Thrift binary (strict mode only) and * compact protocols and then delegates subsequent decoding operations to the appropriate Protocol * implementation. */ -class AutoProtocolImpl : public ProtocolImplBase { +class AutoProtocolImpl : public Protocol { public: - AutoProtocolImpl(ProtocolCallbacks& callbacks) - : ProtocolImplBase(callbacks), name_(ProtocolNames::get().AUTO) {} + AutoProtocolImpl() : name_(ProtocolNames::get().AUTO) {} // Protocol const std::string& name() const override { return name_; } + ProtocolType type() const override { + if (protocol_ != nullptr) { + return protocol_->type(); + } + return ProtocolType::Auto; + } + bool readMessageBegin(Buffer::Instance& buffer, std::string& name, MessageType& msg_type, int32_t& seq_id) override; bool readMessageEnd(Buffer::Instance& buffer) override; diff --git a/source/extensions/filters/network/thrift_proxy/router/BUILD b/source/extensions/filters/network/thrift_proxy/router/BUILD new file mode 100644 index 000000000000..2d32db0ee154 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/router/BUILD @@ -0,0 +1,51 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":router_lib", + "//include/envoy/registry", + "//source/extensions/filters/network/thrift_proxy/filters:factory_base_lib", + "//source/extensions/filters/network/thrift_proxy/filters:filter_config_interface", + "//source/extensions/filters/network/thrift_proxy/filters:well_known_names", + "@envoy_api//envoy/extensions/filters/network/thrift_proxy/v2alpha1/router:router_cc", + ], +) + +envoy_cc_library( + name = "router_interface", + hdrs = ["router.h"], + external_deps = ["abseil_optional"], + deps = [], +) + +envoy_cc_library( + name = "router_lib", + srcs = ["router_impl.cc"], + hdrs = ["router_impl.h"], + deps = [ + ":router_interface", + "//include/envoy/tcp:conn_pool_interface", + "//include/envoy/upstream:cluster_manager_interface", + "//include/envoy/upstream:load_balancer_interface", + "//include/envoy/upstream:thread_local_cluster_interface", + "//source/common/common:logger_lib", + "//source/extensions/filters/network/thrift_proxy:app_exception_lib", + "//source/extensions/filters/network/thrift_proxy:conn_manager_lib", + "//source/extensions/filters/network/thrift_proxy:protocol_converter_lib", + "//source/extensions/filters/network/thrift_proxy:protocol_lib", + "//source/extensions/filters/network/thrift_proxy:transport_lib", + "//source/extensions/filters/network/thrift_proxy/filters:filter_interface", + "@envoy_api//envoy/extensions/filters/network/thrift_proxy/v2alpha1:thrift_proxy_cc", + ], +) diff --git a/source/extensions/filters/network/thrift_proxy/router/config.cc b/source/extensions/filters/network/thrift_proxy/router/config.cc new file mode 100644 index 000000000000..abf7cafbc265 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/router/config.cc @@ -0,0 +1,34 @@ +#include "extensions/filters/network/thrift_proxy/router/config.h" + +#include "envoy/registry/registry.h" + +#include "extensions/filters/network/thrift_proxy/router/router_impl.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { +namespace Router { + +ThriftFilters::FilterFactoryCb RouterFilterConfig::createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::network::thrift_proxy::v2alpha1::router::Router& proto_config, + const std::string& stat_prefix, Server::Configuration::FactoryContext& context) { + UNREFERENCED_PARAMETER(proto_config); + UNREFERENCED_PARAMETER(stat_prefix); + + return [&context](ThriftFilters::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addDecoderFilter(std::make_shared(context.clusterManager())); + }; +} + +/** + * Static registration for the router filter. @see RegisterFactory. + */ +static Registry::RegisterFactory + register_; + +} // namespace Router +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/router/config.h b/source/extensions/filters/network/thrift_proxy/router/config.h new file mode 100644 index 000000000000..ae4629bfd0ad --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/router/config.h @@ -0,0 +1,32 @@ +#pragma once + +#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.pb.h" +#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.pb.validate.h" + +#include "extensions/filters/network/thrift_proxy/filters/factory_base.h" +#include "extensions/filters/network/thrift_proxy/filters/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { +namespace Router { + +class RouterFilterConfig + : public ThriftFilters::FactoryBase< + envoy::extensions::filters::network::thrift_proxy::v2alpha1::router::Router> { +public: + RouterFilterConfig() : FactoryBase(ThriftFilters::ThriftFilterNames::get().ROUTER) {} + +private: + ThriftFilters::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::network::thrift_proxy::v2alpha1::router::Router& + proto_config, + const std::string& stat_prefix, Server::Configuration::FactoryContext& context) override; +}; + +} // namespace Router +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/router/router.h b/source/extensions/filters/network/thrift_proxy/router/router.h new file mode 100644 index 000000000000..32d717de5235 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/router/router.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { +namespace Router { + +/** + * RouteEntry is an individual resolved route entry. + */ +class RouteEntry { +public: + virtual ~RouteEntry() {} + + /** + * @return const std::string& the upstream cluster that owns the route. + */ + virtual const std::string& clusterName() const PURE; +}; + +/** + * Route holds the RouteEntry for a request. + */ +class Route { +public: + virtual ~Route() {} + + /** + * @return the route entry or nullptr if there is no matching route for the request. + */ + virtual const RouteEntry* routeEntry() const PURE; +}; + +typedef std::shared_ptr RouteConstSharedPtr; + +/** + * The router configuration. + */ +class Config { +public: + virtual ~Config() {} + + /** + * Based on the incoming Thrift request transport and/or protocol data, determine the target + * route for the request. + * @param method supplies the thrift method name + * @return the route or nullptr if there is no matching route for the request. + */ + virtual RouteConstSharedPtr route(const std::string& method) const PURE; +}; + +typedef std::shared_ptr ConfigConstSharedPtr; + +} // namespace Router +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/router/router_impl.cc b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc new file mode 100644 index 000000000000..057d529f1398 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc @@ -0,0 +1,305 @@ +#include "extensions/filters/network/thrift_proxy/router/router_impl.h" + +#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" +#include "envoy/upstream/cluster_manager.h" +#include "envoy/upstream/thread_local_cluster.h" + +#include "extensions/filters/network/thrift_proxy/app_exception_impl.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { +namespace Router { + +RouteEntryImplBase::RouteEntryImplBase( + const envoy::extensions::filters::network::thrift_proxy::v2alpha1::Route& route) + : cluster_name_(route.route().cluster()) {} + +const std::string& RouteEntryImplBase::clusterName() const { return cluster_name_; } + +const RouteEntry* RouteEntryImplBase::routeEntry() const { return this; } + +RouteConstSharedPtr RouteEntryImplBase::clusterEntry() const { return shared_from_this(); } + +MethodNameRouteEntryImpl::MethodNameRouteEntryImpl( + const envoy::extensions::filters::network::thrift_proxy::v2alpha1::Route& route) + : RouteEntryImplBase(route), method_name_(route.match().method()) {} + +RouteConstSharedPtr MethodNameRouteEntryImpl::matches(const std::string& method_name) const { + if (method_name_.empty() || method_name_ == method_name) { + return clusterEntry(); + } + + return nullptr; +} + +RouteMatcher::RouteMatcher( + const envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration& config) { + for (const auto& route : config.routes()) { + routes_.emplace_back(new MethodNameRouteEntryImpl(route)); + } +} + +RouteConstSharedPtr RouteMatcher::route(const std::string& method_name) const { + for (const auto& route : routes_) { + RouteConstSharedPtr route_entry = route->matches(method_name); + if (nullptr != route_entry) { + return route_entry; + } + } + + return nullptr; +} + +void Router::onDestroy() { + if (upstream_request_ != nullptr) { + upstream_request_->resetStream(); + } + cleanup(); +} + +void Router::setDecoderFilterCallbacks(ThriftFilters::DecoderFilterCallbacks& callbacks) { + callbacks_ = &callbacks; + + // TODO(zuercher): handle buffer limits +} + +void Router::resetUpstreamConnection() { + if (upstream_request_ != nullptr) { + upstream_request_->resetStream(); + } +} + +ThriftFilters::FilterStatus Router::transportBegin(absl::optional size) { + UNREFERENCED_PARAMETER(size); + return ThriftFilters::FilterStatus::Continue; +} + +ThriftFilters::FilterStatus Router::transportEnd() { + if (upstream_request_->msg_type_ == MessageType::Oneway) { + // No response expected + upstream_request_->onResponseComplete(); + cleanup(); + } + return ThriftFilters::FilterStatus::Continue; +} + +ThriftFilters::FilterStatus Router::messageBegin(absl::string_view name, MessageType msg_type, + int32_t seq_id) { + // TODO(zuercher): route stats (e.g., no_route, no_cluster, upstream_rq_maintenance_mode, no + // healtthy upstream) + + route_ = callbacks_->route(); + if (!route_) { + ENVOY_STREAM_LOG(debug, "no cluster match for method '{}'", *callbacks_, name); + callbacks_->sendLocalReply(ThriftFilters::DirectResponsePtr{ + new AppException(name, seq_id, AppExceptionType::UnknownMethod, + fmt::format("no route for method '{}'", name))}); + return ThriftFilters::FilterStatus::StopIteration; + } + + route_entry_ = route_->routeEntry(); + + Upstream::ThreadLocalCluster* cluster = cluster_manager_.get(route_entry_->clusterName()); + if (!cluster) { + ENVOY_STREAM_LOG(debug, "unknown cluster '{}'", *callbacks_, route_entry_->clusterName()); + callbacks_->sendLocalReply(ThriftFilters::DirectResponsePtr{ + new AppException(name, seq_id, AppExceptionType::InternalError, + fmt::format("unknown cluster '{}'", route_entry_->clusterName()))}); + return ThriftFilters::FilterStatus::StopIteration; + } + + cluster_ = cluster->info(); + ENVOY_STREAM_LOG(debug, "cluster '{}' match for method '{}'", *callbacks_, + route_entry_->clusterName(), name); + + if (cluster_->maintenanceMode()) { + callbacks_->sendLocalReply(ThriftFilters::DirectResponsePtr{new AppException( + name, seq_id, AppExceptionType::InternalError, + fmt::format("maintenance mode for cluster '{}'", route_entry_->clusterName()))}); + return ThriftFilters::FilterStatus::StopIteration; + } + + Tcp::ConnectionPool::Instance* conn_pool = cluster_manager_.tcpConnPoolForCluster( + route_entry_->clusterName(), Upstream::ResourcePriority::Default, this); + if (!conn_pool) { + callbacks_->sendLocalReply(ThriftFilters::DirectResponsePtr{new AppException( + name, seq_id, AppExceptionType::InternalError, + fmt::format("no healthy upstream for '{}'", route_entry_->clusterName()))}); + return ThriftFilters::FilterStatus::StopIteration; + } + + ENVOY_STREAM_LOG(debug, "router decoding request", *callbacks_); + + upstream_request_.reset(new UpstreamRequest(*this, *conn_pool, name, msg_type, seq_id)); + upstream_request_->start(); + return ThriftFilters::FilterStatus::StopIteration; +} + +ThriftFilters::FilterStatus Router::messageEnd() { + ProtocolConverter::messageEnd(); + + Buffer::OwnedImpl transport_buffer; + upstream_request_->transport_->encodeFrame(transport_buffer, upstream_request_buffer_); + upstream_request_->conn_data_->connection().write(transport_buffer, false); + upstream_request_->onRequestComplete(); + return ThriftFilters::FilterStatus::Continue; +} + +void Router::onUpstreamData(Buffer::Instance& data, bool end_stream) { + ASSERT(!upstream_request_->response_complete_); + + if (!upstream_request_->response_started_) { + callbacks_->startUpstreamResponse(upstream_request_->transport_->type(), protocolType()); + upstream_request_->response_started_ = true; + } + + if (callbacks_->upstreamData(data)) { + upstream_request_->onResponseComplete(); + cleanup(); + return; + } + + if (end_stream) { + // Response is incomplete, but no more data is coming. + upstream_request_->onResponseComplete(); + upstream_request_->onResetStream( + Tcp::ConnectionPool::PoolFailureReason::RemoteConnectionFailure); + cleanup(); + } +} + +void Router::onEvent(Network::ConnectionEvent event) { + if (!upstream_request_ || upstream_request_->response_complete_) { + // Client closed connection after completing response. + return; + } + + switch (event) { + case Network::ConnectionEvent::RemoteClose: + upstream_request_->onResetStream( + Tcp::ConnectionPool::PoolFailureReason::RemoteConnectionFailure); + break; + case Network::ConnectionEvent::LocalClose: + upstream_request_->onResetStream( + Tcp::ConnectionPool::PoolFailureReason::LocalConnectionFailure); + break; + default: + // Connected is consumed by the connection pool. + NOT_REACHED_GCOVR_EXCL_LINE; + } +} + +const Network::Connection* Router::downstreamConnection() const { + if (callbacks_ != nullptr) { + return callbacks_->connection(); + } + + return nullptr; +} + +void Router::convertMessageBegin(const std::string& name, MessageType msg_type, int32_t seq_id) { + ProtocolConverter::messageBegin(absl::string_view(name), msg_type, seq_id); +} + +void Router::cleanup() { upstream_request_.reset(); } + +Router::UpstreamRequest::UpstreamRequest(Router& parent, Tcp::ConnectionPool::Instance& pool, + absl::string_view method_name, MessageType msg_type, + int32_t seq_id) + : parent_(parent), conn_pool_(pool), method_name_(std::string(method_name)), + msg_type_(msg_type), seq_id_(seq_id), request_complete_(false), response_started_(false), + response_complete_(false) {} + +Router::UpstreamRequest::~UpstreamRequest() {} + +void Router::UpstreamRequest::start() { + Tcp::ConnectionPool::Cancellable* handle = conn_pool_.newConnection(*this); + if (handle) { + conn_pool_handle_ = handle; + } +} + +void Router::UpstreamRequest::resetStream() { + if (conn_data_ != nullptr) { + conn_data_->connection().close(Network::ConnectionCloseType::NoFlush); + conn_data_ = nullptr; + } +} + +void Router::UpstreamRequest::onPoolFailure(Tcp::ConnectionPool::PoolFailureReason reason, + Upstream::HostDescriptionConstSharedPtr host) { + // Mimic an upstream reset. + onUpstreamHostSelected(host); + onResetStream(reason); +} + +void Router::UpstreamRequest::onPoolReady(Tcp::ConnectionPool::ConnectionData& conn_data, + Upstream::HostDescriptionConstSharedPtr host) { + onUpstreamHostSelected(host); + conn_data_ = &conn_data; + conn_data_->addUpstreamCallbacks(parent_); + + conn_pool_handle_ = nullptr; + + // TODO(zuercher): let cluster specify a specific transport and protocol + transport_ = + NamedTransportConfigFactory::getFactory(parent_.callbacks_->downstreamTransportType()) + .createTransport(); + + parent_.initProtocolConverter( + NamedProtocolConfigFactory::getFactory(parent_.callbacks_->downstreamProtocolType()) + .createProtocol(), + parent_.upstream_request_buffer_); + + // TODO(zuercher): need to use an upstream-connection-specific sequence id + parent_.convertMessageBegin(method_name_, msg_type_, seq_id_); + + parent_.callbacks_->continueDecoding(); +} + +void Router::UpstreamRequest::onRequestComplete() { request_complete_ = true; } + +void Router::UpstreamRequest::onResponseComplete() { + response_complete_ = true; + if (conn_data_ != nullptr) { + conn_data_->release(); + } + conn_data_ = nullptr; +} + +void Router::UpstreamRequest::onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host) { + upstream_host_ = host; +} + +void Router::UpstreamRequest::onResetStream(Tcp::ConnectionPool::PoolFailureReason reason) { + switch (reason) { + case Tcp::ConnectionPool::PoolFailureReason::Overflow: + parent_.callbacks_->sendLocalReply(ThriftFilters::DirectResponsePtr{new AppException( + method_name_, seq_id_, AppExceptionType::InternalError, + fmt::format("too many connections to '{}'", upstream_host_->address()->asString()))}); + break; + case Tcp::ConnectionPool::PoolFailureReason::LocalConnectionFailure: + case Tcp::ConnectionPool::PoolFailureReason::RemoteConnectionFailure: + case Tcp::ConnectionPool::PoolFailureReason::Timeout: + // TODO(zuercher): distinguish between these cases where appropriate (particularly timeout) + if (!response_started_) { + parent_.callbacks_->sendLocalReply(ThriftFilters::DirectResponsePtr{new AppException( + method_name_, seq_id_, AppExceptionType::InternalError, + fmt::format("connection failure '{}'", upstream_host_->address()->asString()))}); + return; + } + + parent_.callbacks_->resetDownstreamConnection(); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } +} + +} // namespace Router +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/router/router_impl.h b/source/extensions/filters/network/thrift_proxy/router/router_impl.h new file mode 100644 index 000000000000..aa202734b559 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.h @@ -0,0 +1,160 @@ +#pragma once + +#include +#include +#include + +#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" +#include "envoy/tcp/conn_pool.h" +#include "envoy/upstream/load_balancer.h" + +#include "common/common/logger.h" + +#include "extensions/filters/network/thrift_proxy/conn_manager.h" +#include "extensions/filters/network/thrift_proxy/filters/filter.h" +#include "extensions/filters/network/thrift_proxy/router/router.h" + +#include "absl/types/optional.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { +namespace Router { + +class RouteEntryImplBase : public RouteEntry, + public Route, + public std::enable_shared_from_this { +public: + RouteEntryImplBase( + const envoy::extensions::filters::network::thrift_proxy::v2alpha1::Route& route); + + // Router::RouteEntry + const std::string& clusterName() const override; + + // Router::Route + const RouteEntry* routeEntry() const override; + + virtual RouteConstSharedPtr matches(const std::string& method_name) const PURE; + +protected: + RouteConstSharedPtr clusterEntry() const; + +private: + const std::string cluster_name_; +}; + +typedef std::shared_ptr RouteEntryImplBaseConstSharedPtr; + +class MethodNameRouteEntryImpl : public RouteEntryImplBase { +public: + MethodNameRouteEntryImpl( + const envoy::extensions::filters::network::thrift_proxy::v2alpha1::Route& route); + + const std::string& methodName() const { return method_name_; } + + // RoutEntryImplBase + RouteConstSharedPtr matches(const std::string& method_name) const override; + +private: + const std::string method_name_; +}; + +class RouteMatcher { +public: + RouteMatcher( + const envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration&); + + RouteConstSharedPtr route(const std::string& method_name) const; + +private: + std::vector routes_; +}; + +class Router : public Tcp::ConnectionPool::UpstreamCallbacks, + public Upstream::LoadBalancerContext, + public ProtocolConverter, + Logger::Loggable { +public: + Router(Upstream::ClusterManager& cluster_manager) : cluster_manager_(cluster_manager) {} + + ~Router() {} + + // ProtocolConverter + void onDestroy() override; + void setDecoderFilterCallbacks(ThriftFilters::DecoderFilterCallbacks& callbacks) override; + void resetUpstreamConnection() override; + ThriftFilters::FilterStatus transportBegin(absl::optional size) override; + ThriftFilters::FilterStatus transportEnd() override; + ThriftFilters::FilterStatus messageBegin(absl::string_view name, MessageType msg_type, + int32_t seq_id) override; + ThriftFilters::FilterStatus messageEnd() override; + + // Upstream::LoadBalancerContext + absl::optional computeHashKey() override { return {}; } + const Envoy::Router::MetadataMatchCriteria* metadataMatchCriteria() override { return nullptr; } + const Network::Connection* downstreamConnection() const override; + const Http::HeaderMap* downstreamHeaders() const override { return nullptr; } + + // Tcp::ConnectionPool::UpstreamCallbacks + void onUpstreamData(Buffer::Instance& data, bool end_stream) override; + void onEvent(Network::ConnectionEvent event) override; + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + +private: + struct UpstreamRequest : public Tcp::ConnectionPool::Callbacks { + UpstreamRequest(Router& parent, Tcp::ConnectionPool::Instance& pool, + absl::string_view method_name, MessageType msg_type, int32_t seq_id); + ~UpstreamRequest(); + + void start(); + void resetStream(); + + // Tcp::ConnectionPool::Callbacks + void onPoolFailure(Tcp::ConnectionPool::PoolFailureReason reason, + Upstream::HostDescriptionConstSharedPtr host) override; + void onPoolReady(Tcp::ConnectionPool::ConnectionData& conn, + Upstream::HostDescriptionConstSharedPtr host) override; + + void onRequestComplete(); + void onResponseComplete(); + void onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host); + void onResetStream(Tcp::ConnectionPool::PoolFailureReason reason); + + Router& parent_; + Tcp::ConnectionPool::Instance& conn_pool_; + const std::string method_name_; + const MessageType msg_type_; + const int32_t seq_id_; + + Tcp::ConnectionPool::Cancellable* conn_pool_handle_{}; + Tcp::ConnectionPool::ConnectionData* conn_data_{}; + Upstream::HostDescriptionConstSharedPtr upstream_host_; + TransportPtr transport_; + ProtocolType proto_type_{ProtocolType::Auto}; + + bool request_complete_ : 1; + bool response_started_ : 1; + bool response_complete_ : 1; + }; + + void convertMessageBegin(const std::string& name, MessageType msg_type, int32_t seq_id); + void cleanup(); + + Upstream::ClusterManager& cluster_manager_; + + ThriftFilters::DecoderFilterCallbacks* callbacks_{}; + RouteConstSharedPtr route_{}; + const RouteEntry* route_entry_{}; + Upstream::ClusterInfoConstSharedPtr cluster_; + + std::unique_ptr upstream_request_; + Buffer::OwnedImpl upstream_request_buffer_; +}; + +} // namespace Router +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/stats.h b/source/extensions/filters/network/thrift_proxy/stats.h new file mode 100644 index 000000000000..a630fc735d03 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/stats.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +#include "envoy/stats/stats.h" +#include "envoy/stats/stats_macros.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { + +/** + * All thrift filter stats. @see stats_macros.h + */ +// clang-format off +#define ALL_THRIFT_FILTER_STATS(COUNTER, GAUGE, HISTOGRAM) \ + COUNTER(request) \ + COUNTER(request_call) \ + COUNTER(request_oneway) \ + COUNTER(request_invalid_type) \ + GAUGE(request_active) \ + COUNTER(request_decoding_error) \ + HISTOGRAM(request_time_ms) \ + COUNTER(response) \ + COUNTER(response_reply) \ + COUNTER(response_success) \ + COUNTER(response_error) \ + COUNTER(response_exception) \ + COUNTER(response_invalid_type) \ + COUNTER(response_decoding_error) \ + COUNTER(cx_destroy_local_with_active_rq) \ + COUNTER(cx_destroy_remote_with_active_rq) +// clang-format on + +/** + * Struct definition for all mongo proxy stats. @see stats_macros.h + */ +struct ThriftFilterStats { + ALL_THRIFT_FILTER_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT, GENERATE_HISTOGRAM_STRUCT) + + static ThriftFilterStats generateStats(const std::string& prefix, Stats::Scope& scope) { + return ThriftFilterStats{ALL_THRIFT_FILTER_STATS(POOL_COUNTER_PREFIX(scope, prefix), + POOL_GAUGE_PREFIX(scope, prefix), + POOL_HISTOGRAM_PREFIX(scope, prefix))}; + } +}; + +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/transport.h b/source/extensions/filters/network/thrift_proxy/transport.h index 516c055ee72b..1bda083e6bc3 100644 --- a/source/extensions/filters/network/thrift_proxy/transport.h +++ b/source/extensions/filters/network/thrift_proxy/transport.h @@ -4,7 +4,10 @@ #include #include "envoy/buffer/buffer.h" +#include "envoy/registry/registry.h" +#include "common/common/assert.h" +#include "common/config/utility.h" #include "common/singleton/const_singleton.h" #include "absl/types/optional.h" @@ -14,6 +17,16 @@ namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { +enum class TransportType { + Framed, + Unframed, + Auto, + + // ATTENTION: MAKE SURE THIS REMAINS EQUAL TO THE LAST TRANSPORT TYPE + LastTransportType = Auto, + +}; + /** * Names of available Transport implementations. */ @@ -27,29 +40,23 @@ class TransportNameValues { // Auto-detection transport const std::string AUTO = "auto"; + + const std::string& fromType(TransportType type) const { + switch (type) { + case TransportType::Framed: + return FRAMED; + case TransportType::Unframed: + return UNFRAMED; + case TransportType::Auto: + return AUTO; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + } }; typedef ConstSingleton TransportNames; -/** - * TransportCallbacks are Thrift transport-level callbacks. - */ -class TransportCallbacks { -public: - virtual ~TransportCallbacks() {} - - /** - * Indicates the start of a Thrift transport frame was detected. - * @param size the size of the message, if available to the transport - */ - virtual void transportFrameStart(absl::optional size) PURE; - - /** - * Indicates the end of a Thrift transport frame was detected. - */ - virtual void transportFrameComplete() PURE; -}; - /** * Transport represents a Thrift transport. The Thrift transport is nominally a generic, * bi-directional byte stream. In Envoy we assume it always represents a network byte stream and @@ -66,20 +73,27 @@ class Transport { */ virtual const std::string& name() const PURE; + /** + * @return TransportType the transport type + */ + virtual TransportType type() const PURE; + /* - * decodeFrameStart decodes the start of a transport message, potentially invoking callbacks. - * If successful, the start of the frame is removed from the buffer. + * Decodes the start of a transport message. If successful, the start of the frame is removed + * from the buffer. * * @param buffer the currently buffered thrift data. + * @param size updated with the frame size on success. If frame size is not encoded, the size + * is cleared on success. * @return bool true if a complete frame header was successfully consumed, false if more data * is required. * @throws EnvoyException if the data is not valid for this transport. */ - virtual bool decodeFrameStart(Buffer::Instance& buffer) PURE; + virtual bool decodeFrameStart(Buffer::Instance& buffer, absl::optional& size) PURE; /* - * decodeFrameEnd decodes the end of a transport message, potentially invoking callbacks. - * If successful, the end of the frame is removed from the buffer. + * Decodes the end of a transport message. If successful, the end of the frame is removed from + * the buffer. * * @param buffer the currently buffered thrift data. * @return bool true if a complete frame trailer was successfully consumed, false if more data @@ -89,8 +103,8 @@ class Transport { virtual bool decodeFrameEnd(Buffer::Instance& buffer) PURE; /** - * encodeFrame wraps the given message buffer with the transport's header and trailer (if any). - * After encoding, message will be empty. + * Wraps the given message buffer with the transport's header and trailer (if any). After + * encoding, message will be empty. * @param buffer is the output buffer * @param message a protocol-encoded message * @throws EnvoyException if the message is too large for the transport @@ -100,6 +114,52 @@ class Transport { typedef std::unique_ptr TransportPtr; +/** + * Implemented by each Thrift transport and registered via Registry::registerFactory or the + * convenience class RegisterFactory. + */ +class NamedTransportConfigFactory { +public: + virtual ~NamedTransportConfigFactory() {} + + /** + * Create a particular Thrift transport. + * @return TransportPtr the transport + */ + virtual TransportPtr createTransport() PURE; + + /** + * @return std::string the identifying name for a particular implementation of thrift transport + * produced by the factory. + */ + virtual std::string name() PURE; + + /** + * Convenience method to lookup a factory by type. + * @param TransportType the transport type + * @return NamedTransportConfigFactory& for the TransportType + */ + static NamedTransportConfigFactory& getFactory(TransportType type) { + const std::string& name = TransportNames::get().fromType(type); + return Envoy::Config::Utility::getAndCheckFactory(name); + } +}; + +/** + * TransportFactoryBase provides a template for a trivial NamedTransportConfigFactory. + */ +template class TransportFactoryBase : public NamedTransportConfigFactory { + TransportPtr createTransport() override { return std::move(std::make_unique()); } + + std::string name() override { return name_; } + +protected: + TransportFactoryBase(const std::string& name) : name_(name) {} + +private: + const std::string name_; +}; + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/source/extensions/filters/network/thrift_proxy/transport_impl.cc b/source/extensions/filters/network/thrift_proxy/transport_impl.cc index 4caa88efcb31..ff177884d94c 100644 --- a/source/extensions/filters/network/thrift_proxy/transport_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/transport_impl.cc @@ -15,7 +15,7 @@ namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { -bool AutoTransportImpl::decodeFrameStart(Buffer::Instance& buffer) { +bool AutoTransportImpl::decodeFrameStart(Buffer::Instance& buffer, absl::optional& size) { if (transport_ == nullptr) { // Not enough data to select a transport. if (buffer.length() < 8) { @@ -30,13 +30,13 @@ bool AutoTransportImpl::decodeFrameStart(Buffer::Instance& buffer) { // is configurable, but defaults to 256 MB (0x1000000). THeaderTransport will take up to ~1GB // (0x3FFFFFFF) when it falls back to framed mode. if (BinaryProtocolImpl::isMagic(proto_start) || CompactProtocolImpl::isMagic(proto_start)) { - setTransport(std::make_unique(callbacks_)); + setTransport(std::make_unique()); } } else { // Check for sane unframed protocol. proto_start = static_cast((size >> 16) & 0xFFFF); if (BinaryProtocolImpl::isMagic(proto_start) || CompactProtocolImpl::isMagic(proto_start)) { - setTransport(std::make_unique(callbacks_)); + setTransport(std::make_unique()); } } @@ -51,7 +51,7 @@ bool AutoTransportImpl::decodeFrameStart(Buffer::Instance& buffer) { } } - return transport_->decodeFrameStart(buffer); + return transport_->decodeFrameStart(buffer, size); } bool AutoTransportImpl::decodeFrameEnd(Buffer::Instance& buffer) { @@ -64,6 +64,16 @@ void AutoTransportImpl::encodeFrame(Buffer::Instance& buffer, Buffer::Instance& transport_->encodeFrame(buffer, message); } +class AutoTransportConfigFactory : public TransportFactoryBase { +public: + AutoTransportConfigFactory() : TransportFactoryBase(TransportNames::get().AUTO) {} +}; + +/** + * Static registration for the auto transport. @see RegisterFactory. + */ +static Registry::RegisterFactory register_; + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/source/extensions/filters/network/thrift_proxy/transport_impl.h b/source/extensions/filters/network/thrift_proxy/transport_impl.h index 137a58a622c9..08281c53c6d1 100644 --- a/source/extensions/filters/network/thrift_proxy/transport_impl.h +++ b/source/extensions/filters/network/thrift_proxy/transport_impl.h @@ -15,33 +15,25 @@ namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { -/* - * TransportImplBase provides a base class for Transport implementations. - */ -class TransportImplBase : public virtual Transport { -public: - TransportImplBase(TransportCallbacks& callbacks) : callbacks_(callbacks) {} - -protected: - void onFrameStart(absl::optional size) const { callbacks_.transportFrameStart(size); } - void onFrameComplete() const { callbacks_.transportFrameComplete(); } - - TransportCallbacks& callbacks_; -}; - /** * AutoTransportImpl implements Transport and attempts to distinguish between the Thrift framed and * unframed transports. Once the transport is detected, subsequent operations are delegated to the * appropriate implementation. */ -class AutoTransportImpl : public TransportImplBase { +class AutoTransportImpl : public Transport { public: - AutoTransportImpl(TransportCallbacks& callbacks) - : TransportImplBase(callbacks), name_(TransportNames::get().AUTO){}; + AutoTransportImpl() : name_(TransportNames::get().AUTO){}; // Transport const std::string& name() const override { return name_; } - bool decodeFrameStart(Buffer::Instance& buffer) override; + TransportType type() const override { + if (transport_ != nullptr) { + return transport_->type(); + } + + return TransportType::Auto; + } + bool decodeFrameStart(Buffer::Instance& buffer, absl::optional& size) override; bool decodeFrameEnd(Buffer::Instance& buffer) override; void encodeFrame(Buffer::Instance& buffer, Buffer::Instance& message) override; diff --git a/source/extensions/filters/network/thrift_proxy/unframed_transport_impl.cc b/source/extensions/filters/network/thrift_proxy/unframed_transport_impl.cc new file mode 100644 index 000000000000..d3a2744540c9 --- /dev/null +++ b/source/extensions/filters/network/thrift_proxy/unframed_transport_impl.cc @@ -0,0 +1,22 @@ +#include "extensions/filters/network/thrift_proxy/unframed_transport_impl.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { + +class UnframedTransportConfigFactory : public TransportFactoryBase { +public: + UnframedTransportConfigFactory() : TransportFactoryBase(TransportNames::get().UNFRAMED) {} +}; + +/** + * Static registration for the unframed transport. @see RegisterFactory. + */ +static Registry::RegisterFactory + register_; + +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/thrift_proxy/unframed_transport_impl.h b/source/extensions/filters/network/thrift_proxy/unframed_transport_impl.h index af7ea884ddba..29992dfc0812 100644 --- a/source/extensions/filters/network/thrift_proxy/unframed_transport_impl.h +++ b/source/extensions/filters/network/thrift_proxy/unframed_transport_impl.h @@ -17,20 +17,18 @@ namespace ThriftProxy { * UnframedTransportImpl implements the Thrift Unframed transport. * See https://github.com/apache/thrift/blob/master/doc/specs/thrift-rpc.md */ -class UnframedTransportImpl : public TransportImplBase { +class UnframedTransportImpl : public Transport { public: - UnframedTransportImpl(TransportCallbacks& callbacks) : TransportImplBase(callbacks) {} + UnframedTransportImpl() {} // Transport const std::string& name() const override { return TransportNames::get().UNFRAMED; } - bool decodeFrameStart(Buffer::Instance&) override { - onFrameStart(absl::optional()); - return true; - } - bool decodeFrameEnd(Buffer::Instance&) override { - onFrameComplete(); + TransportType type() const override { return TransportType::Unframed; } + bool decodeFrameStart(Buffer::Instance&, absl::optional& size) override { + size.reset(); return true; } + bool decodeFrameEnd(Buffer::Instance&) override { return true; } void encodeFrame(Buffer::Instance& buffer, Buffer::Instance& message) override { buffer.move(message); } diff --git a/test/extensions/filters/network/thrift_proxy/BUILD b/test/extensions/filters/network/thrift_proxy/BUILD index faa42cb93aff..1eea2452232b 100644 --- a/test/extensions/filters/network/thrift_proxy/BUILD +++ b/test/extensions/filters/network/thrift_proxy/BUILD @@ -19,7 +19,12 @@ envoy_extension_cc_mock( hdrs = ["mocks.h"], extension_name = "envoy.filters.network.thrift_proxy", deps = [ + "//source/extensions/filters/network/thrift_proxy:conn_manager_lib", + "//source/extensions/filters/network/thrift_proxy:protocol_lib", "//source/extensions/filters/network/thrift_proxy:transport_lib", + "//source/extensions/filters/network/thrift_proxy/filters:filter_interface", + "//source/extensions/filters/network/thrift_proxy/router:router_interface", + "//test/mocks/network:network_mocks", "//test/test_common:printers_lib", ], ) @@ -79,33 +84,40 @@ envoy_extension_cc_test( extension_name = "envoy.filters.network.thrift_proxy", deps = [ "//source/extensions/filters/network/thrift_proxy:config", + "//source/extensions/filters/network/thrift_proxy/router:config", "//test/mocks/server:server_mocks", ], ) envoy_extension_cc_test( - name = "decoder_test", - srcs = ["decoder_test.cc"], + name = "conn_manager_test", + srcs = ["conn_manager_test.cc"], extension_name = "envoy.filters.network.thrift_proxy", deps = [ ":mocks", ":utility_lib", - "//source/extensions/filters/network/thrift_proxy:decoder_lib", + "//source/extensions/filters/network/thrift_proxy:config", + "//source/extensions/filters/network/thrift_proxy:conn_manager_lib", + "//source/extensions/filters/network/thrift_proxy/filters:filter_interface", + "//source/extensions/filters/network/thrift_proxy/router:config", + "//source/extensions/filters/network/thrift_proxy/router:router_interface", + "//test/mocks/network:network_mocks", + "//test/mocks/server:server_mocks", + "//test/mocks/upstream:upstream_mocks", "//test/test_common:printers_lib", - "//test/test_common:utility_lib", ], ) envoy_extension_cc_test( - name = "filter_test", - srcs = ["filter_test.cc"], + name = "decoder_test", + srcs = ["decoder_test.cc"], extension_name = "envoy.filters.network.thrift_proxy", deps = [ + ":mocks", ":utility_lib", - "//source/common/stats:stats_lib", - "//source/extensions/filters/network/thrift_proxy:filter_lib", - "//test/mocks/network:network_mocks", + "//source/extensions/filters/network/thrift_proxy:decoder_lib", "//test/test_common:printers_lib", + "//test/test_common:utility_lib", ], ) @@ -117,7 +129,6 @@ envoy_extension_cc_test( ":mocks", ":utility_lib", "//source/extensions/filters/network/thrift_proxy:transport_lib", - "//test/mocks/buffer:buffer_mocks", "//test/test_common:printers_lib", "//test/test_common:utility_lib", ], @@ -136,6 +147,24 @@ envoy_extension_cc_test( ], ) +envoy_extension_cc_test( + name = "router_test", + srcs = ["router_test.cc"], + extension_name = "envoy.filters.network.thrift_proxy", + deps = [ + ":mocks", + ":utility_lib", + "//source/extensions/filters/network/thrift_proxy:app_exception_lib", + "//source/extensions/filters/network/thrift_proxy/router:config", + "//source/extensions/filters/network/thrift_proxy/router:router_lib", + "//test/mocks/network:network_mocks", + "//test/mocks/server:server_mocks", + "//test/mocks/upstream:upstream_mocks", + "//test/test_common:printers_lib", + "//test/test_common:registry_lib", + ], +) + envoy_extension_cc_test( name = "transport_impl_test", srcs = ["transport_impl_test.cc"], @@ -163,8 +192,8 @@ envoy_extension_cc_test( ) envoy_extension_cc_test( - name = "filter_integration_test", - srcs = ["filter_integration_test.cc"], + name = "integration_test", + srcs = ["integration_test.cc"], data = [ "//test/extensions/filters/network/thrift_proxy/driver:generate_fixture", ], @@ -172,7 +201,8 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/network/tcp_proxy:config", "//source/extensions/filters/network/thrift_proxy:config", - "//source/extensions/filters/network/thrift_proxy:filter_lib", + "//source/extensions/filters/network/thrift_proxy:conn_manager_lib", + "//source/extensions/filters/network/thrift_proxy/router:config", "//test/integration:integration_lib", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", diff --git a/test/extensions/filters/network/thrift_proxy/binary_protocol_impl_test.cc b/test/extensions/filters/network/thrift_proxy/binary_protocol_impl_test.cc index 6ed1723fee09..3db9d588d533 100644 --- a/test/extensions/filters/network/thrift_proxy/binary_protocol_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/binary_protocol_impl_test.cc @@ -4,29 +4,24 @@ #include "extensions/filters/network/thrift_proxy/binary_protocol_impl.h" -#include "test/extensions/filters/network/thrift_proxy/mocks.h" #include "test/extensions/filters/network/thrift_proxy/utility.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" -using testing::StrictMock; - namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { TEST(BinaryProtocolTest, Name) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; EXPECT_EQ(proto.name(), "binary"); } TEST(BinaryProtocolTest, ReadMessageBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Insufficient data { @@ -97,7 +92,6 @@ TEST(BinaryProtocolTest, ReadMessageBegin) { addInt32(buffer, 0); addInt32(buffer, 1234); - EXPECT_CALL(cb, messageStart(absl::string_view(""), MessageType::Call, 1234)); EXPECT_TRUE(proto.readMessageBegin(buffer, name, msg_type, seq_id)); EXPECT_EQ(name, ""); EXPECT_EQ(msg_type, MessageType::Call); @@ -139,7 +133,6 @@ TEST(BinaryProtocolTest, ReadMessageBegin) { addString(buffer, "the_name"); addInt32(buffer, 5678); - EXPECT_CALL(cb, messageStart(absl::string_view("the_name"), MessageType::Call, 5678)); EXPECT_TRUE(proto.readMessageBegin(buffer, name, msg_type, seq_id)); EXPECT_EQ(name, "the_name"); EXPECT_EQ(msg_type, MessageType::Call); @@ -150,34 +143,29 @@ TEST(BinaryProtocolTest, ReadMessageBegin) { TEST(BinaryProtocolTest, ReadMessageEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; - EXPECT_CALL(cb, messageComplete()); EXPECT_TRUE(proto.readMessageEnd(buffer)); } TEST(BinaryProtocolTest, ReadStructBegin) { Buffer::OwnedImpl buffer; - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; std::string name = "-"; - EXPECT_CALL(cb, structBegin(absl::string_view(""))); + EXPECT_TRUE(proto.readStructBegin(buffer, name)); EXPECT_EQ(name, ""); } TEST(BinaryProtocolTest, ReadStructEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - BinaryProtocolImpl proto(cb); - EXPECT_CALL(cb, structEnd()); + BinaryProtocolImpl proto; + EXPECT_TRUE(proto.readStructEnd(buffer)); } TEST(BinaryProtocolTest, ReadFieldBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Insufficient data { @@ -201,7 +189,6 @@ TEST(BinaryProtocolTest, ReadFieldBegin) { addInt8(buffer, FieldType::Stop); - EXPECT_CALL(cb, structField(absl::string_view(""), FieldType::Stop, 0)); EXPECT_TRUE(proto.readFieldBegin(buffer, name, field_type, field_id)); EXPECT_EQ(name, ""); EXPECT_EQ(field_type, FieldType::Stop); @@ -234,7 +221,6 @@ TEST(BinaryProtocolTest, ReadFieldBegin) { addInt8(buffer, FieldType::I32); addInt16(buffer, 99); - EXPECT_CALL(cb, structField(absl::string_view(""), FieldType::I32, 99)); EXPECT_TRUE(proto.readFieldBegin(buffer, name, field_type, field_id)); EXPECT_EQ(name, ""); EXPECT_EQ(field_type, FieldType::I32); @@ -263,14 +249,12 @@ TEST(BinaryProtocolTest, ReadFieldBegin) { TEST(BinaryProtocolTest, ReadFieldEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; EXPECT_TRUE(proto.readFieldEnd(buffer)); } TEST(BinaryProtocolTest, ReadMapBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Insufficient data { @@ -328,14 +312,12 @@ TEST(BinaryProtocolTest, ReadMapBegin) { TEST(BinaryProtocolTest, ReadMapEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; EXPECT_TRUE(proto.readMapEnd(buffer)); } TEST(BinaryProtocolTest, ReadListBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Insufficient data { @@ -385,14 +367,12 @@ TEST(BinaryProtocolTest, ReadListBegin) { TEST(BinaryProtocolTest, ReadListEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; EXPECT_TRUE(proto.readListEnd(buffer)); } TEST(BinaryProtocolTest, ReadSetBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Test only the happy path, since this method is just delegated to readListBegin() Buffer::OwnedImpl buffer; @@ -410,14 +390,12 @@ TEST(BinaryProtocolTest, ReadSetBegin) { TEST(BinaryProtocolTest, ReadSetEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; EXPECT_TRUE(proto.readSetEnd(buffer)); } TEST(BinaryProtocolTest, ReadIntegerTypes) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Bool { @@ -535,8 +513,7 @@ TEST(BinaryProtocolTest, ReadIntegerTypes) { } TEST(BinaryProtocolTest, ReadDouble) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Insufficient data { @@ -564,8 +541,7 @@ TEST(BinaryProtocolTest, ReadDouble) { } TEST(BinaryProtocolTest, ReadString) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Insufficient data to read length { @@ -632,8 +608,7 @@ TEST(BinaryProtocolTest, ReadString) { TEST(BinaryProtocolTest, ReadBinary) { // Test only the happy path, since this method is just delegated to readString() - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; Buffer::OwnedImpl buffer; std::string value = "-"; @@ -646,8 +621,7 @@ TEST(BinaryProtocolTest, ReadBinary) { } TEST(BinaryProtocolTest, WriteMessageBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Named call { @@ -665,32 +639,28 @@ TEST(BinaryProtocolTest, WriteMessageBegin) { } TEST(BinaryProtocolTest, WriteMessageEnd) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeMessageEnd(buffer); EXPECT_EQ(0, buffer.length()); } TEST(BinaryProtocolTest, WriteStructBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeStructBegin(buffer, "unused"); EXPECT_EQ(0, buffer.length()); } TEST(BinaryProtocolTest, WriteStructEnd) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeStructEnd(buffer); EXPECT_EQ(0, buffer.length()); } TEST(BinaryProtocolTest, WriteFieldBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Stop field { @@ -708,16 +678,14 @@ TEST(BinaryProtocolTest, WriteFieldBegin) { } TEST(BinaryProtocolTest, WriteFieldEnd) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeFieldEnd(buffer); EXPECT_EQ(0, buffer.length()); } TEST(BinaryProtocolTest, WriteMapBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Non-empty map { @@ -743,16 +711,14 @@ TEST(BinaryProtocolTest, WriteMapBegin) { } TEST(BinaryProtocolTest, WriteMapEnd) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeMapEnd(buffer); EXPECT_EQ(0, buffer.length()); } TEST(BinaryProtocolTest, WriteListBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Non-empty list { @@ -777,16 +743,14 @@ TEST(BinaryProtocolTest, WriteListBegin) { } TEST(BinaryProtocolTest, WriteListEnd) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeListEnd(buffer); EXPECT_EQ(0, buffer.length()); } TEST(BinaryProtocolTest, WriteSetBegin) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Only test the happy path, as this shares an implementation with writeListBegin // Non-empty list @@ -796,16 +760,14 @@ TEST(BinaryProtocolTest, WriteSetBegin) { } TEST(BinaryProtocolTest, WriteSetEnd) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeSetEnd(buffer); EXPECT_EQ(0, buffer.length()); } TEST(BinaryProtocolTest, WriteBool) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // True { @@ -823,8 +785,7 @@ TEST(BinaryProtocolTest, WriteBool) { } TEST(BinaryProtocolTest, WriteByte) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; { Buffer::OwnedImpl buffer; @@ -840,8 +801,7 @@ TEST(BinaryProtocolTest, WriteByte) { } TEST(BinaryProtocolTest, WriteInt16) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; { Buffer::OwnedImpl buffer; @@ -857,8 +817,7 @@ TEST(BinaryProtocolTest, WriteInt16) { } TEST(BinaryProtocolTest, WriteInt32) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; { Buffer::OwnedImpl buffer; @@ -874,8 +833,7 @@ TEST(BinaryProtocolTest, WriteInt32) { } TEST(BinaryProtocolTest, WriteInt64) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; { Buffer::OwnedImpl buffer; @@ -891,16 +849,14 @@ TEST(BinaryProtocolTest, WriteInt64) { } TEST(BinaryProtocolTest, WriteDouble) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeDouble(buffer, 3.0); EXPECT_EQ(std::string("\x40\x8\0\0\0\0\0\0", 8), buffer.toString()); } TEST(BinaryProtocolTest, WriteString) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; { Buffer::OwnedImpl buffer; @@ -919,8 +875,7 @@ TEST(BinaryProtocolTest, WriteString) { } TEST(BinaryProtocolTest, WriteBinary) { - StrictMock cb; - BinaryProtocolImpl proto(cb); + BinaryProtocolImpl proto; // Happy path only, since this is just a synonym for writeString Buffer::OwnedImpl buffer; @@ -932,14 +887,12 @@ TEST(BinaryProtocolTest, WriteBinary) { } TEST(LaxBinaryProtocolTest, Name) { - StrictMock cb; - LaxBinaryProtocolImpl proto(cb); + LaxBinaryProtocolImpl proto; EXPECT_EQ(proto.name(), "binary/non-strict"); } TEST(LaxBinaryProtocolTest, ReadMessageBegin) { - StrictMock cb; - LaxBinaryProtocolImpl proto(cb); + LaxBinaryProtocolImpl proto; // Insufficient data { @@ -989,7 +942,6 @@ TEST(LaxBinaryProtocolTest, ReadMessageBegin) { addInt8(buffer, MessageType::Call); addInt32(buffer, 1234); - EXPECT_CALL(cb, messageStart(absl::string_view(""), MessageType::Call, 1234)); EXPECT_TRUE(proto.readMessageBegin(buffer, name, msg_type, seq_id)); EXPECT_EQ(name, ""); EXPECT_EQ(msg_type, MessageType::Call); @@ -1027,7 +979,6 @@ TEST(LaxBinaryProtocolTest, ReadMessageBegin) { addInt8(buffer, MessageType::Call); addInt32(buffer, 5678); - EXPECT_CALL(cb, messageStart(absl::string_view("the_name"), MessageType::Call, 5678)); EXPECT_TRUE(proto.readMessageBegin(buffer, name, msg_type, seq_id)); EXPECT_EQ(name, "the_name"); EXPECT_EQ(msg_type, MessageType::Call); @@ -1037,8 +988,7 @@ TEST(LaxBinaryProtocolTest, ReadMessageBegin) { } TEST(LaxBinaryProtocolTest, WriteMessageBegin) { - StrictMock cb; - LaxBinaryProtocolImpl proto(cb); + LaxBinaryProtocolImpl proto; // Named call { diff --git a/test/extensions/filters/network/thrift_proxy/buffer_helper_test.cc b/test/extensions/filters/network/thrift_proxy/buffer_helper_test.cc index 9536177a9655..26030cd7bd9a 100644 --- a/test/extensions/filters/network/thrift_proxy/buffer_helper_test.cc +++ b/test/extensions/filters/network/thrift_proxy/buffer_helper_test.cc @@ -17,50 +17,6 @@ namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { -TEST(BufferWrapperTest, ImplementedFunctions) { - Buffer::OwnedImpl buffer; - addString(buffer, "abcdefghij"); - - BufferWrapper wrapper(buffer); - { - char s[4] = {0}; - wrapper.copyOut(0, 3, s); - EXPECT_EQ("abc", std::string(s)); - EXPECT_EQ(10, wrapper.length()); - EXPECT_EQ(0, wrapper.position()); - } - - { - char s[6] = {0}; - wrapper.copyOut(5, 5, s); - EXPECT_EQ("fghij", std::string(s)); - EXPECT_EQ(10, wrapper.length()); - EXPECT_EQ(0, wrapper.position()); - } - - { - std::string s(static_cast(wrapper.linearize(5)), 5); - EXPECT_EQ("abcde", s); - EXPECT_EQ(0, wrapper.position()); - } - - wrapper.drain(2); - - { - char s[4] = {0}; - wrapper.copyOut(4, 3, s); - EXPECT_EQ("ghi", std::string(s)); - EXPECT_EQ(8, wrapper.length()); - EXPECT_EQ(2, wrapper.position()); - } - - { - std::string s(static_cast(wrapper.linearize(8)), 8); - EXPECT_EQ("cdefghij", s); - EXPECT_EQ(2, wrapper.position()); - } -} - TEST(BufferHelperTest, PeekI8) { { Buffer::OwnedImpl buffer; diff --git a/test/extensions/filters/network/thrift_proxy/compact_protocol_impl_test.cc b/test/extensions/filters/network/thrift_proxy/compact_protocol_impl_test.cc index b784d85e8529..79187821def5 100644 --- a/test/extensions/filters/network/thrift_proxy/compact_protocol_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/compact_protocol_impl_test.cc @@ -4,15 +4,12 @@ #include "extensions/filters/network/thrift_proxy/compact_protocol_impl.h" -#include "test/extensions/filters/network/thrift_proxy/mocks.h" #include "test/extensions/filters/network/thrift_proxy/utility.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" -using testing::NiceMock; -using testing::StrictMock; using testing::TestWithParam; using testing::Values; @@ -22,14 +19,12 @@ namespace NetworkFilters { namespace ThriftProxy { TEST(CompactProtocolTest, Name) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; EXPECT_EQ(proto.name(), "compact"); } TEST(CompactProtocolTest, ReadMessageBegin) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Insufficient data { @@ -170,7 +165,6 @@ TEST(CompactProtocolTest, ReadMessageBegin) { addInt8(buffer, 32); addInt8(buffer, 0); - EXPECT_CALL(cb, messageStart(absl::string_view(""), MessageType::Call, 32)); EXPECT_TRUE(proto.readMessageBegin(buffer, name, msg_type, seq_id)); EXPECT_EQ(name, ""); EXPECT_EQ(msg_type, MessageType::Call); @@ -228,7 +222,6 @@ TEST(CompactProtocolTest, ReadMessageBegin) { addInt8(buffer, 8); addString(buffer, "the_name"); - EXPECT_CALL(cb, messageStart(absl::string_view("the_name"), MessageType::Call, 0x0102)); EXPECT_TRUE(proto.readMessageBegin(buffer, name, msg_type, seq_id)); EXPECT_EQ(name, "the_name"); EXPECT_EQ(msg_type, MessageType::Call); @@ -239,22 +232,19 @@ TEST(CompactProtocolTest, ReadMessageBegin) { TEST(CompactProtocolTest, ReadMessageEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - CompactProtocolImpl proto(cb); - EXPECT_CALL(cb, messageComplete()); + CompactProtocolImpl proto; + EXPECT_TRUE(proto.readMessageEnd(buffer)); } TEST(CompactProtocolTest, ReadStruct) { Buffer::OwnedImpl buffer; - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; std::string name = "-"; - EXPECT_CALL(cb, structBegin(absl::string_view(""))); + EXPECT_TRUE(proto.readStructBegin(buffer, name)); EXPECT_EQ(name, ""); - EXPECT_CALL(cb, structEnd()); EXPECT_TRUE(proto.readStructEnd(buffer)); EXPECT_THROW_WITH_MESSAGE(proto.readStructEnd(buffer), EnvoyException, @@ -262,8 +252,7 @@ TEST(CompactProtocolTest, ReadStruct) { } TEST(CompactProtocolTest, ReadFieldBegin) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Insufficient data { @@ -287,7 +276,6 @@ TEST(CompactProtocolTest, ReadFieldBegin) { addInt8(buffer, 0xF0); - EXPECT_CALL(cb, structField(absl::string_view(""), FieldType::Stop, 0)); EXPECT_TRUE(proto.readFieldBegin(buffer, name, field_type, field_id)); EXPECT_EQ(name, ""); EXPECT_EQ(field_type, FieldType::Stop); @@ -400,7 +388,6 @@ TEST(CompactProtocolTest, ReadFieldBegin) { addInt8(buffer, 0x05); addInt8(buffer, 0x04); - EXPECT_CALL(cb, structField(absl::string_view(""), FieldType::I32, 2)); EXPECT_TRUE(proto.readFieldBegin(buffer, name, field_type, field_id)); EXPECT_EQ(name, ""); EXPECT_EQ(field_type, FieldType::I32); @@ -417,7 +404,6 @@ TEST(CompactProtocolTest, ReadFieldBegin) { addInt8(buffer, 0xF5); - EXPECT_CALL(cb, structField(absl::string_view(""), FieldType::I32, 17)); EXPECT_TRUE(proto.readFieldBegin(buffer, name, field_type, field_id)); EXPECT_EQ(name, ""); EXPECT_EQ(field_type, FieldType::I32); @@ -428,14 +414,12 @@ TEST(CompactProtocolTest, ReadFieldBegin) { TEST(CompactProtocolTest, ReadFieldEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; EXPECT_TRUE(proto.readFieldEnd(buffer)); } TEST(CompactProtocolTest, ReadMapBegin) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Insufficient data { @@ -575,14 +559,12 @@ TEST(CompactProtocolTest, ReadMapBegin) { TEST(CompactProtocolTest, ReadMapEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; EXPECT_TRUE(proto.readMapEnd(buffer)); } TEST(CompactProtocolTest, ReadListBegin) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Insufficient data { @@ -690,14 +672,12 @@ TEST(CompactProtocolTest, ReadListBegin) { TEST(CompactProtocolTest, ReadListEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; EXPECT_TRUE(proto.readListEnd(buffer)); } TEST(CompactProtocolTest, ReadSetBegin) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Test only the happy path, since this method is just delegated to readListBegin() Buffer::OwnedImpl buffer; @@ -714,14 +694,12 @@ TEST(CompactProtocolTest, ReadSetBegin) { TEST(CompactProtocolTest, ReadSetEnd) { Buffer::OwnedImpl buffer; - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; EXPECT_TRUE(proto.readSetEnd(buffer)); } TEST(CompactProtocolTest, ReadBool) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Bool field values are encoded in the field type { @@ -734,7 +712,6 @@ TEST(CompactProtocolTest, ReadBool) { addInt8(buffer, 0x01); addInt8(buffer, 0x04); - EXPECT_CALL(cb, structField(absl::string_view(""), FieldType::Bool, 2)); EXPECT_TRUE(proto.readFieldBegin(buffer, name, field_type, field_id)); EXPECT_EQ(name, ""); EXPECT_EQ(field_type, FieldType::Bool); @@ -751,7 +728,6 @@ TEST(CompactProtocolTest, ReadBool) { addInt8(buffer, 0x02); addInt8(buffer, 0x06); - EXPECT_CALL(cb, structField(absl::string_view(""), FieldType::Bool, 3)); EXPECT_TRUE(proto.readFieldBegin(buffer, name, field_type, field_id)); EXPECT_EQ(name, ""); EXPECT_EQ(field_type, FieldType::Bool); @@ -787,8 +763,7 @@ TEST(CompactProtocolTest, ReadBool) { } TEST(CompactProtocolTest, ReadIntegerTypes) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Byte { @@ -919,8 +894,7 @@ TEST(CompactProtocolTest, ReadIntegerTypes) { } TEST(CompactProtocolTest, ReadDouble) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Insufficient data { @@ -950,8 +924,7 @@ TEST(CompactProtocolTest, ReadDouble) { } TEST(CompactProtocolTest, ReadString) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Insufficient data { @@ -1028,8 +1001,7 @@ TEST(CompactProtocolTest, ReadString) { TEST(CompactProtocolTest, ReadBinary) { // Test only the happy path, since this method is just delegated to readString() - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; Buffer::OwnedImpl buffer; std::string value = "-"; @@ -1046,8 +1018,7 @@ class CompactProtocolFieldTypeTest : public TestWithParam {}; TEST_P(CompactProtocolFieldTypeTest, ConvertsToFieldType) { uint8_t compact_field_type = GetParam(); - NiceMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; std::string name = "-"; int8_t invalid_field_type = static_cast(FieldType::LastFieldType) + 1; FieldType field_type = static_cast(invalid_field_type); @@ -1080,8 +1051,7 @@ INSTANTIATE_TEST_CASE_P(CompactFieldTypes, CompactProtocolFieldTypeTest, Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); TEST(CompactProtocolTest, WriteMessageBegin) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Named call { @@ -1099,16 +1069,14 @@ TEST(CompactProtocolTest, WriteMessageBegin) { } TEST(CompactProtocolTest, WriteMessageEnd) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeMessageEnd(buffer); EXPECT_EQ(0, buffer.length()); } TEST(CompactProtocolTest, WriteStruct) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeStructBegin(buffer, "unused"); @@ -1123,16 +1091,14 @@ TEST(CompactProtocolTest, WriteStruct) { TEST(CompactProtocolTest, WriteFieldBegin) { // Stop field { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeFieldBegin(buffer, "unused", FieldType::Stop, 1); EXPECT_EQ(std::string("\0", 1), buffer.toString()); } { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Short form { @@ -1145,7 +1111,7 @@ TEST(CompactProtocolTest, WriteFieldBegin) { { Buffer::OwnedImpl buffer; proto.writeFieldBegin(buffer, "unused", FieldType::Struct, 17); - EXPECT_EQ(std::string("\xC\0\x11", 3), buffer.toString()); + EXPECT_EQ(std::string("\xC\x22", 2), buffer.toString()); } // Short form @@ -1164,14 +1130,13 @@ TEST(CompactProtocolTest, WriteFieldBegin) { } { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Long form { Buffer::OwnedImpl buffer; proto.writeFieldBegin(buffer, "unused", FieldType::I32, 16); - EXPECT_EQ(std::string("\x5\0\x10", 3), buffer.toString()); + EXPECT_EQ(std::string("\x5\x20", 2), buffer.toString()); } // Short form @@ -1185,21 +1150,20 @@ TEST(CompactProtocolTest, WriteFieldBegin) { { Buffer::OwnedImpl buffer; proto.writeFieldBegin(buffer, "unused", FieldType::Byte, 33); - EXPECT_EQ(std::string("\x3\0\x21", 3), buffer.toString()); + EXPECT_EQ(std::string("\x3\x42", 2), buffer.toString()); } - // Long form + // Long form (3 bytes) { Buffer::OwnedImpl buffer; - proto.writeFieldBegin(buffer, "unused", FieldType::String, 49); - EXPECT_EQ(std::string("\x8\0\x31", 3), buffer.toString()); + proto.writeFieldBegin(buffer, "unused", FieldType::String, 64); + EXPECT_EQ(std::string("\x8\x80\x1", 3), buffer.toString()); } } // Unknown field type { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; Buffer::OwnedImpl buffer; int8_t invalid_field_type = static_cast(FieldType::LastFieldType) + 1; @@ -1212,8 +1176,7 @@ TEST(CompactProtocolTest, WriteFieldBegin) { } TEST(CompactProtocolTest, WriteFieldEnd) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeFieldEnd(buffer); EXPECT_EQ(0, buffer.length()); @@ -1224,8 +1187,7 @@ TEST(CompactProtocolTest, WriteBoolField) { // Short form field { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; { Buffer::OwnedImpl buffer; proto.writeFieldBegin(buffer, "unused", FieldType::Bool, 8); @@ -1245,15 +1207,14 @@ TEST(CompactProtocolTest, WriteBoolField) { // Long form field { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; { Buffer::OwnedImpl buffer; proto.writeFieldBegin(buffer, "unused", FieldType::Bool, 16); EXPECT_EQ(0, buffer.length()); proto.writeBool(buffer, true); - EXPECT_EQ(std::string("\x1\0\x10", 3), buffer.toString()); + EXPECT_EQ(std::string("\x1\x20", 2), buffer.toString()); } { @@ -1261,14 +1222,13 @@ TEST(CompactProtocolTest, WriteBoolField) { proto.writeFieldBegin(buffer, "unused", FieldType::Bool, 32); EXPECT_EQ(0, buffer.length()); proto.writeBool(buffer, false); - EXPECT_EQ(std::string("\x2\0\x20", 3), buffer.toString()); + EXPECT_EQ(std::string("\x2\x40", 2), buffer.toString()); } } } TEST(CompactProtocolTest, WriteMapBegin) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Empty map { @@ -1294,16 +1254,14 @@ TEST(CompactProtocolTest, WriteMapBegin) { } TEST(CompactProtocolTest, WriteMapEnd) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeMapEnd(buffer); EXPECT_EQ(0, buffer.length()); } TEST(CompactProtocolTest, WriteListBegin) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Empty list { @@ -1335,16 +1293,14 @@ TEST(CompactProtocolTest, WriteListBegin) { } TEST(CompactProtocolTest, WriteListEnd) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeListEnd(buffer); EXPECT_EQ(0, buffer.length()); } TEST(CompactProtocolTest, WriteSetBegin) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Empty set only, as writeSetBegin delegates to writeListBegin. Buffer::OwnedImpl buffer; @@ -1353,16 +1309,14 @@ TEST(CompactProtocolTest, WriteSetBegin) { } TEST(CompactProtocolTest, WriteSetEnd) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeSetEnd(buffer); EXPECT_EQ(0, buffer.length()); } TEST(CompactProtocolTest, WriteBool) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // Non-field bools (see WriteBoolField test) { @@ -1379,8 +1333,7 @@ TEST(CompactProtocolTest, WriteBool) { } TEST(CompactProtocolTest, WriteByte) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; { Buffer::OwnedImpl buffer; @@ -1396,8 +1349,7 @@ TEST(CompactProtocolTest, WriteByte) { } TEST(CompactProtocolTest, WriteInt16) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // zigzag(1) = 2 { @@ -1436,8 +1388,7 @@ TEST(CompactProtocolTest, WriteInt16) { } TEST(CompactProtocolTest, WriteInt32) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // zigzag(1) = 2 { @@ -1476,8 +1427,7 @@ TEST(CompactProtocolTest, WriteInt32) { } TEST(CompactProtocolTest, WriteInt64) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // zigzag(1) = 2 { @@ -1516,16 +1466,14 @@ TEST(CompactProtocolTest, WriteInt64) { } TEST(CompactProtocolTest, WriteDouble) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; Buffer::OwnedImpl buffer; proto.writeDouble(buffer, 3.0); EXPECT_EQ(std::string("\x40\x8\0\0\0\0\0\0", 8), buffer.toString()); } TEST(CompactProtocolTest, WriteString) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; { Buffer::OwnedImpl buffer; @@ -1551,8 +1499,7 @@ TEST(CompactProtocolTest, WriteString) { } TEST(CompactProtocolTest, WriteBinary) { - StrictMock cb; - CompactProtocolImpl proto(cb); + CompactProtocolImpl proto; // writeBinary is an alias for writeString Buffer::OwnedImpl buffer; diff --git a/test/extensions/filters/network/thrift_proxy/config_test.cc b/test/extensions/filters/network/thrift_proxy/config_test.cc index 3047282f9c77..56c481075a01 100644 --- a/test/extensions/filters/network/thrift_proxy/config_test.cc +++ b/test/extensions/filters/network/thrift_proxy/config_test.cc @@ -31,7 +31,7 @@ TEST(ThriftFilterConfigTest, ValidProtoConfiguration) { ThriftProxyFilterConfigFactory factory; Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(config, context); Network::MockConnection connection; - EXPECT_CALL(connection, addFilter(_)); + EXPECT_CALL(connection, addReadFilter(_)); cb(connection); } @@ -45,7 +45,7 @@ TEST(ThriftFilterConfigTest, ThriftProxyWithEmptyProto) { Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(config, context); Network::MockConnection connection; - EXPECT_CALL(connection, addFilter(_)); + EXPECT_CALL(connection, addReadFilter(_)); cb(connection); } diff --git a/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc b/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc new file mode 100644 index 000000000000..48dce17b7d40 --- /dev/null +++ b/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc @@ -0,0 +1,760 @@ +#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" + +#include "common/buffer/buffer_impl.h" +#include "common/stats/stats_impl.h" + +#include "extensions/filters/network/thrift_proxy/buffer_helper.h" +#include "extensions/filters/network/thrift_proxy/config.h" +#include "extensions/filters/network/thrift_proxy/conn_manager.h" + +#include "test/extensions/filters/network/thrift_proxy/mocks.h" +#include "test/extensions/filters/network/thrift_proxy/utility.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/mocks/upstream/mocks.h" +#include "test/test_common/printers.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; +using testing::_; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { + +class TestConfigImpl : public ConfigImpl { +public: + TestConfigImpl( + envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy proto_config, + Server::Configuration::MockFactoryContext& context, + ThriftFilters::DecoderFilterSharedPtr decoder_filter, ThriftFilterStats& stats) + : ConfigImpl(proto_config, context), decoder_filter_(decoder_filter), stats_(stats) {} + + // ConfigImpl + ThriftFilterStats& stats() override { return stats_; } + void createFilterChain(ThriftFilters::FilterChainFactoryCallbacks& callbacks) override { + callbacks.addDecoderFilter(decoder_filter_); + } + +private: + ThriftFilters::DecoderFilterSharedPtr decoder_filter_; + ThriftFilterStats& stats_; +}; + +class ThriftConnectionManagerTest : public testing::Test { +public: + ThriftConnectionManagerTest() : stats_(ThriftFilterStats::generateStats("test.", store_)) {} + ~ThriftConnectionManagerTest() { + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + } + + void initializeFilter() { initializeFilter(""); } + + void initializeFilter(const std::string& yaml) { + // Destroy any existing filter first. + filter_ = nullptr; + + for (auto counter : store_.counters()) { + counter->reset(); + } + + if (yaml.empty()) { + proto_config_.set_stat_prefix("test"); + } else { + MessageUtil::loadFromYaml(yaml, proto_config_); + MessageUtil::validate(proto_config_); + } + + proto_config_.set_stat_prefix("test"); + + decoder_filter_.reset(new NiceMock()); + config_.reset(new TestConfigImpl(proto_config_, context_, decoder_filter_, stats_)); + + filter_.reset(new ConnectionManager(*config_)); + filter_->initializeReadFilterCallbacks(filter_callbacks_); + filter_->onNewConnection(); + + // NOP currently. + filter_->onAboveWriteBufferHighWatermark(); + filter_->onBelowWriteBufferLowWatermark(); + } + + void writeFramedBinaryMessage(Buffer::Instance& buffer, MessageType msg_type, int32_t seq_id) { + Buffer::OwnedImpl msg; + ProtocolPtr proto = + NamedProtocolConfigFactory::getFactory(ProtocolType::Binary).createProtocol(); + proto->writeMessageBegin(msg, "name", msg_type, seq_id); + proto->writeStructBegin(msg, "response"); + proto->writeFieldBegin(msg, "success", FieldType::String, 0); + proto->writeString(msg, "field"); + proto->writeFieldEnd(msg); + proto->writeFieldBegin(msg, "", FieldType::Stop, 0); + proto->writeStructEnd(msg); + proto->writeMessageEnd(msg); + + TransportPtr transport = + NamedTransportConfigFactory::getFactory(TransportType::Framed).createTransport(); + transport->encodeFrame(buffer, msg); + } + + void writeComplexFramedBinaryMessage(Buffer::Instance& buffer, MessageType msg_type, + int32_t seq_id) { + Buffer::OwnedImpl msg; + ProtocolPtr proto = + NamedProtocolConfigFactory::getFactory(ProtocolType::Binary).createProtocol(); + proto->writeMessageBegin(msg, "name", msg_type, seq_id); + proto->writeStructBegin(msg, "wrapper"); // call args struct or response struct + proto->writeFieldBegin(msg, "wrapper_field", FieldType::Struct, 0); // call arg/response success + + proto->writeStructBegin(msg, "payload"); + proto->writeFieldBegin(msg, "f1", FieldType::Bool, 1); + proto->writeBool(msg, true); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "f2", FieldType::Byte, 2); + proto->writeByte(msg, 2); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "f3", FieldType::Double, 3); + proto->writeDouble(msg, 3.0); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "f4", FieldType::I16, 4); + proto->writeInt16(msg, 4); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "f5", FieldType::I32, 5); + proto->writeInt32(msg, 5); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "f6", FieldType::I64, 6); + proto->writeInt64(msg, 6); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "f7", FieldType::String, 7); + proto->writeString(msg, "seven"); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "f8", FieldType::Map, 8); + proto->writeMapBegin(msg, FieldType::I32, FieldType::I32, 1); + proto->writeInt32(msg, 8); + proto->writeInt32(msg, 8); + proto->writeMapEnd(msg); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "f9", FieldType::List, 9); + proto->writeListBegin(msg, FieldType::I32, 1); + proto->writeInt32(msg, 8); + proto->writeListEnd(msg); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "f10", FieldType::Set, 10); + proto->writeSetBegin(msg, FieldType::I32, 1); + proto->writeInt32(msg, 8); + proto->writeSetEnd(msg); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "", FieldType::Stop, 0); // payload stop field + proto->writeStructEnd(msg); + proto->writeFieldEnd(msg); + + proto->writeFieldBegin(msg, "", FieldType::Stop, 0); // wrapper stop field + proto->writeStructEnd(msg); + proto->writeMessageEnd(msg); + + TransportPtr transport = + NamedTransportConfigFactory::getFactory(TransportType::Framed).createTransport(); + transport->encodeFrame(buffer, msg); + } + + void writePartialFramedBinaryMessage(Buffer::Instance& buffer, MessageType msg_type, + int32_t seq_id, bool start) { + Buffer::OwnedImpl frame; + writeFramedBinaryMessage(frame, msg_type, seq_id); + + if (start) { + buffer.move(frame, 27); + } else { + frame.drain(27); + buffer.move(frame); + } + } + + void writeFramedBinaryTApplicationException(Buffer::Instance& buffer, int32_t seq_id) { + Buffer::OwnedImpl msg; + ProtocolPtr proto = + NamedProtocolConfigFactory::getFactory(ProtocolType::Binary).createProtocol(); + proto->writeMessageBegin(msg, "name", MessageType::Exception, seq_id); + proto->writeStructBegin(msg, ""); + proto->writeFieldBegin(msg, "", FieldType::String, 1); + proto->writeString(msg, "error"); + proto->writeFieldEnd(msg); + proto->writeFieldBegin(msg, "", FieldType::I32, 2); + proto->writeInt32(msg, 1); + proto->writeFieldEnd(msg); + proto->writeFieldBegin(msg, "", FieldType::Stop, 0); + proto->writeStructEnd(msg); + proto->writeMessageEnd(msg); + + TransportPtr transport = + NamedTransportConfigFactory::getFactory(TransportType::Framed).createTransport(); + transport->encodeFrame(buffer, msg); + } + + void writeFramedBinaryIDLException(Buffer::Instance& buffer, int32_t seq_id) { + Buffer::OwnedImpl msg; + ProtocolPtr proto = + NamedProtocolConfigFactory::getFactory(ProtocolType::Binary).createProtocol(); + proto->writeMessageBegin(msg, "name", MessageType::Reply, seq_id); + proto->writeStructBegin(msg, ""); + proto->writeFieldBegin(msg, "", FieldType::Struct, 2); + + proto->writeStructBegin(msg, ""); + proto->writeFieldBegin(msg, "", FieldType::String, 1); + proto->writeString(msg, "err"); + proto->writeFieldEnd(msg); + proto->writeFieldBegin(msg, "", FieldType::Stop, 0); + proto->writeStructEnd(msg); + + proto->writeFieldEnd(msg); + proto->writeFieldBegin(msg, "", FieldType::Stop, 0); + proto->writeStructEnd(msg); + proto->writeMessageEnd(msg); + + TransportPtr transport = + NamedTransportConfigFactory::getFactory(TransportType::Framed).createTransport(); + transport->encodeFrame(buffer, msg); + } + + NiceMock context_; + std::shared_ptr decoder_filter_; + Stats::IsolatedStoreImpl store_; + ThriftFilterStats stats_; + envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy proto_config_; + + std::unique_ptr config_; + + Buffer::OwnedImpl buffer_; + Buffer::OwnedImpl write_buffer_; + std::unique_ptr filter_; + NiceMock filter_callbacks_; +}; + +TEST_F(ThriftConnectionManagerTest, OnDataHandlesThriftCall) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + EXPECT_EQ(0U, store_.counter("test.request_oneway").value()); + EXPECT_EQ(0U, store_.counter("test.request_invalid_type").value()); + EXPECT_EQ(0U, store_.counter("test.request_decoding_error").value()); + EXPECT_EQ(1U, store_.gauge("test.request_active").value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(ThriftConnectionManagerTest, OnDataHandlesThriftOneWay) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Oneway, 0x0F); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(0U, store_.counter("test.request_call").value()); + EXPECT_EQ(1U, store_.counter("test.request_oneway").value()); + EXPECT_EQ(0U, store_.counter("test.request_invalid_type").value()); + EXPECT_EQ(0U, store_.counter("test.request_decoding_error").value()); + EXPECT_EQ(0U, store_.gauge("test.request_active").value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(ThriftConnectionManagerTest, OnDataHandlesStopIterationAndResume) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Oneway, 0x0F); + + ThriftFilters::DecoderFilterCallbacks* callbacks{}; + EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) + .WillOnce( + Invoke([&](ThriftFilters::DecoderFilterCallbacks& cb) -> void { callbacks = &cb; })); + EXPECT_CALL(*decoder_filter_, messageBegin(_, _, _)) + .WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(0U, store_.counter("test.request").value()); + EXPECT_EQ(1U, store_.gauge("test.request_active").value()); + + // Nothing further happens: we're stopped. + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_EQ(1, callbacks->streamId()); + EXPECT_EQ(TransportType::Framed, callbacks->downstreamTransportType()); + EXPECT_EQ(ProtocolType::Binary, callbacks->downstreamProtocolType()); + EXPECT_EQ(&filter_callbacks_.connection_, callbacks->connection()); + + // Resume processing. + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + callbacks->continueDecoding(); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(0U, store_.counter("test.request_call").value()); + EXPECT_EQ(1U, store_.counter("test.request_oneway").value()); + EXPECT_EQ(0U, store_.counter("test.request_invalid_type").value()); + EXPECT_EQ(0U, store_.counter("test.request_decoding_error").value()); + EXPECT_EQ(1U, store_.gauge("test.request_active").value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + EXPECT_EQ(0U, store_.gauge("test.request_active").value()); +} + +TEST_F(ThriftConnectionManagerTest, OnDataHandlesFrameSplitAcrossBuffers) { + initializeFilter(); + + writePartialFramedBinaryMessage(buffer_, MessageType::Call, 0x10, true); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(0, buffer_.length()); + + // Complete the buffer + writePartialFramedBinaryMessage(buffer_, MessageType::Call, 0x10, false); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(0, buffer_.length()); + + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + EXPECT_EQ(0U, store_.counter("test.request_decoding_error").value()); +} + +TEST_F(ThriftConnectionManagerTest, OnDataHandlesInvalidMsgType) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Reply, 0x0F); // reply is invalid for a request + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(0U, store_.counter("test.request_call").value()); + EXPECT_EQ(0U, store_.counter("test.request_oneway").value()); + EXPECT_EQ(1U, store_.counter("test.request_invalid_type").value()); + EXPECT_EQ(1U, store_.gauge("test.request_active").value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); +} + +TEST_F(ThriftConnectionManagerTest, OnDataHandlesProtocolError) { + initializeFilter(); + addSeq(buffer_, { + 0x00, 0x00, 0x00, 0x1f, // framed: 31 bytes + 0x80, 0x01, 0x00, 0x01, // binary, call + 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name + 0x00, 0x00, 0x00, 0x01, // sequence id + 0x08, 0xff, 0xff // illegal field id + }); + + std::string err = "invalid binary protocol field id -1"; + addSeq(write_buffer_, { + 0x00, 0x00, 0x00, 0x42, // framed: 66 bytes + 0x80, 0x01, 0x00, 0x03, // binary, exception + 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name + 0x00, 0x00, 0x00, 0x01, // sequence id + 0x0b, 0x00, 0x01, // begin string field + }); + addInt32(write_buffer_, err.length()); + addString(write_buffer_, err); + addSeq(write_buffer_, { + 0x08, 0x00, 0x02, // begin i32 field + 0x00, 0x00, 0x00, 0x07, // protocol error + 0x00, // stop field + }); + + EXPECT_CALL(filter_callbacks_.connection_, write(_, false)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool) -> void { + EXPECT_EQ(bufferToString(write_buffer_), bufferToString(buffer)); + })); + EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::FlushWrite)); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request_decoding_error").value()); + EXPECT_EQ(1U, store_.gauge("test.request_active").value()); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + EXPECT_EQ(0U, store_.gauge("test.request_active").value()); +} + +TEST_F(ThriftConnectionManagerTest, OnDataHandlesProtocolErrorDuringMessageBegin) { + initializeFilter(); + addSeq(buffer_, { + 0x00, 0x00, 0x00, 0x1d, // framed: 29 bytes + 0x80, 0x01, 0x00, 0xff, // binary, invalid type + 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name + 0x00, 0x00, 0x00, 0x01, // sequence id + 0x00, // stop field + }); + + EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::FlushWrite)); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_EQ(1U, store_.counter("test.request_decoding_error").value()); +} + +TEST_F(ThriftConnectionManagerTest, OnEvent) { + // No active calls + { + initializeFilter(); + filter_->onEvent(Network::ConnectionEvent::RemoteClose); + filter_->onEvent(Network::ConnectionEvent::LocalClose); + EXPECT_EQ(0U, store_.counter("test.cx_destroy_local_with_active_rq").value()); + EXPECT_EQ(0U, store_.counter("test.cx_destroy_remote_with_active_rq").value()); + } + + // Remote close mid-request + { + initializeFilter(); + addSeq(buffer_, { + 0x00, 0x00, 0x00, 0x1d, // framed: 29 bytes + 0x80, 0x01, 0x00, 0x01, // binary proto, call type + 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name + 0x00, 0x00, 0x00, 0x0F, // seq id + }); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + filter_->onEvent(Network::ConnectionEvent::RemoteClose); + + EXPECT_EQ(1U, store_.counter("test.cx_destroy_remote_with_active_rq").value()); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + } + + // Local close mid-request + { + initializeFilter(); + addSeq(buffer_, { + 0x00, 0x00, 0x00, 0x1d, // framed: 29 bytes + 0x80, 0x01, 0x00, 0x01, // binary proto, call type + 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name + 0x00, 0x00, 0x00, 0x0F, // seq id + }); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + filter_->onEvent(Network::ConnectionEvent::LocalClose); + + EXPECT_EQ(1U, store_.counter("test.cx_destroy_local_with_active_rq").value()); + + buffer_.drain(buffer_.length()); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + } + + // Remote close before response + { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + filter_->onEvent(Network::ConnectionEvent::RemoteClose); + + EXPECT_EQ(1U, store_.counter("test.cx_destroy_remote_with_active_rq").value()); + + buffer_.drain(buffer_.length()); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + } + + // Local close before response + { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + filter_->onEvent(Network::ConnectionEvent::LocalClose); + + EXPECT_EQ(1U, store_.counter("test.cx_destroy_local_with_active_rq").value()); + + buffer_.drain(buffer_.length()); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + } +} + +TEST_F(ThriftConnectionManagerTest, Routing) { + const std::string yaml = R"EOF( +transport: FRAMED +protocol: BINARY +stat_prefix: test +route_config: + name: "routes" + routes: + - match: + method: name + route: + cluster: cluster +)EOF"; + + initializeFilter(yaml); + writeFramedBinaryMessage(buffer_, MessageType::Oneway, 0x0F); + + ThriftFilters::DecoderFilterCallbacks* callbacks{}; + EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) + .WillOnce( + Invoke([&](ThriftFilters::DecoderFilterCallbacks& cb) -> void { callbacks = &cb; })); + EXPECT_CALL(*decoder_filter_, messageBegin(_, _, _)) + .WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(0U, store_.counter("test.request").value()); + EXPECT_EQ(1U, store_.gauge("test.request_active").value()); + + Router::RouteConstSharedPtr route = callbacks->route(); + EXPECT_NE(nullptr, route); + EXPECT_NE(nullptr, route->routeEntry()); + EXPECT_EQ("cluster", route->routeEntry()->clusterName()); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + callbacks->continueDecoding(); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); +} + +TEST_F(ThriftConnectionManagerTest, RequestAndResponse) { + initializeFilter(); + writeComplexFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); + + ThriftFilters::DecoderFilterCallbacks* callbacks{}; + EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) + .WillOnce( + Invoke([&](ThriftFilters::DecoderFilterCallbacks& cb) -> void { callbacks = &cb; })); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + + writeComplexFramedBinaryMessage(write_buffer_, MessageType::Reply, 0x0F); + + callbacks->startUpstreamResponse(TransportType::Framed, ProtocolType::Binary); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + EXPECT_EQ(true, callbacks->upstreamData(write_buffer_)); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + EXPECT_EQ(0U, store_.gauge("test.request_active").value()); + EXPECT_EQ(1U, store_.counter("test.response").value()); + EXPECT_EQ(1U, store_.counter("test.response_reply").value()); + EXPECT_EQ(0U, store_.counter("test.response_exception").value()); + EXPECT_EQ(0U, store_.counter("test.response_invalid_type").value()); + EXPECT_EQ(1U, store_.counter("test.response_success").value()); + EXPECT_EQ(0U, store_.counter("test.response_error").value()); +} + +TEST_F(ThriftConnectionManagerTest, RequestAndExceptionResponse) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); + + ThriftFilters::DecoderFilterCallbacks* callbacks{}; + EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) + .WillOnce( + Invoke([&](ThriftFilters::DecoderFilterCallbacks& cb) -> void { callbacks = &cb; })); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + + writeFramedBinaryTApplicationException(write_buffer_, 0x0F); + + callbacks->startUpstreamResponse(TransportType::Framed, ProtocolType::Binary); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + EXPECT_EQ(true, callbacks->upstreamData(write_buffer_)); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + EXPECT_EQ(0U, store_.gauge("test.request_active").value()); + EXPECT_EQ(1U, store_.counter("test.response").value()); + EXPECT_EQ(0U, store_.counter("test.response_reply").value()); + EXPECT_EQ(0U, store_.counter("test.response_error").value()); + EXPECT_EQ(1U, store_.counter("test.response_exception").value()); + EXPECT_EQ(0U, store_.counter("test.response_invalid_type").value()); + EXPECT_EQ(0U, store_.counter("test.response_success").value()); + EXPECT_EQ(0U, store_.counter("test.response_error").value()); +} + +TEST_F(ThriftConnectionManagerTest, RequestAndErrorResponse) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); + + ThriftFilters::DecoderFilterCallbacks* callbacks{}; + EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) + .WillOnce( + Invoke([&](ThriftFilters::DecoderFilterCallbacks& cb) -> void { callbacks = &cb; })); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + + writeFramedBinaryIDLException(write_buffer_, 0x0F); + + callbacks->startUpstreamResponse(TransportType::Framed, ProtocolType::Binary); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + EXPECT_EQ(true, callbacks->upstreamData(write_buffer_)); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + EXPECT_EQ(0U, store_.gauge("test.request_active").value()); + EXPECT_EQ(1U, store_.counter("test.response").value()); + EXPECT_EQ(1U, store_.counter("test.response_reply").value()); + EXPECT_EQ(0U, store_.counter("test.response_exception").value()); + EXPECT_EQ(0U, store_.counter("test.response_invalid_type").value()); + EXPECT_EQ(0U, store_.counter("test.response_success").value()); + EXPECT_EQ(1U, store_.counter("test.response_error").value()); +} + +TEST_F(ThriftConnectionManagerTest, RequestAndInvalidResponse) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); + + ThriftFilters::DecoderFilterCallbacks* callbacks{}; + EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) + .WillOnce( + Invoke([&](ThriftFilters::DecoderFilterCallbacks& cb) -> void { callbacks = &cb; })); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + + // Call is not valid in a response + writeFramedBinaryMessage(write_buffer_, MessageType::Call, 0x0F); + + callbacks->startUpstreamResponse(TransportType::Framed, ProtocolType::Binary); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + EXPECT_EQ(true, callbacks->upstreamData(write_buffer_)); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + EXPECT_EQ(0U, store_.gauge("test.request_active").value()); + EXPECT_EQ(1U, store_.counter("test.response").value()); + EXPECT_EQ(0U, store_.counter("test.response_reply").value()); + EXPECT_EQ(0U, store_.counter("test.response_exception").value()); + EXPECT_EQ(1U, store_.counter("test.response_invalid_type").value()); + EXPECT_EQ(0U, store_.counter("test.response_success").value()); + EXPECT_EQ(0U, store_.counter("test.response_error").value()); +} + +TEST_F(ThriftConnectionManagerTest, RequestAndResponseProtocolError) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); + + ThriftFilters::DecoderFilterCallbacks* callbacks{}; + EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) + .WillOnce( + Invoke([&](ThriftFilters::DecoderFilterCallbacks& cb) -> void { callbacks = &cb; })); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + + // illegal field id + addSeq(write_buffer_, { + 0x00, 0x00, 0x00, 0x1f, // framed: 31 bytes + 0x80, 0x01, 0x00, 0x02, // binary, reply + 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name + 0x00, 0x00, 0x00, 0x01, // sequence id + 0x08, 0xff, 0xff // illegal field id + }); + + callbacks->startUpstreamResponse(TransportType::Framed, ProtocolType::Binary); + + EXPECT_CALL(filter_callbacks_.connection_, write(_, false)); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + EXPECT_CALL(*decoder_filter_, resetUpstreamConnection()); + EXPECT_EQ(true, callbacks->upstreamData(write_buffer_)); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + EXPECT_EQ(0U, store_.gauge("test.request_active").value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); + EXPECT_EQ(0U, store_.counter("test.response_reply").value()); + EXPECT_EQ(0U, store_.counter("test.response_exception").value()); + EXPECT_EQ(0U, store_.counter("test.response_invalid_type").value()); + EXPECT_EQ(0U, store_.counter("test.response_success").value()); + EXPECT_EQ(0U, store_.counter("test.response_error").value()); + EXPECT_EQ(1U, store_.counter("test.response_decoding_error").value()); +} + +TEST_F(ThriftConnectionManagerTest, PipelinedRequestAndResponse) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x01); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x02); + + std::list callbacks{}; + EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) + .WillRepeatedly(Invoke( + [&](ThriftFilters::DecoderFilterCallbacks& cb) -> void { callbacks.push_back(&cb); })); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(2U, store_.gauge("test.request_active").value()); + EXPECT_EQ(2U, store_.counter("test.request").value()); + EXPECT_EQ(2U, store_.counter("test.request_call").value()); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(2); + + writeFramedBinaryMessage(write_buffer_, MessageType::Reply, 0x01); + callbacks.front()->startUpstreamResponse(TransportType::Framed, ProtocolType::Binary); + EXPECT_EQ(true, callbacks.front()->upstreamData(write_buffer_)); + callbacks.pop_front(); + EXPECT_EQ(1U, store_.counter("test.response").value()); + EXPECT_EQ(1U, store_.counter("test.response_reply").value()); + + writeFramedBinaryMessage(write_buffer_, MessageType::Reply, 0x02); + callbacks.front()->startUpstreamResponse(TransportType::Framed, ProtocolType::Binary); + EXPECT_EQ(true, callbacks.front()->upstreamData(write_buffer_)); + callbacks.pop_front(); + EXPECT_EQ(2U, store_.counter("test.response").value()); + EXPECT_EQ(2U, store_.counter("test.response_reply").value()); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + + EXPECT_EQ(0U, store_.gauge("test.request_active").value()); +} + +TEST_F(ThriftConnectionManagerTest, ResetDownstreamConnection) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); + + ThriftFilters::DecoderFilterCallbacks* callbacks{}; + EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) + .WillOnce( + Invoke([&](ThriftFilters::DecoderFilterCallbacks& cb) -> void { callbacks = &cb; })); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + EXPECT_EQ(1U, store_.gauge("test.request_active").value()); + + EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)); + callbacks->resetDownstreamConnection(); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + EXPECT_EQ(0U, store_.gauge("test.request_active").value()); +} + +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/thrift_proxy/decoder_test.cc b/test/extensions/filters/network/thrift_proxy/decoder_test.cc index 834b04d662e2..9762fddfaf00 100644 --- a/test/extensions/filters/network/thrift_proxy/decoder_test.cc +++ b/test/extensions/filters/network/thrift_proxy/decoder_test.cc @@ -7,6 +7,7 @@ #include "test/test_common/printers.h" #include "test/test_common/utility.h" +#include "absl/strings/string_view.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -33,48 +34,96 @@ namespace NetworkFilters { namespace ThriftProxy { namespace { -Expectation expectValue(NiceMock& proto, FieldType field_type, bool result = true) { +ExpectationSet expectValue(MockProtocol& proto, ThriftFilters::MockDecoderFilter& filter, + FieldType field_type, bool result = true) { + ExpectationSet s; switch (field_type) { case FieldType::Bool: - return EXPECT_CALL(proto, readBool(_, _)).WillOnce(Return(result)); + s += EXPECT_CALL(proto, readBool(_, _)).WillOnce(Return(result)); + if (result) { + s += + EXPECT_CALL(filter, boolValue(_)).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + } + break; case FieldType::Byte: - return EXPECT_CALL(proto, readByte(_, _)).WillOnce(Return(result)); + s += EXPECT_CALL(proto, readByte(_, _)).WillOnce(Return(result)); + if (result) { + s += + EXPECT_CALL(filter, byteValue(_)).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + } + break; case FieldType::Double: - return EXPECT_CALL(proto, readDouble(_, _)).WillOnce(Return(result)); + s += EXPECT_CALL(proto, readDouble(_, _)).WillOnce(Return(result)); + if (result) { + s += EXPECT_CALL(filter, doubleValue(_)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + } + break; case FieldType::I16: - return EXPECT_CALL(proto, readInt16(_, _)).WillOnce(Return(result)); + s += EXPECT_CALL(proto, readInt16(_, _)).WillOnce(Return(result)); + if (result) { + s += EXPECT_CALL(filter, int16Value(_)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + } + break; case FieldType::I32: - return EXPECT_CALL(proto, readInt32(_, _)).WillOnce(Return(result)); + s += EXPECT_CALL(proto, readInt32(_, _)).WillOnce(Return(result)); + if (result) { + s += EXPECT_CALL(filter, int32Value(_)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + } + break; case FieldType::I64: - return EXPECT_CALL(proto, readInt64(_, _)).WillOnce(Return(result)); + s += EXPECT_CALL(proto, readInt64(_, _)).WillOnce(Return(result)); + if (result) { + s += EXPECT_CALL(filter, int64Value(_)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + } + break; case FieldType::String: - return EXPECT_CALL(proto, readString(_, _)).WillOnce(Return(result)); + s += EXPECT_CALL(proto, readString(_, _)).WillOnce(Return(result)); + if (result) { + s += EXPECT_CALL(filter, stringValue(_)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + } + break; default: NOT_REACHED_GCOVR_EXCL_LINE; } + return s; } -ExpectationSet expectContainerStart(NiceMock& proto, FieldType field_type, - FieldType inner_type) { +ExpectationSet expectContainerStart(MockProtocol& proto, ThriftFilters::MockDecoderFilter& filter, + FieldType field_type, FieldType inner_type) { ExpectationSet s; switch (field_type) { case FieldType::Struct: s += EXPECT_CALL(proto, readStructBegin(_, _)).WillOnce(Return(true)); + s += EXPECT_CALL(filter, structBegin(absl::string_view())) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); s += EXPECT_CALL(proto, readFieldBegin(_, _, _, _)) .WillOnce(DoAll(SetArgReferee<2>(inner_type), SetArgReferee<3>(1), Return(true))); + s += EXPECT_CALL(filter, fieldBegin(absl::string_view(), inner_type, 1)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); break; case FieldType::List: s += EXPECT_CALL(proto, readListBegin(_, _, _)) .WillOnce(DoAll(SetArgReferee<1>(inner_type), SetArgReferee<2>(1), Return(true))); + s += EXPECT_CALL(filter, listBegin(inner_type, 1)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); break; case FieldType::Map: s += EXPECT_CALL(proto, readMapBegin(_, _, _, _)) .WillOnce(DoAll(SetArgReferee<1>(inner_type), SetArgReferee<2>(inner_type), SetArgReferee<3>(1), Return(true))); + s += EXPECT_CALL(filter, mapBegin(inner_type, inner_type, 1)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); break; case FieldType::Set: s += EXPECT_CALL(proto, readSetBegin(_, _, _)) .WillOnce(DoAll(SetArgReferee<1>(inner_type), SetArgReferee<2>(1), Return(true))); + s += EXPECT_CALL(filter, setBegin(inner_type, 1)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); break; default: NOT_REACHED_GCOVR_EXCL_LINE; @@ -82,23 +131,29 @@ ExpectationSet expectContainerStart(NiceMock& proto, FieldType fie return s; } -ExpectationSet expectContainerEnd(NiceMock& proto, FieldType field_type) { +ExpectationSet expectContainerEnd(MockProtocol& proto, ThriftFilters::MockDecoderFilter& filter, + FieldType field_type) { ExpectationSet s; switch (field_type) { case FieldType::Struct: s += EXPECT_CALL(proto, readFieldEnd(_)).WillOnce(Return(true)); + s += EXPECT_CALL(filter, fieldEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); s += EXPECT_CALL(proto, readFieldBegin(_, _, _, _)) .WillOnce(DoAll(SetArgReferee<2>(FieldType::Stop), Return(true))); s += EXPECT_CALL(proto, readStructEnd(_)).WillOnce(Return(true)); + s += EXPECT_CALL(filter, structEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); break; case FieldType::List: s += EXPECT_CALL(proto, readListEnd(_)).WillOnce(Return(true)); + s += EXPECT_CALL(filter, listEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); break; case FieldType::Map: s += EXPECT_CALL(proto, readMapEnd(_)).WillOnce(Return(true)); + s += EXPECT_CALL(filter, mapEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); break; case FieldType::Set: s += EXPECT_CALL(proto, readSetEnd(_)).WillOnce(Return(true)); + s += EXPECT_CALL(filter, setEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); break; default: NOT_REACHED_GCOVR_EXCL_LINE; @@ -153,7 +208,8 @@ TEST_P(DecoderStateMachineNonValueTest, NoData) { ProtocolState state = GetParam(); Buffer::OwnedImpl buffer; NiceMock proto; - DecoderStateMachine dsm(proto); + StrictMock filter; + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(state); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); EXPECT_EQ(dsm.currentState(), state); @@ -164,17 +220,18 @@ TEST_P(DecoderStateMachineValueTest, NoFieldValueData) { Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readFieldBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<1>(std::string("")), SetArgReferee<2>(field_type), SetArgReferee<3>(1), Return(true))); - expectValue(proto, field_type, false); - expectValue(proto, field_type, true); + expectValue(proto, filter, field_type, false); + expectValue(proto, filter, field_type, true); EXPECT_CALL(proto, readFieldEnd(Ref(buffer))).WillOnce(Return(true)); EXPECT_CALL(proto, readFieldBegin(Ref(buffer), _, _, _)).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::FieldBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -188,18 +245,19 @@ TEST_P(DecoderStateMachineValueTest, FieldValue) { FieldType field_type = GetParam(); Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readFieldBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<1>(std::string("")), SetArgReferee<2>(field_type), SetArgReferee<3>(1), Return(true))); - expectValue(proto, field_type); + expectValue(proto, filter, field_type); EXPECT_CALL(proto, readFieldEnd(Ref(buffer))).WillOnce(Return(true)); EXPECT_CALL(proto, readFieldBegin(Ref(buffer), _, _, _)).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::FieldBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -209,13 +267,14 @@ TEST_P(DecoderStateMachineValueTest, FieldValue) { TEST(DecoderStateMachineTest, NoListValueData) { Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readListBegin(Ref(buffer), _, _)) .WillOnce(DoAll(SetArgReferee<1>(FieldType::I32), SetArgReferee<2>(1), Return(true))); EXPECT_CALL(proto, readInt32(Ref(buffer), _)).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::ListBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -225,13 +284,14 @@ TEST(DecoderStateMachineTest, NoListValueData) { TEST(DecoderStateMachineTest, EmptyList) { Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readListBegin(Ref(buffer), _, _)) .WillOnce(DoAll(SetArgReferee<1>(FieldType::I32), SetArgReferee<2>(0), Return(true))); EXPECT_CALL(proto, readListEnd(Ref(buffer))).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::ListBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -242,16 +302,17 @@ TEST_P(DecoderStateMachineValueTest, ListValue) { FieldType field_type = GetParam(); Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readListBegin(Ref(buffer), _, _)) .WillOnce(DoAll(SetArgReferee<1>(field_type), SetArgReferee<2>(1), Return(true))); - expectValue(proto, field_type); + expectValue(proto, filter, field_type); EXPECT_CALL(proto, readListEnd(Ref(buffer))).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::ListBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -262,18 +323,19 @@ TEST_P(DecoderStateMachineValueTest, MultipleListValues) { FieldType field_type = GetParam(); Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readListBegin(Ref(buffer), _, _)) .WillOnce(DoAll(SetArgReferee<1>(field_type), SetArgReferee<2>(5), Return(true))); for (int i = 0; i < 5; i++) { - expectValue(proto, field_type); + expectValue(proto, filter, field_type); } EXPECT_CALL(proto, readListEnd(Ref(buffer))).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::ListBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -283,6 +345,7 @@ TEST_P(DecoderStateMachineValueTest, MultipleListValues) { TEST(DecoderStateMachineTest, NoMapKeyData) { Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readMapBegin(Ref(buffer), _, _, _)) @@ -290,7 +353,7 @@ TEST(DecoderStateMachineTest, NoMapKeyData) { SetArgReferee<3>(1), Return(true))); EXPECT_CALL(proto, readInt32(Ref(buffer), _)).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::MapBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -300,6 +363,7 @@ TEST(DecoderStateMachineTest, NoMapKeyData) { TEST(DecoderStateMachineTest, NoMapValueData) { Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readMapBegin(Ref(buffer), _, _, _)) @@ -308,7 +372,7 @@ TEST(DecoderStateMachineTest, NoMapValueData) { EXPECT_CALL(proto, readInt32(Ref(buffer), _)).WillOnce(Return(true)); EXPECT_CALL(proto, readString(Ref(buffer), _)).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::MapBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -318,6 +382,7 @@ TEST(DecoderStateMachineTest, NoMapValueData) { TEST(DecoderStateMachineTest, EmptyMap) { Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readMapBegin(Ref(buffer), _, _, _)) @@ -325,7 +390,7 @@ TEST(DecoderStateMachineTest, EmptyMap) { SetArgReferee<3>(0), Return(true))); EXPECT_CALL(proto, readMapEnd(Ref(buffer))).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::MapBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -336,18 +401,19 @@ TEST_P(DecoderStateMachineValueTest, MapKeyValue) { FieldType field_type = GetParam(); Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readMapBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<1>(field_type), SetArgReferee<2>(FieldType::String), SetArgReferee<3>(1), Return(true))); - expectValue(proto, field_type); // key - expectValue(proto, FieldType::String); // value + expectValue(proto, filter, field_type); // key + expectValue(proto, filter, FieldType::String); // value EXPECT_CALL(proto, readMapEnd(Ref(buffer))).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::MapBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -358,18 +424,19 @@ TEST_P(DecoderStateMachineValueTest, MapValueValue) { FieldType field_type = GetParam(); Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readMapBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<1>(FieldType::I32), SetArgReferee<2>(field_type), SetArgReferee<3>(1), Return(true))); - expectValue(proto, FieldType::I32); // key - expectValue(proto, field_type); // value + expectValue(proto, filter, FieldType::I32); // key + expectValue(proto, filter, field_type); // value EXPECT_CALL(proto, readMapEnd(Ref(buffer))).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::MapBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -380,6 +447,7 @@ TEST_P(DecoderStateMachineValueTest, MultipleMapKeyValues) { FieldType field_type = GetParam(); Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readMapBegin(Ref(buffer), _, _, _)) @@ -387,13 +455,13 @@ TEST_P(DecoderStateMachineValueTest, MultipleMapKeyValues) { SetArgReferee<3>(5), Return(true))); for (int i = 0; i < 5; i++) { - expectValue(proto, FieldType::I32); // key - expectValue(proto, field_type); // value + expectValue(proto, filter, FieldType::I32); // key + expectValue(proto, filter, field_type); // value } EXPECT_CALL(proto, readMapEnd(Ref(buffer))).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::MapBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -403,13 +471,14 @@ TEST_P(DecoderStateMachineValueTest, MultipleMapKeyValues) { TEST(DecoderStateMachineTest, NoSetValueData) { Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readSetBegin(Ref(buffer), _, _)) .WillOnce(DoAll(SetArgReferee<1>(FieldType::I32), SetArgReferee<2>(1), Return(true))); EXPECT_CALL(proto, readInt32(Ref(buffer), _)).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::SetBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -419,13 +488,14 @@ TEST(DecoderStateMachineTest, NoSetValueData) { TEST(DecoderStateMachineTest, EmptySet) { Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readSetBegin(Ref(buffer), _, _)) .WillOnce(DoAll(SetArgReferee<1>(FieldType::I32), SetArgReferee<2>(0), Return(true))); EXPECT_CALL(proto, readSetEnd(Ref(buffer))).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::SetBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -436,16 +506,17 @@ TEST_P(DecoderStateMachineValueTest, SetValue) { FieldType field_type = GetParam(); Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readSetBegin(Ref(buffer), _, _)) .WillOnce(DoAll(SetArgReferee<1>(field_type), SetArgReferee<2>(1), Return(true))); - expectValue(proto, field_type); + expectValue(proto, filter, field_type); EXPECT_CALL(proto, readSetEnd(Ref(buffer))).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::SetBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -456,18 +527,19 @@ TEST_P(DecoderStateMachineValueTest, MultipleSetValues) { FieldType field_type = GetParam(); Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readSetBegin(Ref(buffer), _, _)) .WillOnce(DoAll(SetArgReferee<1>(field_type), SetArgReferee<2>(5), Return(true))); for (int i = 0; i < 5; i++) { - expectValue(proto, field_type); + expectValue(proto, filter, field_type); } EXPECT_CALL(proto, readSetEnd(Ref(buffer))).WillOnce(Return(false)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); dsm.setCurrentState(ProtocolState::SetBegin); EXPECT_EQ(dsm.run(buffer), ProtocolState::WaitForData); @@ -477,6 +549,7 @@ TEST_P(DecoderStateMachineValueTest, MultipleSetValues) { TEST(DecoderStateMachineTest, EmptyStruct) { Buffer::OwnedImpl buffer; NiceMock proto; + NiceMock filter; InSequence dummy; EXPECT_CALL(proto, readMessageBegin(Ref(buffer), _, _, _)) @@ -488,7 +561,7 @@ TEST(DecoderStateMachineTest, EmptyStruct) { EXPECT_CALL(proto, readStructEnd(Ref(buffer))).WillOnce(Return(true)); EXPECT_CALL(proto, readMessageEnd(Ref(buffer))).WillOnce(Return(true)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); EXPECT_EQ(dsm.run(buffer), ProtocolState::Done); EXPECT_EQ(dsm.currentState(), ProtocolState::Done); @@ -498,24 +571,39 @@ TEST_P(DecoderStateMachineValueTest, SingleFieldStruct) { FieldType field_type = GetParam(); Buffer::OwnedImpl buffer; NiceMock proto; + StrictMock filter; InSequence dummy; EXPECT_CALL(proto, readMessageBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<1>("name"), SetArgReferee<2>(MessageType::Call), SetArgReferee<3>(100), Return(true))); + EXPECT_CALL(filter, messageBegin(absl::string_view("name"), MessageType::Call, 100)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(proto, readStructBegin(Ref(buffer), _)).WillOnce(Return(true)); + EXPECT_CALL(filter, structBegin(absl::string_view())) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(proto, readFieldBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<2>(field_type), SetArgReferee<3>(1), Return(true))); + EXPECT_CALL(filter, fieldBegin(absl::string_view(), field_type, 1)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); - expectValue(proto, field_type); + expectValue(proto, filter, field_type); EXPECT_CALL(proto, readFieldEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, fieldEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(proto, readFieldBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<2>(FieldType::Stop), Return(true))); + EXPECT_CALL(proto, readStructEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, structEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(proto, readMessageEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, messageEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); EXPECT_EQ(dsm.run(buffer), ProtocolState::Done); EXPECT_EQ(dsm.currentState(), ProtocolState::Done); @@ -524,6 +612,7 @@ TEST_P(DecoderStateMachineValueTest, SingleFieldStruct) { TEST(DecoderStateMachineTest, MultiFieldStruct) { Buffer::OwnedImpl buffer; NiceMock proto; + StrictMock filter; InSequence dummy; std::vector field_types = {FieldType::Bool, FieldType::Byte, FieldType::Double, @@ -533,24 +622,36 @@ TEST(DecoderStateMachineTest, MultiFieldStruct) { EXPECT_CALL(proto, readMessageBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<1>("name"), SetArgReferee<2>(MessageType::Call), SetArgReferee<3>(100), Return(true))); + EXPECT_CALL(filter, messageBegin(absl::string_view("name"), MessageType::Call, 100)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(proto, readStructBegin(Ref(buffer), _)).WillOnce(Return(true)); + EXPECT_CALL(filter, structBegin(absl::string_view())) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); int16_t field_id = 1; for (FieldType field_type : field_types) { EXPECT_CALL(proto, readFieldBegin(Ref(buffer), _, _, _)) - .WillOnce(DoAll(SetArgReferee<2>(field_type), SetArgReferee<3>(field_id++), Return(true))); + .WillOnce(DoAll(SetArgReferee<2>(field_type), SetArgReferee<3>(field_id), Return(true))); + EXPECT_CALL(filter, fieldBegin(absl::string_view(), field_type, field_id)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + field_id++; - expectValue(proto, field_type); + expectValue(proto, filter, field_type); EXPECT_CALL(proto, readFieldEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, fieldEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); } EXPECT_CALL(proto, readFieldBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<2>(FieldType::Stop), Return(true))); EXPECT_CALL(proto, readStructEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, structEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(proto, readMessageEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, messageEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); EXPECT_EQ(dsm.run(buffer), ProtocolState::Done); EXPECT_EQ(dsm.currentState(), ProtocolState::Done); @@ -562,35 +663,41 @@ TEST_P(DecoderStateMachineNestingTest, NestedTypes) { Buffer::OwnedImpl buffer; NiceMock proto; + StrictMock filter; InSequence dummy; // start of message and outermost struct EXPECT_CALL(proto, readMessageBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<1>("name"), SetArgReferee<2>(MessageType::Call), SetArgReferee<3>(100), Return(true))); - expectContainerStart(proto, FieldType::Struct, outer_field_type); + EXPECT_CALL(filter, messageBegin(absl::string_view("name"), MessageType::Call, 100)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + + expectContainerStart(proto, filter, FieldType::Struct, outer_field_type); - expectContainerStart(proto, outer_field_type, inner_type); + expectContainerStart(proto, filter, outer_field_type, inner_type); int outer_reps = outer_field_type == FieldType::Map ? 2 : 1; for (int i = 0; i < outer_reps; i++) { - expectContainerStart(proto, inner_type, value_type); + expectContainerStart(proto, filter, inner_type, value_type); int inner_reps = inner_type == FieldType::Map ? 2 : 1; for (int j = 0; j < inner_reps; j++) { - expectValue(proto, value_type); + expectValue(proto, filter, value_type); } - expectContainerEnd(proto, inner_type); + expectContainerEnd(proto, filter, inner_type); } - expectContainerEnd(proto, outer_field_type); + expectContainerEnd(proto, filter, outer_field_type); // end of message and outermost struct - expectContainerEnd(proto, FieldType::Struct); + expectContainerEnd(proto, filter, FieldType::Struct); + EXPECT_CALL(proto, readMessageEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, messageEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); - DecoderStateMachine dsm(proto); + DecoderStateMachine dsm(proto, filter); EXPECT_EQ(dsm.run(buffer), ProtocolState::Done); EXPECT_EQ(dsm.currentState(), ProtocolState::Done); @@ -599,63 +706,135 @@ TEST_P(DecoderStateMachineNestingTest, NestedTypes) { TEST(DecoderTest, OnData) { NiceMock* transport = new NiceMock(); NiceMock* proto = new NiceMock(); + NiceMock callbacks; + StrictMock filter; + ON_CALL(callbacks, newDecoderFilter()).WillByDefault(ReturnRef(filter)); + InSequence dummy; - Decoder decoder(TransportPtr{transport}, ProtocolPtr{proto}); + Decoder decoder(TransportPtr{transport}, ProtocolPtr{proto}, callbacks); Buffer::OwnedImpl buffer; - EXPECT_CALL(*transport, decodeFrameStart(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(*transport, decodeFrameStart(Ref(buffer), _)) + .WillOnce(DoAll(SetArgReferee<1>(absl::optional(100)), Return(true))); + EXPECT_CALL(filter, transportBegin(absl::optional(100))) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(*proto, readMessageBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<1>("name"), SetArgReferee<2>(MessageType::Call), SetArgReferee<3>(100), Return(true))); + EXPECT_CALL(filter, messageBegin(absl::string_view("name"), MessageType::Call, 100)) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(*proto, readStructBegin(Ref(buffer), _)).WillOnce(Return(true)); + EXPECT_CALL(filter, structBegin(absl::string_view())) + .WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(*proto, readFieldBegin(Ref(buffer), _, _, _)) .WillOnce(DoAll(SetArgReferee<2>(FieldType::Stop), Return(true))); EXPECT_CALL(*proto, readStructEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, structEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(*proto, readMessageEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, messageEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); + EXPECT_CALL(*transport, decodeFrameEnd(Ref(buffer))).WillOnce(Return(true)); - EXPECT_CALL(*transport, decodeFrameStart(Ref(buffer))).WillOnce(Return(false)); + EXPECT_CALL(filter, transportEnd()).WillOnce(Return(ThriftFilters::FilterStatus::Continue)); - decoder.onData(buffer); + bool underflow = false; + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, decoder.onData(buffer, underflow)); + EXPECT_TRUE(underflow); } TEST(DecoderTest, OnDataResumes) { NiceMock* transport = new NiceMock(); NiceMock* proto = new NiceMock(); + NiceMock callbacks; + NiceMock filter; + ON_CALL(callbacks, newDecoderFilter()).WillByDefault(ReturnRef(filter)); + InSequence dummy; - Decoder decoder(TransportPtr{transport}, ProtocolPtr{proto}); + Decoder decoder(TransportPtr{transport}, ProtocolPtr{proto}, callbacks); Buffer::OwnedImpl buffer; + buffer.add("x"); - EXPECT_CALL(*transport, decodeFrameStart(_)).WillOnce(Return(true)); + EXPECT_CALL(*transport, decodeFrameStart(Ref(buffer), _)) + .WillOnce(DoAll(SetArgReferee<1>(absl::optional(100)), Return(true))); EXPECT_CALL(*proto, readMessageBegin(_, _, _, _)) .WillOnce(DoAll(SetArgReferee<1>("name"), SetArgReferee<2>(MessageType::Call), SetArgReferee<3>(100), Return(true))); EXPECT_CALL(*proto, readStructBegin(_, _)).WillOnce(Return(false)); - decoder.onData(buffer); + bool underflow = false; + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, decoder.onData(buffer, underflow)); + EXPECT_TRUE(underflow); + + EXPECT_CALL(*proto, readStructBegin(_, _)).WillOnce(Return(true)); + EXPECT_CALL(*proto, readFieldBegin(_, _, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(FieldType::Stop), Return(true))); + EXPECT_CALL(*proto, readStructEnd(_)).WillOnce(Return(true)); + EXPECT_CALL(*proto, readMessageEnd(_)).WillOnce(Return(true)); + EXPECT_CALL(*transport, decodeFrameEnd(_)).WillOnce(Return(true)); + + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, decoder.onData(buffer, underflow)); + EXPECT_FALSE(underflow); // buffer.length() == 1 +} + +TEST(DecoderTest, OnDataResumesTransportFrameStart) { + StrictMock* transport = new StrictMock(); + StrictMock* proto = new StrictMock(); + NiceMock callbacks; + NiceMock filter; + ON_CALL(callbacks, newDecoderFilter()).WillByDefault(ReturnRef(filter)); + + EXPECT_CALL(*transport, name()).Times(AnyNumber()); + EXPECT_CALL(*proto, name()).Times(AnyNumber()); + + InSequence dummy; + + Decoder decoder(TransportPtr{transport}, ProtocolPtr{proto}, callbacks); + Buffer::OwnedImpl buffer; + bool underflow = false; + + EXPECT_CALL(*transport, decodeFrameStart(Ref(buffer), _)) + .WillOnce(DoAll(SetArgReferee<1>(absl::optional(100)), Return(false))); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, decoder.onData(buffer, underflow)); + EXPECT_TRUE(underflow); + + EXPECT_CALL(*transport, decodeFrameStart(Ref(buffer), _)) + .WillOnce(DoAll(SetArgReferee<1>(absl::optional(100)), Return(true))); + EXPECT_CALL(*proto, readMessageBegin(_, _, _, _)) + .WillOnce(DoAll(SetArgReferee<1>("name"), SetArgReferee<2>(MessageType::Call), + SetArgReferee<3>(100), Return(true))); EXPECT_CALL(*proto, readStructBegin(_, _)).WillOnce(Return(true)); EXPECT_CALL(*proto, readFieldBegin(_, _, _, _)) .WillOnce(DoAll(SetArgReferee<2>(FieldType::Stop), Return(true))); EXPECT_CALL(*proto, readStructEnd(_)).WillOnce(Return(true)); EXPECT_CALL(*proto, readMessageEnd(_)).WillOnce(Return(true)); EXPECT_CALL(*transport, decodeFrameEnd(_)).WillOnce(Return(true)); - EXPECT_CALL(*transport, decodeFrameStart(_)).WillOnce(Return(false)); - decoder.onData(buffer); + + underflow = false; + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, decoder.onData(buffer, underflow)); + EXPECT_TRUE(underflow); // buffer.length() == 0 } TEST(DecoderTest, OnDataResumesTransportFrameEnd) { StrictMock* transport = new StrictMock(); StrictMock* proto = new StrictMock(); + NiceMock callbacks; + NiceMock filter; + ON_CALL(callbacks, newDecoderFilter()).WillByDefault(ReturnRef(filter)); EXPECT_CALL(*transport, name()).Times(AnyNumber()); EXPECT_CALL(*proto, name()).Times(AnyNumber()); InSequence dummy; - Decoder decoder(TransportPtr{transport}, ProtocolPtr{proto}); + Decoder decoder(TransportPtr{transport}, ProtocolPtr{proto}, callbacks); Buffer::OwnedImpl buffer; - EXPECT_CALL(*transport, decodeFrameStart(_)).WillOnce(Return(true)); + EXPECT_CALL(*transport, decodeFrameStart(Ref(buffer), _)) + .WillOnce(DoAll(SetArgReferee<1>(absl::optional(100)), Return(true))); EXPECT_CALL(*proto, readMessageBegin(_, _, _, _)) .WillOnce(DoAll(SetArgReferee<1>("name"), SetArgReferee<2>(MessageType::Call), SetArgReferee<3>(100), Return(true))); @@ -665,11 +844,90 @@ TEST(DecoderTest, OnDataResumesTransportFrameEnd) { EXPECT_CALL(*proto, readStructEnd(_)).WillOnce(Return(true)); EXPECT_CALL(*proto, readMessageEnd(_)).WillOnce(Return(true)); EXPECT_CALL(*transport, decodeFrameEnd(_)).WillOnce(Return(false)); - decoder.onData(buffer); + + bool underflow = false; + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, decoder.onData(buffer, underflow)); + EXPECT_TRUE(underflow); EXPECT_CALL(*transport, decodeFrameEnd(_)).WillOnce(Return(true)); - EXPECT_CALL(*transport, decodeFrameStart(_)).WillOnce(Return(false)); - decoder.onData(buffer); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, decoder.onData(buffer, underflow)); + EXPECT_TRUE(underflow); // buffer.length() == 0 +} + +TEST(DecoderTest, OnDataHandlesStopIterationAndResumes) { + + StrictMock* transport = new StrictMock(); + EXPECT_CALL(*transport, name()).WillRepeatedly(ReturnRef(transport->name_)); + + StrictMock* proto = new StrictMock(); + EXPECT_CALL(*proto, name()).WillRepeatedly(ReturnRef(proto->name_)); + + NiceMock callbacks; + StrictMock filter; + ON_CALL(callbacks, newDecoderFilter()).WillByDefault(ReturnRef(filter)); + + InSequence dummy; + Decoder decoder(TransportPtr{transport}, ProtocolPtr{proto}, callbacks); + Buffer::OwnedImpl buffer; + bool underflow = true; + + EXPECT_CALL(*transport, decodeFrameStart(Ref(buffer), _)) + .WillOnce(DoAll(SetArgReferee<1>(absl::optional(100)), Return(true))); + EXPECT_CALL(filter, transportBegin(absl::optional(100))) + .WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, decoder.onData(buffer, underflow)); + EXPECT_FALSE(underflow); + + EXPECT_CALL(*proto, readMessageBegin(Ref(buffer), _, _, _)) + .WillOnce(DoAll(SetArgReferee<1>("name"), SetArgReferee<2>(MessageType::Call), + SetArgReferee<3>(100), Return(true))); + EXPECT_CALL(filter, messageBegin(absl::string_view("name"), MessageType::Call, 100)) + .WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, decoder.onData(buffer, underflow)); + EXPECT_FALSE(underflow); + + EXPECT_CALL(*proto, readStructBegin(Ref(buffer), _)).WillOnce(Return(true)); + EXPECT_CALL(filter, structBegin(absl::string_view())) + .WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, decoder.onData(buffer, underflow)); + EXPECT_FALSE(underflow); + + EXPECT_CALL(*proto, readFieldBegin(Ref(buffer), _, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(FieldType::I32), SetArgReferee<3>(1), Return(true))); + EXPECT_CALL(filter, fieldBegin(absl::string_view(), FieldType::I32, 1)) + .WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, decoder.onData(buffer, underflow)); + EXPECT_FALSE(underflow); + + EXPECT_CALL(*proto, readInt32(_, _)).WillOnce(Return(true)); + EXPECT_CALL(filter, int32Value(_)).WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, decoder.onData(buffer, underflow)); + EXPECT_FALSE(underflow); + + EXPECT_CALL(*proto, readFieldEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, fieldEnd()).WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, decoder.onData(buffer, underflow)); + EXPECT_FALSE(underflow); + + EXPECT_CALL(*proto, readFieldBegin(Ref(buffer), _, _, _)) + .WillOnce(DoAll(SetArgReferee<2>(FieldType::Stop), Return(true))); + EXPECT_CALL(*proto, readStructEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, structEnd()).WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, decoder.onData(buffer, underflow)); + EXPECT_FALSE(underflow); + + EXPECT_CALL(*proto, readMessageEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, messageEnd()).WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, decoder.onData(buffer, underflow)); + EXPECT_FALSE(underflow); + + EXPECT_CALL(*transport, decodeFrameEnd(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(filter, transportEnd()).WillOnce(Return(ThriftFilters::FilterStatus::StopIteration)); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, decoder.onData(buffer, underflow)); + EXPECT_FALSE(underflow); + + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, decoder.onData(buffer, underflow)); + EXPECT_TRUE(underflow); } #define TEST_NAME(X) EXPECT_EQ(ProtocolStateNameValues::name(ProtocolState::X), #X); diff --git a/test/extensions/filters/network/thrift_proxy/filter_test.cc b/test/extensions/filters/network/thrift_proxy/filter_test.cc deleted file mode 100644 index d192e4737209..000000000000 --- a/test/extensions/filters/network/thrift_proxy/filter_test.cc +++ /dev/null @@ -1,559 +0,0 @@ -#include "common/buffer/buffer_impl.h" -#include "common/stats/stats_impl.h" - -#include "extensions/filters/network/thrift_proxy/buffer_helper.h" -#include "extensions/filters/network/thrift_proxy/filter.h" - -#include "test/extensions/filters/network/thrift_proxy/utility.h" -#include "test/mocks/network/mocks.h" -#include "test/test_common/printers.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using testing::NiceMock; - -namespace Envoy { -namespace Extensions { -namespace NetworkFilters { -namespace ThriftProxy { - -class ThriftFilterTest : public testing::Test { -public: - ThriftFilterTest() {} - - void initializeFilter() { - for (auto counter : store_.counters()) { - counter->reset(); - } - - filter_.reset(new Filter("test.", store_)); - filter_->initializeReadFilterCallbacks(read_filter_callbacks_); - filter_->onNewConnection(); - - // NOP currently. - filter_->onAboveWriteBufferHighWatermark(); - filter_->onBelowWriteBufferLowWatermark(); - } - - void writeFramedBinaryMessage(Buffer::Instance& buffer, MessageType msg_type, int32_t seq_id) { - uint8_t mt = static_cast(msg_type); - uint8_t s1 = (seq_id >> 24) & 0xFF; - uint8_t s2 = (seq_id >> 16) & 0xFF; - uint8_t s3 = (seq_id >> 8) & 0xFF; - uint8_t s4 = seq_id & 0xFF; - - addSeq(buffer, { - 0x00, 0x00, 0x00, 0x1d, // framed: 29 bytes - 0x80, 0x01, 0x00, mt, // binary proto, type - 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name - s1, s2, s3, s4, // sequence id - 0x0b, 0x00, 0x00, // begin string field - 0x00, 0x00, 0x00, 0x05, 'f', 'i', 'e', 'l', 'd', // string - 0x00, // stop field - }); - } - - void writePartialFramedBinaryMessage(Buffer::Instance& buffer, MessageType msg_type, - int32_t seq_id, bool start) { - if (start) { - uint8_t mt = static_cast(msg_type); - uint8_t s1 = (seq_id >> 24) & 0xFF; - uint8_t s2 = (seq_id >> 16) & 0xFF; - uint8_t s3 = (seq_id >> 8) & 0xFF; - uint8_t s4 = seq_id & 0xFF; - - addSeq(buffer, { - 0x00, 0x00, 0x00, 0x2d, // framed: 45 bytes - 0x80, 0x01, 0x00, mt, // binary proto, type - 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name - s1, s2, s3, s4, // sequence id - 0x0c, 0x00, 0x00, // begin struct field - 0x0b, 0x00, 0x01, // begin string field - 0x00, 0x00, 0x00, 0x05 // string length only - }); - } else { - addSeq(buffer, { - 'f', 'i', 'e', 'l', 'd', // string data - 0x0b, 0x00, 0x02, // begin string field - 0x00, 0x00, 0x00, 0x05, 'x', 'x', 'x', 'x', 'x', // string - 0x00, // stop field - 0x00, // stop field - }); - } - } - - void writeFramedBinaryTApplicationException(Buffer::Instance& buffer, int32_t seq_id) { - uint8_t s1 = (seq_id >> 24) & 0xFF; - uint8_t s2 = (seq_id >> 16) & 0xFF; - uint8_t s3 = (seq_id >> 8) & 0xFF; - uint8_t s4 = seq_id & 0xFF; - - addSeq(buffer, { - 0x00, 0x00, 0x00, 0x24, // framed: 36 bytes - 0x80, 0x01, 0x00, 0x03, // binary, exception - 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name - s1, s2, s3, s4, // sequence id - 0x0B, 0x00, 0x01, // begin string field - 0x00, 0x00, 0x00, 0x05, 'e', 'r', 'r', 'o', 'r', // string - 0x08, 0x00, 0x02, // begin i32 field - 0x00, 0x00, 0x00, 0x01, // exception type 1 - 0x00, // stop field - }); - } - - void writeFramedBinaryIDLException(Buffer::Instance& buffer, int32_t seq_id) { - uint8_t s1 = (seq_id >> 24) & 0xFF; - uint8_t s2 = (seq_id >> 16) & 0xFF; - uint8_t s3 = (seq_id >> 8) & 0xFF; - uint8_t s4 = seq_id & 0xFF; - - addSeq(buffer, { - 0x00, 0x00, 0x00, 0x23, // framed: 35 bytes - 0x80, 0x01, 0x00, 0x02, // binary proto, reply - 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name - s1, s2, s3, s4, // sequence id - 0x0C, 0x00, 0x02, // begin exception struct - 0x0B, 0x00, 0x01, // begin string field - 0x00, 0x00, 0x00, 0x03, 'e', 'r', 'r', // string - 0x00, // exception struct stop - 0x00, // reply struct stop field - }); - } - - Buffer::OwnedImpl buffer_; - Buffer::OwnedImpl write_buffer_; - Stats::IsolatedStoreImpl store_; - std::unique_ptr filter_; - NiceMock read_filter_callbacks_; -}; - -TEST_F(ThriftFilterTest, OnDataHandlesThriftCall) { - initializeFilter(); - writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); - - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.request").value()); - EXPECT_EQ(1U, store_.counter("test.request_call").value()); - EXPECT_EQ(0U, store_.counter("test.request_oneway").value()); - EXPECT_EQ(0U, store_.counter("test.request_invalid_type").value()); - EXPECT_EQ(0U, store_.counter("test.request_decoding_error").value()); - EXPECT_EQ(1U, store_.gauge("test.request_active").value()); - EXPECT_EQ(0U, store_.counter("test.response").value()); -} - -TEST_F(ThriftFilterTest, OnDataHandlesThriftOneWay) { - initializeFilter(); - writeFramedBinaryMessage(buffer_, MessageType::Oneway, 0x0F); - - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.request").value()); - EXPECT_EQ(0U, store_.counter("test.request_call").value()); - EXPECT_EQ(1U, store_.counter("test.request_oneway").value()); - EXPECT_EQ(0U, store_.counter("test.request_invalid_type").value()); - EXPECT_EQ(0U, store_.counter("test.request_decoding_error").value()); - EXPECT_EQ(0U, store_.gauge("test.request_active").value()); - EXPECT_EQ(0U, store_.counter("test.response").value()); -} - -TEST_F(ThriftFilterTest, OnDataHandlesFrameSplitAcrossBuffers) { - initializeFilter(); - - writePartialFramedBinaryMessage(buffer_, MessageType::Call, 0x10, true); - std::string expected_contents = bufferToString(buffer_); - uint64_t len = buffer_.length(); - - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - - // Filter passes on the partial buffer, up to the last 4 bytes which it needs to resume the - // decoder on the next call. - std::string contents = bufferToString(buffer_); - EXPECT_EQ(len - 4, buffer_.length()); - EXPECT_EQ(expected_contents.substr(0, len - 4), contents); - - buffer_.drain(buffer_.length()); - - // Complete the buffer - writePartialFramedBinaryMessage(buffer_, MessageType::Call, 0x10, false); - expected_contents = expected_contents.substr(len - 4) + bufferToString(buffer_); - len = buffer_.length(); - - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - - // Filter buffered bytes from end of first buffer and passes them on now. - contents = bufferToString(buffer_); - EXPECT_EQ(len + 4, buffer_.length()); - EXPECT_EQ(expected_contents, contents); - - EXPECT_EQ(1U, store_.counter("test.request_call").value()); - EXPECT_EQ(0U, store_.counter("test.request_decoding_error").value()); -} - -TEST_F(ThriftFilterTest, OnDataHandlesInvalidMsgType) { - initializeFilter(); - writeFramedBinaryMessage(buffer_, MessageType::Reply, 0x0F); // reply is invalid for a request - - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.request").value()); - EXPECT_EQ(0U, store_.counter("test.request_call").value()); - EXPECT_EQ(0U, store_.counter("test.request_oneway").value()); - EXPECT_EQ(1U, store_.counter("test.request_invalid_type").value()); - EXPECT_EQ(1U, store_.gauge("test.request_active").value()); - EXPECT_EQ(0U, store_.counter("test.response").value()); -} - -TEST_F(ThriftFilterTest, OnDataHandlesProtocolError) { - initializeFilter(); - addSeq(buffer_, { - 0x00, 0x00, 0x00, 0x1d, // framed: 29 bytes - 0x80, 0x01, 0x00, 0xFF, // binary, illegal type - 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name - 0x00, 0x00, 0x00, 0x01, // sequence id - 0x00, // struct stop field - }); - uint64_t len = buffer_.length(); - - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.request_decoding_error").value()); - EXPECT_EQ(len, buffer_.length()); - - // Sniffing is now disabled. - buffer_.drain(buffer_.length()); - writeFramedBinaryMessage(buffer_, MessageType::Oneway, 0x0F); - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(0U, store_.counter("test.request").value()); -} - -TEST_F(ThriftFilterTest, OnDataHandlesProtocolErrorOnWrite) { - initializeFilter(); - - // Start the read buffer - writePartialFramedBinaryMessage(buffer_, MessageType::Call, 0x10, true); - uint64_t len = buffer_.length(); - - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - len -= buffer_.length(); - - // Disable sniffing - addSeq(write_buffer_, { - 0x00, 0x00, 0x00, 0x1d, // framed: 29 bytes - 0x80, 0x01, 0x00, 0xFF, // binary, illegal type - 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name - 0x00, 0x00, 0x00, 0x01, // sequence id - 0x00, // struct stop field - }); - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.response_decoding_error").value()); - - // Complete the read buffer - writePartialFramedBinaryMessage(buffer_, MessageType::Call, 0x10, false); - len += buffer_.length(); - - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - len -= buffer_.length(); - EXPECT_EQ(0, len); -} - -TEST_F(ThriftFilterTest, OnDataStopsSniffingWithTooManyPendingCalls) { - initializeFilter(); - for (int i = 0; i < 64; i++) { - writeFramedBinaryMessage(buffer_, MessageType::Call, i); - } - - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(64U, store_.gauge("test.request_active").value()); - buffer_.drain(buffer_.length()); - - // Sniffing is now disabled. - writeFramedBinaryMessage(buffer_, MessageType::Oneway, 100); - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(64U, store_.gauge("test.request_active").value()); - EXPECT_EQ(1U, store_.counter("test.request_decoding_error").value()); -} - -TEST_F(ThriftFilterTest, OnWriteHandlesThriftReply) { - initializeFilter(); - writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); // set up request - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.request").value()); - EXPECT_EQ(1U, store_.gauge("test.request_active").value()); - - writeFramedBinaryMessage(write_buffer_, MessageType::Reply, 0x0F); - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - - EXPECT_EQ(1U, store_.counter("test.response").value()); - EXPECT_EQ(1U, store_.counter("test.response_reply").value()); - EXPECT_EQ(1U, store_.counter("test.response_success").value()); - EXPECT_EQ(0U, store_.counter("test.response_error").value()); - EXPECT_EQ(0U, store_.counter("test.response_exception").value()); - EXPECT_EQ(0U, store_.counter("test.response_invalid_type").value()); - EXPECT_EQ(0U, store_.counter("test.response_decoding_error").value()); - EXPECT_EQ(0U, store_.gauge("test.request_active").value()); -} - -TEST_F(ThriftFilterTest, OnWriteHandlesOutOrOrderThriftReply) { - initializeFilter(); - - // set up two requests - writeFramedBinaryMessage(buffer_, MessageType::Call, 1); - writeFramedBinaryMessage(buffer_, MessageType::Call, 2); - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(2U, store_.counter("test.request").value()); - EXPECT_EQ(2U, store_.gauge("test.request_active").value()); - - writeFramedBinaryMessage(write_buffer_, MessageType::Reply, 2); - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - - EXPECT_EQ(1U, store_.counter("test.response").value()); - EXPECT_EQ(1U, store_.counter("test.response_reply").value()); - EXPECT_EQ(1U, store_.counter("test.response_success").value()); - EXPECT_EQ(0U, store_.counter("test.response_error").value()); - EXPECT_EQ(1U, store_.gauge("test.request_active").value()); - - write_buffer_.drain(write_buffer_.length()); - writeFramedBinaryMessage(write_buffer_, MessageType::Reply, 1); - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - - EXPECT_EQ(2U, store_.counter("test.response").value()); - EXPECT_EQ(2U, store_.counter("test.response_reply").value()); - EXPECT_EQ(2U, store_.counter("test.response_success").value()); - EXPECT_EQ(0U, store_.counter("test.response_error").value()); - EXPECT_EQ(0U, store_.gauge("test.request_active").value()); -} - -TEST_F(ThriftFilterTest, OnWriteHandlesFrameSplitAcrossBuffers) { - initializeFilter(); - - writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); // set up request - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - - writePartialFramedBinaryMessage(write_buffer_, MessageType::Reply, 0x0F, true); - std::string expected_contents = bufferToString(write_buffer_); - uint64_t len = write_buffer_.length(); - - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - - // Filter passes on the partial buffer, up to the last 4 bytes which it needs to resume the - // decoder on the next call. - std::string contents = bufferToString(write_buffer_); - EXPECT_EQ(len - 4, write_buffer_.length()); - EXPECT_EQ(expected_contents.substr(0, len - 4), contents); - - write_buffer_.drain(write_buffer_.length()); - - // Complete the buffer - writePartialFramedBinaryMessage(write_buffer_, MessageType::Reply, 0x0F, false); - expected_contents = expected_contents.substr(len - 4) + bufferToString(write_buffer_); - len = write_buffer_.length(); - - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - - // Filter buffered bytes from end of first buffer and passes them on now. - contents = bufferToString(write_buffer_); - EXPECT_EQ(len + 4, write_buffer_.length()); - EXPECT_EQ(expected_contents, contents); - - EXPECT_EQ(1U, store_.counter("test.response").value()); - EXPECT_EQ(1U, store_.counter("test.response_reply").value()); - EXPECT_EQ(1U, store_.counter("test.response_success").value()); - EXPECT_EQ(0U, store_.counter("test.response_decoding_error").value()); -} - -TEST_F(ThriftFilterTest, OnWriteHandlesTApplicationException) { - initializeFilter(); - writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); // set up request - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.request").value()); - EXPECT_EQ(1U, store_.gauge("test.request_active").value()); - - writeFramedBinaryTApplicationException(write_buffer_, 0x0F); - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - - EXPECT_EQ(1U, store_.counter("test.response").value()); - EXPECT_EQ(0U, store_.counter("test.response_reply").value()); - EXPECT_EQ(0U, store_.counter("test.response_success").value()); - EXPECT_EQ(0U, store_.counter("test.response_error").value()); - EXPECT_EQ(1U, store_.counter("test.response_exception").value()); - EXPECT_EQ(0U, store_.counter("test.response_invalid_type").value()); - EXPECT_EQ(0U, store_.counter("test.response_decoding_error").value()); - EXPECT_EQ(0U, store_.gauge("test.request_active").value()); -} - -TEST_F(ThriftFilterTest, OnWriteHandlesIDLException) { - initializeFilter(); - writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); // set up request - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.request").value()); - EXPECT_EQ(1U, store_.gauge("test.request_active").value()); - - writeFramedBinaryIDLException(write_buffer_, 0x0F); - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - - EXPECT_EQ(1U, store_.counter("test.response").value()); - EXPECT_EQ(1U, store_.counter("test.response_reply").value()); - EXPECT_EQ(0U, store_.counter("test.response_success").value()); - EXPECT_EQ(1U, store_.counter("test.response_error").value()); - EXPECT_EQ(0U, store_.counter("test.response_exception").value()); - EXPECT_EQ(0U, store_.counter("test.response_invalid_type").value()); - EXPECT_EQ(0U, store_.counter("test.response_decoding_error").value()); - EXPECT_EQ(0U, store_.gauge("test.request_active").value()); -} - -TEST_F(ThriftFilterTest, OnWriteHandlesInvalidMsgType) { - initializeFilter(); - writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.request").value()); - EXPECT_EQ(1U, store_.gauge("test.request_active").value()); - - writeFramedBinaryMessage(write_buffer_, MessageType::Call, 0x0F); // call is invalid for response - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.response").value()); - EXPECT_EQ(0U, store_.counter("test.response_success").value()); - EXPECT_EQ(0U, store_.counter("test.response_error").value()); - EXPECT_EQ(0U, store_.counter("test.response_exception").value()); - EXPECT_EQ(1U, store_.counter("test.response_invalid_type").value()); - EXPECT_EQ(0U, store_.gauge("test.request_active").value()); -} - -TEST_F(ThriftFilterTest, OnWriteHandlesProtocolError) { - initializeFilter(); - addSeq(write_buffer_, { - 0x00, 0x00, 0x00, 0x1d, // framed: 29 bytes - 0x80, 0x01, 0x00, 0xFF, // binary, illegal type - 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name - 0x00, 0x00, 0x00, 0x01, // sequence id - 0x00, // struct stop field - }); - uint64_t len = buffer_.length(); - - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.response_decoding_error").value()); - EXPECT_EQ(len, buffer_.length()); - - // Sniffing is now disabled. - write_buffer_.drain(write_buffer_.length()); - writeFramedBinaryMessage(write_buffer_, MessageType::Reply, 1); - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); -} - -TEST_F(ThriftFilterTest, OnWriteHandlesProtocolErrorOnData) { - initializeFilter(); - - // Set up a request for the partial write - writeFramedBinaryMessage(buffer_, MessageType::Call, 1); - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - buffer_.drain(buffer_.length()); - - // Start the write buffer - writePartialFramedBinaryMessage(write_buffer_, MessageType::Reply, 1, true); - uint64_t len = write_buffer_.length(); - - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - len -= write_buffer_.length(); - - // Force an error on the next request. - addSeq(buffer_, { - 0x00, 0x00, 0x00, 0x1d, // framed: 29 bytes - 0x80, 0x01, 0x00, 0xFF, // binary, illegal type - 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name - 0x00, 0x00, 0x00, 0x02, // sequence id - 0x00, // struct stop field - }); - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - EXPECT_EQ(1U, store_.counter("test.request_decoding_error").value()); - - // Complete the read buffer - writePartialFramedBinaryMessage(write_buffer_, MessageType::Reply, 1, false); - len += write_buffer_.length(); - - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - len -= write_buffer_.length(); - EXPECT_EQ(0, len); -} - -TEST_F(ThriftFilterTest, OnEvent) { - // No active calls - { - initializeFilter(); - filter_->onEvent(Network::ConnectionEvent::RemoteClose); - filter_->onEvent(Network::ConnectionEvent::LocalClose); - EXPECT_EQ(0U, store_.counter("test.cx_destroy_local_with_active_rq").value()); - EXPECT_EQ(0U, store_.counter("test.cx_destroy_remote_with_active_rq").value()); - } - - // Close mid-request - { - initializeFilter(); - addSeq(buffer_, { - 0x00, 0x00, 0x00, 0x1d, // framed: 29 bytes - 0x80, 0x01, 0x00, 0x01, // binary proto, call type - 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name - 0x00, 0x00, 0x00, 0x0F, // seq id - }); - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - - filter_->onEvent(Network::ConnectionEvent::RemoteClose); - EXPECT_EQ(1U, store_.counter("test.cx_destroy_local_with_active_rq").value()); - - filter_->onEvent(Network::ConnectionEvent::LocalClose); - EXPECT_EQ(1U, store_.counter("test.cx_destroy_remote_with_active_rq").value()); - - buffer_.drain(buffer_.length()); - } - - // Close before response - { - initializeFilter(); - writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - - filter_->onEvent(Network::ConnectionEvent::RemoteClose); - EXPECT_EQ(1U, store_.counter("test.cx_destroy_local_with_active_rq").value()); - - filter_->onEvent(Network::ConnectionEvent::LocalClose); - EXPECT_EQ(1U, store_.counter("test.cx_destroy_remote_with_active_rq").value()); - - buffer_.drain(buffer_.length()); - } - - // Close mid-response - { - initializeFilter(); - writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - - addSeq(write_buffer_, { - 0x00, 0x00, 0x00, 0x1d, // framed: 29 bytes - 0x80, 0x01, 0x00, 0x02, // binary proto, reply type - 0x00, 0x00, 0x00, 0x04, 'n', 'a', 'm', 'e', // message name - 0x00, 0x00, 0x00, 0x0F, // seq id - }); - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - - filter_->onEvent(Network::ConnectionEvent::RemoteClose); - EXPECT_EQ(1U, store_.counter("test.cx_destroy_local_with_active_rq").value()); - - filter_->onEvent(Network::ConnectionEvent::LocalClose); - EXPECT_EQ(1U, store_.counter("test.cx_destroy_remote_with_active_rq").value()); - - buffer_.drain(buffer_.length()); - write_buffer_.drain(write_buffer_.length()); - } -} - -TEST_F(ThriftFilterTest, ResponseWithUnknownSequenceID) { - initializeFilter(); - writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); - EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::Continue); - - writeFramedBinaryMessage(write_buffer_, MessageType::Reply, 0x10); - EXPECT_EQ(filter_->onWrite(write_buffer_, false), Network::FilterStatus::Continue); - - EXPECT_EQ(1U, store_.counter("test.response_decoding_error").value()); -} - -} // namespace ThriftProxy -} // namespace NetworkFilters -} // namespace Extensions -} // namespace Envoy diff --git a/test/extensions/filters/network/thrift_proxy/framed_transport_impl_test.cc b/test/extensions/filters/network/thrift_proxy/framed_transport_impl_test.cc index 76ed15709a2f..2999de7edbf5 100644 --- a/test/extensions/filters/network/thrift_proxy/framed_transport_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/framed_transport_impl_test.cc @@ -4,80 +4,80 @@ #include "extensions/filters/network/thrift_proxy/framed_transport_impl.h" -#include "test/extensions/filters/network/thrift_proxy/mocks.h" #include "test/extensions/filters/network/thrift_proxy/utility.h" -#include "test/mocks/buffer/mocks.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" -#include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::StrictMock; - namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { TEST(FramedTransportTest, Name) { - StrictMock cb; - FramedTransportImpl transport(cb); + FramedTransportImpl transport; EXPECT_EQ(transport.name(), "framed"); } +TEST(FramedTransportTest, Type) { + FramedTransportImpl transport; + EXPECT_EQ(transport.type(), TransportType::Framed); +} + TEST(FramedTransportTest, NotEnoughData) { Buffer::OwnedImpl buffer; - StrictMock cb; - FramedTransportImpl transport(cb); + FramedTransportImpl transport; + absl::optional size = 1; - EXPECT_FALSE(transport.decodeFrameStart(buffer)); + EXPECT_FALSE(transport.decodeFrameStart(buffer, size)); + EXPECT_EQ(absl::optional(1), size); addRepeated(buffer, 3, 0); - EXPECT_FALSE(transport.decodeFrameStart(buffer)); + EXPECT_FALSE(transport.decodeFrameStart(buffer, size)); + EXPECT_EQ(absl::optional(1), size); } TEST(FramedTransportTest, InvalidFrameSize) { - StrictMock cb; - FramedTransportImpl transport(cb); + FramedTransportImpl transport; { Buffer::OwnedImpl buffer; addInt32(buffer, -1); - EXPECT_THROW_WITH_MESSAGE(transport.decodeFrameStart(buffer), EnvoyException, + absl::optional size = 1; + EXPECT_THROW_WITH_MESSAGE(transport.decodeFrameStart(buffer, size), EnvoyException, "invalid thrift framed transport frame size -1"); + EXPECT_EQ(absl::optional(1), size); } { Buffer::OwnedImpl buffer; addInt32(buffer, 0x7fffffff); - EXPECT_THROW_WITH_MESSAGE(transport.decodeFrameStart(buffer), EnvoyException, + absl::optional size = 1; + EXPECT_THROW_WITH_MESSAGE(transport.decodeFrameStart(buffer, size), EnvoyException, "invalid thrift framed transport frame size 2147483647"); + EXPECT_EQ(absl::optional(1), size); } } TEST(FramedTransportTest, DecodeFrameStart) { - StrictMock cb; - EXPECT_CALL(cb, transportFrameStart(absl::optional(100U))); - - FramedTransportImpl transport(cb); + FramedTransportImpl transport; Buffer::OwnedImpl buffer; addInt32(buffer, 100); - EXPECT_EQ(buffer.length(), 4); - EXPECT_TRUE(transport.decodeFrameStart(buffer)); + + absl::optional size; + EXPECT_TRUE(transport.decodeFrameStart(buffer, size)); + EXPECT_EQ(absl::optional(100U), size); EXPECT_EQ(buffer.length(), 0); } TEST(FramedTransportTest, DecodeFrameEnd) { - StrictMock cb; - EXPECT_CALL(cb, transportFrameComplete()); - - FramedTransportImpl transport(cb); + FramedTransportImpl transport; Buffer::OwnedImpl buffer; @@ -85,9 +85,7 @@ TEST(FramedTransportTest, DecodeFrameEnd) { } TEST(FramedTransportTest, EncodeFrame) { - StrictMock cb; - - FramedTransportImpl transport(cb); + FramedTransportImpl transport; { Buffer::OwnedImpl message; diff --git a/test/extensions/filters/network/thrift_proxy/filter_integration_test.cc b/test/extensions/filters/network/thrift_proxy/integration_test.cc similarity index 88% rename from test/extensions/filters/network/thrift_proxy/filter_integration_test.cc rename to test/extensions/filters/network/thrift_proxy/integration_test.cc index 8317a11d8a12..fe2b5cafb054 100644 --- a/test/extensions/filters/network/thrift_proxy/filter_integration_test.cc +++ b/test/extensions/filters/network/thrift_proxy/integration_test.cc @@ -29,11 +29,11 @@ enum class CallResult { Exception, }; -class ThriftFilterIntegrationTest +class ThriftConnManagerIntegrationTest : public BaseIntegrationTest, public TestWithParam> { public: - ThriftFilterIntegrationTest() + ThriftConnManagerIntegrationTest() : BaseIntegrationTest(Network::Address::IpVersion::v4, thrift_config) {} static void SetUpTestCase() { @@ -43,10 +43,17 @@ class ThriftFilterIntegrationTest - name: envoy.filters.network.thrift_proxy config: stat_prefix: thrift_stats - - name: envoy.tcp_proxy - config: - stat_prefix: tcp_stats - cluster: cluster_0 + route_config: + name: "routes" + routes: + - match: + method_name: "execute" + route: + cluster: "cluster_0" + - match: + method_name: "poke" + route: + cluster: "cluster_0" )EOF"; } @@ -162,12 +169,12 @@ paramToString(const TestParamInfo>& p } INSTANTIATE_TEST_CASE_P( - TransportAndProtocol, ThriftFilterIntegrationTest, + TransportAndProtocol, ThriftConnManagerIntegrationTest, Combine(Values(TransportNames::get().FRAMED, TransportNames::get().UNFRAMED), Values(ProtocolNames::get().BINARY, ProtocolNames::get().COMPACT), Values(false, true)), paramToString); -TEST_P(ThriftFilterIntegrationTest, Success) { +TEST_P(ThriftConnManagerIntegrationTest, Success) { initializeCall(CallResult::Success); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -176,13 +183,12 @@ TEST_P(ThriftFilterIntegrationTest, Success) { FakeRawConnectionPtr fake_upstream_connection = fake_upstreams_[0]->waitForRawConnection(); Buffer::OwnedImpl upstream_request( fake_upstream_connection->waitForData(request_bytes_.length())); - EXPECT_TRUE(TestUtility::buffersEqual(upstream_request, request_bytes_)); + EXPECT_EQ(request_bytes_.toString(), upstream_request.toString()); fake_upstream_connection->write(response_bytes_.toString()); tcp_client->waitForData(response_bytes_.toString()); tcp_client->close(); - fake_upstream_connection->waitForDisconnect(); EXPECT_TRUE(TestUtility::buffersEqual(Buffer::OwnedImpl(tcp_client->data()), response_bytes_)); @@ -192,7 +198,7 @@ TEST_P(ThriftFilterIntegrationTest, Success) { EXPECT_EQ(1U, counter->value()); } -TEST_P(ThriftFilterIntegrationTest, IDLException) { +TEST_P(ThriftConnManagerIntegrationTest, IDLException) { initializeCall(CallResult::IDLException); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -201,13 +207,12 @@ TEST_P(ThriftFilterIntegrationTest, IDLException) { FakeRawConnectionPtr fake_upstream_connection = fake_upstreams_[0]->waitForRawConnection(); Buffer::OwnedImpl upstream_request( fake_upstream_connection->waitForData(request_bytes_.length())); - EXPECT_TRUE(TestUtility::buffersEqual(upstream_request, request_bytes_)); + EXPECT_EQ(request_bytes_.toString(), upstream_request.toString()); fake_upstream_connection->write(response_bytes_.toString()); tcp_client->waitForData(response_bytes_.toString()); tcp_client->close(); - fake_upstream_connection->waitForDisconnect(); EXPECT_TRUE(TestUtility::buffersEqual(Buffer::OwnedImpl(tcp_client->data()), response_bytes_)); @@ -217,7 +222,7 @@ TEST_P(ThriftFilterIntegrationTest, IDLException) { EXPECT_EQ(1U, counter->value()); } -TEST_P(ThriftFilterIntegrationTest, Exception) { +TEST_P(ThriftConnManagerIntegrationTest, Exception) { initializeCall(CallResult::Exception); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -226,13 +231,12 @@ TEST_P(ThriftFilterIntegrationTest, Exception) { FakeRawConnectionPtr fake_upstream_connection = fake_upstreams_[0]->waitForRawConnection(); Buffer::OwnedImpl upstream_request( fake_upstream_connection->waitForData(request_bytes_.length())); - EXPECT_TRUE(TestUtility::buffersEqual(upstream_request, request_bytes_)); + EXPECT_EQ(request_bytes_.toString(), upstream_request.toString()); fake_upstream_connection->write(response_bytes_.toString()); tcp_client->waitForData(response_bytes_.toString()); tcp_client->close(); - fake_upstream_connection->waitForDisconnect(); EXPECT_TRUE(TestUtility::buffersEqual(Buffer::OwnedImpl(tcp_client->data()), response_bytes_)); @@ -242,7 +246,7 @@ TEST_P(ThriftFilterIntegrationTest, Exception) { EXPECT_EQ(1U, counter->value()); } -TEST_P(ThriftFilterIntegrationTest, Oneway) { +TEST_P(ThriftConnManagerIntegrationTest, Oneway) { initializeOneway(); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); @@ -251,10 +255,9 @@ TEST_P(ThriftFilterIntegrationTest, Oneway) { FakeRawConnectionPtr fake_upstream_connection = fake_upstreams_[0]->waitForRawConnection(); Buffer::OwnedImpl upstream_request( fake_upstream_connection->waitForData(request_bytes_.length())); - EXPECT_TRUE(TestUtility::buffersEqual(upstream_request, request_bytes_)); + EXPECT_EQ(request_bytes_.toString(), upstream_request.toString()); tcp_client->close(); - fake_upstream_connection->waitForDisconnect(); Stats::CounterSharedPtr counter = test_server_->counter("thrift.thrift_stats.request_oneway"); EXPECT_EQ(1U, counter->value()); diff --git a/test/extensions/filters/network/thrift_proxy/mocks.cc b/test/extensions/filters/network/thrift_proxy/mocks.cc index b44d9b95dab5..caa93654233e 100644 --- a/test/extensions/filters/network/thrift_proxy/mocks.cc +++ b/test/extensions/filters/network/thrift_proxy/mocks.cc @@ -2,25 +2,77 @@ #include "gtest/gtest.h" +using testing::Return; using testing::ReturnRef; +using testing::_; namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { -MockTransportCallbacks::MockTransportCallbacks() {} -MockTransportCallbacks::~MockTransportCallbacks() {} +MockConfig::MockConfig() {} +MockConfig::~MockConfig() {} -MockTransport::MockTransport() { ON_CALL(*this, name()).WillByDefault(ReturnRef(name_)); } +MockTransport::MockTransport() { + ON_CALL(*this, name()).WillByDefault(ReturnRef(name_)); + ON_CALL(*this, type()).WillByDefault(Return(type_)); +} MockTransport::~MockTransport() {} -MockProtocolCallbacks::MockProtocolCallbacks() {} -MockProtocolCallbacks::~MockProtocolCallbacks() {} - -MockProtocol::MockProtocol() { ON_CALL(*this, name()).WillByDefault(ReturnRef(name_)); } +MockProtocol::MockProtocol() { + ON_CALL(*this, name()).WillByDefault(ReturnRef(name_)); + ON_CALL(*this, type()).WillByDefault(Return(type_)); +} MockProtocol::~MockProtocol() {} +MockDecoderCallbacks::MockDecoderCallbacks() {} +MockDecoderCallbacks::~MockDecoderCallbacks() {} + +namespace ThriftFilters { + +MockDecoderFilter::MockDecoderFilter() { + ON_CALL(*this, transportBegin(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, transportEnd()).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, messageBegin(_, _, _)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, messageEnd()).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, structBegin(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, structEnd()).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, fieldBegin(_, _, _)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, fieldEnd()).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, boolValue(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, byteValue(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, int16Value(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, int32Value(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, int64Value(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, doubleValue(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, stringValue(_)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, mapBegin(_, _, _)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, mapEnd()).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, listBegin(_, _)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, listEnd()).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, setBegin(_, _)).WillByDefault(Return(FilterStatus::Continue)); + ON_CALL(*this, setEnd()).WillByDefault(Return(FilterStatus::Continue)); +} +MockDecoderFilter::~MockDecoderFilter() {} + +MockDecoderFilterCallbacks::MockDecoderFilterCallbacks() { + ON_CALL(*this, streamId()).WillByDefault(Return(stream_id_)); + ON_CALL(*this, connection()).WillByDefault(Return(&connection_)); +} +MockDecoderFilterCallbacks::~MockDecoderFilterCallbacks() {} + +} // namespace ThriftFilters + +namespace Router { + +MockRouteEntry::MockRouteEntry() {} +MockRouteEntry::~MockRouteEntry() {} + +MockRoute::MockRoute() {} +MockRoute::~MockRoute() {} + +} // namespace Router } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/test/extensions/filters/network/thrift_proxy/mocks.h b/test/extensions/filters/network/thrift_proxy/mocks.h index 1668c42593ef..f932bc808d41 100644 --- a/test/extensions/filters/network/thrift_proxy/mocks.h +++ b/test/extensions/filters/network/thrift_proxy/mocks.h @@ -1,25 +1,33 @@ #pragma once +#include "extensions/filters/network/thrift_proxy/conn_manager.h" +#include "extensions/filters/network/thrift_proxy/filters/filter.h" #include "extensions/filters/network/thrift_proxy/protocol.h" +#include "extensions/filters/network/thrift_proxy/router/router.h" #include "extensions/filters/network/thrift_proxy/transport.h" +#include "test/mocks/network/mocks.h" #include "test/test_common/printers.h" #include "gmock/gmock.h" +using testing::NiceMock; + namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { -class MockTransportCallbacks : public TransportCallbacks { +class MockConfig : public Config { public: - MockTransportCallbacks(); - ~MockTransportCallbacks(); - - // ThriftProxy::TransportCallbacks - MOCK_METHOD1(transportFrameStart, void(absl::optional size)); - MOCK_METHOD0(transportFrameComplete, void()); + MockConfig(); + ~MockConfig(); + + // ThriftProxy::Config + MOCK_METHOD0(filterFactory, ThriftFilters::FilterChainFactory&()); + MOCK_METHOD0(stats, ThriftFilterStats&()); + MOCK_METHOD1(createDecoder, DecoderPtr(DecoderCallbacks&)); + MOCK_METHOD0(routerConfig, Router::Config&()); }; class MockTransport : public Transport { @@ -29,24 +37,13 @@ class MockTransport : public Transport { // ThriftProxy::Transport MOCK_CONST_METHOD0(name, const std::string&()); - MOCK_METHOD1(decodeFrameStart, bool(Buffer::Instance&)); + MOCK_CONST_METHOD0(type, TransportType()); + MOCK_METHOD2(decodeFrameStart, bool(Buffer::Instance&, absl::optional&)); MOCK_METHOD1(decodeFrameEnd, bool(Buffer::Instance&)); MOCK_METHOD2(encodeFrame, void(Buffer::Instance&, Buffer::Instance&)); std::string name_{"mock"}; -}; - -class MockProtocolCallbacks : public ProtocolCallbacks { -public: - MockProtocolCallbacks(); - ~MockProtocolCallbacks(); - - // ThriftProxy::ProtocolCallbacks - MOCK_METHOD3(messageStart, void(const absl::string_view, MessageType, int32_t)); - MOCK_METHOD1(structBegin, void(const absl::string_view)); - MOCK_METHOD3(structField, void(const absl::string_view, FieldType, int16_t)); - MOCK_METHOD0(structEnd, void()); - MOCK_METHOD0(messageComplete, void()); + TransportType type_{TransportType::Auto}; }; class MockProtocol : public Protocol { @@ -56,6 +53,7 @@ class MockProtocol : public Protocol { // ThriftProxy::Protocol MOCK_CONST_METHOD0(name, const std::string&()); + MOCK_CONST_METHOD0(type, ProtocolType()); MOCK_METHOD4(readMessageBegin, bool(Buffer::Instance& buffer, std::string& name, MessageType& msg_type, int32_t& seq_id)); MOCK_METHOD1(readMessageEnd, bool(Buffer::Instance& buffer)); @@ -105,8 +103,100 @@ class MockProtocol : public Protocol { MOCK_METHOD2(writeBinary, void(Buffer::Instance& buffer, const std::string& value)); std::string name_{"mock"}; + ProtocolType type_{ProtocolType::Auto}; +}; + +class MockDecoderCallbacks : public DecoderCallbacks { +public: + MockDecoderCallbacks(); + ~MockDecoderCallbacks(); + + // ThriftProxy::DecoderCallbacks + MOCK_METHOD0(newDecoderFilter, ThriftFilters::DecoderFilter&()); +}; + +namespace ThriftFilters { + +class MockDecoderFilter : public DecoderFilter { +public: + MockDecoderFilter(); + ~MockDecoderFilter(); + + // ThriftProxy::ThriftFilters::DecoderFilter + MOCK_METHOD0(onDestroy, void()); + MOCK_METHOD1(setDecoderFilterCallbacks, void(DecoderFilterCallbacks& callbacks)); + MOCK_METHOD0(resetUpstreamConnection, void()); + MOCK_METHOD1(transportBegin, FilterStatus(absl::optional size)); + MOCK_METHOD0(transportEnd, FilterStatus()); + MOCK_METHOD3(messageBegin, + FilterStatus(const absl::string_view name, MessageType msg_type, int32_t seq_id)); + MOCK_METHOD0(messageEnd, FilterStatus()); + MOCK_METHOD1(structBegin, FilterStatus(const absl::string_view name)); + MOCK_METHOD0(structEnd, FilterStatus()); + MOCK_METHOD3(fieldBegin, + FilterStatus(const absl::string_view name, FieldType msg_type, int16_t field_id)); + MOCK_METHOD0(fieldEnd, FilterStatus()); + MOCK_METHOD1(boolValue, FilterStatus(bool value)); + MOCK_METHOD1(byteValue, FilterStatus(uint8_t value)); + MOCK_METHOD1(int16Value, FilterStatus(int16_t value)); + MOCK_METHOD1(int32Value, FilterStatus(int32_t value)); + MOCK_METHOD1(int64Value, FilterStatus(int64_t value)); + MOCK_METHOD1(doubleValue, FilterStatus(double value)); + MOCK_METHOD1(stringValue, FilterStatus(absl::string_view value)); + MOCK_METHOD3(mapBegin, FilterStatus(FieldType key_type, FieldType value_type, uint32_t size)); + MOCK_METHOD0(mapEnd, FilterStatus()); + MOCK_METHOD2(listBegin, FilterStatus(FieldType elem_type, uint32_t size)); + MOCK_METHOD0(listEnd, FilterStatus()); + MOCK_METHOD2(setBegin, FilterStatus(FieldType elem_type, uint32_t size)); + MOCK_METHOD0(setEnd, FilterStatus()); +}; + +class MockDecoderFilterCallbacks : public DecoderFilterCallbacks { +public: + MockDecoderFilterCallbacks(); + ~MockDecoderFilterCallbacks(); + + // ThriftProxy::ThriftFilters::DecoderFilterCallbacks + MOCK_CONST_METHOD0(streamId, uint64_t()); + MOCK_CONST_METHOD0(connection, const Network::Connection*()); + MOCK_METHOD0(continueDecoding, void()); + MOCK_METHOD0(route, Router::RouteConstSharedPtr()); + MOCK_CONST_METHOD0(downstreamTransportType, TransportType()); + MOCK_CONST_METHOD0(downstreamProtocolType, ProtocolType()); + void sendLocalReply(DirectResponsePtr&& response) override { sendLocalReply_(response); } + MOCK_METHOD2(startUpstreamResponse, void(TransportType, ProtocolType)); + MOCK_METHOD1(upstreamData, bool(Buffer::Instance&)); + MOCK_METHOD0(resetDownstreamConnection, void()); + + MOCK_METHOD1(sendLocalReply_, void(DirectResponsePtr&)); + + uint64_t stream_id_{1}; + NiceMock connection_; +}; + +} // namespace ThriftFilters + +namespace Router { + +class MockRouteEntry : public RouteEntry { +public: + MockRouteEntry(); + ~MockRouteEntry(); + + // ThriftProxy::Router::RouteEntry + MOCK_CONST_METHOD0(clusterName, const std::string&()); +}; + +class MockRoute : public Route { +public: + MockRoute(); + ~MockRoute(); + + // ThriftProxy::Router::Route + MOCK_CONST_METHOD0(routeEntry, const RouteEntry*()); }; +} // namespace Router } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/test/extensions/filters/network/thrift_proxy/protocol_impl_test.cc b/test/extensions/filters/network/thrift_proxy/protocol_impl_test.cc index 58420a049262..7a8fef74a149 100644 --- a/test/extensions/filters/network/thrift_proxy/protocol_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/protocol_impl_test.cc @@ -24,10 +24,16 @@ namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { +TEST(ProtocolNames, FromType) { + for (int i = 0; i <= static_cast(ProtocolType::LastProtocolType); i++) { + ProtocolType type = static_cast(i); + EXPECT_NE("", ProtocolNames::get().fromType(type)); + } +} + TEST(AutoProtocolTest, NotEnoughData) { Buffer::OwnedImpl buffer; - NiceMock cb; - AutoProtocolImpl proto(cb); + AutoProtocolImpl proto; std::string name = "-"; MessageType msg_type = MessageType::Oneway; int32_t seq_id = -1; @@ -41,8 +47,7 @@ TEST(AutoProtocolTest, NotEnoughData) { TEST(AutoProtocolTest, UnknownProtocol) { Buffer::OwnedImpl buffer; - NiceMock cb; - AutoProtocolImpl proto(cb); + AutoProtocolImpl proto; std::string name = "-"; MessageType msg_type = MessageType::Oneway; int32_t seq_id = -1; @@ -59,8 +64,7 @@ TEST(AutoProtocolTest, UnknownProtocol) { TEST(AutoProtocolTest, ReadMessageBegin) { // Binary Protocol { - NiceMock cb; - AutoProtocolImpl proto(cb); + AutoProtocolImpl proto; std::string name = "-"; MessageType msg_type = MessageType::Oneway; int32_t seq_id = -1; @@ -79,12 +83,12 @@ TEST(AutoProtocolTest, ReadMessageBegin) { EXPECT_EQ(seq_id, 1); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(proto.name(), "binary(auto)"); + EXPECT_EQ(proto.type(), ProtocolType::Binary); } // Compact protocol { - NiceMock cb; - AutoProtocolImpl proto(cb); + AutoProtocolImpl proto; std::string name = "-"; MessageType msg_type = MessageType::Oneway; int32_t seq_id = 1; @@ -101,13 +105,13 @@ TEST(AutoProtocolTest, ReadMessageBegin) { EXPECT_EQ(seq_id, 0x0102); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(proto.name(), "compact(auto)"); + EXPECT_EQ(proto.type(), ProtocolType::Compact); } } TEST(AutoProtocolTest, ReadDelegation) { NiceMock* proto = new NiceMock(); - NiceMock dummy_cb; - AutoProtocolImpl auto_proto(dummy_cb); + AutoProtocolImpl auto_proto; auto_proto.setProtocol(ProtocolPtr{proto}); // readMessageBegin @@ -232,8 +236,7 @@ TEST(AutoProtocolTest, ReadDelegation) { TEST(AutoProtocolTest, WriteDelegation) { NiceMock* proto = new NiceMock(); - NiceMock dummy_cb; - AutoProtocolImpl auto_proto(dummy_cb); + AutoProtocolImpl auto_proto; auto_proto.setProtocol(ProtocolPtr{proto}); // writeMessageBegin @@ -319,11 +322,15 @@ TEST(AutoProtocolTest, WriteDelegation) { } TEST(AutoProtocolTest, Name) { - NiceMock cb; - AutoProtocolImpl proto(cb); + AutoProtocolImpl proto; EXPECT_EQ(proto.name(), "auto"); } +TEST(AutoProtocolTest, Type) { + AutoProtocolImpl proto; + EXPECT_EQ(proto.type(), ProtocolType::Auto); +} + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/test/extensions/filters/network/thrift_proxy/router_test.cc b/test/extensions/filters/network/thrift_proxy/router_test.cc new file mode 100644 index 000000000000..9b623f37df2d --- /dev/null +++ b/test/extensions/filters/network/thrift_proxy/router_test.cc @@ -0,0 +1,631 @@ +#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.pb.h" +#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.pb.validate.h" +#include "envoy/tcp/conn_pool.h" + +#include "common/buffer/buffer_impl.h" + +#include "extensions/filters/network/thrift_proxy/app_exception_impl.h" +#include "extensions/filters/network/thrift_proxy/router/config.h" +#include "extensions/filters/network/thrift_proxy/router/router_impl.h" + +#include "test/extensions/filters/network/thrift_proxy/mocks.h" +#include "test/extensions/filters/network/thrift_proxy/utility.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/mocks/upstream/mocks.h" +#include "test/test_common/printers.h" +#include "test/test_common/registry.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::ContainsRegex; +using testing::Invoke; +using testing::NiceMock; +using testing::Ref; +using testing::Return; +using testing::ReturnRef; +using testing::Test; +using testing::TestWithParam; +using testing::Values; +using testing::_; + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace ThriftProxy { +namespace Router { + +namespace { + +envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration +parseRouteConfigurationFromV2Yaml(const std::string& yaml) { + envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration route_config; + MessageUtil::loadFromYaml(yaml, route_config); + MessageUtil::validate(route_config); + return route_config; +} + +class TestNamedTransportConfigFactory : public NamedTransportConfigFactory { +public: + TestNamedTransportConfigFactory(std::function f) : f_(f) {} + + TransportPtr createTransport() override { return TransportPtr{f_()}; } + std::string name() override { return TransportNames::get().FRAMED; } + + std::function f_; +}; + +class TestNamedProtocolConfigFactory : public NamedProtocolConfigFactory { +public: + TestNamedProtocolConfigFactory(std::function f) : f_(f) {} + + ProtocolPtr createProtocol() override { return ProtocolPtr{f_()}; } + std::string name() override { return ProtocolNames::get().BINARY; } + + std::function f_; +}; + +} // namespace + +class ThriftRouterTestBase { +public: + ThriftRouterTestBase() + : transport_factory_([&]() -> MockTransport* { return transport_; }), + protocol_factory_([&]() -> MockProtocol* { return protocol_; }), + transport_register_(transport_factory_), protocol_register_(protocol_factory_) {} + + void initializeRouter() { + route_ = new NiceMock(); + route_ptr_.reset(route_); + + host_ = new NiceMock(); + host_ptr_.reset(host_); + + router_.reset(new Router(context_.clusterManager())); + + EXPECT_EQ(nullptr, router_->downstreamConnection()); + + router_->setDecoderFilterCallbacks(callbacks_); + } + + void startRequest(MessageType msg_type) { + msg_type_ = msg_type; + + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->transportBegin({})); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + + EXPECT_CALL(context_.cluster_manager_.tcp_conn_pool_, newConnection(_)) + .WillOnce( + Invoke([&](Tcp::ConnectionPool::Callbacks& cb) -> Tcp::ConnectionPool::Cancellable* { + conn_pool_callbacks_ = &cb; + return &handle_; + })); + + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, + router_->messageBegin(method_name_, msg_type_, seq_id_)); + EXPECT_NE(nullptr, conn_pool_callbacks_); + + NiceMock connection; + EXPECT_CALL(callbacks_, connection()).WillRepeatedly(Return(&connection)); + EXPECT_EQ(&connection, router_->downstreamConnection()); + + // Not yet implemented: + EXPECT_EQ(absl::optional(), router_->computeHashKey()); + EXPECT_EQ(nullptr, router_->metadataMatchCriteria()); + EXPECT_EQ(nullptr, router_->downstreamHeaders()); + } + + void connectUpstream() { + EXPECT_CALL(conn_data_, addUpstreamCallbacks(_)) + .WillOnce(Invoke([&](Tcp::ConnectionPool::UpstreamCallbacks& cb) -> void { + upstream_callbacks_ = &cb; + })); + + EXPECT_CALL(callbacks_, downstreamTransportType()).WillOnce(Return(TransportType::Framed)); + transport_ = new NiceMock(); + ON_CALL(*transport_, type()).WillByDefault(Return(TransportType::Framed)); + + EXPECT_CALL(callbacks_, downstreamProtocolType()).WillOnce(Return(ProtocolType::Binary)); + protocol_ = new NiceMock(); + ON_CALL(*protocol_, type()).WillByDefault(Return(ProtocolType::Binary)); + EXPECT_CALL(*protocol_, writeMessageBegin(_, method_name_, msg_type_, seq_id_)); + + EXPECT_CALL(callbacks_, continueDecoding()); + conn_pool_callbacks_->onPoolReady(conn_data_, host_ptr_); + EXPECT_NE(nullptr, upstream_callbacks_); + } + + void sendTrivialStruct(FieldType field_type) { + EXPECT_CALL(*protocol_, writeStructBegin(_, "")); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->structBegin({})); + + EXPECT_CALL(*protocol_, writeFieldBegin(_, "", field_type, 1)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->fieldBegin({}, field_type, 1)); + + sendTrivialValue(field_type); + + EXPECT_CALL(*protocol_, writeFieldEnd(_)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->fieldEnd()); + + EXPECT_CALL(*protocol_, writeFieldBegin(_, "", FieldType::Stop, 0)); + EXPECT_CALL(*protocol_, writeStructEnd(_)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->structEnd()); + } + + void sendTrivialValue(FieldType field_type) { + switch (field_type) { + case FieldType::Bool: + EXPECT_CALL(*protocol_, writeBool(_, true)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->boolValue(true)); + break; + case FieldType::Byte: + EXPECT_CALL(*protocol_, writeByte(_, 2)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->byteValue(2)); + break; + case FieldType::I16: + EXPECT_CALL(*protocol_, writeInt16(_, 3)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->int16Value(3)); + break; + case FieldType::I32: + EXPECT_CALL(*protocol_, writeInt32(_, 4)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->int32Value(4)); + break; + case FieldType::I64: + EXPECT_CALL(*protocol_, writeInt64(_, 5)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->int64Value(5)); + break; + case FieldType::Double: + EXPECT_CALL(*protocol_, writeDouble(_, 6.0)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->doubleValue(6.0)); + break; + case FieldType::String: + EXPECT_CALL(*protocol_, writeString(_, "seven")); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->stringValue("seven")); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + } + + void completeRequest() { + EXPECT_CALL(*protocol_, writeMessageEnd(_)); + EXPECT_CALL(*transport_, encodeFrame(_, _)); + EXPECT_CALL(conn_data_.connection_, write(_, false)); + + if (msg_type_ == MessageType::Oneway) { + EXPECT_CALL(conn_data_, release()); + } + + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->messageEnd()); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->transportEnd()); + } + + void returnResponse() { + Buffer::OwnedImpl buffer; + + EXPECT_CALL(callbacks_, startUpstreamResponse(TransportType::Framed, ProtocolType::Binary)); + + EXPECT_CALL(callbacks_, upstreamData(Ref(buffer))).WillOnce(Return(false)); + upstream_callbacks_->onUpstreamData(buffer, false); + + EXPECT_CALL(callbacks_, upstreamData(Ref(buffer))).WillOnce(Return(true)); + EXPECT_CALL(conn_data_, release()); + upstream_callbacks_->onUpstreamData(buffer, false); + } + + void destroyRouter() { + router_->onDestroy(); + router_.reset(); + } + + TestNamedTransportConfigFactory transport_factory_; + TestNamedProtocolConfigFactory protocol_factory_; + Registry::InjectFactory transport_register_; + Registry::InjectFactory protocol_register_; + + NiceMock context_; + NiceMock callbacks_; + NiceMock* transport_{}; + NiceMock* protocol_{}; + NiceMock* route_{}; + NiceMock route_entry_; + NiceMock* host_{}; + + RouteConstSharedPtr route_ptr_; + Upstream::HostDescriptionConstSharedPtr host_ptr_; + + std::unique_ptr router_; + + std::string cluster_name_{"cluster"}; + + std::string method_name_{"method"}; + MessageType msg_type_{MessageType::Call}; + int32_t seq_id_{1}; + + NiceMock handle_; + NiceMock conn_data_; + Tcp::ConnectionPool::Callbacks* conn_pool_callbacks_{}; + Tcp::ConnectionPool::UpstreamCallbacks* upstream_callbacks_{}; +}; + +class ThriftRouterTest : public ThriftRouterTestBase, public Test { +public: + ThriftRouterTest() {} +}; + +class ThriftRouterFieldTypeTest : public ThriftRouterTestBase, public TestWithParam { +public: + ThriftRouterFieldTypeTest() {} +}; + +INSTANTIATE_TEST_CASE_P(PrimitiveFieldTypes, ThriftRouterFieldTypeTest, + Values(FieldType::Bool, FieldType::Byte, FieldType::I16, FieldType::I32, + FieldType::I64, FieldType::Double, FieldType::String), + fieldTypeParamToString); + +class ThriftRouterContainerTest : public ThriftRouterTestBase, public TestWithParam { +public: + ThriftRouterContainerTest() {} +}; + +INSTANTIATE_TEST_CASE_P(ContainerFieldTypes, ThriftRouterContainerTest, + Values(FieldType::Map, FieldType::List, FieldType::Set), + fieldTypeParamToString); + +TEST_F(ThriftRouterTest, PoolRemoteConnectionFailure) { + initializeRouter(); + + startRequest(MessageType::Call); + + EXPECT_CALL(callbacks_, sendLocalReply_(_)) + .WillOnce(Invoke([&](ThriftFilters::DirectResponsePtr& response) -> void { + auto* app_ex = dynamic_cast(response.get()); + EXPECT_NE(nullptr, app_ex); + EXPECT_EQ(method_name_, app_ex->method_name_); + EXPECT_EQ(seq_id_, app_ex->seq_id_); + EXPECT_EQ(AppExceptionType::InternalError, app_ex->type_); + EXPECT_THAT(app_ex->error_message_, ContainsRegex(".*connection failure.*")); + })); + conn_pool_callbacks_->onPoolFailure( + Tcp::ConnectionPool::PoolFailureReason::RemoteConnectionFailure, host_ptr_); +} + +TEST_F(ThriftRouterTest, PoolLocalConnectionFailure) { + initializeRouter(); + + startRequest(MessageType::Call); + + EXPECT_CALL(callbacks_, sendLocalReply_(_)) + .WillOnce(Invoke([&](ThriftFilters::DirectResponsePtr& response) -> void { + auto* app_ex = dynamic_cast(response.get()); + EXPECT_NE(nullptr, app_ex); + EXPECT_EQ(method_name_, app_ex->method_name_); + EXPECT_EQ(seq_id_, app_ex->seq_id_); + EXPECT_EQ(AppExceptionType::InternalError, app_ex->type_); + EXPECT_THAT(app_ex->error_message_, ContainsRegex(".*connection failure.*")); + })); + conn_pool_callbacks_->onPoolFailure( + Tcp::ConnectionPool::PoolFailureReason::LocalConnectionFailure, host_ptr_); +} + +TEST_F(ThriftRouterTest, PoolTimeout) { + initializeRouter(); + + startRequest(MessageType::Call); + + EXPECT_CALL(callbacks_, sendLocalReply_(_)) + .WillOnce(Invoke([&](ThriftFilters::DirectResponsePtr& response) -> void { + auto* app_ex = dynamic_cast(response.get()); + EXPECT_NE(nullptr, app_ex); + EXPECT_EQ(method_name_, app_ex->method_name_); + EXPECT_EQ(seq_id_, app_ex->seq_id_); + EXPECT_EQ(AppExceptionType::InternalError, app_ex->type_); + EXPECT_THAT(app_ex->error_message_, ContainsRegex(".*connection failure.*")); + })); + conn_pool_callbacks_->onPoolFailure(Tcp::ConnectionPool::PoolFailureReason::Timeout, host_ptr_); +} + +TEST_F(ThriftRouterTest, PoolOverflowFailure) { + initializeRouter(); + + startRequest(MessageType::Call); + + EXPECT_CALL(callbacks_, sendLocalReply_(_)) + .WillOnce(Invoke([&](ThriftFilters::DirectResponsePtr& response) -> void { + auto* app_ex = dynamic_cast(response.get()); + EXPECT_NE(nullptr, app_ex); + EXPECT_EQ(method_name_, app_ex->method_name_); + EXPECT_EQ(seq_id_, app_ex->seq_id_); + EXPECT_EQ(AppExceptionType::InternalError, app_ex->type_); + EXPECT_THAT(app_ex->error_message_, ContainsRegex(".*too many connections.*")); + })); + conn_pool_callbacks_->onPoolFailure(Tcp::ConnectionPool::PoolFailureReason::Overflow, host_ptr_); +} + +TEST_F(ThriftRouterTest, NoRoute) { + initializeRouter(); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(nullptr)); + EXPECT_CALL(callbacks_, sendLocalReply_(_)) + .WillOnce(Invoke([&](ThriftFilters::DirectResponsePtr& response) -> void { + auto* app_ex = dynamic_cast(response.get()); + EXPECT_NE(nullptr, app_ex); + if (app_ex != nullptr) { + EXPECT_EQ(method_name_, app_ex->method_name_); + EXPECT_EQ(seq_id_, app_ex->seq_id_); + EXPECT_EQ(AppExceptionType::UnknownMethod, app_ex->type_); + EXPECT_THAT(app_ex->error_message_, ContainsRegex(".*no route.*")); + } + })); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, + router_->messageBegin(method_name_, MessageType::Call, seq_id_)); +} + +TEST_F(ThriftRouterTest, NoCluster) { + initializeRouter(); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + EXPECT_CALL(context_.cluster_manager_, get(cluster_name_)).WillOnce(Return(nullptr)); + EXPECT_CALL(callbacks_, sendLocalReply_(_)) + .WillOnce(Invoke([&](ThriftFilters::DirectResponsePtr& response) -> void { + auto* app_ex = dynamic_cast(response.get()); + EXPECT_NE(nullptr, app_ex); + EXPECT_EQ(method_name_, app_ex->method_name_); + EXPECT_EQ(seq_id_, app_ex->seq_id_); + EXPECT_EQ(AppExceptionType::InternalError, app_ex->type_); + EXPECT_THAT(app_ex->error_message_, ContainsRegex(".*unknown cluster.*")); + })); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, + router_->messageBegin(method_name_, MessageType::Call, seq_id_)); +} + +TEST_F(ThriftRouterTest, ClusterMaintenanceMode) { + initializeRouter(); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + EXPECT_CALL(*context_.cluster_manager_.thread_local_cluster_.cluster_.info_, maintenanceMode()) + .WillOnce(Return(true)); + + EXPECT_CALL(callbacks_, sendLocalReply_(_)) + .WillOnce(Invoke([&](ThriftFilters::DirectResponsePtr& response) -> void { + auto* app_ex = dynamic_cast(response.get()); + EXPECT_NE(nullptr, app_ex); + EXPECT_EQ(method_name_, app_ex->method_name_); + EXPECT_EQ(seq_id_, app_ex->seq_id_); + EXPECT_EQ(AppExceptionType::InternalError, app_ex->type_); + EXPECT_THAT(app_ex->error_message_, ContainsRegex(".*maintenance mode.*")); + })); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, + router_->messageBegin(method_name_, MessageType::Call, seq_id_)); +} + +TEST_F(ThriftRouterTest, NoHealthyHosts) { + initializeRouter(); + + EXPECT_CALL(callbacks_, route()).WillOnce(Return(route_ptr_)); + EXPECT_CALL(*route_, routeEntry()).WillOnce(Return(&route_entry_)); + EXPECT_CALL(route_entry_, clusterName()).WillRepeatedly(ReturnRef(cluster_name_)); + EXPECT_CALL(context_.cluster_manager_, tcpConnPoolForCluster(cluster_name_, _, _)) + .WillOnce(Return(nullptr)); + + EXPECT_CALL(callbacks_, sendLocalReply_(_)) + .WillOnce(Invoke([&](ThriftFilters::DirectResponsePtr& response) -> void { + auto* app_ex = dynamic_cast(response.get()); + EXPECT_NE(nullptr, app_ex); + EXPECT_EQ(method_name_, app_ex->method_name_); + EXPECT_EQ(seq_id_, app_ex->seq_id_); + EXPECT_EQ(AppExceptionType::InternalError, app_ex->type_); + EXPECT_THAT(app_ex->error_message_, ContainsRegex(".*no healthy upstream.*")); + })); + EXPECT_EQ(ThriftFilters::FilterStatus::StopIteration, + router_->messageBegin(method_name_, MessageType::Call, seq_id_)); +} + +TEST_F(ThriftRouterTest, TruncatedResponse) { + initializeRouter(); + startRequest(MessageType::Call); + connectUpstream(); + sendTrivialStruct(FieldType::String); + completeRequest(); + + Buffer::OwnedImpl buffer; + + EXPECT_CALL(callbacks_, startUpstreamResponse(TransportType::Framed, ProtocolType::Binary)); + EXPECT_CALL(callbacks_, upstreamData(Ref(buffer))).WillOnce(Return(false)); + EXPECT_CALL(conn_data_, release()); + EXPECT_CALL(callbacks_, resetDownstreamConnection()); + + upstream_callbacks_->onUpstreamData(buffer, true); + destroyRouter(); +} + +TEST_F(ThriftRouterTest, UpstreamDataTriggersReset) { + initializeRouter(); + startRequest(MessageType::Call); + connectUpstream(); + sendTrivialStruct(FieldType::String); + completeRequest(); + + Buffer::OwnedImpl buffer; + + EXPECT_CALL(callbacks_, startUpstreamResponse(TransportType::Framed, ProtocolType::Binary)); + EXPECT_CALL(callbacks_, upstreamData(Ref(buffer))) + .WillOnce(Invoke([&](Buffer::Instance&) -> bool { + router_->resetUpstreamConnection(); + return true; + })); + EXPECT_CALL(conn_data_.connection_, close(Network::ConnectionCloseType::NoFlush)); + + upstream_callbacks_->onUpstreamData(buffer, true); + destroyRouter(); +} + +TEST_F(ThriftRouterTest, UnexpectedRouterDestroyBeforeUpstreamConnect) { + initializeRouter(); + startRequest(MessageType::Call); + destroyRouter(); +} + +TEST_F(ThriftRouterTest, UnexpectedRouterDestroy) { + initializeRouter(); + startRequest(MessageType::Call); + connectUpstream(); + EXPECT_CALL(conn_data_.connection_, close(Network::ConnectionCloseType::NoFlush)); + destroyRouter(); +} + +TEST_P(ThriftRouterFieldTypeTest, OneWay) { + FieldType field_type = GetParam(); + + initializeRouter(); + startRequest(MessageType::Oneway); + connectUpstream(); + sendTrivialStruct(field_type); + completeRequest(); + destroyRouter(); +} + +TEST_P(ThriftRouterFieldTypeTest, Call) { + FieldType field_type = GetParam(); + + initializeRouter(); + startRequest(MessageType::Call); + connectUpstream(); + sendTrivialStruct(field_type); + completeRequest(); + returnResponse(); + destroyRouter(); +} + +TEST_P(ThriftRouterContainerTest, DecoderFilterCallbacks) { + FieldType field_type = GetParam(); + + initializeRouter(); + + startRequest(MessageType::Oneway); + connectUpstream(); + + EXPECT_CALL(*protocol_, writeStructBegin(_, "")); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->structBegin({})); + + EXPECT_CALL(*protocol_, writeFieldBegin(_, "", field_type, 1)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->fieldBegin({}, field_type, 1)); + + switch (field_type) { + case FieldType::Map: + EXPECT_CALL(*protocol_, writeMapBegin(_, FieldType::I32, FieldType::I32, 2)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, + router_->mapBegin(FieldType::I32, FieldType::I32, 2)); + for (int i = 0; i < 2; i++) { + EXPECT_CALL(*protocol_, writeInt32(_, i)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->int32Value(i)); + EXPECT_CALL(*protocol_, writeInt32(_, i + 100)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->int32Value(i + 100)); + } + EXPECT_CALL(*protocol_, writeMapEnd(_)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->mapEnd()); + break; + case FieldType::List: + EXPECT_CALL(*protocol_, writeListBegin(_, FieldType::I32, 3)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->listBegin(FieldType::I32, 3)); + for (int i = 0; i < 3; i++) { + EXPECT_CALL(*protocol_, writeInt32(_, i)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->int32Value(i)); + } + EXPECT_CALL(*protocol_, writeListEnd(_)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->listEnd()); + break; + case FieldType::Set: + EXPECT_CALL(*protocol_, writeSetBegin(_, FieldType::I32, 4)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->setBegin(FieldType::I32, 4)); + for (int i = 0; i < 4; i++) { + EXPECT_CALL(*protocol_, writeInt32(_, i)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->int32Value(i)); + } + EXPECT_CALL(*protocol_, writeSetEnd(_)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->setEnd()); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + + EXPECT_CALL(*protocol_, writeFieldEnd(_)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->fieldEnd()); + + EXPECT_CALL(*protocol_, writeFieldBegin(_, _, FieldType::Stop, 0)); + EXPECT_CALL(*protocol_, writeStructEnd(_)); + EXPECT_EQ(ThriftFilters::FilterStatus::Continue, router_->structEnd()); + + completeRequest(); + destroyRouter(); +} + +TEST(RouteMatcherTest, Route) { + const std::string yaml = R"EOF( +name: config +routes: + - match: + method: "method1" + route: + cluster: "cluster1" + - match: + method: "method2" + route: + cluster: "cluster2" +)EOF"; + + envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + + RouteMatcher matcher(config); + EXPECT_EQ(nullptr, matcher.route("unknown")); + EXPECT_EQ(nullptr, matcher.route("METHOD1")); + + RouteConstSharedPtr route = matcher.route("method1"); + EXPECT_NE(nullptr, route); + EXPECT_EQ("cluster1", route->routeEntry()->clusterName()); + + RouteConstSharedPtr route2 = matcher.route("method2"); + EXPECT_NE(nullptr, route2); + EXPECT_EQ("cluster2", route2->routeEntry()->clusterName()); +} + +TEST(RouteMatcherTest, RouteMatchAny) { + const std::string yaml = R"EOF( +name: config +routes: + - match: + method: "method1" + route: + cluster: "cluster1" + - match: {} + route: + cluster: "cluster2" +)EOF"; + + envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration config = + parseRouteConfigurationFromV2Yaml(yaml); + + RouteMatcher matcher(config); + RouteConstSharedPtr route = matcher.route("method1"); + EXPECT_NE(nullptr, route); + EXPECT_EQ("cluster1", route->routeEntry()->clusterName()); + + RouteConstSharedPtr route2 = matcher.route("anything"); + EXPECT_NE(nullptr, route2); + EXPECT_EQ("cluster2", route2->routeEntry()->clusterName()); +} + +} // namespace Router +} // namespace ThriftProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/network/thrift_proxy/transport_impl_test.cc b/test/extensions/filters/network/thrift_proxy/transport_impl_test.cc index ea6d4e1e1656..64ab3cce319b 100644 --- a/test/extensions/filters/network/thrift_proxy/transport_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/transport_impl_test.cc @@ -14,28 +14,35 @@ using testing::NiceMock; using testing::Ref; -using testing::StrictMock; namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { +TEST(TransportNames, FromType) { + for (int i = 0; i <= static_cast(TransportType::LastTransportType); i++) { + TransportType type = static_cast(i); + EXPECT_NE("", TransportNames::get().fromType(type)); + } +} + TEST(AutoTransportTest, NotEnoughData) { Buffer::OwnedImpl buffer; - StrictMock cb; - AutoTransportImpl transport(cb); + AutoTransportImpl transport; + absl::optional size = 100; - EXPECT_FALSE(transport.decodeFrameStart(buffer)); + EXPECT_FALSE(transport.decodeFrameStart(buffer, size)); + EXPECT_EQ(absl::optional(100), size); addRepeated(buffer, 7, 0); - EXPECT_FALSE(transport.decodeFrameStart(buffer)); + EXPECT_FALSE(transport.decodeFrameStart(buffer, size)); + EXPECT_EQ(absl::optional(100), size); } TEST(AutoTransportTest, UnknownTransport) { - StrictMock cb; - AutoTransportImpl transport(cb); + AutoTransportImpl transport; // Looks like unframed, but fails protocol check. { @@ -43,8 +50,10 @@ TEST(AutoTransportTest, UnknownTransport) { addInt32(buffer, 0); addInt32(buffer, 0); - EXPECT_THROW_WITH_MESSAGE(transport.decodeFrameStart(buffer), EnvoyException, + absl::optional size = 100; + EXPECT_THROW_WITH_MESSAGE(transport.decodeFrameStart(buffer, size), EnvoyException, "unknown thrift auto transport frame start 00 00 00 00 00 00 00 00"); + EXPECT_EQ(absl::optional(100), size); } // Looks like framed, but fails protocol check. @@ -53,91 +62,95 @@ TEST(AutoTransportTest, UnknownTransport) { addInt32(buffer, 0xFF); addInt32(buffer, 0); - EXPECT_THROW_WITH_MESSAGE(transport.decodeFrameStart(buffer), EnvoyException, + absl::optional size = 100; + EXPECT_THROW_WITH_MESSAGE(transport.decodeFrameStart(buffer, size), EnvoyException, "unknown thrift auto transport frame start 00 00 00 ff 00 00 00 00"); + EXPECT_EQ(absl::optional(100), size); } } TEST(AutoTransportTest, DecodeFrameStart) { - StrictMock cb; - // Framed transport + binary protocol { - AutoTransportImpl transport(cb); + AutoTransportImpl transport; Buffer::OwnedImpl buffer; addInt32(buffer, 0xFF); addInt16(buffer, 0x8001); addInt16(buffer, 0); - EXPECT_CALL(cb, transportFrameStart(absl::optional(255U))); - EXPECT_TRUE(transport.decodeFrameStart(buffer)); + absl::optional size; + EXPECT_TRUE(transport.decodeFrameStart(buffer, size)); + EXPECT_EQ(absl::optional(255), size); EXPECT_EQ(transport.name(), "framed(auto)"); + EXPECT_EQ(transport.type(), TransportType::Framed); EXPECT_EQ(buffer.length(), 4); } // Framed transport + compact protocol { - AutoTransportImpl transport(cb); + AutoTransportImpl transport; Buffer::OwnedImpl buffer; addInt32(buffer, 0xFFF); addInt16(buffer, 0x8201); addInt16(buffer, 0); - EXPECT_CALL(cb, transportFrameStart(absl::optional(4095U))); - EXPECT_TRUE(transport.decodeFrameStart(buffer)); + absl::optional size; + EXPECT_TRUE(transport.decodeFrameStart(buffer, size)); + EXPECT_EQ(absl::optional(4095), size); EXPECT_EQ(transport.name(), "framed(auto)"); + EXPECT_EQ(transport.type(), TransportType::Framed); EXPECT_EQ(buffer.length(), 4); } // Unframed transport + binary protocol { - AutoTransportImpl transport(cb); + AutoTransportImpl transport; Buffer::OwnedImpl buffer; addInt16(buffer, 0x8001); addRepeated(buffer, 6, 0); - EXPECT_CALL(cb, transportFrameStart(absl::optional())); - EXPECT_TRUE(transport.decodeFrameStart(buffer)); + absl::optional size = 1; + EXPECT_TRUE(transport.decodeFrameStart(buffer, size)); + EXPECT_FALSE(size.has_value()); EXPECT_EQ(transport.name(), "unframed(auto)"); + EXPECT_EQ(transport.type(), TransportType::Unframed); EXPECT_EQ(buffer.length(), 8); } // Unframed transport + compact protocol { - AutoTransportImpl transport(cb); + AutoTransportImpl transport; Buffer::OwnedImpl buffer; addInt16(buffer, 0x8201); addRepeated(buffer, 6, 0); - EXPECT_CALL(cb, transportFrameStart(absl::optional())); - EXPECT_TRUE(transport.decodeFrameStart(buffer)); + absl::optional size = 1; + EXPECT_TRUE(transport.decodeFrameStart(buffer, size)); + EXPECT_FALSE(size.has_value()); EXPECT_EQ(transport.name(), "unframed(auto)"); + EXPECT_EQ(transport.type(), TransportType::Unframed); EXPECT_EQ(buffer.length(), 8); } } TEST(AutoTransportTest, DecodeFrameEnd) { - StrictMock cb; - - AutoTransportImpl transport(cb); + AutoTransportImpl transport; Buffer::OwnedImpl buffer; addInt32(buffer, 0xFF); addInt16(buffer, 0x8001); addInt16(buffer, 0); - EXPECT_CALL(cb, transportFrameStart(absl::optional(255U))); - EXPECT_TRUE(transport.decodeFrameStart(buffer)); + absl::optional size; + EXPECT_TRUE(transport.decodeFrameStart(buffer, size)); EXPECT_EQ(buffer.length(), 4); - EXPECT_CALL(cb, transportFrameComplete()); EXPECT_TRUE(transport.decodeFrameEnd(buffer)); } TEST(AutoTransportTest, EncodeFrame) { - StrictMock cb; MockTransport* mock_transport = new NiceMock(); - AutoTransportImpl transport(cb); + AutoTransportImpl transport; transport.setTransport(TransportPtr{mock_transport}); Buffer::OwnedImpl buffer; @@ -148,11 +161,15 @@ TEST(AutoTransportTest, EncodeFrame) { } TEST(AutoTransportTest, Name) { - StrictMock cb; - AutoTransportImpl transport(cb); + AutoTransportImpl transport; EXPECT_EQ(transport.name(), "auto"); } +TEST(AutoTransportTest, Type) { + AutoTransportImpl transport; + EXPECT_EQ(transport.type(), TransportType::Auto); +} + } // namespace ThriftProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/test/extensions/filters/network/thrift_proxy/unframed_transport_impl_test.cc b/test/extensions/filters/network/thrift_proxy/unframed_transport_impl_test.cc index f26a75219ab6..f83119ffaf38 100644 --- a/test/extensions/filters/network/thrift_proxy/unframed_transport_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/unframed_transport_impl_test.cc @@ -2,55 +2,49 @@ #include "extensions/filters/network/thrift_proxy/unframed_transport_impl.h" -#include "test/extensions/filters/network/thrift_proxy/mocks.h" #include "test/extensions/filters/network/thrift_proxy/utility.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" -#include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::StrictMock; - namespace Envoy { namespace Extensions { namespace NetworkFilters { namespace ThriftProxy { TEST(UnframedTransportTest, Name) { - StrictMock cb; - UnframedTransportImpl transport(cb); + UnframedTransportImpl transport; EXPECT_EQ(transport.name(), "unframed"); } -TEST(UnframedTransportTest, DecodeFrameStart) { - StrictMock cb; - EXPECT_CALL(cb, transportFrameStart(absl::optional())); +TEST(UnframedTransportTest, Type) { + UnframedTransportImpl transport; + EXPECT_EQ(transport.type(), TransportType::Unframed); +} - UnframedTransportImpl transport(cb); +TEST(UnframedTransportTest, DecodeFrameStart) { + UnframedTransportImpl transport; Buffer::OwnedImpl buffer; addInt32(buffer, 0xDEADBEEF); - EXPECT_EQ(buffer.length(), 4); - EXPECT_TRUE(transport.decodeFrameStart(buffer)); + + absl::optional size = 1; + EXPECT_TRUE(transport.decodeFrameStart(buffer, size)); + EXPECT_FALSE(size.has_value()); EXPECT_EQ(buffer.length(), 4); } TEST(UnframedTransportTest, DecodeFrameEnd) { - StrictMock cb; - EXPECT_CALL(cb, transportFrameComplete()); - - UnframedTransportImpl transport(cb); + UnframedTransportImpl transport; Buffer::OwnedImpl buffer; EXPECT_TRUE(transport.decodeFrameEnd(buffer)); } TEST(UnframedTransportTest, EncodeFrame) { - StrictMock cb; - - UnframedTransportImpl transport(cb); + UnframedTransportImpl transport; Buffer::OwnedImpl message; message.add("fake message"); diff --git a/test/mocks/tcp/mocks.cc b/test/mocks/tcp/mocks.cc index 1713d04e6128..757f7556f32b 100644 --- a/test/mocks/tcp/mocks.cc +++ b/test/mocks/tcp/mocks.cc @@ -1,7 +1,8 @@ #include "mocks.h" #include "gmock/gmock.h" -#include "gtest/gtest.h" + +using testing::ReturnRef; using testing::Invoke; using testing::ReturnRef; From 486806144a44028da9b7be94ca6e96fcb98128cf Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 26 Jul 2018 17:23:36 -0400 Subject: [PATCH 41/46] thirdpary: explicit libdir path (#3962) on some hosts (amd64 centos and fedora) the libdir path defaults to lib64, though elsewhere the path is expected to be ./lib/ Signed-off-by: Vincent Batts --- ci/build_container/build_recipes/nghttp2.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/build_container/build_recipes/nghttp2.sh b/ci/build_container/build_recipes/nghttp2.sh index f4abbc558ab5..cea6ab963292 100755 --- a/ci/build_container/build_recipes/nghttp2.sh +++ b/ci/build_container/build_recipes/nghttp2.sh @@ -36,6 +36,7 @@ mkdir build cd build cmake -G "Ninja" -DCMAKE_INSTALL_PREFIX="$THIRDPARTY_BUILD" \ + -DCMAKE_INSTALL_LIBDIR="$THIRDPARTY_BUILD/lib" \ -DENABLE_STATIC_LIB=on \ -DENABLE_LIB_ONLY=on \ .. From 42109e564224ead0099138c194cf22cfe6c752c6 Mon Sep 17 00:00:00 2001 From: Dhi Aurrahman Date: Fri, 27 Jul 2018 04:30:08 +0700 Subject: [PATCH 42/46] health check: allow request header formatting for HTTP health check request (#3882) Signed-off-by: Dhi Aurrahman --- api/envoy/api/v2/core/health_check.proto | 4 +- docs/root/intro/version_history.rst | 2 + source/common/upstream/health_checker_impl.cc | 22 +++-- source/common/upstream/health_checker_impl.h | 5 +- .../upstream/health_checker_impl_test.cc | 90 +++++++++++++++---- 5 files changed, 93 insertions(+), 30 deletions(-) diff --git a/api/envoy/api/v2/core/health_check.proto b/api/envoy/api/v2/core/health_check.proto index 94c3f7817962..ad35ca61536a 100644 --- a/api/envoy/api/v2/core/health_check.proto +++ b/api/envoy/api/v2/core/health_check.proto @@ -95,7 +95,9 @@ message HealthCheck { string service_name = 5; // Specifies a list of HTTP headers that should be added to each request that is sent to the - // health checked cluster. + // health checked cluster. For more information, including details on header value syntax, see + // the documentation on :ref:`custom request headers + // `. repeated core.HeaderValueOption request_headers_to_add = 6; // If set, health checks will be made using http/2. diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index c1629a469788..bc8a1ca734c3 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -14,6 +14,8 @@ Version history * health check: added support for :ref:`custom health check `. * health check: added support for :ref:`specifying jitter as a percentage `. * health_check: added support for :ref:`health check event logging `. +* health_check: added support for specifying :ref:`custom request headers ` + to HTTP health checker requests. * http: added support for a per-stream idle timeout. This applies at both :ref:`connection manager ` and :ref:`per-route granularity `. The timeout diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index faeeaf1a0672..0e135b26eae4 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -10,6 +10,7 @@ #include "common/config/well_known_names.h" #include "common/grpc/common.h" #include "common/http/header_map_impl.h" +#include "common/network/address_impl.h" #include "common/router/router.h" #include "common/upstream/host_utility.h" @@ -98,7 +99,13 @@ HttpHealthCheckerImpl::HttpHealthCheckerImpl(const Cluster& cluster, HttpHealthCheckerImpl::HttpActiveHealthCheckSession::HttpActiveHealthCheckSession( HttpHealthCheckerImpl& parent, const HostSharedPtr& host) - : ActiveHealthCheckSession(parent, host), parent_(parent) {} + : ActiveHealthCheckSession(parent, host), parent_(parent), + hostname_(parent_.host_value_.empty() ? parent_.cluster_.info()->name() + : parent_.host_value_), + protocol_(parent_.codec_client_type_ == Http::CodecClient::Type::HTTP1 + ? Http::Protocol::Http11 + : Http::Protocol::Http2), + local_address_(std::make_shared("127.0.0.1")) {} HttpHealthCheckerImpl::HttpActiveHealthCheckSession::~HttpActiveHealthCheckSession() { if (client_) { @@ -127,9 +134,6 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onEvent(Network::Conne } } -const RequestInfo::RequestInfoImpl - HttpHealthCheckerImpl::HttpActiveHealthCheckSession::REQUEST_INFO; - void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onInterval() { if (!client_) { Upstream::Host::CreateConnectionData conn = @@ -144,13 +148,15 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onInterval() { Http::HeaderMapImpl request_headers{ {Http::Headers::get().Method, "GET"}, - {Http::Headers::get().Host, - parent_.host_value_.empty() ? parent_.cluster_.info()->name() : parent_.host_value_}, + {Http::Headers::get().Host, hostname_}, {Http::Headers::get().Path, parent_.path_}, {Http::Headers::get().UserAgent, Http::Headers::get().UserAgentValues.EnvoyHealthChecker}}; Router::FilterUtility::setUpstreamScheme(request_headers, *parent_.cluster_.info()); - - parent_.request_headers_parser_->evaluateHeaders(request_headers, REQUEST_INFO); + RequestInfo::RequestInfoImpl request_info(protocol_); + request_info.setDownstreamLocalAddress(local_address_); + request_info.setDownstreamRemoteAddress(local_address_); + request_info.onUpstreamHostSelected(host_); + parent_.request_headers_parser_->evaluateHeaders(request_headers, request_info); request_encoder_->encodeHeaders(request_headers, true); request_encoder_ = nullptr; } diff --git a/source/common/upstream/health_checker_impl.h b/source/common/upstream/health_checker_impl.h index 2dc1c66e41b9..0db4bfd70a3f 100644 --- a/source/common/upstream/health_checker_impl.h +++ b/source/common/upstream/health_checker_impl.h @@ -90,13 +90,14 @@ class HttpHealthCheckerImpl : public HealthCheckerImplBase { HttpActiveHealthCheckSession& parent_; }; - static const RequestInfo::RequestInfoImpl REQUEST_INFO; - ConnectionCallbackImpl connection_callback_impl_{*this}; HttpHealthCheckerImpl& parent_; Http::CodecClientPtr client_; Http::StreamEncoder* request_encoder_{}; Http::HeaderMapPtr response_headers_; + const std::string& hostname_; + const Http::Protocol protocol_; + Network::Address::InstanceConstSharedPtr local_address_; bool expect_reset_{}; }; diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 04d9766fa7fc..3606f7a9fa2f 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -322,6 +322,24 @@ class HttpHealthCheckerImplTest : public testing::Test { key: user-agent value: CoolEnvoy/HC append: false + - header: + key: x-protocol + value: "%PROTOCOL%" + - header: + key: x-upstream-metadata + value: "%UPSTREAM_METADATA([\"namespace\", \"key\"])%" + - header: + key: x-downstream-remote-address-without-port + value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%" + - header: + key: x-downstream-local-address + value: "%DOWNSTREAM_LOCAL_ADDRESS%" + - header: + key: x-downstream-local-address-without-port + value: "%DOWNSTREAM_LOCAL_ADDRESS_WITHOUT_PORT%" + - header: + key: x-start-time + value: "%START_TIME(%s.%9f)%" )EOF"; health_checker_.reset(new TestHttpHealthCheckerImpl(*cluster_, parseHealthCheckFromV2Yaml(yaml), @@ -697,15 +715,28 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValue) { } TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { - const Http::LowerCaseString headerOk("x-envoy-ok"); - const Http::LowerCaseString headerCool("x-envoy-cool"); - const Http::LowerCaseString headerAwesome("x-envoy-awesome"); - - const std::string valueOk = "ok"; - const std::string valueCool = "cool"; - const std::string valueAwesome = "awesome"; - - const std::string valueUserAgent = "CoolEnvoy/HC"; + const Http::LowerCaseString header_ok("x-envoy-ok"); + const Http::LowerCaseString header_cool("x-envoy-cool"); + const Http::LowerCaseString header_awesome("x-envoy-awesome"); + const Http::LowerCaseString upstream_metadata("x-upstream-metadata"); + const Http::LowerCaseString protocol("x-protocol"); + const Http::LowerCaseString downstream_remote_address_without_port( + "x-downstream-remote-address-without-port"); + const Http::LowerCaseString downstream_local_address("x-downstream-local-address"); + const Http::LowerCaseString downstream_local_address_without_port( + "x-downstream-local-address-without-port"); + const Http::LowerCaseString start_time("x-start-time"); + + const std::string value_ok = "ok"; + const std::string value_cool = "cool"; + const std::string value_awesome = "awesome"; + + const std::string value_user_agent = "CoolEnvoy/HC"; + const std::string value_upstream_metadata = "value"; + const std::string value_protocol = "HTTP/1.1"; + const std::string value_downstream_remote_address_without_port = "127.0.0.1"; + const std::string value_downstream_local_address = "127.0.0.1:0"; + const std::string value_downstream_local_address_without_port = "127.0.0.1"; setupServiceValidationWithAdditionalHeaders(); // requires non-empty `service_name` in config. @@ -713,20 +744,39 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { .WillOnce(Return(true)); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)).Times(1); - + auto metadata = TestUtility::parseYaml( + R"EOF( + filter_metadata: + namespace: + key: value + )EOF"); + + std::string current_start_time; cluster_->prioritySet().getMockHostSet(0)->hosts_ = { - makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", metadata)}; cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) - .WillOnce(Invoke([&](const Http::HeaderMap& headers, bool) { - EXPECT_EQ(headers.get(headerOk)->value().c_str(), valueOk); - EXPECT_EQ(headers.get(headerCool)->value().c_str(), valueCool); - EXPECT_EQ(headers.get(headerAwesome)->value().c_str(), valueAwesome); - - EXPECT_EQ(headers.UserAgent()->value().c_str(), valueUserAgent); + .WillRepeatedly(Invoke([&](const Http::HeaderMap& headers, bool) { + EXPECT_EQ(headers.get(header_ok)->value().c_str(), value_ok); + EXPECT_EQ(headers.get(header_cool)->value().c_str(), value_cool); + EXPECT_EQ(headers.get(header_awesome)->value().c_str(), value_awesome); + + EXPECT_EQ(headers.UserAgent()->value().c_str(), value_user_agent); + EXPECT_EQ(headers.get(upstream_metadata)->value().c_str(), value_upstream_metadata); + + EXPECT_EQ(headers.get(protocol)->value().c_str(), value_protocol); + EXPECT_EQ(headers.get(downstream_remote_address_without_port)->value().c_str(), + value_downstream_remote_address_without_port); + EXPECT_EQ(headers.get(downstream_local_address)->value().c_str(), + value_downstream_local_address); + EXPECT_EQ(headers.get(downstream_local_address_without_port)->value().c_str(), + value_downstream_local_address_without_port); + + EXPECT_NE(headers.get(start_time)->value().c_str(), current_start_time); + current_start_time = headers.get(start_time)->value().c_str(); })); health_checker_->start(); @@ -738,6 +788,10 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { absl::optional health_checked_cluster("locations-production-iad"); respond(0, "200", false, true, false, health_checked_cluster); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthy()); + + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + expectStreamCreate(0); + test_sessions_[0]->interval_timer_->callback_(); } TEST_F(HttpHealthCheckerImplTest, ServiceDoesNotMatchFail) { @@ -775,8 +829,6 @@ TEST_F(HttpHealthCheckerImplTest, ServiceNotPresentInResponseFail) { EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)).Times(1); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - ; - ; cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; From 0e2c79599d3944bd154020e669ff045988c17c54 Mon Sep 17 00:00:00 2001 From: Anirudh Date: Fri, 27 Jul 2018 05:42:36 +0530 Subject: [PATCH 43/46] fuzz: fixes oss-fuzz: 9204 (#3935) oss-fuzz issue (9204): https://oss-fuzz.com/v2/testcase-detail/5366294281977856 I suppose Envoy doesn't support multiple health checks still. Instead of assertion, have replaced it with throw. This doesn't crash the build. Let me know if any changes. Risk Level: Low Testing: Tested unit tests (bazel test //server:server_fuzz_test), built and ran fuzzers with oss-fuzz. Signed-off-by: Anirudh M --- source/common/upstream/upstream_impl.cc | 9 +- .../upstream/cluster_manager_impl_test.cc | 21 +++ ...testcase-server_fuzz_test-5366294281977856 | 126 ++++++++++++++++++ 3 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5366294281977856 diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index a2f7d8bc85ce..0ac8dc532041 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -422,9 +422,12 @@ ClusterSharedPtr ClusterImplBase::create( if (!cluster.health_checks().empty()) { // TODO(htuch): Need to support multiple health checks in v2. - ASSERT(cluster.health_checks().size() == 1); - new_cluster->setHealthChecker(HealthCheckerFactory::create( - cluster.health_checks()[0], *new_cluster, runtime, random, dispatcher, log_manager)); + if (cluster.health_checks().size() != 1) { + throw EnvoyException("Multiple health checks not supported"); + } else { + new_cluster->setHealthChecker(HealthCheckerFactory::create( + cluster.health_checks()[0], *new_cluster, runtime, random, dispatcher, log_manager)); + } } new_cluster->setOutlierDetector(Outlier::DetectorImplFactory::createForCluster( diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index 8082c0c090c0..56080b449dcf 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -193,6 +193,27 @@ TEST_F(ClusterManagerImplTest, MultipleProtocolClusterFail) { "'protocol_selection' values"); } +TEST_F(ClusterManagerImplTest, MultipleHealthCheckFail) { + const std::string yaml = R"EOF( + static_resources: + clusters: + - name: service_google + connect_timeout: 0.25s + health_checks: + - timeout: 1s + interval: 1s + http_health_check: + path: "/blah" + - timeout: 1s + interval: 1s + http_health_check: + path: "/" + )EOF"; + + EXPECT_THROW_WITH_MESSAGE(create(parseBootstrapFromV2Yaml(yaml)), EnvoyException, + "Multiple health checks not supported"); +} + TEST_F(ClusterManagerImplTest, MultipleProtocolCluster) { EXPECT_CALL(system_time_source_, currentTime()) .WillRepeatedly(Return(SystemTime(std::chrono::milliseconds(1234567891234)))); diff --git a/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5366294281977856 b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5366294281977856 new file mode 100644 index 000000000000..675806abca08 --- /dev/null +++ b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5366294281977856 @@ -0,0 +1,126 @@ +static_resources { + clusters { + name: "9" + connect_timeout { + nanos: 1 + } + hosts { + pipe { + path: "N" + } + } + hosts { + pipe { + path: "n" + } + } + hosts { + pipe { + path: "=" + } + } + hosts { + pipe { + path: "s" + } + } + hosts { + pipe { + path: "n" + } + } + hosts { + pipe { + path: "N" + } + } + hosts { + pipe { + path: "W" + } + } + hosts { + pipe { + path: "=" + } + } + hosts { + pipe { + path: "N" + } + } + hosts { + pipe { + path: "n" + } + } + health_checks { + timeout { + nanos: 1 + } + interval { + nanos: 1 + } + unhealthy_threshold { + } + healthy_threshold { + value: 1701650432 + } + http_health_check { + path: "~" + request_headers_to_add { + } + request_headers_to_add { + header { + value: "W" + } + } + request_headers_to_add { + } + request_headers_to_add { + } + request_headers_to_add { + } + request_headers_to_add { + } + request_headers_to_add { + } + request_headers_to_add { + } + request_headers_to_add { + } + use_http2: true + } + } + health_checks { + timeout { + nanos: 1 + } + interval { + nanos: 1 + } + unhealthy_threshold { + } + healthy_threshold { + } + http_health_check { + path: "E" + request_headers_to_add { + } + request_headers_to_add { + } + request_headers_to_add { + } + } + } + } +} +admin { + access_log_path: "@\'" + address { + socket_address { + address: "::" + port_value: 0 + } + } +} From 1dfde38343e7dfafe2c9f211229109bb22491257 Mon Sep 17 00:00:00 2001 From: Stephan Zuercher Date: Thu, 26 Jul 2018 18:44:37 -0700 Subject: [PATCH 44/46] thrift_proxy: move protobuf to api/config hierarchy (#3963) As discussed in Slack, these should have been under api/config/filter/network to begin with. Added a note to the style doc to make this clear in the future. *Risk level*: low (rename only) *Testing*: existing tests suffice *Doc Changes*: n/a *Release Notes*: updated Signed-off-by: Stephan Zuercher --- api/STYLE.md | 3 + .../network/thrift_proxy/v2alpha1/BUILD | 0 .../network/thrift_proxy/v2alpha1/README.md | 0 .../network/thrift_proxy/v2alpha1/route.proto | 2 +- .../thrift_proxy/v2alpha1/router/BUILD | 0 .../thrift_proxy/v2alpha1/router/router.proto | 2 +- .../thrift_proxy/v2alpha1/thrift_proxy.proto | 4 +- docs/root/intro/version_history.rst | 1 + .../filters/network/thrift_proxy/BUILD | 2 +- .../filters/network/thrift_proxy/config.cc | 66 +++++++++---------- .../filters/network/thrift_proxy/config.h | 14 ++-- .../filters/network/thrift_proxy/router/BUILD | 4 +- .../network/thrift_proxy/router/config.cc | 2 +- .../network/thrift_proxy/router/config.h | 9 ++- .../thrift_proxy/router/router_impl.cc | 8 +-- .../network/thrift_proxy/router/router_impl.h | 10 ++- .../network/thrift_proxy/config_test.cc | 15 ++--- .../network/thrift_proxy/conn_manager_test.cc | 11 ++-- .../network/thrift_proxy/router_test.cc | 12 ++-- 19 files changed, 81 insertions(+), 84 deletions(-) rename api/envoy/{extensions/filters => config/filter}/network/thrift_proxy/v2alpha1/BUILD (100%) rename api/envoy/{extensions/filters => config/filter}/network/thrift_proxy/v2alpha1/README.md (100%) rename api/envoy/{extensions/filters => config/filter}/network/thrift_proxy/v2alpha1/route.proto (95%) rename api/envoy/{extensions/filters => config/filter}/network/thrift_proxy/v2alpha1/router/BUILD (100%) rename api/envoy/{extensions/filters => config/filter}/network/thrift_proxy/v2alpha1/router/router.proto (66%) rename api/envoy/{extensions/filters => config/filter}/network/thrift_proxy/v2alpha1/thrift_proxy.proto (93%) diff --git a/api/STYLE.md b/api/STYLE.md index d932c3a3b17c..92592d4aac2e 100644 --- a/api/STYLE.md +++ b/api/STYLE.md @@ -131,3 +131,6 @@ the build system to prevent circular dependency formation. Package group `//envoy/api/v2:friends` selects consumers of the core API package (services and configs) and is the default visibility for the core API packages. The default visibility for services and configs should be `//docs` (proto documentation tool). + +Extensions should use the regular hierarchy. For example, configuration for network filters belongs +in a package under `envoy.config.filter.network`. diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/BUILD b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/BUILD similarity index 100% rename from api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/BUILD rename to api/envoy/config/filter/network/thrift_proxy/v2alpha1/BUILD diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/README.md b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/README.md similarity index 100% rename from api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/README.md rename to api/envoy/config/filter/network/thrift_proxy/v2alpha1/README.md diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.proto b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto similarity index 95% rename from api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.proto rename to api/envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto index 5c9af1c48755..f70523e57f21 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.proto +++ b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package envoy.extensions.filters.network.thrift_proxy.v2alpha1; +package envoy.config.filter.network.thrift_proxy.v2alpha1; option go_package = "v2"; import "validate/validate.proto"; diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/BUILD b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/router/BUILD similarity index 100% rename from api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/BUILD rename to api/envoy/config/filter/network/thrift_proxy/v2alpha1/router/BUILD diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.proto b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/router/router.proto similarity index 66% rename from api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.proto rename to api/envoy/config/filter/network/thrift_proxy/v2alpha1/router/router.proto index 2818c044e7b0..5ad9863b07de 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.proto +++ b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/router/router.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package envoy.extensions.filters.network.thrift_proxy.v2alpha1.router; +package envoy.config.filter.network.thrift_proxy.v2alpha1.router; option go_package = "router"; // [#protodoc-title: Thrift Router] diff --git a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.proto b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.proto similarity index 93% rename from api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.proto rename to api/envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.proto index 889729227320..1a7176dc3303 100644 --- a/api/envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.proto +++ b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.proto @@ -1,9 +1,9 @@ syntax = "proto3"; -package envoy.extensions.filters.network.thrift_proxy.v2alpha1; +package envoy.config.filter.network.thrift_proxy.v2alpha1; option go_package = "v2"; -import "envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.proto"; +import "envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto"; import "validate/validate.proto"; import "gogoproto/gogo.proto"; diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index bc8a1ca734c3..77afa09c4494 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -48,6 +48,7 @@ Version history * router: added ability to set request/response headers at the :ref:`envoy_api_msg_route.Route` level. * tracing: added support for configuration of :ref:`tracing sampling `. +* thrift_proxy: introduced thrift routing, moved configuration to correct location * upstream: added configuration option to the subset load balancer to take locality weights into account when selecting a host from a subset. * access log: added RESPONSE_DURATION and RESPONSE_TX_DURATION. diff --git a/source/extensions/filters/network/thrift_proxy/BUILD b/source/extensions/filters/network/thrift_proxy/BUILD index c69373d53e37..05264d60abac 100644 --- a/source/extensions/filters/network/thrift_proxy/BUILD +++ b/source/extensions/filters/network/thrift_proxy/BUILD @@ -45,7 +45,7 @@ envoy_cc_library( "//source/extensions/filters/network/thrift_proxy/filters:filter_config_interface", "//source/extensions/filters/network/thrift_proxy/filters:well_known_names", "//source/extensions/filters/network/thrift_proxy/router:router_lib", - "@envoy_api//envoy/extensions/filters/network/thrift_proxy/v2alpha1:thrift_proxy_cc", + "@envoy_api//envoy/config/filter/network/thrift_proxy/v2alpha1:thrift_proxy_cc", ], ) diff --git a/source/extensions/filters/network/thrift_proxy/config.cc b/source/extensions/filters/network/thrift_proxy/config.cc index ffab3d37f53c..22ce4fbf3e16 100644 --- a/source/extensions/filters/network/thrift_proxy/config.cc +++ b/source/extensions/filters/network/thrift_proxy/config.cc @@ -25,52 +25,50 @@ namespace NetworkFilters { namespace ThriftProxy { namespace { -typedef std::map< - envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy_TransportType, - TransportType> +typedef std::map TransportTypeMap; static const TransportTypeMap& transportTypeMap() { - CONSTRUCT_ON_FIRST_USE(TransportTypeMap, - { - {envoy::extensions::filters::network::thrift_proxy::v2alpha1:: - ThriftProxy_TransportType_AUTO_TRANSPORT, - TransportType::Auto}, - {envoy::extensions::filters::network::thrift_proxy::v2alpha1:: - ThriftProxy_TransportType_FRAMED, - TransportType::Framed}, - {envoy::extensions::filters::network::thrift_proxy::v2alpha1:: - ThriftProxy_TransportType_UNFRAMED, - TransportType::Unframed}, - }); + CONSTRUCT_ON_FIRST_USE( + TransportTypeMap, + { + {envoy::config::filter::network::thrift_proxy::v2alpha1:: + ThriftProxy_TransportType_AUTO_TRANSPORT, + TransportType::Auto}, + {envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy_TransportType_FRAMED, + TransportType::Framed}, + {envoy::config::filter::network::thrift_proxy::v2alpha1:: + ThriftProxy_TransportType_UNFRAMED, + TransportType::Unframed}, + }); } -typedef std::map< - envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy_ProtocolType, - ProtocolType> +typedef std::map ProtocolTypeMap; static const ProtocolTypeMap& protocolTypeMap() { - CONSTRUCT_ON_FIRST_USE(ProtocolTypeMap, { - {envoy::extensions::filters::network::thrift_proxy:: - v2alpha1::ThriftProxy_ProtocolType_AUTO_PROTOCOL, - ProtocolType::Auto}, - {envoy::extensions::filters::network::thrift_proxy:: - v2alpha1::ThriftProxy_ProtocolType_BINARY, - ProtocolType::Binary}, - {envoy::extensions::filters::network::thrift_proxy:: - v2alpha1::ThriftProxy_ProtocolType_LAX_BINARY, - ProtocolType::LaxBinary}, - {envoy::extensions::filters::network::thrift_proxy:: - v2alpha1::ThriftProxy_ProtocolType_COMPACT, - ProtocolType::Compact}, - }); + CONSTRUCT_ON_FIRST_USE( + ProtocolTypeMap, + { + {envoy::config::filter::network::thrift_proxy::v2alpha1:: + ThriftProxy_ProtocolType_AUTO_PROTOCOL, + ProtocolType::Auto}, + {envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy_ProtocolType_BINARY, + ProtocolType::Binary}, + {envoy::config::filter::network::thrift_proxy::v2alpha1:: + ThriftProxy_ProtocolType_LAX_BINARY, + ProtocolType::LaxBinary}, + {envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy_ProtocolType_COMPACT, + ProtocolType::Compact}, + }); } } // namespace Network::FilterFactoryCb ThriftProxyFilterConfigFactory::createFilterFactoryFromProtoTyped( - const envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy& proto_config, + const envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy& proto_config, Server::Configuration::FactoryContext& context) { std::shared_ptr filter_config(new ConfigImpl(proto_config, context)); @@ -87,7 +85,7 @@ static Registry::RegisterFactory #include -#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" -#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.validate.h" +#include "envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" +#include "envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.pb.validate.h" #include "envoy/stats/stats.h" #include "extensions/filters/network/common/factory_base.h" @@ -23,13 +23,13 @@ namespace ThriftProxy { */ class ThriftProxyFilterConfigFactory : public Common::FactoryBase< - envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy> { + envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy> { public: ThriftProxyFilterConfigFactory() : FactoryBase(NetworkFilterNames::get().ThriftProxy) {} private: Network::FilterFactoryCb createFilterFactoryFromProtoTyped( - const envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy& proto_config, + const envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy& proto_config, Server::Configuration::FactoryContext& context) override; }; @@ -38,7 +38,7 @@ class ConfigImpl : public Config, public ThriftFilters::FilterChainFactory, Logger::Loggable { public: - ConfigImpl(const envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy& config, + ConfigImpl(const envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy& config, Server::Configuration::FactoryContext& context); // ThriftFilters::FilterChainFactory @@ -62,8 +62,8 @@ class ConfigImpl : public Config, Server::Configuration::FactoryContext& context_; const std::string stats_prefix_; ThriftFilterStats stats_; - envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy_TransportType transport_; - envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy_ProtocolType proto_; + envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy_TransportType transport_; + envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy_ProtocolType proto_; std::unique_ptr route_matcher_; std::list filter_factories_; diff --git a/source/extensions/filters/network/thrift_proxy/router/BUILD b/source/extensions/filters/network/thrift_proxy/router/BUILD index 2d32db0ee154..b06fc47112a9 100644 --- a/source/extensions/filters/network/thrift_proxy/router/BUILD +++ b/source/extensions/filters/network/thrift_proxy/router/BUILD @@ -18,7 +18,7 @@ envoy_cc_library( "//source/extensions/filters/network/thrift_proxy/filters:factory_base_lib", "//source/extensions/filters/network/thrift_proxy/filters:filter_config_interface", "//source/extensions/filters/network/thrift_proxy/filters:well_known_names", - "@envoy_api//envoy/extensions/filters/network/thrift_proxy/v2alpha1/router:router_cc", + "@envoy_api//envoy/config/filter/network/thrift_proxy/v2alpha1/router:router_cc", ], ) @@ -46,6 +46,6 @@ envoy_cc_library( "//source/extensions/filters/network/thrift_proxy:protocol_lib", "//source/extensions/filters/network/thrift_proxy:transport_lib", "//source/extensions/filters/network/thrift_proxy/filters:filter_interface", - "@envoy_api//envoy/extensions/filters/network/thrift_proxy/v2alpha1:thrift_proxy_cc", + "@envoy_api//envoy/config/filter/network/thrift_proxy/v2alpha1:thrift_proxy_cc", ], ) diff --git a/source/extensions/filters/network/thrift_proxy/router/config.cc b/source/extensions/filters/network/thrift_proxy/router/config.cc index abf7cafbc265..8f6bddcf6fbd 100644 --- a/source/extensions/filters/network/thrift_proxy/router/config.cc +++ b/source/extensions/filters/network/thrift_proxy/router/config.cc @@ -11,7 +11,7 @@ namespace ThriftProxy { namespace Router { ThriftFilters::FilterFactoryCb RouterFilterConfig::createFilterFactoryFromProtoTyped( - const envoy::extensions::filters::network::thrift_proxy::v2alpha1::router::Router& proto_config, + const envoy::config::filter::network::thrift_proxy::v2alpha1::router::Router& proto_config, const std::string& stat_prefix, Server::Configuration::FactoryContext& context) { UNREFERENCED_PARAMETER(proto_config); UNREFERENCED_PARAMETER(stat_prefix); diff --git a/source/extensions/filters/network/thrift_proxy/router/config.h b/source/extensions/filters/network/thrift_proxy/router/config.h index ae4629bfd0ad..937847d78a84 100644 --- a/source/extensions/filters/network/thrift_proxy/router/config.h +++ b/source/extensions/filters/network/thrift_proxy/router/config.h @@ -1,7 +1,7 @@ #pragma once -#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.pb.h" -#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/router/router.pb.validate.h" +#include "envoy/config/filter/network/thrift_proxy/v2alpha1/router/router.pb.h" +#include "envoy/config/filter/network/thrift_proxy/v2alpha1/router/router.pb.validate.h" #include "extensions/filters/network/thrift_proxy/filters/factory_base.h" #include "extensions/filters/network/thrift_proxy/filters/well_known_names.h" @@ -14,14 +14,13 @@ namespace Router { class RouterFilterConfig : public ThriftFilters::FactoryBase< - envoy::extensions::filters::network::thrift_proxy::v2alpha1::router::Router> { + envoy::config::filter::network::thrift_proxy::v2alpha1::router::Router> { public: RouterFilterConfig() : FactoryBase(ThriftFilters::ThriftFilterNames::get().ROUTER) {} private: ThriftFilters::FilterFactoryCb createFilterFactoryFromProtoTyped( - const envoy::extensions::filters::network::thrift_proxy::v2alpha1::router::Router& - proto_config, + const envoy::config::filter::network::thrift_proxy::v2alpha1::router::Router& proto_config, const std::string& stat_prefix, Server::Configuration::FactoryContext& context) override; }; diff --git a/source/extensions/filters/network/thrift_proxy/router/router_impl.cc b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc index 057d529f1398..2b4fb77946c3 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc @@ -1,6 +1,6 @@ #include "extensions/filters/network/thrift_proxy/router/router_impl.h" -#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" +#include "envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" #include "envoy/upstream/cluster_manager.h" #include "envoy/upstream/thread_local_cluster.h" @@ -13,7 +13,7 @@ namespace ThriftProxy { namespace Router { RouteEntryImplBase::RouteEntryImplBase( - const envoy::extensions::filters::network::thrift_proxy::v2alpha1::Route& route) + const envoy::config::filter::network::thrift_proxy::v2alpha1::Route& route) : cluster_name_(route.route().cluster()) {} const std::string& RouteEntryImplBase::clusterName() const { return cluster_name_; } @@ -23,7 +23,7 @@ const RouteEntry* RouteEntryImplBase::routeEntry() const { return this; } RouteConstSharedPtr RouteEntryImplBase::clusterEntry() const { return shared_from_this(); } MethodNameRouteEntryImpl::MethodNameRouteEntryImpl( - const envoy::extensions::filters::network::thrift_proxy::v2alpha1::Route& route) + const envoy::config::filter::network::thrift_proxy::v2alpha1::Route& route) : RouteEntryImplBase(route), method_name_(route.match().method()) {} RouteConstSharedPtr MethodNameRouteEntryImpl::matches(const std::string& method_name) const { @@ -35,7 +35,7 @@ RouteConstSharedPtr MethodNameRouteEntryImpl::matches(const std::string& method_ } RouteMatcher::RouteMatcher( - const envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration& config) { + const envoy::config::filter::network::thrift_proxy::v2alpha1::RouteConfiguration& config) { for (const auto& route : config.routes()) { routes_.emplace_back(new MethodNameRouteEntryImpl(route)); } diff --git a/source/extensions/filters/network/thrift_proxy/router/router_impl.h b/source/extensions/filters/network/thrift_proxy/router/router_impl.h index aa202734b559..117c99ca2635 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_impl.h +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.h @@ -4,7 +4,7 @@ #include #include -#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" +#include "envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" #include "envoy/tcp/conn_pool.h" #include "envoy/upstream/load_balancer.h" @@ -26,8 +26,7 @@ class RouteEntryImplBase : public RouteEntry, public Route, public std::enable_shared_from_this { public: - RouteEntryImplBase( - const envoy::extensions::filters::network::thrift_proxy::v2alpha1::Route& route); + RouteEntryImplBase(const envoy::config::filter::network::thrift_proxy::v2alpha1::Route& route); // Router::RouteEntry const std::string& clusterName() const override; @@ -49,7 +48,7 @@ typedef std::shared_ptr RouteEntryImplBaseConstSharedP class MethodNameRouteEntryImpl : public RouteEntryImplBase { public: MethodNameRouteEntryImpl( - const envoy::extensions::filters::network::thrift_proxy::v2alpha1::Route& route); + const envoy::config::filter::network::thrift_proxy::v2alpha1::Route& route); const std::string& methodName() const { return method_name_; } @@ -62,8 +61,7 @@ class MethodNameRouteEntryImpl : public RouteEntryImplBase { class RouteMatcher { public: - RouteMatcher( - const envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration&); + RouteMatcher(const envoy::config::filter::network::thrift_proxy::v2alpha1::RouteConfiguration&); RouteConstSharedPtr route(const std::string& method_name) const; diff --git a/test/extensions/filters/network/thrift_proxy/config_test.cc b/test/extensions/filters/network/thrift_proxy/config_test.cc index 56c481075a01..220ce5c3fddf 100644 --- a/test/extensions/filters/network/thrift_proxy/config_test.cc +++ b/test/extensions/filters/network/thrift_proxy/config_test.cc @@ -1,4 +1,4 @@ -#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.validate.h" +#include "envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.pb.validate.h" #include "extensions/filters/network/thrift_proxy/config.h" @@ -16,14 +16,13 @@ namespace ThriftProxy { TEST(ThriftFilterConfigTest, ValidateFail) { NiceMock context; - EXPECT_THROW( - ThriftProxyFilterConfigFactory().createFilterFactoryFromProto( - envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy(), context), - ProtoValidationException); + EXPECT_THROW(ThriftProxyFilterConfigFactory().createFilterFactoryFromProto( + envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy(), context), + ProtoValidationException); } TEST(ThriftFilterConfigTest, ValidProtoConfiguration) { - envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy config{}; + envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy config{}; config.set_stat_prefix("my_stat_prefix"); @@ -38,8 +37,8 @@ TEST(ThriftFilterConfigTest, ValidProtoConfiguration) { TEST(ThriftFilterConfigTest, ThriftProxyWithEmptyProto) { NiceMock context; ThriftProxyFilterConfigFactory factory; - envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy config = - *dynamic_cast( + envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy config = + *dynamic_cast( factory.createEmptyConfigProto().get()); config.set_stat_prefix("my_stat_prefix"); diff --git a/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc b/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc index 48dce17b7d40..accae1a9e3c7 100644 --- a/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc +++ b/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc @@ -1,4 +1,4 @@ -#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" +#include "envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.pb.h" #include "common/buffer/buffer_impl.h" #include "common/stats/stats_impl.h" @@ -30,10 +30,9 @@ namespace ThriftProxy { class TestConfigImpl : public ConfigImpl { public: - TestConfigImpl( - envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy proto_config, - Server::Configuration::MockFactoryContext& context, - ThriftFilters::DecoderFilterSharedPtr decoder_filter, ThriftFilterStats& stats) + TestConfigImpl(envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy proto_config, + Server::Configuration::MockFactoryContext& context, + ThriftFilters::DecoderFilterSharedPtr decoder_filter, ThriftFilterStats& stats) : ConfigImpl(proto_config, context), decoder_filter_(decoder_filter), stats_(stats) {} // ConfigImpl @@ -236,7 +235,7 @@ class ThriftConnectionManagerTest : public testing::Test { std::shared_ptr decoder_filter_; Stats::IsolatedStoreImpl store_; ThriftFilterStats stats_; - envoy::extensions::filters::network::thrift_proxy::v2alpha1::ThriftProxy proto_config_; + envoy::config::filter::network::thrift_proxy::v2alpha1::ThriftProxy proto_config_; std::unique_ptr config_; diff --git a/test/extensions/filters/network/thrift_proxy/router_test.cc b/test/extensions/filters/network/thrift_proxy/router_test.cc index 9b623f37df2d..94afe87c750d 100644 --- a/test/extensions/filters/network/thrift_proxy/router_test.cc +++ b/test/extensions/filters/network/thrift_proxy/router_test.cc @@ -1,5 +1,5 @@ -#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.pb.h" -#include "envoy/extensions/filters/network/thrift_proxy/v2alpha1/route.pb.validate.h" +#include "envoy/config/filter/network/thrift_proxy/v2alpha1/route.pb.h" +#include "envoy/config/filter/network/thrift_proxy/v2alpha1/route.pb.validate.h" #include "envoy/tcp/conn_pool.h" #include "common/buffer/buffer_impl.h" @@ -38,9 +38,9 @@ namespace Router { namespace { -envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration +envoy::config::filter::network::thrift_proxy::v2alpha1::RouteConfiguration parseRouteConfigurationFromV2Yaml(const std::string& yaml) { - envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration route_config; + envoy::config::filter::network::thrift_proxy::v2alpha1::RouteConfiguration route_config; MessageUtil::loadFromYaml(yaml, route_config); MessageUtil::validate(route_config); return route_config; @@ -582,7 +582,7 @@ name: config cluster: "cluster2" )EOF"; - envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration config = + envoy::config::filter::network::thrift_proxy::v2alpha1::RouteConfiguration config = parseRouteConfigurationFromV2Yaml(yaml); RouteMatcher matcher(config); @@ -611,7 +611,7 @@ name: config cluster: "cluster2" )EOF"; - envoy::extensions::filters::network::thrift_proxy::v2alpha1::RouteConfiguration config = + envoy::config::filter::network::thrift_proxy::v2alpha1::RouteConfiguration config = parseRouteConfigurationFromV2Yaml(yaml); RouteMatcher matcher(config); From 280baee6dc45e48a4bf38ac03d32711fe5eaeee1 Mon Sep 17 00:00:00 2001 From: Lizan Zhou Date: Thu, 26 Jul 2018 20:49:26 -0700 Subject: [PATCH 45/46] rds: split subscription out from RdsRouteConfigProviderImpl (#3960) Signed-off-by: Lizan Zhou --- source/common/router/rds_impl.cc | 131 +++++++++++++++-------- source/common/router/rds_impl.h | 88 +++++++++------ test/common/router/rds_impl_test.cc | 18 ++-- test/integration/ads_integration_test.cc | 84 ++++++++++++++- 4 files changed, 228 insertions(+), 93 deletions(-) diff --git a/source/common/router/rds_impl.cc b/source/common/router/rds_impl.cc index 88ac8d4ed3eb..4586845b2691 100644 --- a/source/common/router/rds_impl.cc +++ b/source/common/router/rds_impl.cc @@ -47,23 +47,19 @@ StaticRouteConfigProviderImpl::StaticRouteConfigProviderImpl( // TODO(htuch): If support for multiple clusters is added per #1170 cluster_name_ // initialization needs to be fixed. -RdsRouteConfigProviderImpl::RdsRouteConfigProviderImpl( +RdsRouteConfigSubscription::RdsRouteConfigSubscription( const envoy::config::filter::network::http_connection_manager::v2::Rds& rds, const std::string& manager_identifier, Server::Configuration::FactoryContext& factory_context, - const std::string& stat_prefix, RouteConfigProviderManagerImpl& route_config_provider_manager) - : factory_context_(factory_context), tls_(factory_context.threadLocal().allocateSlot()), - route_config_name_(rds.route_config_name()), + const std::string& stat_prefix, + Envoy::Router::RouteConfigProviderManagerImpl& route_config_provider_manager) + : route_config_name_(rds.route_config_name()), scope_(factory_context.scope().createScope(stat_prefix + "rds." + route_config_name_ + ".")), stats_({ALL_RDS_STATS(POOL_COUNTER(*scope_))}), route_config_provider_manager_(route_config_provider_manager), - manager_identifier_(manager_identifier), + manager_identifier_(manager_identifier), time_source_(factory_context.systemTimeSource()), last_updated_(factory_context.systemTimeSource().currentTime()) { ::Envoy::Config::Utility::checkLocalInfo("rds", factory_context.localInfo()); - ConfigConstSharedPtr initial_config(new NullConfigImpl()); - tls_->set([initial_config](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { - return std::make_shared(initial_config); - }); subscription_ = Envoy::Config::SubscriptionFactory::subscriptionFromConfigSource< envoy::api::v2::RouteConfiguration>( rds.config_source(), factory_context.localInfo().node(), factory_context.dispatcher(), @@ -77,10 +73,9 @@ RdsRouteConfigProviderImpl::RdsRouteConfigProviderImpl( }, "envoy.api.v2.RouteDiscoveryService.FetchRoutes", "envoy.api.v2.RouteDiscoveryService.StreamRoutes"); - config_source_ = MessageUtil::getJsonStringFromMessage(rds.config_source(), true); } -RdsRouteConfigProviderImpl::~RdsRouteConfigProviderImpl() { +RdsRouteConfigSubscription::~RdsRouteConfigSubscription() { // If we get destroyed during initialization, make sure we signal that we "initialized". runInitializeCallbackIfAny(); @@ -88,16 +83,12 @@ RdsRouteConfigProviderImpl::~RdsRouteConfigProviderImpl() { // hold a shared_ptr to it. The RouteConfigProviderManager holds weak_ptrs to the // RdsRouteConfigProviders. Therefore, the map entry for the RdsRouteConfigProvider has to get // cleaned by the RdsRouteConfigProvider's destructor. - route_config_provider_manager_.route_config_providers_.erase(manager_identifier_); + route_config_provider_manager_.route_config_subscriptions_.erase(manager_identifier_); } -Router::ConfigConstSharedPtr RdsRouteConfigProviderImpl::config() { - return tls_->getTyped().config_; -} - -void RdsRouteConfigProviderImpl::onConfigUpdate(const ResourceVector& resources, +void RdsRouteConfigSubscription::onConfigUpdate(const ResourceVector& resources, const std::string& version_info) { - last_updated_ = factory_context_.systemTimeSource().currentTime(); + last_updated_ = time_source_.currentTime(); if (resources.empty()) { ENVOY_LOG(debug, "Missing RouteConfiguration for {} in onConfigUpdate()", route_config_name_); @@ -115,35 +106,79 @@ void RdsRouteConfigProviderImpl::onConfigUpdate(const ResourceVector& resources, throw EnvoyException(fmt::format("Unexpected RDS configuration (expecting {}): {}", route_config_name_, route_config.name())); } + const uint64_t new_hash = MessageUtil::hash(route_config); if (!config_info_ || new_hash != config_info_.value().last_config_hash_) { - ConfigConstSharedPtr new_config(new ConfigImpl(route_config, factory_context_, false)); config_info_ = {new_hash, version_info}; + route_config_proto_ = route_config; stats_.config_reload_.inc(); ENVOY_LOG(debug, "rds: loading new configuration: config_name={} hash={}", route_config_name_, new_hash); - tls_->runOnAllThreads( - [this, new_config]() -> void { tls_->getTyped().config_ = new_config; }); - route_config_proto_ = route_config; + for (auto* provider : route_config_providers_) { + provider->onConfigUpdate(); + } } + runInitializeCallbackIfAny(); } -void RdsRouteConfigProviderImpl::onConfigUpdateFailed(const EnvoyException*) { +void RdsRouteConfigSubscription::onConfigUpdateFailed(const EnvoyException*) { // We need to allow server startup to continue, even if we have a bad // config. runInitializeCallbackIfAny(); } -void RdsRouteConfigProviderImpl::runInitializeCallbackIfAny() { +void RdsRouteConfigSubscription::registerInitTarget(Init::Manager& init_manager) { + init_manager.registerTarget(*this); +} + +void RdsRouteConfigSubscription::runInitializeCallbackIfAny() { if (initialize_callback_) { initialize_callback_(); initialize_callback_ = nullptr; } } -void RdsRouteConfigProviderImpl::registerInitTarget(Init::Manager& init_manager) { - init_manager.registerTarget(*this); +RdsRouteConfigProviderImpl::RdsRouteConfigProviderImpl( + RdsRouteConfigSubscriptionSharedPtr&& subscription, + Server::Configuration::FactoryContext& factory_context) + : subscription_(std::move(subscription)), factory_context_(factory_context), + tls_(factory_context.threadLocal().allocateSlot()) { + ConfigConstSharedPtr initial_config; + if (subscription_->config_info_.has_value()) { + initial_config = + std::make_shared(subscription_->route_config_proto_, factory_context_, false); + } else { + initial_config = std::make_shared(); + } + tls_->set([initial_config](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { + return std::make_shared(initial_config); + }); + subscription_->route_config_providers_.insert(this); +} + +RdsRouteConfigProviderImpl::~RdsRouteConfigProviderImpl() { + subscription_->route_config_providers_.erase(this); +} + +Router::ConfigConstSharedPtr RdsRouteConfigProviderImpl::config() { + return tls_->getTyped().config_; +} + +absl::optional RdsRouteConfigProviderImpl::configInfo() const { + if (!subscription_->config_info_) { + return {}; + } else { + return ConfigInfo{subscription_->route_config_proto_, + subscription_->config_info_.value().last_config_version_}; + } +} + +void RdsRouteConfigProviderImpl::onConfigUpdate() { + ConfigConstSharedPtr new_config( + new ConfigImpl(subscription_->route_config_proto_, factory_context_, false)); + tls_->runOnAllThreads( + [this, new_config]() -> void { tls_->getTyped().config_ = new_config; }); } RouteConfigProviderManagerImpl::RouteConfigProviderManagerImpl(Server::Admin& admin) { @@ -157,14 +192,15 @@ RouteConfigProviderManagerImpl::RouteConfigProviderManagerImpl(Server::Admin& ad std::vector RouteConfigProviderManagerImpl::getRdsRouteConfigProviders() { std::vector ret; - ret.reserve(route_config_providers_.size()); - for (const auto& element : route_config_providers_) { + ret.reserve(route_config_subscriptions_.size()); + for (const auto& element : route_config_subscriptions_) { // Because the RouteConfigProviderManager's weak_ptrs only get cleaned up - // in the RdsRouteConfigProviderImpl destructor, and the single threaded nature + // in the RdsRouteConfigSubscription destructor, and the single threaded nature // of this code, locking the weak_ptr will not fail. - RouteConfigProviderSharedPtr provider = element.second.lock(); - ASSERT(provider); - ret.push_back(provider); + auto subscription = element.second.lock(); + ASSERT(subscription); + ASSERT(subscription->route_config_providers_.size() > 0); + ret.push_back((*subscription->route_config_providers_.begin())->shared_from_this()); } return ret; }; @@ -190,31 +226,34 @@ Router::RouteConfigProviderSharedPtr RouteConfigProviderManagerImpl::getRdsRoute const envoy::config::filter::network::http_connection_manager::v2::Rds& rds, Server::Configuration::FactoryContext& factory_context, const std::string& stat_prefix) { - // RdsRouteConfigProviders are unique based on their serialized RDS config. + // RdsRouteConfigSubscriptions are unique based on their serialized RDS config. // TODO(htuch): Full serialization here gives large IDs, could get away with a // strong hash instead. const std::string manager_identifier = rds.SerializeAsString(); - auto it = route_config_providers_.find(manager_identifier); - if (it == route_config_providers_.end()) { + RdsRouteConfigSubscriptionSharedPtr subscription; + + auto it = route_config_subscriptions_.find(manager_identifier); + if (it == route_config_subscriptions_.end()) { // std::make_shared does not work for classes with private constructors. There are ways // around it. However, since this is not a performance critical path we err on the side // of simplicity. - std::shared_ptr new_provider{new RdsRouteConfigProviderImpl( - rds, manager_identifier, factory_context, stat_prefix, *this)}; - - new_provider->registerInitTarget(factory_context.initManager()); + subscription.reset(new RdsRouteConfigSubscription(rds, manager_identifier, factory_context, + stat_prefix, *this)); - route_config_providers_.insert({manager_identifier, new_provider}); + subscription->registerInitTarget(factory_context.initManager()); - return new_provider; + route_config_subscriptions_.insert({manager_identifier, subscription}); + } else { + // Because the RouteConfigProviderManager's weak_ptrs only get cleaned up + // in the RdsRouteConfigSubscription destructor, and the single threaded nature + // of this code, locking the weak_ptr will not fail. + subscription = it->second.lock(); } + ASSERT(subscription); - // Because the RouteConfigProviderManager's weak_ptrs only get cleaned up - // in the RdsRouteConfigProviderImpl destructor, and the single threaded nature - // of this code, locking the weak_ptr will not fail. - Router::RouteConfigProviderSharedPtr new_provider = it->second.lock(); - ASSERT(new_provider); + Router::RouteConfigProviderSharedPtr new_provider{ + new RdsRouteConfigProviderImpl(std::move(subscription), factory_context)}; return new_provider; }; diff --git a/source/common/router/rds_impl.h b/source/common/router/rds_impl.h index 60f39aa2a809..261759248f9d 100644 --- a/source/common/router/rds_impl.h +++ b/source/common/router/rds_impl.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "envoy/api/v2/rds.pb.h" #include "envoy/api/v2/route/route.pb.h" @@ -80,18 +81,18 @@ struct RdsStats { }; class RouteConfigProviderManagerImpl; +class RdsRouteConfigProviderImpl; /** - * Implementation of RouteConfigProvider that fetches the route configuration dynamically using - * the RDS API. + * A class that fetches the route configuration dynamically using the RDS API and updates them to + * RDS config providers. */ -class RdsRouteConfigProviderImpl - : public RouteConfigProvider, - public Init::Target, +class RdsRouteConfigSubscription + : public Init::Target, Envoy::Config::SubscriptionCallbacks, Logger::Loggable { public: - ~RdsRouteConfigProviderImpl(); + ~RdsRouteConfigSubscription(); // Init::Target void initialize(std::function callback) override { @@ -99,17 +100,6 @@ class RdsRouteConfigProviderImpl subscription_->start({route_config_name_}, *this); } - // Router::RouteConfigProvider - Router::ConfigConstSharedPtr config() override; - absl::optional configInfo() const override { - if (!config_info_) { - return {}; - } else { - return ConfigInfo{route_config_proto_, config_info_.value().last_config_version_}; - } - } - SystemTime lastUpdated() const override { return last_updated_; } - // Config::SubscriptionCallbacks void onConfigUpdate(const ResourceVector& resources, const std::string& version_info) override; void onConfigUpdateFailed(const EnvoyException* e) override; @@ -118,18 +108,12 @@ class RdsRouteConfigProviderImpl } private: - struct ThreadLocalConfig : public ThreadLocal::ThreadLocalObject { - ThreadLocalConfig(ConfigConstSharedPtr initial_config) : config_(initial_config) {} - - ConfigConstSharedPtr config_; - }; - struct LastConfigInfo { uint64_t last_config_hash_; std::string last_config_version_; }; - RdsRouteConfigProviderImpl( + RdsRouteConfigSubscription( const envoy::config::filter::network::http_connection_manager::v2::Rds& rds, const std::string& manager_identifier, Server::Configuration::FactoryContext& factory_context, const std::string& stat_prefix, @@ -138,19 +122,57 @@ class RdsRouteConfigProviderImpl void registerInitTarget(Init::Manager& init_manager); void runInitializeCallbackIfAny(); - Server::Configuration::FactoryContext& factory_context_; std::unique_ptr> subscription_; - ThreadLocal::SlotPtr tls_; - std::string config_source_; + std::function initialize_callback_; const std::string route_config_name_; - absl::optional config_info_; Stats::ScopePtr scope_; RdsStats stats_; - std::function initialize_callback_; RouteConfigProviderManagerImpl& route_config_provider_manager_; const std::string manager_identifier_; - envoy::api::v2::RouteConfiguration route_config_proto_; + SystemTimeSource& time_source_; SystemTime last_updated_; + absl::optional config_info_; + envoy::api::v2::RouteConfiguration route_config_proto_; + std::unordered_set route_config_providers_; + + friend class RouteConfigProviderManagerImpl; + friend class RdsRouteConfigProviderImpl; +}; + +typedef std::shared_ptr RdsRouteConfigSubscriptionSharedPtr; + +/** + * Implementation of RouteConfigProvider that fetches the route configuration dynamically using + * the subscription. + */ +class RdsRouteConfigProviderImpl : public RouteConfigProvider, + public std::enable_shared_from_this, + Logger::Loggable { +public: + ~RdsRouteConfigProviderImpl(); + + RdsRouteConfigSubscription& subscription() { return *subscription_; } + void onConfigUpdate(); + + // Router::RouteConfigProvider + Router::ConfigConstSharedPtr config() override; + absl::optional configInfo() const override; + SystemTime lastUpdated() const override { return subscription_->last_updated_; } + +private: + struct ThreadLocalConfig : public ThreadLocal::ThreadLocalObject { + ThreadLocalConfig(ConfigConstSharedPtr initial_config) : config_(initial_config) {} + + ConfigConstSharedPtr config_; + }; + + RdsRouteConfigProviderImpl(RdsRouteConfigSubscriptionSharedPtr&& subscription, + Server::Configuration::FactoryContext& factory_context); + + RdsRouteConfigSubscriptionSharedPtr subscription_; + Server::Configuration::FactoryContext& factory_context_; + ThreadLocal::SlotPtr tls_; + const std::string route_config_name_; friend class RouteConfigProviderManagerImpl; }; @@ -180,12 +202,12 @@ class RouteConfigProviderManagerImpl : public RouteConfigProviderManager, // as in ConfigTracker. I.e. the ProviderImpls would have an EntryOwner for these lists // Then the lifetime management stuff is centralized and opaque. Plus the copypasta // in getRdsRouteConfigProviders()/getStaticRouteConfigProviders() goes away. - std::unordered_map> - route_config_providers_; + std::unordered_map> + route_config_subscriptions_; std::vector> static_route_config_providers_; Server::ConfigTracker::EntryOwnerPtr config_tracker_entry_; - friend class RdsRouteConfigProviderImpl; + friend class RdsRouteConfigSubscription; }; } // namespace Router diff --git a/test/common/router/rds_impl_test.cc b/test/common/router/rds_impl_test.cc index 2d649ca392fe..a918767606e7 100644 --- a/test/common/router/rds_impl_test.cc +++ b/test/common/router/rds_impl_test.cc @@ -513,9 +513,10 @@ TEST_F(RouteConfigProviderManagerImplTest, Basic) { RouteConfigProviderSharedPtr provider2 = route_config_provider_manager_->getRdsRouteConfigProvider(rds_, factory_context_, "foo_prefix"); - // So this means that both shared_ptrs should be the same. - EXPECT_EQ(provider_, provider2); - EXPECT_EQ(2UL, provider_.use_count()); + // So this means that both provider have same subscription. + EXPECT_NE(provider_, provider2); + EXPECT_EQ(&dynamic_cast(*provider_).subscription(), + &dynamic_cast(*provider2).subscription()); std::string config_json2 = R"EOF( { @@ -541,13 +542,11 @@ TEST_F(RouteConfigProviderManagerImplTest, Basic) { route_config_provider_manager_->getRdsRouteConfigProvider(rds2, factory_context_, "foo_prefix"); EXPECT_NE(provider3, provider_); - EXPECT_EQ(2UL, provider_.use_count()); EXPECT_EQ(1UL, provider3.use_count()); std::vector configured_providers = route_config_provider_manager_->getRdsRouteConfigProviders(); EXPECT_EQ(2UL, configured_providers.size()); - EXPECT_EQ(3UL, provider_.use_count()); EXPECT_EQ(2UL, provider3.use_count()); provider_.reset(); @@ -575,7 +574,8 @@ TEST_F(RouteConfigProviderManagerImplTest, ValidateFail) { auto* route_config = route_configs.Add(); route_config->set_name("foo_route_config"); route_config->mutable_virtual_hosts()->Add(); - EXPECT_THROW(provider_impl.onConfigUpdate(route_configs, ""), ProtoValidationException); + EXPECT_THROW(provider_impl.subscription().onConfigUpdate(route_configs, ""), + ProtoValidationException); } TEST_F(RouteConfigProviderManagerImplTest, onConfigUpdateEmpty) { @@ -583,7 +583,7 @@ TEST_F(RouteConfigProviderManagerImplTest, onConfigUpdateEmpty) { factory_context_.init_manager_.initialize(); auto& provider_impl = dynamic_cast(*provider_.get()); EXPECT_CALL(factory_context_.init_manager_.initialized_, ready()); - provider_impl.onConfigUpdate({}, ""); + provider_impl.subscription().onConfigUpdate({}, ""); EXPECT_EQ( 1UL, factory_context_.scope_.counter("foo_prefix.rds.foo_route_config.update_empty").value()); } @@ -596,8 +596,8 @@ TEST_F(RouteConfigProviderManagerImplTest, onConfigUpdateWrongSize) { route_configs.Add(); route_configs.Add(); EXPECT_CALL(factory_context_.init_manager_.initialized_, ready()); - EXPECT_THROW_WITH_MESSAGE(provider_impl.onConfigUpdate(route_configs, ""), EnvoyException, - "Unexpected RDS resource length: 2"); + EXPECT_THROW_WITH_MESSAGE(provider_impl.subscription().onConfigUpdate(route_configs, ""), + EnvoyException, "Unexpected RDS resource length: 2"); } } // namespace diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index d0a72ed610d5..e25eaa1902d2 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -198,9 +198,10 @@ class AdsIntegrationTest : public AdsIntegrationBaseTest, fake_upstreams_[0]->localAddress()->ip()->port())); } - envoy::api::v2::Listener buildListener(const std::string& name, const std::string& route_config) { - return TestUtility::parseYaml( - fmt::format(R"EOF( + envoy::api::v2::Listener buildListener(const std::string& name, const std::string& route_config, + const std::string& stat_prefix = "ads_test") { + return TestUtility::parseYaml(fmt::format( + R"EOF( name: {} address: socket_address: @@ -210,14 +211,14 @@ class AdsIntegrationTest : public AdsIntegrationBaseTest, filters: - name: envoy.http_connection_manager config: - stat_prefix: ads_test + stat_prefix: {} codec_type: HTTP2 rds: route_config_name: {} config_source: {{ ads: {{}} }} http_filters: [{{ name: envoy.router }}] )EOF", - name, Network::Test::getLoopbackAddressString(ipVersion()), route_config)); + name, Network::Test::getLoopbackAddressString(ipVersion()), stat_prefix, route_config)); } envoy::api::v2::RouteConfiguration buildRouteConfig(const std::string& name, @@ -471,6 +472,79 @@ TEST_P(AdsIntegrationTest, Failure) { makeSingleRequest(); } +// Regression test for the use-after-free crash when processing RDS update (#3953). +TEST_P(AdsIntegrationTest, RdsAfterLdsWithNoRdsChanges) { + initialize(); + + // Send initial configuration. + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {buildCluster("cluster_0")}, "1"); + sendDiscoveryResponse( + Config::TypeUrl::get().ClusterLoadAssignment, {buildClusterLoadAssignment("cluster_0")}, "1"); + sendDiscoveryResponse( + Config::TypeUrl::get().Listener, {buildListener("listener_0", "route_config_0")}, "1"); + sendDiscoveryResponse( + Config::TypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_0", "cluster_0")}, + "1"); + test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + + // Validate that we can process a request. + makeSingleRequest(); + + // Update existing LDS (change stat_prefix). + sendDiscoveryResponse( + Config::TypeUrl::get().Listener, {buildListener("listener_0", "route_config_0", "rds_crash")}, + "2"); + test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + + // Update existing RDS (no changes). + sendDiscoveryResponse( + Config::TypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_0", "cluster_0")}, + "2"); + + // Validate that we can process a request again + makeSingleRequest(); +} + +// Regression test for the use-after-free crash when processing RDS update (#3953). +TEST_P(AdsIntegrationTest, RdsAfterLdsWithRdsChange) { + initialize(); + + // Send initial configuration. + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {buildCluster("cluster_0")}, "1"); + sendDiscoveryResponse( + Config::TypeUrl::get().ClusterLoadAssignment, {buildClusterLoadAssignment("cluster_0")}, "1"); + sendDiscoveryResponse( + Config::TypeUrl::get().Listener, {buildListener("listener_0", "route_config_0")}, "1"); + sendDiscoveryResponse( + Config::TypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_0", "cluster_0")}, + "1"); + test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + + // Validate that we can process a request. + makeSingleRequest(); + + // Update existing LDS (change stat_prefix). + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {buildCluster("cluster_1")}, "2"); + sendDiscoveryResponse( + Config::TypeUrl::get().ClusterLoadAssignment, {buildClusterLoadAssignment("cluster_1")}, "2"); + sendDiscoveryResponse( + Config::TypeUrl::get().Listener, {buildListener("listener_0", "route_config_0", "rds_crash")}, + "2"); + test_server_->waitForCounterGe("listener_manager.listener_create_success", 2); + + // Update existing RDS (migrate traffic to cluster_1). + sendDiscoveryResponse( + Config::TypeUrl::get().RouteConfiguration, {buildRouteConfig("route_config_0", "cluster_1")}, + "2"); + + // Validate that we can process a request after RDS update + test_server_->waitForCounterGe("http.ads_test.rds.route_config_0.config_reload", 2); + makeSingleRequest(); +} + class AdsFailIntegrationTest : public AdsIntegrationBaseTest, public Grpc::GrpcClientIntegrationParamTest { public: From 324e628b7c85bcbeacfb49050dd7bfdcc86702b4 Mon Sep 17 00:00:00 2001 From: Venil Noronha Date: Fri, 27 Jul 2018 11:26:25 -0700 Subject: [PATCH 46/46] syscall: refactor address APIs for deeper errno latching (#3897) * Refactor address APIs for deeper errno latching The errno set by a syscall can be overwritten by code (e.g. logging) as it propagates up through the call stack. This commit refactors the bind and connect methods in the address API to allow for returning the errno from deeper down the call stack i.e. as soon as a syscall is performed. Signed-off-by: Venil Noronha --- include/envoy/api/os_sys_calls.h | 16 ++++++ include/envoy/network/BUILD | 1 + include/envoy/network/address.h | 18 +++---- source/common/network/address_impl.cc | 49 +++++++++++-------- source/common/network/address_impl.h | 12 ++--- source/common/network/connection_impl.cc | 16 +++--- source/common/network/listen_socket_impl.cc | 6 +-- .../stat_sinks/common/statsd/statsd.cc | 4 +- test/common/network/address_impl_test.cc | 18 +++---- test/common/network/dns_impl_test.cc | 4 +- test/mocks/network/mocks.h | 4 +- test/test_common/network_utility.cc | 44 ++++++----------- 12 files changed, 100 insertions(+), 92 deletions(-) diff --git a/include/envoy/api/os_sys_calls.h b/include/envoy/api/os_sys_calls.h index 648816a76b06..2d13094b4e7d 100644 --- a/include/envoy/api/os_sys_calls.h +++ b/include/envoy/api/os_sys_calls.h @@ -14,6 +14,22 @@ namespace Envoy { namespace Api { +/** + * SysCallResult holds the rc and errno values resulting from a system call. + */ +struct SysCallResult { + + /** + * The return code from the system call. + */ + int rc_; + + /** + * The errno value as captured after the system call. + */ + int errno_; +}; + class OsSysCalls { public: virtual ~OsSysCalls() {} diff --git a/include/envoy/network/BUILD b/include/envoy/network/BUILD index 31781824fa09..5f7e0ebd06ac 100644 --- a/include/envoy/network/BUILD +++ b/include/envoy/network/BUILD @@ -11,6 +11,7 @@ envoy_package() envoy_cc_library( name = "address_interface", hdrs = ["address.h"], + deps = ["//include/envoy/api:os_sys_calls_interface"], ) envoy_cc_library( diff --git a/include/envoy/network/address.h b/include/envoy/network/address.h index 949747484018..13004a1f19b0 100644 --- a/include/envoy/network/address.h +++ b/include/envoy/network/address.h @@ -8,6 +8,7 @@ #include #include +#include "envoy/api/os_sys_calls.h" #include "envoy/common/pure.h" #include "absl/numeric/int128.h" @@ -128,19 +129,19 @@ class Instance { * Bind a socket to this address. The socket should have been created with a call to socket() on * an Instance of the same address family. * @param fd supplies the platform socket handle. - * @return 0 for success and -1 for failure. The error code associated with a failure will - * be accessible in a plaform dependent fashion (e.g. errno for Unix platforms). + * @return a Api::SysCallResult with rc_ = 0 for success and rc_ = -1 for failure. If the call + * is successful, errno_ shouldn't be used. */ - virtual int bind(int fd) const PURE; + virtual Api::SysCallResult bind(int fd) const PURE; /** * Connect a socket to this address. The socket should have been created with a call to socket() * on this object. * @param fd supplies the platform socket handle. - * @return 0 for success and -1 for failure. The error code associated with a failure will - * be accessible in a plaform dependent fashion (e.g. errno for Unix platforms). + * @return a Api::SysCallResult with rc_ = 0 for success and rc_ = -1 for failure. If the call + * is successful, errno_ shouldn't be used. */ - virtual int connect(int fd) const PURE; + virtual Api::SysCallResult connect(int fd) const PURE; /** * @return the IP address information IFF type() == Type::Ip, otherwise nullptr. @@ -150,9 +151,8 @@ class Instance { /** * Create a socket for this address. * @param type supplies the socket type to create. - * @return the file descriptor naming the socket for success and -1 for failure. The error - * code associated with a failure will be accessible in a plaform dependent fashion (e.g. - * errno for Unix platforms). + * @return the file descriptor naming the socket. In case of a failure, the program would be + * aborted. */ virtual int socket(SocketType type) const PURE; diff --git a/source/common/network/address_impl.cc b/source/common/network/address_impl.cc index 6faf57ac090d..c7765e3ae957 100644 --- a/source/common/network/address_impl.cc +++ b/source/common/network/address_impl.cc @@ -212,14 +212,16 @@ bool Ipv4Instance::operator==(const Instance& rhs) const { (ip_.port() == rhs_casted->ip_.port())); } -int Ipv4Instance::bind(int fd) const { - return ::bind(fd, reinterpret_cast(&ip_.ipv4_.address_), - sizeof(ip_.ipv4_.address_)); +Api::SysCallResult Ipv4Instance::bind(int fd) const { + const int rc = ::bind(fd, reinterpret_cast(&ip_.ipv4_.address_), + sizeof(ip_.ipv4_.address_)); + return {rc, errno}; } -int Ipv4Instance::connect(int fd) const { - return ::connect(fd, reinterpret_cast(&ip_.ipv4_.address_), - sizeof(ip_.ipv4_.address_)); +Api::SysCallResult Ipv4Instance::connect(int fd) const { + const int rc = ::connect(fd, reinterpret_cast(&ip_.ipv4_.address_), + sizeof(ip_.ipv4_.address_)); + return {rc, errno}; } int Ipv4Instance::socket(SocketType type) const { return socketFromSocketType(type); } @@ -275,19 +277,20 @@ bool Ipv6Instance::operator==(const Instance& rhs) const { (ip_.port() == rhs_casted->ip_.port())); } -int Ipv6Instance::bind(int fd) const { - return ::bind(fd, reinterpret_cast(&ip_.ipv6_.address_), - sizeof(ip_.ipv6_.address_)); +Api::SysCallResult Ipv6Instance::bind(int fd) const { + const int rc = ::bind(fd, reinterpret_cast(&ip_.ipv6_.address_), + sizeof(ip_.ipv6_.address_)); + return {rc, errno}; } -int Ipv6Instance::connect(int fd) const { - return ::connect(fd, reinterpret_cast(&ip_.ipv6_.address_), - sizeof(ip_.ipv6_.address_)); +Api::SysCallResult Ipv6Instance::connect(int fd) const { + const int rc = ::connect(fd, reinterpret_cast(&ip_.ipv6_.address_), + sizeof(ip_.ipv6_.address_)); + return {rc, errno}; } int Ipv6Instance::socket(SocketType type) const { const int fd = socketFromSocketType(type); - // Setting IPV6_V6ONLY resticts the IPv6 socket to IPv6 connections only. const int v6only = ip_.v6only_; RELEASE_ASSERT(::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) != -1, ""); @@ -333,24 +336,28 @@ PipeInstance::PipeInstance(const std::string& pipe_path) : InstanceBase(Type::Pi bool PipeInstance::operator==(const Instance& rhs) const { return asString() == rhs.asString(); } -int PipeInstance::bind(int fd) const { +Api::SysCallResult PipeInstance::bind(int fd) const { if (abstract_namespace_) { - return ::bind(fd, reinterpret_cast(&address_), - offsetof(struct sockaddr_un, sun_path) + address_length_); + const int rc = ::bind(fd, reinterpret_cast(&address_), + offsetof(struct sockaddr_un, sun_path) + address_length_); + return {rc, errno}; } // Try to unlink an existing filesystem object at the requested path. Ignore // errors -- it's fine if the path doesn't exist, and if it exists but can't // be unlinked then `::bind()` will generate a reasonable errno. unlink(address_.sun_path); - return ::bind(fd, reinterpret_cast(&address_), sizeof(address_)); + const int rc = ::bind(fd, reinterpret_cast(&address_), sizeof(address_)); + return {rc, errno}; } -int PipeInstance::connect(int fd) const { +Api::SysCallResult PipeInstance::connect(int fd) const { if (abstract_namespace_) { - return ::connect(fd, reinterpret_cast(&address_), - offsetof(struct sockaddr_un, sun_path) + address_length_); + const int rc = ::connect(fd, reinterpret_cast(&address_), + offsetof(struct sockaddr_un, sun_path) + address_length_); + return {rc, errno}; } - return ::connect(fd, reinterpret_cast(&address_), sizeof(address_)); + const int rc = ::connect(fd, reinterpret_cast(&address_), sizeof(address_)); + return {rc, errno}; } int PipeInstance::socket(SocketType type) const { return socketFromSocketType(type); } diff --git a/source/common/network/address_impl.h b/source/common/network/address_impl.h index 0bb41d26e422..1e691ddee48a 100644 --- a/source/common/network/address_impl.h +++ b/source/common/network/address_impl.h @@ -97,8 +97,8 @@ class Ipv4Instance : public InstanceBase { // Network::Address::Instance bool operator==(const Instance& rhs) const override; - int bind(int fd) const override; - int connect(int fd) const override; + Api::SysCallResult bind(int fd) const override; + Api::SysCallResult connect(int fd) const override; const Ip* ip() const override { return &ip_; } int socket(SocketType type) const override; @@ -157,8 +157,8 @@ class Ipv6Instance : public InstanceBase { // Network::Address::Instance bool operator==(const Instance& rhs) const override; - int bind(int fd) const override; - int connect(int fd) const override; + Api::SysCallResult bind(int fd) const override; + Api::SysCallResult connect(int fd) const override; const Ip* ip() const override { return &ip_; } int socket(SocketType type) const override; @@ -214,8 +214,8 @@ class PipeInstance : public InstanceBase { // Network::Address::Instance bool operator==(const Instance& rhs) const override; - int bind(int fd) const override; - int connect(int fd) const override; + Api::SysCallResult bind(int fd) const override; + Api::SysCallResult connect(int fd) const override; const Ip* ip() const override { return nullptr; } int socket(SocketType type) const override; diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index b08c483ca8a7..073cead4dd66 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -551,10 +551,10 @@ ClientConnectionImpl::ClientConnectionImpl( } if (source_address != nullptr) { - const int rc = source_address->bind(fd()); - if (rc < 0) { + const Api::SysCallResult result = source_address->bind(fd()); + if (result.rc_ < 0) { ENVOY_LOG_MISC(debug, "Bind failure. Failed to bind to {}: {}", source_address->asString(), - strerror(errno)); + strerror(result.errno_)); bind_error_ = true; // Set a special error state to ensure asynchronous close to give the owner of the // ConnectionImpl a chance to add callbacks and detect the "disconnect". @@ -568,19 +568,19 @@ ClientConnectionImpl::ClientConnectionImpl( void ClientConnectionImpl::connect() { ENVOY_CONN_LOG(debug, "connecting to {}", *this, socket_->remoteAddress()->asString()); - const int rc = socket_->remoteAddress()->connect(fd()); - if (rc == 0) { + const Api::SysCallResult result = socket_->remoteAddress()->connect(fd()); + if (result.rc_ == 0) { // write will become ready. ASSERT(connecting_); } else { - ASSERT(rc == -1); - if (errno == EINPROGRESS) { + ASSERT(result.rc_ == -1); + if (result.errno_ == EINPROGRESS) { ASSERT(connecting_); ENVOY_CONN_LOG(debug, "connection in progress", *this); } else { immediate_error_event_ = ConnectionEvent::RemoteClose; connecting_ = false; - ENVOY_CONN_LOG(debug, "immediate connection error: {}", *this, errno); + ENVOY_CONN_LOG(debug, "immediate connection error: {}", *this, result.errno_); // Trigger a write event. This is needed on OSX and seems harmless on Linux. file_event_->activate(Event::FileReadyType::Write); diff --git a/source/common/network/listen_socket_impl.cc b/source/common/network/listen_socket_impl.cc index 3e79e13edc80..bc7e8a7858d9 100644 --- a/source/common/network/listen_socket_impl.cc +++ b/source/common/network/listen_socket_impl.cc @@ -16,11 +16,11 @@ namespace Envoy { namespace Network { void ListenSocketImpl::doBind() { - int rc = local_address_->bind(fd_); - if (rc == -1) { + const Api::SysCallResult result = local_address_->bind(fd_); + if (result.rc_ == -1) { close(); throw EnvoyException( - fmt::format("cannot bind '{}': {}", local_address_->asString(), strerror(errno))); + fmt::format("cannot bind '{}': {}", local_address_->asString(), strerror(result.errno_))); } if (local_address_->type() == Address::Type::Ip && local_address_->ip()->port() == 0) { // If the port we bind is zero, then the OS will pick a free port for us (assuming there are diff --git a/source/extensions/stat_sinks/common/statsd/statsd.cc b/source/extensions/stat_sinks/common/statsd/statsd.cc index 3326dcaedfc0..a026f8ab90c0 100644 --- a/source/extensions/stat_sinks/common/statsd/statsd.cc +++ b/source/extensions/stat_sinks/common/statsd/statsd.cc @@ -23,8 +23,8 @@ Writer::Writer(Network::Address::InstanceConstSharedPtr address) { fd_ = address->socket(Network::Address::SocketType::Datagram); ASSERT(fd_ != -1); - int rc = address->connect(fd_); - ASSERT(rc != -1); + const Api::SysCallResult result = address->connect(fd_); + ASSERT(result.rc_ != -1); } Writer::~Writer() { diff --git a/test/common/network/address_impl_test.cc b/test/common/network/address_impl_test.cc index 338ad1fe3e18..839f4437699c 100644 --- a/test/common/network/address_impl_test.cc +++ b/test/common/network/address_impl_test.cc @@ -64,9 +64,9 @@ void testSocketBindAndConnect(Network::Address::IpVersion ip_version, bool v6onl } // Bind the socket to the desired address and port. - const int rc = addr_port->bind(listen_fd); - const int err = errno; - ASSERT_EQ(rc, 0) << addr_port->asString() << "\nerror: " << strerror(err) << "\nerrno: " << err; + const Api::SysCallResult result = addr_port->bind(listen_fd); + ASSERT_EQ(result.rc_, 0) << addr_port->asString() << "\nerror: " << strerror(result.errno_) + << "\nerrno: " << result.errno_; // Do a bare listen syscall. Not bothering to accept connections as that would // require another thread. @@ -85,9 +85,9 @@ void testSocketBindAndConnect(Network::Address::IpVersion ip_version, bool v6onl makeFdBlocking(client_fd); // Connect to the server. - const int rc = addr_port->connect(client_fd); - const int err = errno; - ASSERT_EQ(rc, 0) << addr_port->asString() << "\nerror: " << strerror(err) << "\nerrno: " << err; + const Api::SysCallResult result = addr_port->connect(client_fd); + ASSERT_EQ(result.rc_, 0) << addr_port->asString() << "\nerror: " << strerror(result.errno_) + << "\nerrno: " << result.errno_; }; client_connect(addr_port); @@ -314,9 +314,9 @@ TEST(PipeInstanceTest, UnlinksExistingFile) { ASSERT_GE(listen_fd, 0) << address.asString(); ScopedFdCloser closer(listen_fd); - const int rc = address.bind(listen_fd); - const int err = errno; - ASSERT_EQ(rc, 0) << address.asString() << "\nerror: " << strerror(err) << "\nerrno: " << err; + const Api::SysCallResult result = address.bind(listen_fd); + ASSERT_EQ(result.rc_, 0) << address.asString() << "\nerror: " << strerror(result.errno_) + << "\nerrno: " << result.errno_; }; const std::string path = TestEnvironment::unixDomainSocketPath("UnlinksExistingFile.sock"); diff --git a/test/common/network/dns_impl_test.cc b/test/common/network/dns_impl_test.cc index f275a86fa733..d8de2de5b253 100644 --- a/test/common/network/dns_impl_test.cc +++ b/test/common/network/dns_impl_test.cc @@ -359,8 +359,8 @@ class CustomInstance : public Address::Instance { } const std::string& asString() const override { return antagonistic_name_; } const std::string& logicalName() const override { return antagonistic_name_; } - int bind(int fd) const override { return instance_.bind(fd); } - int connect(int fd) const override { return instance_.connect(fd); } + Api::SysCallResult bind(int fd) const override { return instance_.bind(fd); } + Api::SysCallResult connect(int fd) const override { return instance_.connect(fd); } const Address::Ip* ip() const override { return instance_.ip(); } int socket(Address::SocketType type) const override { return instance_.socket(type); } Address::Type type() const override { return instance_.type(); } diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index 8b21c1bc3c47..e5e820d4e036 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -409,8 +409,8 @@ class MockResolvedAddress : public Address::Instance { return asString() == other.asString(); } - MOCK_CONST_METHOD1(bind, int(int)); - MOCK_CONST_METHOD1(connect, int(int)); + MOCK_CONST_METHOD1(bind, Api::SysCallResult(int)); + MOCK_CONST_METHOD1(connect, Api::SysCallResult(int)); MOCK_CONST_METHOD0(ip, Address::Ip*()); MOCK_CONST_METHOD1(socket, int(Address::SocketType)); MOCK_CONST_METHOD0(type, Address::Type()); diff --git a/test/test_common/network_utility.cc b/test/test_common/network_utility.cc index ae42b3b4cbe2..76e190170ddb 100644 --- a/test/test_common/network_utility.cc +++ b/test/test_common/network_utility.cc @@ -27,29 +27,24 @@ Address::InstanceConstSharedPtr findOrCheckFreePort(Address::InstanceConstShared return nullptr; } const int fd = addr_port->socket(type); - if (fd < 0) { - const int err = errno; - ADD_FAILURE() << "socket failed for '" << addr_port->asString() - << "' with error: " << strerror(err) << " (" << err << ")"; - return nullptr; - } ScopedFdCloser closer(fd); // Not setting REUSEADDR, therefore if the address has been recently used we won't reuse it here. // However, because we're going to use the address while checking if it is available, we'll need // to set REUSEADDR on listener sockets created by tests using an address validated by this means. - int rc = addr_port->bind(fd); + Api::SysCallResult result = addr_port->bind(fd); + int err; const char* failing_fn = nullptr; - if (rc != 0) { + if (result.rc_ != 0) { + err = result.errno_; failing_fn = "bind"; } else if (type == Address::SocketType::Stream) { // Try listening on the port also, if the type is TCP. - rc = ::listen(fd, 1); - if (rc != 0) { + if (::listen(fd, 1) != 0) { + err = errno; failing_fn = "listen"; } } if (failing_fn != nullptr) { - const int err = errno; if (err == EADDRINUSE) { // The port is already in use. Perfectly normal. return nullptr; @@ -148,11 +143,7 @@ Address::InstanceConstSharedPtr getAnyAddress(const Address::IpVersion version, bool supportsIpVersion(const Address::IpVersion version) { Address::InstanceConstSharedPtr addr = getCanonicalLoopbackAddress(version); const int fd = addr->socket(Address::SocketType::Stream); - if (fd < 0) { - // Socket creation failed. - return false; - } - if (0 != addr->bind(fd)) { + if (0 != addr->bind(fd).rc_) { // Socket bind failed. RELEASE_ASSERT(::close(fd) == 0, ""); return false; @@ -164,23 +155,16 @@ bool supportsIpVersion(const Address::IpVersion version) { std::pair bindFreeLoopbackPort(Address::IpVersion version, Address::SocketType type) { Address::InstanceConstSharedPtr addr = getCanonicalLoopbackAddress(version); - const char* failing_fn = nullptr; const int fd = addr->socket(type); - if (fd < 0) { - failing_fn = "socket"; - } else if (0 != addr->bind(fd)) { - failing_fn = "bind"; - } else { - return std::make_pair(Address::addressFromFd(fd), fd); - } - const int err = errno; - if (fd >= 0) { + Api::SysCallResult result = addr->bind(fd); + if (0 != result.rc_) { close(fd); + std::string msg = fmt::format("bind failed for address {} with error: {} ({})", + addr->asString(), strerror(result.errno_), result.errno_); + ADD_FAILURE() << msg; + throw EnvoyException(msg); } - std::string msg = fmt::format("{} failed for address {} with error: {} ({})", failing_fn, - addr->asString(), strerror(err), err); - ADD_FAILURE() << msg; - throw EnvoyException(msg); + return std::make_pair(Address::addressFromFd(fd), fd); } TransportSocketPtr createRawBufferSocket() { return std::make_unique(); }