From f0144d9df572b929ff27b1d38924e279cef9848e Mon Sep 17 00:00:00 2001 From: code Date: Wed, 6 Sep 2023 07:55:33 +0800 Subject: [PATCH 01/55] generic proxy: file access log support (#29295) * generic proxy: file access log support Signed-off-by: wbpcode * fix format Signed-off-by: wbpcode * add test and fix build Signed-off-by: wbpcode * Kick CI Signed-off-by: wbpcode * add TODO Signed-off-by: wbpcode --------- Signed-off-by: wbpcode --- .../filters/network/generic_proxy/v3/BUILD | 1 + .../generic_proxy/v3/generic_proxy.proto | 6 +- .../filters/network/source/BUILD | 29 ++++ .../filters/network/source/access_log.cc | 162 ++++++++++++++++++ .../filters/network/source/access_log.h | 41 +++++ .../filters/network/source/config.cc | 12 +- .../filters/network/source/file_access_log.h | 124 ++++++++++++++ .../filters/network/source/interface/BUILD | 1 + .../network/source/interface/proxy_config.h | 6 + .../filters/network/source/proxy.cc | 5 + .../filters/network/source/proxy.h | 8 +- .../filters/network/test/config_test.cc | 47 +++++ .../filters/network/test/proxy_test.cc | 45 ++++- envoy/access_log/BUILD | 10 ++ envoy/access_log/access_log.h | 34 ++++ envoy/access_log/access_log_config.h | 88 ++++++++++ source/common/access_log/BUILD | 1 + source/common/access_log/access_log_impl.h | 45 +++++ 18 files changed, 656 insertions(+), 9 deletions(-) create mode 100644 contrib/generic_proxy/filters/network/source/access_log.cc create mode 100644 contrib/generic_proxy/filters/network/source/access_log.h create mode 100644 contrib/generic_proxy/filters/network/source/file_access_log.h create mode 100644 envoy/access_log/access_log_config.h diff --git a/api/contrib/envoy/extensions/filters/network/generic_proxy/v3/BUILD b/api/contrib/envoy/extensions/filters/network/generic_proxy/v3/BUILD index 6b5b06b72563..71c5730a78f0 100644 --- a/api/contrib/envoy/extensions/filters/network/generic_proxy/v3/BUILD +++ b/api/contrib/envoy/extensions/filters/network/generic_proxy/v3/BUILD @@ -6,6 +6,7 @@ licenses(["notice"]) # Apache 2 api_proto_package( deps = [ + "//envoy/config/accesslog/v3:pkg", "//envoy/config/core/v3:pkg", "//envoy/extensions/filters/network/http_connection_manager/v3:pkg", "@com_github_cncf_udpa//udpa/annotations:pkg", diff --git a/api/contrib/envoy/extensions/filters/network/generic_proxy/v3/generic_proxy.proto b/api/contrib/envoy/extensions/filters/network/generic_proxy/v3/generic_proxy.proto index 0c5cbd7e60ea..84a34265f736 100644 --- a/api/contrib/envoy/extensions/filters/network/generic_proxy/v3/generic_proxy.proto +++ b/api/contrib/envoy/extensions/filters/network/generic_proxy/v3/generic_proxy.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.filters.network.generic_proxy.v3; import "contrib/envoy/extensions/filters/network/generic_proxy/v3/route.proto"; +import "envoy/config/accesslog/v3/accesslog.proto"; import "envoy/config/core/v3/config_source.proto"; import "envoy/config/core/v3/extension.proto"; import "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto"; @@ -23,7 +24,7 @@ option (xds.annotations.v3.file_status).work_in_progress = true; // Generic proxy. // [#extension: envoy.filters.network.generic_proxy] -// [#next-free-field: 7] +// [#next-free-field: 8] message GenericProxy { // The human readable prefix to use when emitting statistics. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; @@ -51,6 +52,9 @@ message GenericProxy { // Tracing configuration for the generic proxy. http_connection_manager.v3.HttpConnectionManager.Tracing tracing = 6; + + // Configuration for :ref:`access logs ` emitted by generic proxy. + repeated config.accesslog.v3.AccessLog access_log = 7; } message GenericRds { diff --git a/contrib/generic_proxy/filters/network/source/BUILD b/contrib/generic_proxy/filters/network/source/BUILD index db06f8a9e373..e46ae6c4ba10 100644 --- a/contrib/generic_proxy/filters/network/source/BUILD +++ b/contrib/generic_proxy/filters/network/source/BUILD @@ -53,6 +53,7 @@ envoy_cc_contrib_extension( ":proxy_lib", "//contrib/generic_proxy/filters/network/source/router:config", "//envoy/server:filter_config_interface", + "//source/common/access_log:access_log_lib", "@envoy_api//contrib/envoy/extensions/filters/network/generic_proxy/v3:pkg_cc_proto", ], ) @@ -143,3 +144,31 @@ envoy_cc_library( "//envoy/stats:stats_macros", ], ) + +envoy_cc_library( + name = "file_access_log_lib", + hdrs = [ + "file_access_log.h", + ], + deps = [ + "//envoy/access_log:access_log_config_interface", + "//envoy/access_log:access_log_interface", + "//source/common/common:utility_lib", + "//source/common/formatter:substitution_format_string_lib", + "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "access_log_lib", + srcs = [ + "access_log.cc", + ], + hdrs = [ + "access_log.h", + ], + deps = [ + ":file_access_log_lib", + "//contrib/generic_proxy/filters/network/source/interface:stream_interface", + ], +) diff --git a/contrib/generic_proxy/filters/network/source/access_log.cc b/contrib/generic_proxy/filters/network/source/access_log.cc new file mode 100644 index 000000000000..b89a73780c8c --- /dev/null +++ b/contrib/generic_proxy/filters/network/source/access_log.cc @@ -0,0 +1,162 @@ +#include "contrib/generic_proxy/filters/network/source/access_log.h" + +#include "envoy/registry/registry.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace GenericProxy { + +class StringValueFormatterProvider : public FormatterProvider { +public: + using ValueExtractor = std::function(const FormatterContext&, + const StreamInfo::StreamInfo&)>; + + StringValueFormatterProvider(ValueExtractor f, absl::optional max_length = absl::nullopt) + : value_extractor_(f), max_length_(max_length) {} + + // FormatterProvider + absl::optional + formatWithContext(const FormatterContext& context, + const StreamInfo::StreamInfo& stream_info) const override { + auto optional_str = value_extractor_(context, stream_info); + if (!optional_str) { + return absl::nullopt; + } + if (max_length_.has_value()) { + if (optional_str->length() > max_length_.value()) { + optional_str->resize(max_length_.value()); + } + } + return optional_str; + } + ProtobufWkt::Value + formatValueWithContext(const FormatterContext& context, + const StreamInfo::StreamInfo& stream_info) const override { + return ValueUtil::optionalStringValue(formatWithContext(context, stream_info)); + } + +private: + ValueExtractor value_extractor_; + absl::optional max_length_; +}; + +class SimpleCommandParser : public CommandParser { +public: + using ProviderFunc = + std::function max_length)>; + using ProviderFuncTable = absl::flat_hash_map; + + // CommandParser + FormatterProviderPtr parse(const std::string& command, const std::string& command_arg, + absl::optional& max_length) const override { + const auto& provider_func_table = providerFuncTable(); + const auto func_iter = provider_func_table.find(std::string(command)); + if (func_iter == provider_func_table.end()) { + return nullptr; + } + return func_iter->second(command_arg, max_length); + } + +private: + static const ProviderFuncTable& providerFuncTable() { + CONSTRUCT_ON_FIRST_USE( + ProviderFuncTable, + { + {"METHOD", + [](absl::string_view, absl::optional) -> FormatterProviderPtr { + return std::make_unique( + [](const FormatterContext& context, + const StreamInfo::StreamInfo&) -> absl::optional { + if (context.request_) { + return std::string(context.request_->method()); + } + return absl::nullopt; + }); + }}, + {"HOST", + [](absl::string_view, absl::optional) -> FormatterProviderPtr { + return std::make_unique( + [](const FormatterContext& context, + const StreamInfo::StreamInfo&) -> absl::optional { + if (context.request_) { + return std::string(context.request_->host()); + } + return absl::nullopt; + }); + }}, + {"PATH", + [](absl::string_view, absl::optional) -> FormatterProviderPtr { + return std::make_unique( + [](const FormatterContext& context, + const StreamInfo::StreamInfo&) -> absl::optional { + if (context.request_) { + return std::string(context.request_->path()); + } + return absl::nullopt; + }); + }}, + {"PROTOCOL", + [](absl::string_view, absl::optional) -> FormatterProviderPtr { + return std::make_unique( + [](const FormatterContext& context, + const StreamInfo::StreamInfo&) -> absl::optional { + if (context.request_) { + return std::string(context.request_->protocol()); + } + return absl::nullopt; + }); + }}, + {"REQUEST_PROPERTY", + [](absl::string_view command_arg, absl::optional) -> FormatterProviderPtr { + return std::make_unique( + [key = std::string(command_arg)]( + const FormatterContext& context, + const StreamInfo::StreamInfo&) -> absl::optional { + if (!context.request_) { + return absl::nullopt; + } + auto optional_view = context.request_->getByKey(key); + if (!optional_view.has_value()) { + return absl::nullopt; + } + return std::string(optional_view.value()); + }); + }}, + {"RESPONSE_PROPERTY", + [](absl::string_view command_arg, absl::optional) -> FormatterProviderPtr { + return std::make_unique( + [key = std::string(command_arg)]( + const FormatterContext& context, + const StreamInfo::StreamInfo&) -> absl::optional { + if (!context.response_) { + return absl::nullopt; + } + auto optional_view = context.response_->getByKey(key); + if (!optional_view.has_value()) { + return absl::nullopt; + } + return std::string(optional_view.value()); + }); + }}, + }); + } +}; + +// Register the access log for the FormatterContext. +REGISTER_FACTORY(FileAccessLogFactory, AccessLogInstanceFactory); + +} // namespace GenericProxy +} // namespace NetworkFilters +} // namespace Extensions + +namespace Formatter { + +using FormatterContext = Extensions::NetworkFilters::GenericProxy::FormatterContext; +using SimpleCommandParser = Extensions::NetworkFilters::GenericProxy::SimpleCommandParser; +// Regiter the built-in command parsers for the FormatterContext. +REGISTER_BUILT_IN_COMMAND_PARSER(FormatterContext, SimpleCommandParser); + +} // namespace Formatter + +} // namespace Envoy diff --git a/contrib/generic_proxy/filters/network/source/access_log.h b/contrib/generic_proxy/filters/network/source/access_log.h new file mode 100644 index 000000000000..69a725082db1 --- /dev/null +++ b/contrib/generic_proxy/filters/network/source/access_log.h @@ -0,0 +1,41 @@ +#pragma once + +#include "contrib/generic_proxy/filters/network/source/file_access_log.h" +#include "contrib/generic_proxy/filters/network/source/interface/stream.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace GenericProxy { + +struct FormatterContext { + const Request* request_{}; + const Response* response_{}; + + static constexpr absl::string_view category() { return "generic_proxy"; } +}; + +// Formatter for generic proxy. +using Formatter = Formatter::FormatterBase; +using FormatterProvider = Envoy::Formatter::FormatterProviderBase; +using FormatterProviderPtr = std::unique_ptr; +using CommandParser = Envoy::Formatter::CommandParserBase; +using CommandParserFactory = Envoy::Formatter::CommandParserFactoryBase; +using BuiltInCommandParsers = Envoy::Formatter::BuiltInCommandParsersBase; + +// Access log for generic proxy. +using AccessLogFilter = AccessLog::FilterBase; +using AccessLogFilterPtr = std::unique_ptr; +using AccessLogFilterFactory = AccessLog::ExtensionFilterFactoryBase; +using AccessLogInstance = AccessLog::InstanceBase; +using AccessLogInstanceSharedPtr = std::shared_ptr; +using AccessLogInstanceFactory = AccessLog::AccessLogInstanceFactoryBase; + +// File access log for generic proxy. +using FileAccessLog = FileAccessLogBase; +using FileAccessLogFactory = FileAccessLogFactoryBase; + +} // namespace GenericProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/generic_proxy/filters/network/source/config.cc b/contrib/generic_proxy/filters/network/source/config.cc index 9bbd71d133d8..1725dd262bdb 100644 --- a/contrib/generic_proxy/filters/network/source/config.cc +++ b/contrib/generic_proxy/filters/network/source/config.cc @@ -1,7 +1,9 @@ #include "contrib/generic_proxy/filters/network/source/config.h" +#include "source/common/access_log/access_log_impl.h" #include "source/common/tracing/tracer_manager_impl.h" +#include "access_log.h" #include "contrib/generic_proxy/filters/network/source/rds.h" #include "contrib/generic_proxy/filters/network/source/rds_impl.h" @@ -106,11 +108,19 @@ Factory::createFilterFactoryFromProtoTyped(const ProxyConfig& proto_config, context.direction(), proto_config.tracing()); } + // Access log configuration. + std::vector access_logs; + for (const auto& access_log : proto_config.access_log()) { + AccessLogInstanceSharedPtr current_access_log = + AccessLog::AccessLogFactory::accessLoggerFromProto(access_log, context); + access_logs.push_back(current_access_log); + } + const FilterConfigSharedPtr config = std::make_shared( proto_config.stat_prefix(), std::move(factories.first), routeConfigProviderFromProto(proto_config, context, *route_config_provider_manager), filtersFactoryFromProto(proto_config.filters(), proto_config.stat_prefix(), context), - std::move(tracer), std::move(tracing_config), context); + std::move(tracer), std::move(tracing_config), std::move(access_logs), context); return [route_config_provider_manager, tracer_manager, config, &context, custom_proxy_factory](Envoy::Network::FilterManager& filter_manager) -> void { diff --git a/contrib/generic_proxy/filters/network/source/file_access_log.h b/contrib/generic_proxy/filters/network/source/file_access_log.h new file mode 100644 index 000000000000..084d460fb775 --- /dev/null +++ b/contrib/generic_proxy/filters/network/source/file_access_log.h @@ -0,0 +1,124 @@ +#pragma once + +#include "envoy/access_log/access_log.h" +#include "envoy/access_log/access_log_config.h" +#include "envoy/extensions/access_loggers/file/v3/file.pb.h" +#include "envoy/extensions/access_loggers/file/v3/file.pb.validate.h" + +#include "source/common/common/utility.h" +#include "source/common/formatter/substitution_format_string.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace GenericProxy { + +template class FileAccessLogBase : public AccessLog::InstanceBase { +public: + FileAccessLogBase(const Filesystem::FilePathAndType& access_log_file_info, + AccessLog::FilterBasePtr&& filter, + Formatter::FormatterBasePtr&& formatter, + AccessLog::AccessLogManager& log_manager) + : filter_(std::move(filter)), formatter_(std::move(formatter)) { + log_file_ = log_manager.createAccessLog(access_log_file_info); + } + + void log(const Context& context, const StreamInfo::StreamInfo& stream_info) override { + if (filter_ != nullptr && !filter_->evaluate(context, stream_info)) { + return; + } + log_file_->write(formatter_->formatWithContext(context, stream_info)); + } + +private: + AccessLog::AccessLogFileSharedPtr log_file_; + AccessLog::FilterBasePtr filter_; + Formatter::FormatterBasePtr formatter_; +}; + +template +class FileAccessLogFactoryBase : public AccessLog::AccessLogInstanceFactoryBase { +public: + FileAccessLogFactoryBase() + : name_(fmt::format("envoy.{}.access_loggers.file", Context::category())) {} + + AccessLog::InstanceBaseSharedPtr + createAccessLogInstance(const Protobuf::Message& config, + AccessLog::FilterBasePtr&& filter, + Server::Configuration::CommonFactoryContext& context) override { + const auto& typed_config = MessageUtil::downcastAndValidate< + const envoy::extensions::access_loggers::file::v3::FileAccessLog&>( + config, context.messageValidationVisitor()); + + Formatter::FormatterBasePtr formatter; + + switch (typed_config.access_log_format_case()) { + case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase::kFormat: + if (typed_config.format().empty()) { + formatter = getDefaultFormatter(); + } else { + envoy::config::core::v3::SubstitutionFormatString sff_config; + sff_config.mutable_text_format_source()->set_inline_string(typed_config.format()); + formatter = + Formatter::SubstitutionFormatStringUtils::fromProtoConfig(sff_config, context); + } + break; + case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase:: + kJsonFormat: + formatter = Formatter::SubstitutionFormatStringUtils::createJsonFormatter( + typed_config.json_format(), false, false, false); + break; + case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase:: + kTypedJsonFormat: { + envoy::config::core::v3::SubstitutionFormatString sff_config; + *sff_config.mutable_json_format() = typed_config.typed_json_format(); + formatter = + Formatter::SubstitutionFormatStringUtils::fromProtoConfig(sff_config, context); + break; + } + case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase:: + kLogFormat: + formatter = Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + typed_config.log_format(), context); + break; + case envoy::extensions::access_loggers::file::v3::FileAccessLog::AccessLogFormatCase:: + ACCESS_LOG_FORMAT_NOT_SET: + formatter = getDefaultFormatter(); + break; + } + if (formatter == nullptr) { + ExceptionUtil::throwEnvoyException( + "Access log: no format and no default format for file access log"); + } + + Filesystem::FilePathAndType file_info{Filesystem::DestinationType::File, typed_config.path()}; + return std::make_shared>( + file_info, std::move(filter), std::move(formatter), context.accessLogManager()); + } + + AccessLog::InstanceBaseSharedPtr createAccessLogInstance( + const Protobuf::Message& config, AccessLog::FilterBasePtr&& filter, + Server::Configuration::ListenerAccessLogFactoryContext& context) override { + return createAccessLogInstance( + config, std::move(filter), + static_cast(context)); + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return ProtobufTypes::MessagePtr{ + new envoy::extensions::access_loggers::file::v3::FileAccessLog()}; + } + + std::string name() const override { return name_; } + +protected: + virtual Formatter::FormatterBasePtr getDefaultFormatter() const { return nullptr; } + +private: + const std::string name_; +}; + +} // namespace GenericProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/generic_proxy/filters/network/source/interface/BUILD b/contrib/generic_proxy/filters/network/source/interface/BUILD index daaa17adde56..61d2812c51c5 100644 --- a/contrib/generic_proxy/filters/network/source/interface/BUILD +++ b/contrib/generic_proxy/filters/network/source/interface/BUILD @@ -95,6 +95,7 @@ envoy_cc_library( ":codec_interface", ":filter_interface", ":route_interface", + "//contrib/generic_proxy/filters/network/source:access_log_lib", "//envoy/tracing:trace_config_interface", "//envoy/tracing:tracer_interface", ], diff --git a/contrib/generic_proxy/filters/network/source/interface/proxy_config.h b/contrib/generic_proxy/filters/network/source/interface/proxy_config.h index e3c4b6b2ad9e..08aa92bce14f 100644 --- a/contrib/generic_proxy/filters/network/source/interface/proxy_config.h +++ b/contrib/generic_proxy/filters/network/source/interface/proxy_config.h @@ -3,6 +3,7 @@ #include "envoy/tracing/trace_config.h" #include "envoy/tracing/tracer.h" +#include "contrib/generic_proxy/filters/network/source/access_log.h" #include "contrib/generic_proxy/filters/network/source/interface/codec.h" #include "contrib/generic_proxy/filters/network/source/interface/filter.h" #include "contrib/generic_proxy/filters/network/source/interface/route.h" @@ -51,6 +52,11 @@ class FilterConfig : public FilterChainFactory { * @return stats to use. */ virtual GenericFilterStats& stats() PURE; + + /** + * @return const std::vector& access logs. + */ + virtual const std::vector& accessLogs() const PURE; }; } // namespace GenericProxy diff --git a/contrib/generic_proxy/filters/network/source/proxy.cc b/contrib/generic_proxy/filters/network/source/proxy.cc index 97119374c0ff..3dd7fb484023 100644 --- a/contrib/generic_proxy/filters/network/source/proxy.cc +++ b/contrib/generic_proxy/filters/network/source/proxy.cc @@ -193,6 +193,11 @@ void ActiveStream::completeRequest() { *this, false); } + for (const auto& access_log : parent_.config_->accessLogs()) { + access_log->log({downstream_request_stream_.get(), local_or_upstream_response_stream_.get()}, + stream_info_); + } + for (auto& filter : decoder_filters_) { filter->filter_->onDestroy(); } diff --git a/contrib/generic_proxy/filters/network/source/proxy.h b/contrib/generic_proxy/filters/network/source/proxy.h index 19a64e965235..cbfdaf66b069 100644 --- a/contrib/generic_proxy/filters/network/source/proxy.h +++ b/contrib/generic_proxy/filters/network/source/proxy.h @@ -56,13 +56,14 @@ class FilterConfigImpl : public FilterConfig { Rds::RouteConfigProviderSharedPtr route_config_provider, std::vector factories, Tracing::TracerSharedPtr tracer, Tracing::ConnectionManagerTracingConfigPtr tracing_config, + std::vector&& access_logs, Envoy::Server::Configuration::FactoryContext& context) : stat_prefix_(stat_prefix), stats_(GenericFilterStats::generateStats(stat_prefix_, context.scope())), codec_factory_(std::move(codec)), route_config_provider_(std::move(route_config_provider)), factories_(std::move(factories)), drain_decision_(context.drainDecision()), tracer_(std::move(tracer)), tracing_config_(std::move(tracing_config)), - time_source_(context.timeSource()) {} + access_logs_(std::move(access_logs)), time_source_(context.timeSource()) {} // FilterConfig RouteEntryConstSharedPtr routeEntry(const Request& request) const override { @@ -77,8 +78,10 @@ class FilterConfigImpl : public FilterConfig { OptRef tracingConfig() const override { return makeOptRefFromPtr(tracing_config_.get()); } - GenericFilterStats& stats() override { return stats_; } + const std::vector& accessLogs() const override { + return access_logs_; + } // FilterChainFactory void createFilterChain(FilterChainManager& manager) override { @@ -104,6 +107,7 @@ class FilterConfigImpl : public FilterConfig { Tracing::TracerSharedPtr tracer_; Tracing::ConnectionManagerTracingConfigPtr tracing_config_; + std::vector access_logs_; TimeSource& time_source_; }; diff --git a/contrib/generic_proxy/filters/network/test/config_test.cc b/contrib/generic_proxy/filters/network/test/config_test.cc index eede52caf229..f0705ffba751 100644 --- a/contrib/generic_proxy/filters/network/test/config_test.cc +++ b/contrib/generic_proxy/filters/network/test/config_test.cc @@ -383,6 +383,53 @@ TEST(BasicFilterConfigTest, TestConfigurationWithTracing) { cb(filter_manager); } +TEST(BasicFilterConfigTest, TestConfigurationWithAccessLog) { + const std::string config_yaml = R"EOF( + stat_prefix: ingress + filters: + - name: envoy.filters.generic.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.generic_proxy.router.v3.Router + codec_config: + name: mock + typed_config: + "@type": type.googleapis.com/xds.type.v3.TypedStruct + type_url: envoy.generic_proxy.codecs.mock.type + value: {} + generic_rds: + config_source: { resource_api_version: V3, ads: {} } + route_config_name: test_route + access_log: + - name: envoy.generic_proxy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: "/dev/stdout" + log_format: + text_format_source: + inline_string: "%METHOD% %PATH% %HOST% %PROTOCOL% %REQUEST_PROPERTY(key)% RESPONSE_PROPERTY(key)\n" + )EOF"; + + NiceMock codec_factory_config; + Registry::InjectFactory registration(codec_factory_config); + + NiceMock factory_context; + + Factory factory; + + envoy::extensions::filters::network::generic_proxy::v3::GenericProxy config; + TestUtility::loadFromYaml(config_yaml, config); + + auto mock_codec_factory = std::make_unique>(); + + EXPECT_CALL(codec_factory_config, createCodecFactory(_, _)) + .WillOnce(Return(testing::ByMove(std::move(mock_codec_factory)))); + + Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(config, factory_context); + EXPECT_NE(nullptr, cb); + NiceMock filter_manager; + cb(filter_manager); +} + } // namespace } // namespace GenericProxy } // namespace NetworkFilters diff --git a/contrib/generic_proxy/filters/network/test/proxy_test.cc b/contrib/generic_proxy/filters/network/test/proxy_test.cc index 56f069499686..d75797f9c90e 100644 --- a/contrib/generic_proxy/filters/network/test/proxy_test.cc +++ b/contrib/generic_proxy/filters/network/test/proxy_test.cc @@ -26,6 +26,11 @@ namespace NetworkFilters { namespace GenericProxy { namespace { +static const std::string DEFAULT_LOG_FORMAT = + "%HOST% %PATH% %METHOD% %PROTOCOL% %REQUEST_PROPERTY(request-key)% " + "%RESPONSE_PROPERTY(response-key)% " + "%REQUEST_PROPERTY(non-exist-key)%"; + class MockRouteConfigProvider : public Rds::RouteConfigProvider { public: MockRouteConfigProvider() { ON_CALL(*this, config()).WillByDefault(Return(route_config_)); } @@ -40,7 +45,7 @@ class MockRouteConfigProvider : public Rds::RouteConfigProvider { class FilterConfigTest : public testing::Test { public: - void initializeFilterConfig(bool with_tracing = false) { + void initializeFilterConfig(bool with_tracing = false, AccessLogInstanceSharedPtr logger = {}) { if (with_tracing) { tracer_ = std::make_shared>(); @@ -80,9 +85,26 @@ class FilterConfigTest : public testing::Test { mock_route_entry_ = std::make_shared>(); + std::vector access_logs; + if (logger) { + access_logs.push_back(logger); + } + filter_config_ = std::make_shared( "test_prefix", std::move(codec_factory), route_config_provider_, factories, tracer_, - std::move(tracing_config_), factory_context_); + std::move(tracing_config_), std::move(access_logs), factory_context_); + } + + AccessLogInstanceSharedPtr loggerFormFormat(const std::string& format = DEFAULT_LOG_FORMAT) { + envoy::config::core::v3::SubstitutionFormatString sff_config; + sff_config.mutable_text_format_source()->set_inline_string(format); + auto formatter = + Envoy::Formatter::SubstitutionFormatStringUtils::fromProtoConfig( + sff_config, factory_context_); + + return std::make_shared(Filesystem::FilePathAndType{}, nullptr, + std::move(formatter), + factory_context_.accessLogManager()); } std::shared_ptr> tracer_; @@ -154,8 +176,9 @@ TEST_F(FilterConfigTest, CodecFactory) { class FilterTest : public FilterConfigTest { public: - void initializeFilter(bool with_tracing = false, bool bind_upstream = false) { - FilterConfigTest::initializeFilterConfig(with_tracing); + void initializeFilter(bool with_tracing = false, bool bind_upstream = false, + AccessLogInstanceSharedPtr logger = {}) { + FilterConfigTest::initializeFilterConfig(with_tracing, logger); auto encoder = std::make_unique>(); encoder_ = encoder.get(); @@ -675,9 +698,15 @@ TEST_F(FilterTest, NewStreamAndReplyNormally) { auto mock_decoder_filter_0 = std::make_shared>(); mock_decoder_filters_ = {{"mock_0", mock_decoder_filter_0}}; - initializeFilter(); + // The logger is used to test the log format. + initializeFilter(false, false, loggerFormFormat()); auto request = std::make_unique(); + request->host_ = "host-value"; + request->path_ = "/path-value"; + request->method_ = "method-value"; + request->protocol_ = "protocol-value"; + request->data_["request-key"] = "request-value"; filter_->newDownstreamRequest(std::move(request), ExtendedOptions()); EXPECT_EQ(1, filter_->activeStreamsForTest().size()); @@ -693,10 +722,16 @@ TEST_F(FilterTest, NewStreamAndReplyNormally) { callback.onEncodingSuccess(buffer); })); + EXPECT_CALL( + *factory_context_.access_log_manager_.file_, + write("host-value /path-value method-value protocol-value request-value response-value -")); + EXPECT_CALL(factory_context_.drain_manager_, drainClose()).WillOnce(Return(false)); EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)); auto response = std::make_unique(); + response->data_["response-key"] = "response-value"; + active_stream->upstreamResponse(std::move(response), ExtendedOptions()); } diff --git a/envoy/access_log/BUILD b/envoy/access_log/BUILD index 2efbbd196f75..207c63e5a25e 100644 --- a/envoy/access_log/BUILD +++ b/envoy/access_log/BUILD @@ -20,3 +20,13 @@ envoy_cc_library( "@envoy_api//envoy/data/accesslog/v3:pkg_cc_proto", ], ) + +envoy_cc_library( + name = "access_log_config_interface", + hdrs = ["access_log_config.h"], + deps = [ + "//envoy/access_log:access_log_interface", + "//envoy/server:filter_config_interface", + "//source/common/protobuf", + ], +) diff --git a/envoy/access_log/access_log.h b/envoy/access_log/access_log.h index ff0caadad5ca..6bc75b9c398c 100644 --- a/envoy/access_log/access_log.h +++ b/envoy/access_log/access_log.h @@ -57,6 +57,40 @@ class AccessLogManager { using AccessLogManagerPtr = std::unique_ptr; using AccessLogType = envoy::data::accesslog::v3::AccessLogType; +/** + * Templated interface for access log filters. + */ +template class FilterBase { +public: + virtual ~FilterBase() = default; + + /** + * Evaluate whether an access log should be written based on request and response data. + * @return TRUE if the log should be written. + */ + virtual bool evaluate(const Context& context, const StreamInfo::StreamInfo& info) const PURE; +}; +template using FilterBasePtr = std::unique_ptr>; + +/** + * Templated interface for access log instances. + * TODO(wbpcode): refactor existing access log instances and related other interfaces to use this + * interface. See https://github.com/envoyproxy/envoy/issues/28773. + */ +template class InstanceBase { +public: + virtual ~InstanceBase() = default; + + /** + * Log a completed request. + * @param context supplies the context for the log. + * @param stream_info supplies additional information about the request not + * contained in the request headers. + */ + virtual void log(const Context& context, const StreamInfo::StreamInfo& stream_info) PURE; +}; +template using InstanceBaseSharedPtr = std::shared_ptr>; + /** * Interface for access log filters. */ diff --git a/envoy/access_log/access_log_config.h b/envoy/access_log/access_log_config.h new file mode 100644 index 000000000000..ee0810aa6e30 --- /dev/null +++ b/envoy/access_log/access_log_config.h @@ -0,0 +1,88 @@ +#pragma once + +#include + +#include "envoy/access_log/access_log.h" +#include "envoy/config/typed_config.h" +#include "envoy/server/filter_config.h" + +#include "source/common/protobuf/protobuf.h" + +namespace Envoy { +namespace AccessLog { + +/** + * Extension filter factory that reads from ExtensionFilter proto. + */ +template class ExtensionFilterFactoryBase : public Config::TypedFactory { +public: + ExtensionFilterFactoryBase() + : category_(fmt::format("envoy.{}.access_loggers.extension_filters", Context::category())) {} + + ~ExtensionFilterFactoryBase() override = default; + + /** + * Create a particular extension filter implementation from a config proto. If the + * implementation is unable to produce a filter with the provided parameters, it should throw an + * EnvoyException. The returned pointer should never be nullptr. + * @param config supplies the custom configuration for this filter type. + * @param context supplies the server factory context. + * @return an instance of extension filter implementation from a config proto. + */ + virtual FilterBasePtr + createFilter(const Protobuf::Message& config, + Server::Configuration::CommonFactoryContext& context) PURE; + + std::string category() const override { return category_; } + +private: + const std::string category_; +}; + +/** + * Implemented for each AccessLog::Instance and registered via Registry::registerFactory or the + * convenience class RegisterFactory. + */ +template class AccessLogInstanceFactoryBase : public Config::TypedFactory { +public: + AccessLogInstanceFactoryBase() + : category_(fmt::format("envoy.{}.access_loggers", Context::category())) {} + + ~AccessLogInstanceFactoryBase() override = default; + + /** + * Create a particular AccessLog::Instance implementation from a config proto. If the + * implementation is unable to produce a factory with the provided parameters, it should throw an + * EnvoyException. The returned pointer should never be nullptr. + * @param config the custom configuration for this access log type. + * @param filter filter to determine whether a particular request should be logged. If no filter + * was specified in the configuration, argument will be nullptr. + * @param context access log context through which persistent resources can be accessed. + */ + virtual AccessLog::InstanceBaseSharedPtr + createAccessLogInstance(const Protobuf::Message& config, + AccessLog::FilterBasePtr&& filter, + Server::Configuration::ListenerAccessLogFactoryContext& context) PURE; + + /** + * Create a particular AccessLog::Instance implementation from a config proto. If the + * implementation is unable to produce a factory with the provided parameters, it should throw an + * EnvoyException. The returned pointer should never be nullptr. + * @param config the custom configuration for this access log type. + * @param filter filter to determine whether a particular request should be logged. If no filter + * was specified in the configuration, argument will be nullptr. + * @param context general filter context through which persistent resources can be accessed. + */ + virtual AccessLog::InstanceBaseSharedPtr + createAccessLogInstance(const Protobuf::Message& config, + AccessLog::FilterBasePtr&& filter, + Server::Configuration::CommonFactoryContext& context) PURE; + + std::string category() const override { return category_; } + +private: + const std::string category_; +}; + +} // namespace AccessLog +} // namespace Envoy diff --git a/source/common/access_log/BUILD b/source/common/access_log/BUILD index 0ba22ffe9a4d..92959e853067 100644 --- a/source/common/access_log/BUILD +++ b/source/common/access_log/BUILD @@ -16,6 +16,7 @@ envoy_cc_library( "abseil_hash", ], deps = [ + "//envoy/access_log:access_log_config_interface", "//envoy/access_log:access_log_interface", "//envoy/config:typed_config_interface", "//envoy/filesystem:filesystem_interface", diff --git a/source/common/access_log/access_log_impl.h b/source/common/access_log/access_log_impl.h index 0a0fe9a05775..a60a09bbcb56 100644 --- a/source/common/access_log/access_log_impl.h +++ b/source/common/access_log/access_log_impl.h @@ -5,6 +5,7 @@ #include #include "envoy/access_log/access_log.h" +#include "envoy/access_log/access_log_config.h" #include "envoy/common/random_generator.h" #include "envoy/config/accesslog/v3/accesslog.pb.h" #include "envoy/config/typed_config.h" @@ -13,6 +14,8 @@ #include "envoy/type/v3/percent.pb.h" #include "source/common/common/matchers.h" +#include "source/common/common/utility.h" +#include "source/common/config/utility.h" #include "source/common/grpc/status.h" #include "source/common/http/header_utility.h" #include "source/common/protobuf/protobuf.h" @@ -305,6 +308,48 @@ class AccessLogFactory { */ static InstanceSharedPtr fromProto(const envoy::config::accesslog::v3::AccessLog& config, Server::Configuration::CommonFactoryContext& context); + + /** + * Template method to create an access log filter from proto configuration for non-HTTP access + * loggers. + */ + template + static FilterBasePtr + accessLogFilterFromProto(const envoy::config::accesslog::v3::AccessLogFilter& config, + Server::Configuration::CommonFactoryContext& context) { + if (!config.has_extension_filter()) { + ExceptionUtil::throwEnvoyException( + "Access log filter: only extension filter is supported by non-HTTP access loggers."); + } + + auto& factory = Config::Utility::getAndCheckFactory>( + config.extension_filter()); + auto typed_filter_config = Config::Utility::translateToFactoryConfig( + config.extension_filter(), context.messageValidationVisitor(), factory); + + return factory.createFilter(*typed_filter_config, context); + } + + /** + * Template method to create an access logger instance from proto configuration for non-HTTP + * access loggers. + */ + template + static InstanceBaseSharedPtr + accessLoggerFromProto(const envoy::config::accesslog::v3::AccessLog& config, + Server::Configuration::CommonFactoryContext& context) { + FilterBasePtr filter; + if (config.has_filter()) { + filter = accessLogFilterFromProto(config.filter(), context); + } + + auto& factory = + Config::Utility::getAndCheckFactory>(config); + ProtobufTypes::MessagePtr message = Config::Utility::translateToFactoryConfig( + config, context.messageValidationVisitor(), factory); + + return factory.createAccessLogInstance(*message, std::move(filter), context); + } }; } // namespace AccessLog From 1107a433516b083497b1482900aedf8e7493f44e Mon Sep 17 00:00:00 2001 From: Anurag Aggarwal Date: Wed, 6 Sep 2023 08:37:36 +0530 Subject: [PATCH 02/55] dns: [fix] cares resolver should respect cname ttl (#29231) * dns: [fix] cares resolver should respect cname ttl cares currently only uses node ttl to in the DNS response which completely ignores the cname ttl. In case cname has a lower ttl, the updates would not be applied. Thus to always ensure the records are updated, we use minimum ttl of cname and node. Fixes #28925 Signed-off-by: Anurag Aggarwal * add cname to spelling dictionary Signed-off-by: Anurag Aggarwal * update comment Signed-off-by: Anurag Aggarwal * add tests for zero and max_ttl Signed-off-by: Anurag Aggarwal * * add changelogs * DNS Response for AddressInfo bounds ttl values to [0, 2^31-1] - RFC 2181 * update test cases Signed-off-by: Anurag Aggarwal * add minMax logic to constructor Signed-off-by: Anurag Aggarwal --------- Signed-off-by: Anurag Aggarwal --- changelogs/current.yaml | 9 +++ envoy/network/dns.h | 5 +- .../network/dns_resolver/cares/dns_impl.cc | 11 +++- .../dns_resolver/cares/dns_impl_test.cc | 63 ++++++++++++++++--- tools/spelling/spelling_dictionary.txt | 1 + 5 files changed, 77 insertions(+), 12 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index b9ff2242af86..cbd08feade01 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -64,6 +64,15 @@ bug_fixes: - area: tls change: | fixed a bug where handshake may fail when both private key provider and cert validation are set. +- area: dns + change: | + Fixed a bug where when respect_dns_ttl was set to true, c-ares dns resolver only considered address record for ttl calculation + while ignoring CNAME records TTL. Now when respect_dns_ttl is set to true minimum of all TTL records is considered. +- area: dns + change: | + Fixed a bug where dns response was not always conforming [RFC 2181](https://datatracker.ietf.org/doc/html/rfc2181) for TTL values. + Previously a malicious user could add a TTL greater than 2^31 - 1, and with c-ares library using 32 bit signed int data type + would overflow and send a negative TTL. - area: healthcheck change: | The default behavior of unejecting outlier-detection-ejected host on successful active health checking can diff --git a/envoy/network/dns.h b/envoy/network/dns.h index c69b69eb164d..d3e7504b02a9 100644 --- a/envoy/network/dns.h +++ b/envoy/network/dns.h @@ -62,7 +62,10 @@ enum class DnsLookupFamily { V4Only, V6Only, Auto, V4Preferred, All }; class DnsResponse { public: DnsResponse(const Address::InstanceConstSharedPtr& address, const std::chrono::seconds ttl) - : response_(AddrInfoResponse{address, ttl}) {} + : response_(AddrInfoResponse{ + address, + std::chrono::seconds(std::min(std::chrono::seconds::rep(INT_MAX), + std::max(ttl.count(), std::chrono::seconds::rep(0))))}) {} DnsResponse(const std::string& host, uint16_t port, uint16_t priority, uint16_t weight) : response_(SrvResponse{host, port, priority, weight}) {} diff --git a/source/extensions/network/dns_resolver/cares/dns_impl.cc b/source/extensions/network/dns_resolver/cares/dns_impl.cc index 12cf91e9c33f..f1fec3b6bcc8 100644 --- a/source/extensions/network/dns_resolver/cares/dns_impl.cc +++ b/source/extensions/network/dns_resolver/cares/dns_impl.cc @@ -204,6 +204,13 @@ void DnsResolverImpl::AddrInfoPendingResolution::onAresGetAddrInfoCallback( bool can_process_v6 = (!parent_.filter_unroutable_families_ || available_interfaces_.v6_available_); + int min_ttl = INT_MAX; // [RFC 2181](https://datatracker.ietf.org/doc/html/rfc2181) + // Loop through CNAME and get min_ttl + for (const ares_addrinfo_cname* cname = addrinfo->cnames; cname != nullptr; + cname = cname->next) { + min_ttl = std::min(min_ttl, cname->ttl); + } + for (const ares_addrinfo_node* ai = addrinfo->nodes; ai != nullptr; ai = ai->ai_next) { if (ai->ai_family == AF_INET && can_process_v4) { sockaddr_in address; @@ -214,7 +221,7 @@ void DnsResolverImpl::AddrInfoPendingResolution::onAresGetAddrInfoCallback( pending_response_.address_list_.emplace_back( DnsResponse(std::make_shared(&address), - std::chrono::seconds(ai->ai_ttl))); + std::chrono::seconds(std::min(min_ttl, ai->ai_ttl)))); } else if (ai->ai_family == AF_INET6 && can_process_v6) { sockaddr_in6 address; memset(&address, 0, sizeof(address)); @@ -223,7 +230,7 @@ void DnsResolverImpl::AddrInfoPendingResolution::onAresGetAddrInfoCallback( address.sin6_addr = reinterpret_cast(ai->ai_addr)->sin6_addr; pending_response_.address_list_.emplace_back( DnsResponse(std::make_shared(address), - std::chrono::seconds(ai->ai_ttl))); + std::chrono::seconds(std::min(min_ttl, ai->ai_ttl)))); } } } diff --git a/test/extensions/network/dns_resolver/cares/dns_impl_test.cc b/test/extensions/network/dns_resolver/cares/dns_impl_test.cc index 0e6454937106..b4959988c4cb 100644 --- a/test/extensions/network/dns_resolver/cares/dns_impl_test.cc +++ b/test/extensions/network/dns_resolver/cares/dns_impl_test.cc @@ -69,11 +69,12 @@ using CNameMap = absl::node_hash_map; class TestDnsServerQuery { public: TestDnsServerQuery(ConnectionPtr connection, const HostMap& hosts_a, const HostMap& hosts_aaaa, - const CNameMap& cnames, const std::chrono::seconds& record_ttl, bool refused, - bool error_on_a, bool error_on_aaaa) + const CNameMap& cnames, const std::chrono::seconds& record_ttl, + const std::chrono::seconds& cname_ttl_, bool refused, bool error_on_a, + bool error_on_aaaa) : connection_(std::move(connection)), hosts_a_(hosts_a), hosts_aaaa_(hosts_aaaa), - cnames_(cnames), record_ttl_(record_ttl), refused_(refused), error_on_a_(error_on_a), - error_on_aaaa_(error_on_aaaa) { + cnames_(cnames), record_ttl_(record_ttl), cname_ttl_(cname_ttl_), refused_(refused), + error_on_a_(error_on_a), error_on_aaaa_(error_on_aaaa) { connection_->addReadFilter(Network::ReadFilterSharedPtr{new ReadFilter(*this)}); } @@ -291,7 +292,7 @@ class TestDnsServerQuery { DNS_RR_SET_TYPE(cname_rr_fixed, T_CNAME); DNS_RR_SET_LEN(cname_rr_fixed, cname.size() + 1); DNS_RR_SET_CLASS(cname_rr_fixed, C_IN); - DNS_RR_SET_TTL(cname_rr_fixed, parent_.record_ttl_.count()); + DNS_RR_SET_TTL(cname_rr_fixed, parent_.cname_ttl_.count()); buf.add(encoded_name.c_str(), encoded_name.size() + 1); buf.add(cname_rr_fixed, RRFIXEDSZ); buf.add(cname.c_str(), cname.size() + 1); @@ -329,6 +330,7 @@ class TestDnsServerQuery { const HostMap& hosts_aaaa_; const CNameMap& cnames_; const std::chrono::seconds& record_ttl_; + const std::chrono::seconds& cname_ttl_; const bool refused_; const bool error_on_a_; const bool error_on_aaaa_; @@ -337,14 +339,15 @@ class TestDnsServerQuery { class TestDnsServer : public TcpListenerCallbacks { public: TestDnsServer(Event::Dispatcher& dispatcher) - : dispatcher_(dispatcher), record_ttl_(0), stream_info_(dispatcher.timeSource(), nullptr) {} + : dispatcher_(dispatcher), record_ttl_(0), cname_ttl_(0), + stream_info_(dispatcher.timeSource(), nullptr) {} void onAccept(ConnectionSocketPtr&& socket) override { Network::ConnectionPtr new_connection = dispatcher_.createServerConnection( std::move(socket), Network::Test::createRawBufferSocket(), stream_info_); TestDnsServerQuery* query = new TestDnsServerQuery(std::move(new_connection), hosts_a_, hosts_aaaa_, cnames_, - record_ttl_, refused_, error_on_a_, error_on_aaaa_); + record_ttl_, cname_ttl_, refused_, error_on_a_, error_on_aaaa_); queries_.emplace_back(query); } @@ -364,6 +367,7 @@ class TestDnsServer : public TcpListenerCallbacks { } void setRecordTtl(const std::chrono::seconds& ttl) { record_ttl_ = ttl; } + void setCnameTtl(const std::chrono::seconds& ttl) { cname_ttl_ = ttl; } void setRefused(bool refused) { refused_ = refused; } void setErrorOnQtypeA(bool error) { error_on_a_ = error; } void setErrorOnQtypeAAAA(bool error) { error_on_aaaa_ = error; } @@ -375,6 +379,7 @@ class TestDnsServer : public TcpListenerCallbacks { HostMap hosts_aaaa_; CNameMap cnames_; std::chrono::seconds record_ttl_; + std::chrono::seconds cname_ttl_; bool refused_{}; bool error_on_a_{}; bool error_on_aaaa_{}; @@ -1204,10 +1209,12 @@ TEST_P(DnsImplTest, MultiARecordLookup) { TEST_P(DnsImplTest, CNameARecordLookupV4) { server_->addCName("root.cnam.domain", "result.cname.domain"); server_->addHosts("result.cname.domain", {"201.134.56.7"}, RecordType::A); + server_->setRecordTtl(std::chrono::seconds(300)); + server_->setCnameTtl(std::chrono::seconds(60)); EXPECT_NE(nullptr, resolveWithExpectations("root.cnam.domain", DnsLookupFamily::V4Only, DnsResolver::ResolutionStatus::Success, - {"201.134.56.7"}, {}, absl::nullopt)); + {"201.134.56.7"}, {}, std::chrono::seconds(60))); dispatcher_->run(Event::Dispatcher::RunType::Block); checkStats(1 /*resolve_total*/, 0 /*pending_resolutions*/, 0 /*not_found*/, 0 /*get_addr_failure*/, 0 /*timeouts*/); @@ -1216,15 +1223,53 @@ TEST_P(DnsImplTest, CNameARecordLookupV4) { TEST_P(DnsImplTest, CNameARecordLookupWithV6) { server_->addCName("root.cnam.domain", "result.cname.domain"); server_->addHosts("result.cname.domain", {"201.134.56.7"}, RecordType::A); + server_->setRecordTtl(std::chrono::seconds(300)); + server_->setCnameTtl(std::chrono::seconds(60)); EXPECT_NE(nullptr, resolveWithExpectations("root.cnam.domain", DnsLookupFamily::Auto, DnsResolver::ResolutionStatus::Success, - {"201.134.56.7"}, {}, absl::nullopt)); + {"201.134.56.7"}, {}, std::chrono::seconds(60))); dispatcher_->run(Event::Dispatcher::RunType::Block); checkStats(2 /*resolve_total*/, 0 /*pending_resolutions*/, 1 /*not_found*/, 0 /*get_addr_failure*/, 0 /*timeouts*/); } +// RFC 2181: TTL values can be between [0, 2^31-1] +TEST_P(DnsImplTest, CNameARecordLookupV4InvalidTTL) { + server_->addCName("root.cnam.domain", "result.cname.domain"); + server_->addHosts("result.cname.domain", {"201.134.56.7"}, RecordType::A); + + // Case 1: Negative TTL + server_->setRecordTtl(std::chrono::seconds(-5)); + server_->setCnameTtl(std::chrono::seconds(60)); + + EXPECT_NE(nullptr, resolveWithExpectations("root.cnam.domain", DnsLookupFamily::V4Only, + DnsResolver::ResolutionStatus::Success, + {"201.134.56.7"}, {}, std::chrono::seconds(0))); + dispatcher_->run(Event::Dispatcher::RunType::Block); + + // Case 2: TTL Overflow + server_->setRecordTtl(std::chrono::seconds(2147483648)); + server_->setCnameTtl(std::chrono::seconds(2147483648)); + + EXPECT_NE(nullptr, resolveWithExpectations("root.cnam.domain", DnsLookupFamily::V4Only, + DnsResolver::ResolutionStatus::Success, + {"201.134.56.7"}, {}, std::chrono::seconds(0))); + dispatcher_->run(Event::Dispatcher::RunType::Block); + + // Case 3: Max TTL + server_->setRecordTtl(std::chrono::seconds(2147483647)); + server_->setCnameTtl(std::chrono::seconds(2147483647)); + + EXPECT_NE(nullptr, + resolveWithExpectations("root.cnam.domain", DnsLookupFamily::V4Only, + DnsResolver::ResolutionStatus::Success, {"201.134.56.7"}, {}, + std::chrono::seconds(2147483647))); + dispatcher_->run(Event::Dispatcher::RunType::Block); + checkStats(3 /*resolve_total*/, 0 /*pending_resolutions*/, 0 /*not_found*/, + 0 /*get_addr_failure*/, 0 /*timeouts*/); +} + TEST_P(DnsImplTest, MultiARecordLookupWithV6) { server_->addHosts("some.good.domain", {"201.134.56.7", "123.4.5.6", "6.5.4.3"}, RecordType::A); server_->addHosts("some.good.domain", {"1::2", "1::2:3", "1::2:3:4"}, RecordType::AAAA); diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index d56e1bc7ff77..ea7b558f6f5e 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1458,3 +1458,4 @@ SSLKEYLOGFILE DLB PCIE EDNS +CNAME From 594d96004d90c01c4b7d0f23aa142e65cef1eebb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 09:27:04 +0100 Subject: [PATCH 03/55] build(deps): bump node from 20.5-bullseye-slim to 20.6-bullseye-slim in /examples/shared/node (#29444) build(deps): bump node in /examples/shared/node Bumps node from 20.5-bullseye-slim to 20.6-bullseye-slim. --- updated-dependencies: - dependency-name: node dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/node/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/node/Dockerfile b/examples/shared/node/Dockerfile index 3f6717d00149..7f1a6340b1b3 100644 --- a/examples/shared/node/Dockerfile +++ b/examples/shared/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.5-bullseye-slim@sha256:f54a16be368537403c6f20e6e9cfa400f4b71c71ae9e1e93558b33a08f109db6 as node-base +FROM node:20.6-bullseye-slim@sha256:36d587c8542eafc209a8419b1b8ba3225b002b6d15e75bd094c78ca6cd605ec3 as node-base FROM node-base as node-http-auth From f55a4a50339270e1dae477d9fce560fa09455900 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 6 Sep 2023 10:51:10 +0100 Subject: [PATCH 04/55] tools/format: Cleanups (#29435) * tools/format: Remove unused script * readme: Update code format commands * bazel/ci: Prevent leaking of build tools into env * mobile/ci: Include format script in CI triggers Signed-off-by: Ryan Northey --- .github/workflows/mobile-format.yml | 2 -- bazel/README.md | 4 ++-- ci/build_setup.sh | 3 --- mobile/tools/what_to_run.sh | 2 +- tools/code_format/paths.py | 15 --------------- 5 files changed, 3 insertions(+), 23 deletions(-) delete mode 100644 tools/code_format/paths.py diff --git a/.github/workflows/mobile-format.yml b/.github/workflows/mobile-format.yml index 535fe2bd1b9b..c85a7bf8eeca 100644 --- a/.github/workflows/mobile-format.yml +++ b/.github/workflows/mobile-format.yml @@ -34,8 +34,6 @@ jobs: image: ${{ needs.env.outputs.build_image_ubuntu }} env: CLANG_FORMAT: /opt/llvm/bin/clang-format - BUILDIFIER_BIN: /usr/local/bin/buildifier - BUILDOZER_BIN: /usr/local/bin/buildozer ENVOY_BAZEL_PREFIX: "@envoy" steps: - uses: actions/checkout@v4 diff --git a/bazel/README.md b/bazel/README.md index 38cd9a9f0df3..fe497fe46b60 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -987,9 +987,9 @@ export PATH=$PATH:$PWD/bin/ Once this is set up, you can run clang-format without docker: ```shell -./tools/code_format/check_format.py check +bazel run //tools/code_format:check_format -- check ./tools/spelling/check_spelling_pedantic.py check -./tools/code_format/check_format.py fix +bazel run //tools/code_format:check_format -- fix ./tools/spelling/check_spelling_pedantic.py fix ``` diff --git a/ci/build_setup.sh b/ci/build_setup.sh index 9dc4c1b0deb0..f5a6e95aea41 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -194,9 +194,6 @@ mkdir -p "${ENVOY_FAILED_TEST_LOGS}" export ENVOY_BUILD_PROFILE="${ENVOY_BUILD_DIR}"/generated/build-profile mkdir -p "${ENVOY_BUILD_PROFILE}" -export BUILDIFIER_BIN="${BUILDIFIER_BIN:-/usr/local/bin/buildifier}" -export BUILDOZER_BIN="${BUILDOZER_BIN:-/usr/local/bin/buildozer}" - if [[ "${ENVOY_BUILD_FILTER_EXAMPLE}" == "true" ]]; then # shellcheck source=ci/filter_example_setup.sh . "$(dirname "$0")"/filter_example_setup.sh diff --git a/mobile/tools/what_to_run.sh b/mobile/tools/what_to_run.sh index 939d23173b8e..8daacd10892e 100755 --- a/mobile/tools/what_to_run.sh +++ b/mobile/tools/what_to_run.sh @@ -5,7 +5,7 @@ set -euo pipefail BRANCH_NAME="$GITHUB_REF_NAME" BASE_COMMIT="$(git merge-base origin/main HEAD)" CHANGED_FILES="$(git diff "${BASE_COMMIT}" --name-only)" -CHANGE_MATCH='^mobile/|^bazel/repository_locations\.bzl|^\.bazelrc|^\.bazelversion|^\.github/workflows/mobile-*|^\.github/workflows/_env.yml' +CHANGE_MATCH='^mobile/|^bazel/repository_locations\.bzl|^\.bazelrc|^\.bazelversion|^\.github/workflows/mobile-*|^\.github/workflows/_env.yml^tools/code_format/check_format.py' # The logic in this file is roughly: # diff --git a/tools/code_format/paths.py b/tools/code_format/paths.py deleted file mode 100644 index 03530e66a300..000000000000 --- a/tools/code_format/paths.py +++ /dev/null @@ -1,15 +0,0 @@ -import os -import os.path -import shutil - - -def get_buildifier(): - return os.getenv("BUILDIFIER_BIN") or ( - os.path.expandvars("$GOPATH/bin/buildifier") - if os.getenv("GOPATH") else shutil.which("buildifier")) - - -def get_buildozer(): - return os.getenv("BUILDOZER_BIN") or ( - os.path.expandvars("$GOPATH/bin/buildozer") - if os.getenv("GOPATH") else shutil.which("buildozer")) From 403d1db30f55721eb98ee655e61b5e4c73da6645 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 6 Sep 2023 08:20:19 -0400 Subject: [PATCH 05/55] terminate_handler: using try macros (#29367) Signed-off-by: Alyssa Wilk --- source/exe/terminate_handler.cc | 20 ++++++++++---------- test/exe/terminate_handler_test.cc | 14 -------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/source/exe/terminate_handler.cc b/source/exe/terminate_handler.cc index 40b987a4065d..c5bc33f6185d 100644 --- a/source/exe/terminate_handler.cc +++ b/source/exe/terminate_handler.cc @@ -5,6 +5,7 @@ #include "envoy/common/exception.h" #include "source/common/common/logger.h" +#include "source/common/common/thread.h" #include "source/server/backtrace.h" #include "absl/strings/str_format.h" @@ -21,16 +22,15 @@ std::terminate_handler TerminateHandler::logOnTerminate() const { void TerminateHandler::logException(const std::exception_ptr current_exception) { if (current_exception != nullptr) { - try { - std::rethrow_exception(current_exception); - } catch (const EnvoyException& e) { - ENVOY_LOG(critical, "std::terminate called! Uncaught EnvoyException '{}', see trace.", - e.what()); - } catch (const std::exception& e) { - ENVOY_LOG(critical, "std::terminate called! Uncaught exception '{}', see trace.", e.what()); - } catch (...) { - ENVOY_LOG(critical, "std::terminate called! Uncaught unknown exception, see trace."); - } + TRY_NEEDS_AUDIT { std::rethrow_exception(current_exception); } + END_TRY + MULTI_CATCH( + const EnvoyException& e, + { + ENVOY_LOG(critical, "std::terminate called! Uncaught EnvoyException '{}', see trace.", + e.what()); + }, + { ENVOY_LOG(critical, "std::terminate called! Uncaught unknown exception, see trace."); }); } else { ENVOY_LOG(critical, "std::terminate called! See trace."); } diff --git a/test/exe/terminate_handler_test.cc b/test/exe/terminate_handler_test.cc index e6a07c334e06..782478536620 100644 --- a/test/exe/terminate_handler_test.cc +++ b/test/exe/terminate_handler_test.cc @@ -24,20 +24,6 @@ TEST_F(TerminateHandlerTest, LogsEnvoyException) { logException(std::make_exception_ptr(EnvoyException("boom")))); } -#ifdef WIN32 -TEST_F(TerminateHandlerTest, LogsStdException) { - EXPECT_LOG_CONTAINS("critical", - "std::terminate called! Uncaught exception 'Unknown exception', see trace.", - logException(std::make_exception_ptr(std::exception()))); -} -#else -TEST_F(TerminateHandlerTest, LogsStdException) { - EXPECT_LOG_CONTAINS("critical", - "std::terminate called! Uncaught exception 'std::exception', see trace.", - logException(std::make_exception_ptr(std::exception()))); -} -#endif - TEST_F(TerminateHandlerTest, LogsUnknownException) { EXPECT_LOG_CONTAINS("critical", "std::terminate called! Uncaught unknown exception, see trace.", logException(std::make_exception_ptr("Boom"))); From f318e6bc19e741b9eb98c75a527756df0a8d9415 Mon Sep 17 00:00:00 2001 From: yanjunxiang-google <78807980+yanjunxiang-google@users.noreply.github.com> Date: Wed, 6 Sep 2023 09:49:43 -0400 Subject: [PATCH 06/55] Fix ext_proc issue with BUFFERED_PARTIAL mode (#29330) In BUFFERED_PARTIAL mode, after receiving trailer, if there is data pending to be sent in the chunk_queue_, those data need to be sent to the ext_proc server before sending the trailer there. Before sending, Envoy will first consolidate the chunks and send all the data in one gRPC. The behavior is very similar to BUFFERED mode. There is code to handle such case for BUFFERED mode. But it is missing for BUFFERED_PARTIAL mode. --------- Signed-off-by: Yanjun Xiang --- .../filters/http/ext_proc/ext_proc.cc | 22 ++++++++++----- .../ext_proc/ext_proc_integration_test.cc | 27 +++++++++++++++++++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 95cd64e40562..f4b56b93a515 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -455,7 +455,9 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap& return FilterTrailersStatus::StopIteration; } - if (!body_delivered && state.bufferedData() && state.bodyMode() == ProcessingMode::BUFFERED) { + if (!body_delivered && + ((state.bufferedData() && state.bodyMode() == ProcessingMode::BUFFERED) || + (!state.chunkQueue().empty() && state.bodyMode() == ProcessingMode::BUFFERED_PARTIAL))) { // If no gRPC stream yet, opens it before sending data. switch (openStream()) { case StreamOpenState::Error: @@ -466,11 +468,19 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap& // Fall through break; } - // We would like to process the body in a buffered way, but until now the complete - // body has not arrived. With the arrival of trailers, we now know that the body - // has arrived. - sendBodyChunk(state, *state.bufferedData(), ProcessorState::CallbackState::BufferedBodyCallback, - false); + + if (state.bodyMode() == ProcessingMode::BUFFERED) { + // Sending data left over in the buffer. + sendBodyChunk(state, *state.bufferedData(), + ProcessorState::CallbackState::BufferedBodyCallback, false); + } else { + // Sending data left over in the queue. + const auto& all_data = state.consolidateStreamedChunks(); + ENVOY_LOG(debug, "Sending {} bytes of data in buffered partial mode. end_stream = {}", + state.chunkQueue().receivedData().length(), all_data.end_stream); + sendBodyChunk(state, state.chunkQueue().receivedData(), + ProcessorState::CallbackState::BufferedPartialBodyCallback, false); + } state.setPaused(true); return FilterTrailersStatus::StopIteration; } diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index 85463dfa3a1a..16625bbdfdb9 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -3091,4 +3091,31 @@ TEST_P(ExtProcIntegrationTest, StreamingRequestBodyWithTrailer) { verifyDownstreamResponse(*response, 200); } +TEST_P(ExtProcIntegrationTest, SendBodyBufferedPartialWithTrailer) { + proto_config_.mutable_processing_mode()->set_request_body_mode(ProcessingMode::BUFFERED_PARTIAL); + proto_config_.mutable_processing_mode()->set_request_trailer_mode(ProcessingMode::SEND); + proto_config_.mutable_processing_mode()->set_response_header_mode(ProcessingMode::SKIP); + proto_config_.mutable_processing_mode()->set_request_header_mode(ProcessingMode::SKIP); + initializeConfig(); + HttpIntegrationTest::initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + auto encoder_decoder = codec_client_->startRequest(headers); + request_encoder_ = &encoder_decoder.first; + IntegrationStreamDecoderPtr response = std::move(encoder_decoder.second); + + // Send some data. + codec_client_->sendData(*request_encoder_, 10, false); + Http::TestRequestTrailerMapImpl request_trailers{{"request", "trailer"}}; + codec_client_->sendTrailers(*request_encoder_, request_trailers); + + processRequestBodyMessage(*grpc_upstreams_[0], true, absl::nullopt); + processRequestTrailersMessage(*grpc_upstreams_[0], false, absl::nullopt); + + handleUpstreamRequest(); + verifyDownstreamResponse(*response, 200); +} + } // namespace Envoy From 20676d718086b5a91b991f9dad45e029493f2ab9 Mon Sep 17 00:00:00 2001 From: DiazAlan <109677874+DiazAlan@users.noreply.github.com> Date: Wed, 6 Sep 2023 10:08:18 -0400 Subject: [PATCH 07/55] Optimize AsyncClientManager by moving hashing to control plane (#29199) This change removes the hashing of the config away from the data plane on every request to the control plane when the filter callback is created. This is accomplished by creating a wrapper object for the config which precomputes the hash and also serializes it to string ahead of time so that calls to the AsyncClientManager with that wrapper can avoid unnecessary overhead. Signed-off-by: AlanDiaz --- envoy/grpc/async_client_manager.h | 43 ++++++++++ .../common/grpc/async_client_manager_impl.cc | 37 ++++++--- .../common/grpc/async_client_manager_impl.h | 20 +++-- .../filters/http/ext_authz/config.cc | 27 ++----- test/common/grpc/BUILD | 27 +++++++ .../grpc/async_client_manager_benchmark.cc | 79 +++++++++++++++++++ .../grpc/async_client_manager_impl_test.cc | 47 ++++++++--- .../filters/http/ext_authz/config_test.cc | 21 +++-- test/mocks/grpc/mocks.h | 4 + 9 files changed, 249 insertions(+), 56 deletions(-) create mode 100644 test/common/grpc/async_client_manager_benchmark.cc diff --git a/envoy/grpc/async_client_manager.h b/envoy/grpc/async_client_manager.h index 7cf027ee9053..629873e9f16d 100644 --- a/envoy/grpc/async_client_manager.h +++ b/envoy/grpc/async_client_manager.h @@ -32,6 +32,32 @@ class AsyncClientFactory { using AsyncClientFactoryPtr = std::unique_ptr; +class GrpcServiceConfigWithHashKey { +public: + explicit GrpcServiceConfigWithHashKey(const envoy::config::core::v3::GrpcService& config) + : config_(config), pre_computed_hash_(Envoy::MessageUtil::hash(config)){}; + + template friend H AbslHashValue(H h, const GrpcServiceConfigWithHashKey& wrapper) { + return H::combine(std::move(h), wrapper.pre_computed_hash_); + } + + std::size_t getPreComputedHash() const { return pre_computed_hash_; } + + friend bool operator==(const GrpcServiceConfigWithHashKey& lhs, + const GrpcServiceConfigWithHashKey& rhs) { + if (lhs.pre_computed_hash_ == rhs.pre_computed_hash_) { + return Protobuf::util::MessageDifferencer::Equivalent(lhs.config_, rhs.config_); + } + return false; + } + + const envoy::config::core::v3::GrpcService& config() const { return config_; } + +private: + const envoy::config::core::v3::GrpcService config_; + const std::size_t pre_computed_hash_; +}; + // Singleton gRPC client manager. Grpc::AsyncClientManager can be used to create per-service // Grpc::AsyncClientFactory instances. All manufactured Grpc::AsyncClients must // be destroyed before the AsyncClientManager can be safely destructed. @@ -39,6 +65,7 @@ class AsyncClientManager { public: virtual ~AsyncClientManager() = default; + // TODO(diazalan) deprecate old getOrCreateRawAsyncClient once all filters have been transitioned /** * Create a Grpc::RawAsyncClient. The async client is cached thread locally and shared across * different filter instances. @@ -54,6 +81,22 @@ class AsyncClientManager { getOrCreateRawAsyncClient(const envoy::config::core::v3::GrpcService& grpc_service, Stats::Scope& scope, bool skip_cluster_check) PURE; + /** + * Create a Grpc::RawAsyncClient. The async client is cached thread locally and shared across + * different filter instances. + * @param grpc_service Envoy::Grpc::GrpcServiceConfigWithHashKey which contains config and + * hashkey. + * @param scope stats scope. + * @param skip_cluster_check if set to true skips checks for cluster presence and being statically + * configured. + * @param cache_option always use cache or use cache when runtime is enabled. + * @return RawAsyncClientPtr a grpc async client. + * @throws EnvoyException when grpc_service validation fails. + */ + virtual RawAsyncClientSharedPtr + getOrCreateRawAsyncClientWithHashKey(const GrpcServiceConfigWithHashKey& grpc_service, + Stats::Scope& scope, bool skip_cluster_check) PURE; + /** * Create a Grpc::AsyncClients factory for a service. Validation of the service is performed and * will raise an exception on failure. diff --git a/source/common/grpc/async_client_manager_impl.cc b/source/common/grpc/async_client_manager_impl.cc index 949ae4bd7539..e741ec56383d 100644 --- a/source/common/grpc/async_client_manager_impl.cc +++ b/source/common/grpc/async_client_manager_impl.cc @@ -5,6 +5,7 @@ #include "source/common/common/base64.h" #include "source/common/grpc/async_client_impl.h" +#include "source/common/protobuf/utility.h" #include "absl/strings/match.h" @@ -136,12 +137,27 @@ AsyncClientManagerImpl::factoryForGrpcService(const envoy::config::core::v3::Grp RawAsyncClientSharedPtr AsyncClientManagerImpl::getOrCreateRawAsyncClient( const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, bool skip_cluster_check) { - RawAsyncClientSharedPtr client = raw_async_client_cache_->getCache(config); + const GrpcServiceConfigWithHashKey config_with_hash_key = GrpcServiceConfigWithHashKey(config); + RawAsyncClientSharedPtr client = raw_async_client_cache_->getCache(config_with_hash_key); if (client != nullptr) { return client; } - client = factoryForGrpcService(config, scope, skip_cluster_check)->createUncachedRawAsyncClient(); - raw_async_client_cache_->setCache(config, client); + client = factoryForGrpcService(config_with_hash_key.config(), scope, skip_cluster_check) + ->createUncachedRawAsyncClient(); + raw_async_client_cache_->setCache(config_with_hash_key, client); + return client; +} + +RawAsyncClientSharedPtr AsyncClientManagerImpl::getOrCreateRawAsyncClientWithHashKey( + const GrpcServiceConfigWithHashKey& config_with_hash_key, Stats::Scope& scope, + bool skip_cluster_check) { + RawAsyncClientSharedPtr client = raw_async_client_cache_->getCache(config_with_hash_key); + if (client != nullptr) { + return client; + } + client = factoryForGrpcService(config_with_hash_key.config(), scope, skip_cluster_check) + ->createUncachedRawAsyncClient(); + raw_async_client_cache_->setCache(config_with_hash_key, client); return client; } @@ -151,11 +167,12 @@ AsyncClientManagerImpl::RawAsyncClientCache::RawAsyncClientCache(Event::Dispatch } void AsyncClientManagerImpl::RawAsyncClientCache::setCache( - const envoy::config::core::v3::GrpcService& config, const RawAsyncClientSharedPtr& client) { - ASSERT(lru_map_.find(config) == lru_map_.end()); + const GrpcServiceConfigWithHashKey& config_with_hash_key, + const RawAsyncClientSharedPtr& client) { + ASSERT(lru_map_.find(config_with_hash_key) == lru_map_.end()); // Create a new cache entry at the beginning of the list. - lru_list_.emplace_front(config, client, dispatcher_.timeSource().monotonicTime()); - lru_map_[config] = lru_list_.begin(); + lru_list_.emplace_front(config_with_hash_key, client, dispatcher_.timeSource().monotonicTime()); + lru_map_[config_with_hash_key] = lru_list_.begin(); // If inserting to an empty cache, enable eviction timer. if (lru_list_.size() == 1) { evictEntriesAndResetEvictionTimer(); @@ -163,8 +180,8 @@ void AsyncClientManagerImpl::RawAsyncClientCache::setCache( } RawAsyncClientSharedPtr AsyncClientManagerImpl::RawAsyncClientCache::getCache( - const envoy::config::core::v3::GrpcService& config) { - auto it = lru_map_.find(config); + const GrpcServiceConfigWithHashKey& config_with_hash_key) { + auto it = lru_map_.find(config_with_hash_key); if (it == lru_map_.end()) { return nullptr; } @@ -189,7 +206,7 @@ void AsyncClientManagerImpl::RawAsyncClientCache::evictEntriesAndResetEvictionTi MonotonicTime next_expire = lru_list_.back().accessed_time_ + EntryTimeoutInterval; if (now >= next_expire) { // Erase the expired entry. - lru_map_.erase(lru_list_.back().config_); + lru_map_.erase(lru_list_.back().config_with_hash_key_); lru_list_.pop_back(); } else { cache_eviction_timer_->enableTimer( diff --git a/source/common/grpc/async_client_manager_impl.h b/source/common/grpc/async_client_manager_impl.h index eee1400ff84c..eadf3009490c 100644 --- a/source/common/grpc/async_client_manager_impl.h +++ b/source/common/grpc/async_client_manager_impl.h @@ -9,6 +9,7 @@ #include "envoy/upstream/cluster_manager.h" #include "source/common/grpc/stat_names.h" +#include "source/common/protobuf/utility.h" namespace Envoy { namespace Grpc { @@ -51,32 +52,35 @@ class AsyncClientManagerImpl : public AsyncClientManager { getOrCreateRawAsyncClient(const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, bool skip_cluster_check) override; + RawAsyncClientSharedPtr + getOrCreateRawAsyncClientWithHashKey(const GrpcServiceConfigWithHashKey& config_with_hash_key, + Stats::Scope& scope, bool skip_cluster_check) override; + AsyncClientFactoryPtr factoryForGrpcService(const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, bool skip_cluster_check) override; class RawAsyncClientCache : public ThreadLocal::ThreadLocalObject { public: explicit RawAsyncClientCache(Event::Dispatcher& dispatcher); - void setCache(const envoy::config::core::v3::GrpcService& config, + void setCache(const GrpcServiceConfigWithHashKey& config_with_hash_key, const RawAsyncClientSharedPtr& client); - RawAsyncClientSharedPtr getCache(const envoy::config::core::v3::GrpcService& config); + RawAsyncClientSharedPtr getCache(const GrpcServiceConfigWithHashKey& config_with_hash_key); private: void evictEntriesAndResetEvictionTimer(); struct CacheEntry { - CacheEntry(const envoy::config::core::v3::GrpcService& config, + CacheEntry(const GrpcServiceConfigWithHashKey& config_with_hash_key, RawAsyncClientSharedPtr const& client, MonotonicTime create_time) - : config_(config), client_(client), accessed_time_(create_time) {} - envoy::config::core::v3::GrpcService config_; + : config_with_hash_key_(config_with_hash_key), client_(client), + accessed_time_(create_time) {} + GrpcServiceConfigWithHashKey config_with_hash_key_; RawAsyncClientSharedPtr client_; MonotonicTime accessed_time_; }; using LruList = std::list; - absl::flat_hash_map - lru_map_; LruList lru_list_; + absl::flat_hash_map lru_map_; Event::Dispatcher& dispatcher_; Envoy::Event::TimerPtr cache_eviction_timer_; static constexpr std::chrono::seconds EntryTimeoutInterval{50}; diff --git a/source/extensions/filters/http/ext_authz/config.cc b/source/extensions/filters/http/ext_authz/config.cc index 54ca6fc5db45..1e15833e5fc1 100644 --- a/source/extensions/filters/http/ext_authz/config.cc +++ b/source/extensions/filters/http/ext_authz/config.cc @@ -6,6 +6,7 @@ #include "envoy/config/core/v3/grpc_service.pb.h" #include "envoy/extensions/filters/http/ext_authz/v3/ext_authz.pb.h" #include "envoy/extensions/filters/http/ext_authz/v3/ext_authz.pb.validate.h" +#include "envoy/grpc/async_client_manager.h" #include "envoy/registry/registry.h" #include "source/common/config/utility.h" @@ -42,34 +43,22 @@ Http::FilterFactoryCb ExtAuthzFilterConfig::createFilterFactoryFromProtoTyped( context.clusterManager(), client_config); callbacks.addStreamFilter(std::make_shared(filter_config, std::move(client))); }; - } else if (proto_config.grpc_service().has_google_grpc()) { - // Google gRPC client. + } else { + // gRPC client. const uint32_t timeout_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config.grpc_service(), timeout, DefaultTimeout); Config::Utility::checkTransportVersion(proto_config); + Envoy::Grpc::GrpcServiceConfigWithHashKey config_with_hash_key = + Envoy::Grpc::GrpcServiceConfigWithHashKey(proto_config.grpc_service()); callback = [&context, filter_config, timeout_ms, - proto_config](Http::FilterChainFactoryCallbacks& callbacks) { + config_with_hash_key](Http::FilterChainFactoryCallbacks& callbacks) { auto client = std::make_unique( - context.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( - proto_config.grpc_service(), context.scope(), true), + context.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClientWithHashKey( + config_with_hash_key, context.scope(), true), std::chrono::milliseconds(timeout_ms)); callbacks.addStreamFilter(std::make_shared(filter_config, std::move(client))); }; - } else { - // Envoy gRPC client. - const uint32_t timeout_ms = - PROTOBUF_GET_MS_OR_DEFAULT(proto_config.grpc_service(), timeout, DefaultTimeout); - Config::Utility::checkTransportVersion(proto_config); - callback = [grpc_service = proto_config.grpc_service(), &context, filter_config, - timeout_ms](Http::FilterChainFactoryCallbacks& callbacks) { - Grpc::RawAsyncClientSharedPtr raw_client = - context.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( - grpc_service, context.scope(), true); - auto client = std::make_unique( - raw_client, std::chrono::milliseconds(timeout_ms)); - callbacks.addStreamFilter(std::make_shared(filter_config, std::move(client))); - }; } return callback; diff --git a/test/common/grpc/BUILD b/test/common/grpc/BUILD index 6cf74e7f7d7e..01458712de95 100644 --- a/test/common/grpc/BUILD +++ b/test/common/grpc/BUILD @@ -1,5 +1,7 @@ load( "//bazel:envoy_build_system.bzl", + "envoy_benchmark_test", + "envoy_cc_benchmark_binary", "envoy_cc_fuzz_test", "envoy_cc_test", "envoy_cc_test_library", @@ -213,3 +215,28 @@ envoy_cc_test( "//test/test_common:utility_lib", ], ) + +envoy_cc_benchmark_binary( + name = "async_client_manager_benchmark", + srcs = ["async_client_manager_benchmark.cc"], + external_deps = [ + "benchmark", + ], + deps = [ + "//source/common/api:api_lib", + "//source/common/grpc:async_client_manager_lib", + "//test/mocks/stats:stats_mocks", + "//test/mocks/thread_local:thread_local_mocks", + "//test/mocks/upstream:cluster_manager_mocks", + "//test/mocks/upstream:cluster_priority_set_mocks", + "//test/test_common:test_runtime_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + +envoy_benchmark_test( + name = "async_client_manager_benchmark_test", + timeout = "long", + benchmark_binary = "async_client_manager_benchmark", +) diff --git a/test/common/grpc/async_client_manager_benchmark.cc b/test/common/grpc/async_client_manager_benchmark.cc new file mode 100644 index 000000000000..f23a0aba54e5 --- /dev/null +++ b/test/common/grpc/async_client_manager_benchmark.cc @@ -0,0 +1,79 @@ +#include + +#include "envoy/config/core/v3/grpc_service.pb.h" +#include "envoy/grpc/async_client.h" + +#include "source/common/api/api_impl.h" +#include "source/common/event/dispatcher_impl.h" +#include "source/common/grpc/async_client_manager_impl.h" + +#include "test/benchmark/main.h" +#include "test/mocks/stats/mocks.h" +#include "test/mocks/thread_local/mocks.h" +#include "test/mocks/upstream/cluster_manager.h" +#include "test/mocks/upstream/cluster_priority_set.h" +#include "test/test_common/test_runtime.h" +#include "test/test_common/test_time.h" +#include "test/test_common/utility.h" + +#include "benchmark/benchmark.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Grpc { +namespace { + +class AsyncClientManagerImplTest { +public: + AsyncClientManagerImplTest() + : api_(Api::createApiForTest()), stat_names_(scope_.symbolTable()), + async_client_manager_(cm_, tls_, test_time_.timeSystem(), *api_, stat_names_) {} + + Upstream::MockClusterManager cm_; + NiceMock tls_; + Stats::MockStore store_; + Stats::MockScope& scope_{store_.mockScope()}; + DangerousDeprecatedTestTime test_time_; + Api::ApiPtr api_; + StatNames stat_names_; + AsyncClientManagerImpl async_client_manager_; +}; + +void testGetOrCreateAsyncClientWithConfig(::benchmark::State& state) { + AsyncClientManagerImplTest async_client_man_test; + + envoy::config::core::v3::GrpcService grpc_service; + grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); + + for (auto _ : state) { + for (int i = 0; i < 1000; i++) { + RawAsyncClientSharedPtr foo_client0 = + async_client_man_test.async_client_manager_.getOrCreateRawAsyncClient( + grpc_service, async_client_man_test.scope_, true); + } + } +} + +void testGetOrCreateAsyncClientWithHashConfig(::benchmark::State& state) { + AsyncClientManagerImplTest async_client_man_test; + + envoy::config::core::v3::GrpcService grpc_service; + grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); + GrpcServiceConfigWithHashKey config_with_hash_key_a = GrpcServiceConfigWithHashKey(grpc_service); + + for (auto _ : state) { + for (int i = 0; i < 1000; i++) { + RawAsyncClientSharedPtr foo_client0 = + async_client_man_test.async_client_manager_.getOrCreateRawAsyncClientWithHashKey( + config_with_hash_key_a, async_client_man_test.scope_, true); + } + } +} + +BENCHMARK(testGetOrCreateAsyncClientWithConfig)->Unit(::benchmark::kMicrosecond); +BENCHMARK(testGetOrCreateAsyncClientWithHashConfig)->Unit(::benchmark::kMicrosecond); + +} // namespace +} // namespace Grpc +} // namespace Envoy diff --git a/test/common/grpc/async_client_manager_impl_test.cc b/test/common/grpc/async_client_manager_impl_test.cc index 121f8cbc533d..36dae3f1001e 100644 --- a/test/common/grpc/async_client_manager_impl_test.cc +++ b/test/common/grpc/async_client_manager_impl_test.cc @@ -52,16 +52,17 @@ class RawAsyncClientCacheTest : public testing::Test { TEST_F(RawAsyncClientCacheTest, CacheEviction) { envoy::config::core::v3::GrpcService foo_service; foo_service.mutable_envoy_grpc()->set_cluster_name("foo"); + GrpcServiceConfigWithHashKey config_with_hash_key(foo_service); RawAsyncClientSharedPtr foo_client = std::make_shared(); - client_cache_.setCache(foo_service, foo_client); + client_cache_.setCache(config_with_hash_key, foo_client); waitForSeconds(49); // Cache entry hasn't been evicted because it was created 49s ago. - EXPECT_EQ(client_cache_.getCache(foo_service).get(), foo_client.get()); + EXPECT_EQ(client_cache_.getCache(config_with_hash_key).get(), foo_client.get()); waitForSeconds(49); // Cache entry hasn't been evicted because it was accessed 49s ago. - EXPECT_EQ(client_cache_.getCache(foo_service).get(), foo_client.get()); + EXPECT_EQ(client_cache_.getCache(config_with_hash_key).get(), foo_client.get()); waitForSeconds(51); - EXPECT_EQ(client_cache_.getCache(foo_service).get(), nullptr); + EXPECT_EQ(client_cache_.getCache(config_with_hash_key).get(), nullptr); } TEST_F(RawAsyncClientCacheTest, MultipleCacheEntriesEviction) { @@ -69,23 +70,27 @@ TEST_F(RawAsyncClientCacheTest, MultipleCacheEntriesEviction) { RawAsyncClientSharedPtr foo_client = std::make_shared(); for (int i = 1; i <= 50; i++) { grpc_service.mutable_envoy_grpc()->set_cluster_name(std::to_string(i)); - client_cache_.setCache(grpc_service, foo_client); + GrpcServiceConfigWithHashKey config_with_hash_key(grpc_service); + client_cache_.setCache(config_with_hash_key, foo_client); } waitForSeconds(20); for (int i = 51; i <= 100; i++) { grpc_service.mutable_envoy_grpc()->set_cluster_name(std::to_string(i)); - client_cache_.setCache(grpc_service, foo_client); + GrpcServiceConfigWithHashKey config_with_hash_key(grpc_service); + client_cache_.setCache(config_with_hash_key, foo_client); } waitForSeconds(30); // Cache entries created 50s before have expired. for (int i = 1; i <= 50; i++) { grpc_service.mutable_envoy_grpc()->set_cluster_name(std::to_string(i)); - EXPECT_EQ(client_cache_.getCache(grpc_service).get(), nullptr); + GrpcServiceConfigWithHashKey config_with_hash_key(grpc_service); + EXPECT_EQ(client_cache_.getCache(config_with_hash_key).get(), nullptr); } // Cache entries 30s before haven't expired. for (int i = 51; i <= 100; i++) { grpc_service.mutable_envoy_grpc()->set_cluster_name(std::to_string(i)); - EXPECT_EQ(client_cache_.getCache(grpc_service).get(), foo_client.get()); + GrpcServiceConfigWithHashKey config_with_hash_key(grpc_service); + EXPECT_EQ(client_cache_.getCache(config_with_hash_key).get(), foo_client.get()); } } @@ -95,14 +100,15 @@ TEST_F(RawAsyncClientCacheTest, GetExpiredButNotEvictedCacheEntry) { envoy::config::core::v3::GrpcService foo_service; foo_service.mutable_envoy_grpc()->set_cluster_name("foo"); RawAsyncClientSharedPtr foo_client = std::make_shared(); - client_cache_.setCache(foo_service, foo_client); + GrpcServiceConfigWithHashKey config_with_hash_key(foo_service); + client_cache_.setCache(config_with_hash_key, foo_client); time_system_.advanceTimeAsyncImpl(std::chrono::seconds(50)); // Cache entry hasn't been evicted because it is accessed before timer fire. - EXPECT_EQ(client_cache_.getCache(foo_service).get(), foo_client.get()); + EXPECT_EQ(client_cache_.getCache(config_with_hash_key).get(), foo_client.get()); time_system_.advanceTimeAndRun(std::chrono::seconds(50), *dispatcher_, Event::Dispatcher::RunType::NonBlock); // Cache entry has been evicted because it is accessed after timer fire. - EXPECT_EQ(client_cache_.getCache(foo_service).get(), nullptr); + EXPECT_EQ(client_cache_.getCache(config_with_hash_key).get(), nullptr); } class AsyncClientManagerImplTest : public testing::Test { @@ -128,6 +134,25 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcOk) { async_client_manager_.factoryForGrpcService(grpc_service, scope_, false); } +TEST_F(AsyncClientManagerImplTest, GrpcServiceConfigWithHashKeyTest) { + envoy::config::core::v3::GrpcService grpc_service; + grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); + envoy::config::core::v3::GrpcService grpc_service_c; + grpc_service.mutable_envoy_grpc()->set_cluster_name("bar"); + + GrpcServiceConfigWithHashKey config_with_hash_key_a = GrpcServiceConfigWithHashKey(grpc_service); + GrpcServiceConfigWithHashKey config_with_hash_key_b = GrpcServiceConfigWithHashKey(grpc_service); + GrpcServiceConfigWithHashKey config_with_hash_key_c = + GrpcServiceConfigWithHashKey(grpc_service_c); + EXPECT_TRUE(config_with_hash_key_a == config_with_hash_key_b); + EXPECT_FALSE(config_with_hash_key_a == config_with_hash_key_c); + + EXPECT_EQ(config_with_hash_key_a.getPreComputedHash(), + config_with_hash_key_b.getPreComputedHash()); + EXPECT_NE(config_with_hash_key_a.getPreComputedHash(), + config_with_hash_key_c.getPreComputedHash()); +} + TEST_F(AsyncClientManagerImplTest, RawAsyncClientCache) { envoy::config::core::v3::GrpcService grpc_service; grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); diff --git a/test/extensions/filters/http/ext_authz/config_test.cc b/test/extensions/filters/http/ext_authz/config_test.cc index 6995304446fb..2eddc7f01cec 100644 --- a/test/extensions/filters/http/ext_authz/config_test.cc +++ b/test/extensions/filters/http/ext_authz/config_test.cc @@ -63,12 +63,14 @@ class ExtAuthzFilterTest : public Event::TestUsingSimulatedTime, const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& ext_authz_config) { // Delegate call to mock async client manager to real async client manager. ON_CALL(context_, getServerFactoryContext()).WillByDefault(testing::ReturnRef(server_context_)); - ON_CALL(context_.cluster_manager_.async_client_manager_, getOrCreateRawAsyncClient(_, _, _)) - .WillByDefault(Invoke([&](const envoy::config::core::v3::GrpcService& config, - Stats::Scope& scope, bool skip_cluster_check) { - return async_client_manager_->getOrCreateRawAsyncClient(config, scope, - skip_cluster_check); - })); + ON_CALL(context_.cluster_manager_.async_client_manager_, + getOrCreateRawAsyncClientWithHashKey(_, _, _)) + .WillByDefault( + Invoke([&](const Envoy::Grpc::GrpcServiceConfigWithHashKey& config_with_hash_key, + Stats::Scope& scope, bool skip_cluster_check) { + return async_client_manager_->getOrCreateRawAsyncClientWithHashKey( + config_with_hash_key, scope, skip_cluster_check); + })); ExtAuthzFilterConfig factory; return factory.createFilterFactoryFromProto(ext_authz_config, "stats", context_); } @@ -204,8 +206,11 @@ class ExtAuthzFilterGrpcTest : public ExtAuthzFilterTest { void expectGrpcClientSentRequest( const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz& ext_authz_config, int requests_sent_per_thread) { - Grpc::RawAsyncClientSharedPtr async_client = async_client_manager_->getOrCreateRawAsyncClient( - ext_authz_config.grpc_service(), context_.scope(), false); + Envoy::Grpc::GrpcServiceConfigWithHashKey config_with_hash_key = + Envoy::Grpc::GrpcServiceConfigWithHashKey(ext_authz_config.grpc_service()); + Grpc::RawAsyncClientSharedPtr async_client = + async_client_manager_->getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, + context_.scope(), false); Grpc::MockAsyncClient* mock_async_client = dynamic_cast(async_client.get()); EXPECT_NE(mock_async_client, nullptr); diff --git a/test/mocks/grpc/mocks.h b/test/mocks/grpc/mocks.h index 12e242c6c4d9..2fdf85b34062 100644 --- a/test/mocks/grpc/mocks.h +++ b/test/mocks/grpc/mocks.h @@ -116,6 +116,10 @@ class MockAsyncClientManager : public AsyncClientManager { MOCK_METHOD(RawAsyncClientSharedPtr, getOrCreateRawAsyncClient, (const envoy::config::core::v3::GrpcService& grpc_service, Stats::Scope& scope, bool skip_cluster_check)); + + MOCK_METHOD(RawAsyncClientSharedPtr, getOrCreateRawAsyncClientWithHashKey, + (const GrpcServiceConfigWithHashKey& config_with_hash_key, Stats::Scope& scope, + bool skip_cluster_check)); }; #if defined(ENVOY_ENABLE_FULL_PROTOS) From 951b6234c97ef64dcf54efa104fae088b848b813 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Wed, 6 Sep 2023 11:31:37 -0400 Subject: [PATCH 08/55] gRPC transcoder now works with UHV (#29390) Signed-off-by: Yan Avlasov --- .../grpc_json_transcoder_integration_test.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc index 51b536a91405..6fb01ce288c0 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc @@ -351,9 +351,6 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, QueryParamsDecodedName) { }, R"({"id":"20","theme":"Children"})"); -#ifndef ENVOY_ENABLE_UHV - // TODO(#23291) - UHV validate JSON-encoded gRPC query parameters - // json_name = "search[decoded]", "search[decoded]" should work testTranscoding( Http::TestRequestHeaderMapImpl{{":method", "POST"}, {":path", "/shelf?shelf.search[decoded]=Google"}, @@ -365,7 +362,6 @@ TEST_P(GrpcJsonTranscoderIntegrationTest, QueryParamsDecodedName) { {"content-type", "application/json"}, }, R"({"id":"20","theme":"Children"})"); -#endif // json_name = "search%5Bencoded%5D", "search[encode]" should fail. // It is tested in test case "DecodedQueryParameterWithEncodedJsonName" From d70c831e1d9a08dd964ba807dfeaca6a18ea26a0 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 6 Sep 2023 11:49:09 -0400 Subject: [PATCH 09/55] mobile: fixing fix_format (#29436) Signed-off-by: Alyssa Wilk --- .github/workflows/mobile-format.yml | 1 - mobile/tools/check_format.sh | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/mobile-format.yml b/.github/workflows/mobile-format.yml index c85a7bf8eeca..5e432e4e797c 100644 --- a/.github/workflows/mobile-format.yml +++ b/.github/workflows/mobile-format.yml @@ -34,7 +34,6 @@ jobs: image: ${{ needs.env.outputs.build_image_ubuntu }} env: CLANG_FORMAT: /opt/llvm/bin/clang-format - ENVOY_BAZEL_PREFIX: "@envoy" steps: - uses: actions/checkout@v4 - name: Add safe directory diff --git a/mobile/tools/check_format.sh b/mobile/tools/check_format.sh index 60009d7a437e..920e6a8206d0 100755 --- a/mobile/tools/check_format.sh +++ b/mobile/tools/check_format.sh @@ -47,4 +47,4 @@ FORMAT_ARGS+=( ./library/common/extensions ./test/java ./test/kotlin ./test/objective-c ./test/swift ./experimental/swift) -./bazelw run @envoy//tools/code_format:check_format -- --path "$PWD" "${FORMAT_ARGS[@]}" +export ENVOY_BAZEL_PREFIX="@envoy" && ./bazelw run @envoy//tools/code_format:check_format -- --path "$PWD" "${FORMAT_ARGS[@]}" From a6cdc5e272a867a84998c55fb66e101fe1da2728 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 6 Sep 2023 13:39:30 -0400 Subject: [PATCH 10/55] mobile: fixing dumpStats from Java (#29426) Also adding a stats parsing utility and cronet tests. Risk Level: low Testing: integration tests for java/kotlin Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- .../envoymobile/engine/EnvoyEngineImpl.java | 2 +- .../envoymobile/engine/JniLibrary.java | 3 +- .../envoymobile/utilities/StatsUtils.java | 29 +++++++++++++++++++ .../chromium/net/impl/CronvoyEngineTest.java | 9 ++++++ .../test/kotlin/integration/EngineApiTest.kt | 3 +- 5 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 mobile/library/java/io/envoyproxy/envoymobile/utilities/StatsUtils.java diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java index 773333d98687..f0d82f9d6866 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java @@ -61,7 +61,7 @@ public void flushStats() { @Override public String dumpStats() { - return JniLibrary.dumpStats(); + return JniLibrary.dumpStats(engineHandle); } /** diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java index 5dc3698f29d5..4bda63f72edc 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -214,9 +214,10 @@ protected static native int recordCounterInc(long engine, String elements, byte[ /** * Retrieve the value of all active stats. Note that this function may block for some time. + * @param engine, handle to the engine that owns the counter. * @return The list of active stats and their values, or empty string of the operation failed */ - protected static native String dumpStats(); + protected static native String dumpStats(long engine); /** * Register a platform-provided key-value store implementation. diff --git a/mobile/library/java/io/envoyproxy/envoymobile/utilities/StatsUtils.java b/mobile/library/java/io/envoyproxy/envoymobile/utilities/StatsUtils.java new file mode 100644 index 000000000000..c8cfebc3a07b --- /dev/null +++ b/mobile/library/java/io/envoyproxy/envoymobile/utilities/StatsUtils.java @@ -0,0 +1,29 @@ +package io.envoyproxy.envoymobile.utilities; + +import java.util.HashMap; +import java.util.Map; + +/** + * Class for utilities around Envoy stats. + */ +public class StatsUtils { + + /** + * Takes in a stats string from engine.dumpStats() and parses it into individual map entries. + * @param statsString, the string from dumpStats + * @return a map of stats entries, e.g. "runtime.load_success", "1" + */ + public static Map statsToList(String statsString) { + Map stats = new HashMap<>(); + + for (String line : statsString.split("\n")) { + String[] keyValue = line.split(": "); + if (keyValue.length != 2) { + System.out.println("Unexpected stats token"); + continue; + } + stats.put(keyValue[0], keyValue[1]); + } + return stats; + } +} diff --git a/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java b/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java index 160e50eaafa5..a4a7de14702c 100644 --- a/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java +++ b/mobile/test/java/org/chromium/net/impl/CronvoyEngineTest.java @@ -10,6 +10,7 @@ import androidx.test.core.app.ApplicationProvider; import io.envoyproxy.envoymobile.RequestMethod; import io.envoyproxy.envoymobile.engine.AndroidJniLibrary; +import io.envoyproxy.envoymobile.utilities.StatsUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; @@ -88,6 +89,14 @@ public void get_simple() throws Exception { assertThat(response.getResponseCode()).isEqualTo(HTTP_OK); assertThat(response.getBodyAsString()).isEqualTo("hello, world"); assertThat(response.getCronetException()).withFailMessage(response.getErrorMessage()).isNull(); + + // Do some basic stats accounting. + String stats = cronvoyEngine.getEnvoyEngine().dumpStats(); + Map statsMap = StatsUtils.statsToList(stats); + assertThat(statsMap.containsKey("http.hcm.downstream_rq_2xx")); + assertThat(statsMap.containsKey("http.hcm.downstream_total")); + assertThat(statsMap.containsKey("runtime.load_success")); + assertThat(statsMap.get("runtime.load_success")).isEqualTo("1"); } @Test diff --git a/mobile/test/kotlin/integration/EngineApiTest.kt b/mobile/test/kotlin/integration/EngineApiTest.kt index 0b71a46d744a..540e10806878 100644 --- a/mobile/test/kotlin/integration/EngineApiTest.kt +++ b/mobile/test/kotlin/integration/EngineApiTest.kt @@ -28,8 +28,7 @@ class EngineApiTest { engine.pulseClient().counter(Element("foo"), Element("bar")).increment(1) - // FIXME(jpsim): Fix crash that occurs when uncommenting this line - // assertThat(engine?.dumpStats()).contains("pulse.foo.bar: 1") + assertThat(engine.dumpStats()).contains("pulse.foo.bar: 1") engine.terminate() } From 0546512d1f2962b9c418f1ce462e9033f079d884 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 6 Sep 2023 14:21:36 -0400 Subject: [PATCH 11/55] tooling: removing files without throw from "throw exempt" checks (#29451) Signed-off-by: Alyssa Wilk --- tools/code_format/config.yaml | 41 ----------------------------------- 1 file changed, 41 deletions(-) diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 7a8f1c869bc4..951db5311eb5 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -93,7 +93,6 @@ paths: - source/extensions/common/matcher/trie_matcher.h # legacy core files which throw exceptions. We can add to this list but strongly prefer # StausOr where possible. - - source/common/upstream/wrsq_scheduler.h - source/common/upstream/cds_api_helper.cc - source/common/upstream/thread_aware_lb_impl.cc - source/common/upstream/load_balancer_impl.cc @@ -102,18 +101,13 @@ paths: - source/common/upstream/cds_api_impl.cc - source/common/upstream/outlier_detection_impl.cc - source/common/upstream/upstream_impl.cc - - source/common/network/connection_impl.cc - source/common/network/listen_socket_impl.cc - source/common/network/io_socket_handle_impl.cc - source/common/network/address_impl.cc - - source/common/network/address_impl.h - source/common/network/cidr_range.cc - source/common/network/utility.cc - source/common/network/dns_resolver/dns_factory_util.cc - - source/common/network/dns_resolver/dns_factory_util.h - source/common/network/resolver_impl.cc - - source/common/network/utility.h - - source/common/network/lc_trie.h - source/common/network/socket_impl.cc - source/common/ssl/tls_certificate_config_impl.cc - source/common/formatter/http_specific_formatter.cc @@ -128,62 +122,35 @@ paths: - source/common/http/filter_chain_helper.cc - source/common/http/utility.cc - source/common/http/hash_policy.cc - - source/common/http/utility.h - - source/common/http/filter_chain_helper.h - source/common/http/conn_manager_utility.cc - source/common/http/match_delegate/config.cc - source/common/protobuf/yaml_utility.cc - - source/common/protobuf/visitor_helper.h - source/common/protobuf/visitor.cc - source/common/protobuf/utility.cc - - source/common/protobuf/utility.h - source/common/protobuf/message_validator_impl.cc - - source/common/access_log/access_log_impl.h - source/common/access_log/access_log_manager_impl.cc - source/common/secret/secret_manager_impl.cc - - source/common/secret/sds_api.h - source/common/secret/sds_api.cc - source/common/grpc/async_client_manager_impl.cc - source/common/grpc/common.cc - source/common/grpc/google_grpc_utils.cc - - source/common/grpc/common.h - source/common/tcp_proxy/tcp_proxy.cc - - source/common/config/xds_resource.h - source/common/config/subscription_factory_impl.cc - source/common/config/xds_resource.cc - source/common/config/datasource.cc - - source/common/config/xds_mux/delta_subscription_state.cc - - source/common/config/xds_mux/sotw_subscription_state.cc - - source/common/config/xds_mux/grpc_mux_impl.cc - source/common/config/utility.cc - - source/common/config/watch_map.h - - source/common/config/datasource.h - - source/common/config/null_grpc_mux_impl.h - - source/common/config/utility.h - - source/common/config/custom_config_validators_impl.h - source/common/config/xds_context_params.cc - - source/common/config/custom_config_validators_impl.cc - source/common/runtime/runtime_impl.cc - source/common/quic/quic_transport_socket_factory.cc - source/common/filter/config_discovery_impl.cc - - source/common/event/timer_impl.h - - source/common/matcher/map_matcher.h - - source/common/matcher/field_matcher.h - source/common/json/json_internal.cc - - source/common/json/json_internal.h - - source/common/json/json_sanitizer.cc - - source/common/router/header_formatter.cc - source/common/router/scoped_rds.cc - - source/common/router/rds_impl.cc - source/common/router/config_impl.cc - - source/common/router/config_impl.h - source/common/router/scoped_config_impl.cc - source/common/router/router_ratelimit.cc - source/common/router/vhds.cc - source/common/router/config_utility.cc - - source/common/router/scoped_rds.h - source/common/router/header_parser.cc - - source/common/router/config_utility.h - source/common/rds/rds_route_config_subscription.cc - source/common/filesystem/inotify/watcher_impl.cc - source/common/filesystem/posix/directory_iterator_impl.cc @@ -194,22 +161,14 @@ paths: - source/common/filesystem/win32/watcher_impl.cc - source/common/common/utility.cc - source/common/common/regex.cc - - source/common/common/logger.h - - source/common/common/thread.h - - source/common/common/utility.h - - source/common/common/matchers.h - source/common/common/matchers.cc - source/exe/stripped_main_base.cc - source/server/options_impl.cc - - source/server/options_impl.h - - source/server/server.h - source/server/overload_manager_impl.cc - - source/server/config_validation/cluster_manager.cc - source/server/config_validation/server.cc - source/server/admin/html/active_stats.js - source/server/server.cc - source/server/utils.cc - - source/server/configuration_impl.h - source/server/hot_restarting_base.cc - source/server/hot_restart_impl.cc - source/server/ssl_context_manager.cc From 41353a7977a8cb60a6b4a2ad99a91b9660118f50 Mon Sep 17 00:00:00 2001 From: Tanuj Mittal Date: Wed, 6 Sep 2023 12:24:39 -0700 Subject: [PATCH 12/55] Add set_envoy_filter_state proxy wasm foreign function (#29320) Proxy wasm supports setProperty method. However that method always prefixes all property keys with `wasm.`. So currently there's no way to set envoy filter state from wasm which prevents from using it for some scenarios (like chaining a custom network wasm filter with tcp proxy filter and setting `envoy.tcp_proxy.cluster` filter state to select upstream cluster). This change adds a new wasm foreign function `set_envoy_filter_state` and uses the [object factory to create filter state objects](https://github.com/envoyproxy/envoy/pull/29030). Related Issue: https://github.com/envoyproxy/envoy/issues/28673 Commit Message: Add set_envoy_filter_state proxy wasm foreign function Additional Description: Adds a proxy wasm foreign function to set envoy filter state Risk Level: low Testing: done Signed-off-by: Tanuj Mittal --- source/extensions/common/wasm/BUILD | 1 + source/extensions/common/wasm/context.cc | 23 +++++++ source/extensions/common/wasm/context.h | 2 + source/extensions/common/wasm/ext/BUILD | 17 ++++++ .../wasm/ext/set_envoy_filter_state.proto | 14 +++++ source/extensions/common/wasm/foreign.cc | 44 ++++++++----- test/extensions/common/wasm/BUILD | 2 + test/extensions/common/wasm/foreign_test.cc | 61 +++++++++++++++++++ 8 files changed, 149 insertions(+), 15 deletions(-) create mode 100644 source/extensions/common/wasm/ext/set_envoy_filter_state.proto diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index ba67df990189..e99b5474fb47 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -92,6 +92,7 @@ envoy_cc_extension( "//source/common/tracing:http_tracer_lib", "//source/extensions/common/wasm/ext:declare_property_cc_proto", "//source/extensions/common/wasm/ext:envoy_null_vm_wasm_api", + "//source/extensions/common/wasm/ext:set_envoy_filter_state_cc_proto", "//source/extensions/filters/common/expr:context_lib", "@com_google_cel_cpp//eval/public:builtin_func_registrar", "@com_google_cel_cpp//eval/public:cel_expr_builder_factory", diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index adcc4be4c8b3..e43f296255e9 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -1125,6 +1125,29 @@ WasmResult Context::setProperty(std::string_view path, std::string_view value) { return WasmResult::Ok; } +WasmResult Context::setEnvoyFilterState(std::string_view path, std::string_view value, + StreamInfo::FilterState::LifeSpan life_span) { + auto* factory = + Registry::FactoryRegistry::getFactory(path); + if (!factory) { + return WasmResult::NotFound; + } + + auto object = factory->createFromBytes(value); + if (!object) { + return WasmResult::BadArgument; + } + + auto* stream_info = getRequestStreamInfo(); + if (!stream_info) { + return WasmResult::NotFound; + } + + stream_info->filterState()->setData(path, std::move(object), + StreamInfo::FilterState::StateType::Mutable, life_span); + return WasmResult::Ok; +} + WasmResult Context::declareProperty(std::string_view path, Filters::Common::Expr::CelStatePrototypeConstPtr state_prototype) { diff --git a/source/extensions/common/wasm/context.h b/source/extensions/common/wasm/context.h index 51778de53881..e755d26a0871 100644 --- a/source/extensions/common/wasm/context.h +++ b/source/extensions/common/wasm/context.h @@ -207,6 +207,8 @@ class Context : public proxy_wasm::ContextBase, // State accessors WasmResult getProperty(std::string_view path, std::string* result) override; WasmResult setProperty(std::string_view path, std::string_view value) override; + WasmResult setEnvoyFilterState(std::string_view path, std::string_view value, + StreamInfo::FilterState::LifeSpan life_span); WasmResult declareProperty(std::string_view path, Filters::Common::Expr::CelStatePrototypeConstPtr state_prototype); diff --git a/source/extensions/common/wasm/ext/BUILD b/source/extensions/common/wasm/ext/BUILD index c42895a0526b..3d437b4a3b55 100644 --- a/source/extensions/common/wasm/ext/BUILD +++ b/source/extensions/common/wasm/ext/BUILD @@ -30,6 +30,7 @@ envoy_cc_library( ], deps = [ ":declare_property_cc_proto", + ":set_envoy_filter_state_cc_proto", "//source/common/grpc:async_client_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], @@ -44,6 +45,7 @@ cc_library( tags = ["manual"], deps = [ ":declare_property_cc_proto", + ":set_envoy_filter_state_cc_proto", ":node_subset_cc_proto", "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", ], @@ -84,3 +86,18 @@ cc_proto_library( # "//external:protobuf_clib", ], ) + +# NB: this target is compiled both to native code and to Wasm. Hence the generic rule. +proto_library( + name = "set_envoy_filter_state_proto", + srcs = ["set_envoy_filter_state.proto"], + deps = [ + "declare_property_proto", + ], +) + +# NB: this target is compiled both to native code and to Wasm. Hence the generic rule. +cc_proto_library( + name = "set_envoy_filter_state_cc_proto", + deps = [":set_envoy_filter_state_proto"], +) diff --git a/source/extensions/common/wasm/ext/set_envoy_filter_state.proto b/source/extensions/common/wasm/ext/set_envoy_filter_state.proto new file mode 100644 index 000000000000..9f2587aa6c55 --- /dev/null +++ b/source/extensions/common/wasm/ext/set_envoy_filter_state.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package envoy.source.extensions.common.wasm; + +import "source/extensions/common/wasm/ext/declare_property.proto"; + +message SetEnvoyFilterStateArguments { + // path is the filter state key + string path = 1; + // value is the filter state object factory input + string value = 2; + // span is the life span of the filter state object + LifeSpan span = 3; +}; diff --git a/source/extensions/common/wasm/foreign.cc b/source/extensions/common/wasm/foreign.cc index 3f153461e96c..17527f8daccd 100644 --- a/source/extensions/common/wasm/foreign.cc +++ b/source/extensions/common/wasm/foreign.cc @@ -1,5 +1,6 @@ #include "source/common/common/logger.h" #include "source/extensions/common/wasm/ext/declare_property.pb.h" +#include "source/extensions/common/wasm/ext/set_envoy_filter_state.pb.h" #include "source/extensions/common/wasm/wasm.h" #if defined(WASM_USE_CEL_PARSER) @@ -24,6 +25,20 @@ template WasmForeignFunction createFromClass() { return c->create(c); } +inline StreamInfo::FilterState::LifeSpan +toFilterStateLifeSpan(envoy::source::extensions::common::wasm::LifeSpan span) { + switch (span) { + case envoy::source::extensions::common::wasm::LifeSpan::FilterChain: + return StreamInfo::FilterState::LifeSpan::FilterChain; + case envoy::source::extensions::common::wasm::LifeSpan::DownstreamRequest: + return StreamInfo::FilterState::LifeSpan::Request; + case envoy::source::extensions::common::wasm::LifeSpan::DownstreamConnection: + return StreamInfo::FilterState::LifeSpan::Connection; + default: + return StreamInfo::FilterState::LifeSpan::FilterChain; + } +} + RegisterForeignFunction registerCompressForeignFunction( "compress", [](WasmBase&, std::string_view arguments, @@ -61,6 +76,19 @@ RegisterForeignFunction registerUncompressForeignFunction( } }); +RegisterForeignFunction registerSetEnvoyFilterStateForeignFunction( + "set_envoy_filter_state", + [](WasmBase&, std::string_view arguments, + const std::function&) -> WasmResult { + envoy::source::extensions::common::wasm::SetEnvoyFilterStateArguments args; + if (args.ParseFromArray(arguments.data(), arguments.size())) { + auto context = static_cast(proxy_wasm::current_context_); + return context->setEnvoyFilterState(args.path(), args.value(), + toFilterStateLifeSpan(args.span())); + } + return WasmResult::BadArgument; + }); + #if defined(WASM_USE_CEL_PARSER) class ExpressionFactory : public Logger::Loggable { protected: @@ -242,21 +270,7 @@ class DeclarePropertyFactory { // do nothing break; } - StreamInfo::FilterState::LifeSpan span = StreamInfo::FilterState::LifeSpan::FilterChain; - switch (args.span()) { - case envoy::source::extensions::common::wasm::LifeSpan::FilterChain: - span = StreamInfo::FilterState::LifeSpan::FilterChain; - break; - case envoy::source::extensions::common::wasm::LifeSpan::DownstreamRequest: - span = StreamInfo::FilterState::LifeSpan::Request; - break; - case envoy::source::extensions::common::wasm::LifeSpan::DownstreamConnection: - span = StreamInfo::FilterState::LifeSpan::Connection; - break; - default: - // do nothing - break; - } + StreamInfo::FilterState::LifeSpan span = toFilterStateLifeSpan(args.span()); auto context = static_cast(proxy_wasm::current_context_); return context->declareProperty( args.name(), std::make_unique( diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index 113d5e5ecba6..bfbfb093be58 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -131,6 +131,8 @@ envoy_cc_test( ], }), deps = [ + "//source/common/network:filter_state_dst_address_lib", + "//source/common/tcp_proxy", "//source/extensions/common/wasm:wasm_hdr", "//source/extensions/common/wasm:wasm_lib", "//test/mocks/http:http_mocks", diff --git a/test/extensions/common/wasm/foreign_test.cc b/test/extensions/common/wasm/foreign_test.cc index 00cdc74feb09..2d5969976418 100644 --- a/test/extensions/common/wasm/foreign_test.cc +++ b/test/extensions/common/wasm/foreign_test.cc @@ -1,5 +1,8 @@ #include "source/common/event/dispatcher_impl.h" +#include "source/common/network/filter_state_dst_address.h" #include "source/common/stats/isolated_store_impl.h" +#include "source/common/tcp_proxy/tcp_proxy.h" +#include "source/extensions/common/wasm/ext/set_envoy_filter_state.pb.h" #include "source/extensions/common/wasm/wasm.h" #include "test/mocks/local_info/mocks.h" @@ -17,8 +20,14 @@ namespace Wasm { class TestContext : public Context {}; class ForeignTest : public testing::Test { +public: + ForeignTest() {} + + void initializeFilterCallbacks() { ctx_.initializeReadFilterCallbacks(read_filter_callbacks_); } + protected: TestContext ctx_; + testing::NiceMock read_filter_callbacks_; }; TEST_F(ForeignTest, ForeignFunctionEdgeCaseTest) { @@ -51,6 +60,58 @@ TEST_F(ForeignTest, ForeignFunctionEdgeCaseTest) { EXPECT_EQ(result, WasmResult::BadArgument); } +TEST_F(ForeignTest, ForeignFunctionSetEnvoyFilterTest) { + initializeFilterCallbacks(); // so that we can test setting filter state + Stats::IsolatedStoreImpl stats_store; + Api::ApiPtr api = Api::createApiForTest(stats_store); + Upstream::MockClusterManager cluster_manager; + Event::DispatcherPtr dispatcher(api->allocateDispatcher("wasm_test")); + auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); + testing::NiceMock local_info; + + envoy::extensions::wasm::v3::PluginConfig plugin_config; + auto plugin = std::make_shared( + plugin_config, envoy::config::core::v3::TrafficDirection::UNSPECIFIED, local_info, nullptr); + Wasm wasm(plugin->wasmConfig(), "", scope, *api, cluster_manager, *dispatcher); + proxy_wasm::current_context_ = &ctx_; + + auto function = proxy_wasm::getForeignFunction("set_envoy_filter_state"); + ASSERT_NE(function, nullptr); + + auto result = function(wasm, "bad_arg", [](size_t size) { return malloc(size); }); + EXPECT_EQ(result, WasmResult::BadArgument); + + envoy::source::extensions::common::wasm::SetEnvoyFilterStateArguments args; + std::string in; + + args.set_path("invalid.path"); + args.set_value("unicorns"); + args.SerializeToString(&in); + result = function(wasm, in, [](size_t size) { return malloc(size); }); + EXPECT_EQ(result, WasmResult::NotFound); + + auto* stream_info = ctx_.getRequestStreamInfo(); + ASSERT_NE(stream_info, nullptr); + + args.set_path(TcpProxy::PerConnectionCluster::key()); + args.set_value("unicorns"); + args.set_span(envoy::source::extensions::common::wasm::LifeSpan::DownstreamRequest); + args.SerializeToString(&in); + result = function(wasm, in, [](size_t size) { return malloc(size); }); + EXPECT_EQ(result, WasmResult::Ok); + EXPECT_TRUE(stream_info->filterState()->hasData( + TcpProxy::PerConnectionCluster::key())); + + args.set_path(Network::DestinationAddress::key()); + args.set_value("1.2.3.4:80"); + args.set_span(envoy::source::extensions::common::wasm::LifeSpan::DownstreamRequest); + args.SerializeToString(&in); + result = function(wasm, in, [](size_t size) { return malloc(size); }); + EXPECT_EQ(result, WasmResult::Ok); + EXPECT_TRUE(stream_info->filterState()->hasData( + Network::DestinationAddress::key())); +} + } // namespace Wasm } // namespace Common } // namespace Extensions From b8dfc0927722f4c96fe7d0cb1727e77554c99786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20B=C3=A9ky?= Date: Wed, 6 Sep 2023 16:34:36 -0400 Subject: [PATCH 13/55] Default-enable BalsaParser. (#28896) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This feature has been widely deployed by two large projects at different companies without any production issues. A third project has enabled it in staging and in end-to-end tests. Manual load testing does not show significant change in CPU or memory usage compared to the legacy HTTP/1 parser. Tracking issue: #21245 Commit Message: Default-enable BalsaParser. Additional Description: Risk Level: medium Testing: unit tests and e2e test within Envoy and by multiple embedders Docs Changes: n/a Release Notes: Default-enable BalsaParser. Platform Specific Features: n/a Signed-off-by: Bence Béky Co-authored-by: Bence Béky --- changelogs/current.yaml | 5 +++++ source/common/runtime/runtime_features.cc | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index cbd08feade01..774cde421735 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -14,6 +14,11 @@ behavior_changes: :ref:`initial_fetch_timeout ` times out, and will then apply the cached assignment and finish updating the warmed cluster. This change is disabled by default, and can be enabled by setting the runtime flag ``envoy.restart_features.use_eds_cache_for_ads`` to true. +- area: http + change: | + Switch from http_parser to BalsaParser for handling HTTP/1.1 traffic. See https://github.com/envoyproxy/envoy/issues/21245 for + details. This behavioral change can be reverted by setting runtime flag ``envoy.reloadable_features.http1_use_balsa_parser`` to + false. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 64aee5daf7db..f56e402b4958 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -49,6 +49,7 @@ RUNTIME_GUARD(envoy_reloadable_features_format_ports_as_numbers); RUNTIME_GUARD(envoy_reloadable_features_handle_uppercase_scheme); RUNTIME_GUARD(envoy_reloadable_features_hmac_base64_encoding_only); RUNTIME_GUARD(envoy_reloadable_features_http1_allow_codec_error_response_after_1xx_headers); +RUNTIME_GUARD(envoy_reloadable_features_http1_use_balsa_parser); RUNTIME_GUARD(envoy_reloadable_features_http2_decode_metadata_with_quiche); RUNTIME_GUARD(envoy_reloadable_features_http2_validate_authority_with_quiche); RUNTIME_GUARD(envoy_reloadable_features_http_allow_partial_urls_in_referer); @@ -101,8 +102,6 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_unified_mux); FALSE_RUNTIME_GUARD(envoy_reloadable_features_defer_processing_backedup_streams); // TODO(birenroy) flip after a burn-in period FALSE_RUNTIME_GUARD(envoy_reloadable_features_http2_use_oghttp2); -// TODO(bencebeky): Flip true after sufficient canarying. -FALSE_RUNTIME_GUARD(envoy_reloadable_features_http1_use_balsa_parser); // Used to track if runtime is initialized. FALSE_RUNTIME_GUARD(envoy_reloadable_features_runtime_initialized); // TODO(mattklein123): Flip this to true and/or remove completely once verified by Envoy Mobile. From eaefc43fde9d494bfd829b24eba6836a8475075a Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 6 Sep 2023 20:00:58 -0400 Subject: [PATCH 14/55] http: changing the alt protocols cache filter so it can be used for clusters with different caches (#27996) http: changing the alt protocols cache filter so it can be used across clusters with different caches. Signed-off-by: Alyssa Wilk --- changelogs/current.yaml | 5 ++ source/common/runtime/runtime_features.cc | 1 + .../http/alternate_protocols_cache/filter.cc | 26 ++++-- .../http/alternate_protocols_cache/filter.h | 7 +- .../http/alternate_protocols_cache/BUILD | 1 + .../alternate_protocols_cache/filter_test.cc | 85 +++++++++++++++++-- 6 files changed, 111 insertions(+), 14 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 774cde421735..f5051f85f19e 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -25,6 +25,11 @@ minor_behavior_changes: - area: ext_authz change: | removing any query parameter in the presence of repeated query parameter keys no longer drops the repeats. +- area: alternate_protocols_cache_filter + change: | + Changed the alternate protocols cache filter to get the cache from cluster config rather than filter config. + This allows one downstream filter to be used with multiple clusters with different caches. This change can be reverted by + setting runtime guard ``envoy.reloadable_features.use_cluster_cache_for_alt_protocols_filter`` to false. - area: ext_authz change: | Don't append the local address to ``x-forwarded-for`` header when sending an http (not gRPC) auth request. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index f56e402b4958..008fcdd50e96 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -83,6 +83,7 @@ RUNTIME_GUARD(envoy_reloadable_features_token_passed_entirely); RUNTIME_GUARD(envoy_reloadable_features_uhv_allow_malformed_url_encoding); RUNTIME_GUARD(envoy_reloadable_features_upstream_allow_connect_with_2xx); RUNTIME_GUARD(envoy_reloadable_features_upstream_wait_for_response_headers_before_disabling_read); +RUNTIME_GUARD(envoy_reloadable_features_use_cluster_cache_for_alt_protocols_filter); RUNTIME_GUARD(envoy_reloadable_features_use_http3_header_normalisation); RUNTIME_GUARD(envoy_reloadable_features_validate_connect); RUNTIME_GUARD(envoy_reloadable_features_validate_detailed_override_host_statuses); diff --git a/source/extensions/filters/http/alternate_protocols_cache/filter.cc b/source/extensions/filters/http/alternate_protocols_cache/filter.cc index 5982ceceb3e6..fb8339cb44b1 100644 --- a/source/extensions/filters/http/alternate_protocols_cache/filter.cc +++ b/source/extensions/filters/http/alternate_protocols_cache/filter.cc @@ -8,6 +8,7 @@ #include "source/common/http/headers.h" #include "source/common/http/http_server_properties_cache_impl.h" #include "source/common/http/http_server_properties_cache_manager_impl.h" +#include "source/common/runtime/runtime_features.h" namespace Envoy { namespace Extensions { @@ -35,23 +36,34 @@ FilterConfig::getAlternateProtocolCache(Event::Dispatcher& dispatcher) { void Filter::onDestroy() {} Filter::Filter(const FilterConfigSharedPtr& config, Event::Dispatcher& dispatcher) - : cache_(config->getAlternateProtocolCache(dispatcher)), time_source_(config->timeSource()) {} + : config_(config), dispatcher_(dispatcher) {} Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers, bool) { - - if (!cache_) { - return Http::FilterHeadersStatus::Continue; - } const auto alt_svc = headers.get(Http::CustomHeaders::get().AltSvc); if (alt_svc.empty()) { return Http::FilterHeadersStatus::Continue; } + const bool use_cluster_cache = Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.use_cluster_cache_for_alt_protocols_filter"); + Http::HttpServerPropertiesCacheSharedPtr cache; + if (use_cluster_cache) { + auto info = encoder_callbacks_->streamInfo().upstreamClusterInfo(); + if (info && (*info)->alternateProtocolsCacheOptions()) { + cache = config_->alternateProtocolCacheManager()->getCache( + *((*info)->alternateProtocolsCacheOptions()), dispatcher_); + } + } else { + cache = config_->getAlternateProtocolCache(dispatcher_); + } + if (!cache) { + return Http::FilterHeadersStatus::Continue; + } std::vector protocols; for (size_t i = 0; i < alt_svc.size(); ++i) { std::vector advertised_protocols = Http::HttpServerPropertiesCacheImpl::alternateProtocolsFromString( - alt_svc[i]->value().getStringView(), time_source_, false); + alt_svc[i]->value().getStringView(), config_->timeSource(), false); if (advertised_protocols.empty()) { ENVOY_LOG(trace, "Invalid Alt-Svc header received: '{}'", alt_svc[i]->value().getStringView()); @@ -77,7 +89,7 @@ Http::FilterHeadersStatus Filter::encodeHeaders(Http::ResponseHeaderMap& headers const uint32_t port = host->address()->ip()->port(); Http::HttpServerPropertiesCache::Origin origin(Http::Headers::get().SchemeValues.Https, hostname, port); - cache_->setAlternatives(origin, protocols); + cache->setAlternatives(origin, protocols); return Http::FilterHeadersStatus::Continue; } diff --git a/source/extensions/filters/http/alternate_protocols_cache/filter.h b/source/extensions/filters/http/alternate_protocols_cache/filter.h index 11d1a4105adf..32bc82494030 100644 --- a/source/extensions/filters/http/alternate_protocols_cache/filter.h +++ b/source/extensions/filters/http/alternate_protocols_cache/filter.h @@ -26,6 +26,9 @@ class FilterConfig { Http::HttpServerPropertiesCacheSharedPtr getAlternateProtocolCache(Event::Dispatcher& dispatcher); TimeSource& timeSource() { return time_source_; } + const Http::HttpServerPropertiesCacheManagerSharedPtr alternateProtocolCacheManager() { + return alternate_protocol_cache_manager_; + } private: const Http::HttpServerPropertiesCacheManagerSharedPtr alternate_protocol_cache_manager_; @@ -50,8 +53,8 @@ class Filter : public Http::PassThroughEncoderFilter, void onDestroy() override; private: - const Http::HttpServerPropertiesCacheSharedPtr cache_; - TimeSource& time_source_; + FilterConfigSharedPtr config_; + Event::Dispatcher& dispatcher_; }; } // namespace AlternateProtocolsCache diff --git a/test/extensions/filters/http/alternate_protocols_cache/BUILD b/test/extensions/filters/http/alternate_protocols_cache/BUILD index f76cee6c60b8..b6e4e01a9268 100644 --- a/test/extensions/filters/http/alternate_protocols_cache/BUILD +++ b/test/extensions/filters/http/alternate_protocols_cache/BUILD @@ -20,6 +20,7 @@ envoy_extension_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/http:http_server_properties_cache_mocks", "//test/mocks/network:network_mocks", + "//test/test_common:test_runtime_lib", "@envoy_api//envoy/extensions/filters/http/alternate_protocols_cache/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/http/alternate_protocols_cache/filter_test.cc b/test/extensions/filters/http/alternate_protocols_cache/filter_test.cc index b8df625eb291..920a6f296f4c 100644 --- a/test/extensions/filters/http/alternate_protocols_cache/filter_test.cc +++ b/test/extensions/filters/http/alternate_protocols_cache/filter_test.cc @@ -6,6 +6,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" #include "test/test_common/simulated_time_system.h" +#include "test/test_common/test_runtime.h" using testing::Return; using testing::ReturnRef; @@ -33,6 +34,7 @@ class FilterTest : public testing::Test, public Event::TestUsingSimulatedTime { if (populate_config) { proto_config.mutable_alternate_protocols_cache_options()->set_name("foo"); EXPECT_CALL(*alternate_protocols_cache_manager_, getCache(_, _)) + .Times(testing::AnyNumber()) .WillOnce(Return(alternate_protocols_cache_)); } filter_config_ = std::make_shared( @@ -47,7 +49,7 @@ class FilterTest : public testing::Test, public Event::TestUsingSimulatedTime { std::shared_ptr alternate_protocols_cache_; FilterConfigSharedPtr filter_config_; std::unique_ptr filter_; - Http::MockStreamEncoderFilterCallbacks callbacks_; + NiceMock callbacks_; Http::TestRequestHeaderMapImpl request_headers_{{":authority", "foo"}}; }; @@ -82,6 +84,21 @@ TEST_F(FilterTest, NoAltSvc) { TEST_F(FilterTest, InvalidAltSvc) { Http::TestResponseHeaderMapImpl headers{{":status", "200"}, {"alt-svc", "garbage"}}; + // Set up the cluster info correctly to have a cache configuration. + envoy::extensions::filters::http::alternate_protocols_cache::v3::FilterConfig proto_config; + proto_config.mutable_alternate_protocols_cache_options()->set_name("foo"); + auto info = std::make_shared>(); + callbacks_.stream_info_.upstream_cluster_info_ = info; + absl::optional options = + proto_config.alternate_protocols_cache_options(); + ON_CALL(*info, alternateProtocolsCacheOptions()).WillByDefault(ReturnRef(options)); + EXPECT_CALL(*alternate_protocols_cache_manager_, getCache(_, _)) + .Times(testing::AnyNumber()) + .WillOnce(Return(alternate_protocols_cache_)); + EXPECT_CALL(callbacks_, streamInfo()) + .Times(testing::AtLeast(1)) + .WillRepeatedly(ReturnRef(callbacks_.stream_info_)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(headers, false)); filter_->onDestroy(); } @@ -102,16 +119,74 @@ TEST_F(FilterTest, ValidAltSvc) { std::make_shared("1.2.3.4:443", "1.2.3.4:443"); Network::MockIp ip; std::string hostname = "host1"; - StreamInfo::MockStreamInfo stream_info; + + // Set up the cluster info correctly to have a cache configuration. + envoy::extensions::filters::http::alternate_protocols_cache::v3::FilterConfig proto_config; + proto_config.mutable_alternate_protocols_cache_options()->set_name("foo"); + auto info = std::make_shared>(); + callbacks_.stream_info_.upstream_cluster_info_ = info; + absl::optional options = + proto_config.alternate_protocols_cache_options(); + ON_CALL(*info, alternateProtocolsCacheOptions()).WillByDefault(ReturnRef(options)); + EXPECT_CALL(*alternate_protocols_cache_manager_, getCache(_, _)) + .Times(testing::AnyNumber()) + .WillOnce(Return(alternate_protocols_cache_)); EXPECT_CALL(callbacks_, streamInfo()) .Times(testing::AtLeast(1)) - .WillRepeatedly(ReturnRef(stream_info)); - EXPECT_CALL(stream_info, upstreamInfo()).Times(testing::AtLeast(1)); + .WillRepeatedly(ReturnRef(callbacks_.stream_info_)); + EXPECT_CALL(callbacks_.stream_info_, upstreamClusterInfo()) + .Times(testing::AtLeast(1)) + .WillRepeatedly(Return(info)); + EXPECT_CALL(callbacks_.stream_info_, upstreamInfo()).Times(testing::AtLeast(1)); + // Get the pointer to MockHostDescription. + std::shared_ptr hd = + std::dynamic_pointer_cast( + callbacks_.stream_info_.upstreamInfo()->upstreamHost()); + EXPECT_CALL(*hd, hostname()).WillOnce(ReturnRef(hostname)); + EXPECT_CALL(*hd, address()).WillOnce(Return(address)); + EXPECT_CALL(*address, ip()).WillOnce(Return(&ip)); + EXPECT_CALL(ip, port()).WillOnce(Return(443)); + EXPECT_CALL(*alternate_protocols_cache_, setAlternatives(_, _)) + .WillOnce(testing::DoAll( + testing::WithArg<0>(Invoke([expected_origin](auto& actual_origin) { + EXPECT_EQ(expected_origin, actual_origin) + << dumpOrigin(expected_origin) << dumpOrigin(actual_origin); + })), + testing::WithArg<1>(Invoke([expected_protocols](auto& actual_protocols) { + EXPECT_EQ(expected_protocols, actual_protocols) << dumpAlternative(actual_protocols[0]); + ; + })))); + + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(headers, false)); + filter_->onDestroy(); +} + +TEST_F(FilterTest, ValidAltSvcLegacy) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.use_cluster_cache_for_alt_protocols_filter", "false"}}); + + Http::TestResponseHeaderMapImpl headers{ + {":status", "200"}, {"alt-svc", "h3-29=\":443\"; ma=86400, h3=\":443\"; ma=60"}}; + Http::HttpServerPropertiesCache::Origin expected_origin("https", "host1", 443); + MonotonicTime now = simTime().monotonicTime(); + const std::vector expected_protocols = { + Http::HttpServerPropertiesCache::AlternateProtocol("h3-29", "", 443, + now + std::chrono::seconds(86400)), + Http::HttpServerPropertiesCache::AlternateProtocol("h3", "", 443, + now + std::chrono::seconds(60)), + }; + + std::shared_ptr address = + std::make_shared("1.2.3.4:443", "1.2.3.4:443"); + Network::MockIp ip; + std::string hostname = "host1"; + // Get the pointer to MockHostDescription. std::shared_ptr hd = std::dynamic_pointer_cast( - stream_info.upstreamInfo()->upstreamHost()); + callbacks_.stream_info_.upstreamInfo()->upstreamHost()); EXPECT_CALL(*hd, hostname()).WillOnce(ReturnRef(hostname)); EXPECT_CALL(*hd, address()).WillOnce(Return(address)); EXPECT_CALL(*address, ip()).WillOnce(Return(&ip)); From 71066db120fc9b32e6ad6be5ac44eab0762033d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Sep 2023 09:04:13 +0100 Subject: [PATCH 15/55] build(deps): bump setuptools from 68.1.2 to 68.2.0 in /tools/base (#29464) Bumps [setuptools](https://github.com/pypa/setuptools) from 68.1.2 to 68.2.0. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v68.1.2...v68.2.0) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index afffb02a3206..833e96839b05 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -1543,9 +1543,9 @@ zstandard==0.21.0 \ # via envoy-base-utils # The following packages are considered to be unsafe in a requirements file: -setuptools==68.1.2 \ - --hash=sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d \ - --hash=sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b +setuptools==68.2.0 \ + --hash=sha256:00478ca80aeebeecb2f288d3206b0de568df5cd2b8fada1209843cc9a8d88a48 \ + --hash=sha256:af3d5949030c3f493f550876b2fd1dd5ec66689c4ee5d5344f009746f71fd5a8 # via # -r requirements.in # sphinxcontrib-jquery From 66529505d9451fcd8340d2e1e79dad5fbc521ae6 Mon Sep 17 00:00:00 2001 From: Jacob Bohanon <57016439+jbohanon@users.noreply.github.com> Date: Thu, 7 Sep 2023 06:29:16 -0400 Subject: [PATCH 16/55] fix push hook to use the new bazel-y check format call (#29458) Signed-off-by: Jacob Bohanon --- support/hooks/pre-push | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support/hooks/pre-push b/support/hooks/pre-push index c07afe49bc18..cd9687dbb651 100755 --- a/support/hooks/pre-push +++ b/support/hooks/pre-push @@ -72,7 +72,7 @@ do # either of these things aren't true, the check fails. for i in $(git diff --name-only "$RANGE" --diff-filter=ACMR --ignore-submodules=all 2>&1); do echo -ne " Checking format for $i - " - "$SCRIPT_DIR"/code_format/check_format.py check "$i" || exit 1 + bazel run //tools/code_format:check_format -- check "$i" || exit 1 # TODO(phlax): It seems this is not running in CI anymore and is now finding issues # in merged PRs. Unify this hook and format checks in CI when the new format tool is rolled From a19966b92bc3afcdd68ebeffe53c4b6848733e79 Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Thu, 7 Sep 2023 14:27:22 +0300 Subject: [PATCH 17/55] otlp_stats_sink: add stats prefix config option (#29204) Adds a config option to set a stat prefix for all stats flushed by the otlp stats sink. Resolves #28962. Risk Level: low Testing: Unit tests, integration tests Docs Changes: API docs Signed-off-by: ohadvano --- .../open_telemetry/v3/open_telemetry.proto | 8 ++- changelogs/current.yaml | 5 +- .../open_telemetry/open_telemetry_impl.cc | 11 +++- .../open_telemetry/open_telemetry_impl.h | 2 + .../stats_sinks/open_telemetry/config_test.cc | 3 + .../open_telemetry_impl_test.cc | 18 +++++- .../open_telemetry_integration_test.cc | 58 ++++++++++++++----- 7 files changed, 85 insertions(+), 20 deletions(-) diff --git a/api/envoy/extensions/stat_sinks/open_telemetry/v3/open_telemetry.proto b/api/envoy/extensions/stat_sinks/open_telemetry/v3/open_telemetry.proto index 3e2b84c8919e..eb7232297628 100644 --- a/api/envoy/extensions/stat_sinks/open_telemetry/v3/open_telemetry.proto +++ b/api/envoy/extensions/stat_sinks/open_telemetry/v3/open_telemetry.proto @@ -19,7 +19,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Stats configuration proto schema for ``envoy.stat_sinks.open_telemetry`` sink. // [#extension: envoy.stat_sinks.open_telemetry] -// [#next-free-field: 6] +// [#next-free-field: 7] message SinkConfig { oneof protocol_specifier { option (validate.required) = true; @@ -44,4 +44,10 @@ message SinkConfig { // If set to true, metric names will be represented as the tag extracted name instead // of the full metric name. Default value is true. google.protobuf.BoolValue use_tag_extracted_name = 5; + + // If set, emitted stats names will be prepended with a prefix, so full stat name will be + // .. For example, if the stat name is "foo.bar" and prefix is + // "pre", the full stat name will be "pre.foo.bar". If this field is not set, there is no + // prefix added. According to the example, the full stat name will remain "foo.bar". + string prefix = 6; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index f5051f85f19e..b5e5b79f9764 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -154,7 +154,6 @@ new_features: - area: access_log change: | added a field lookup to %FILTER_STATE% for objects that have reflection enabled. - - area: http change: | added :ref:`Json-To-Metadata filter `. @@ -183,6 +182,10 @@ new_features: change: | added :ref:`custom_sink ` type to enable writing tap data out to a custom sink extension. +- area: otlp_stats_sink + change: | + added :ref:` stats prefix option` + to OTLP stats sink that enables adding a static prefix to all stats flushed by this sink. - area: tap change: | added :ref:`record_headers_received_time ` diff --git a/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.cc b/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.cc index 66de1f3f67b3..dbbd376faee5 100644 --- a/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.cc +++ b/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.cc @@ -13,7 +13,8 @@ OtlpOptions::OtlpOptions(const SinkConfig& sink_config) emit_tags_as_attributes_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(sink_config, emit_tags_as_attributes, true)), use_tag_extracted_name_( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(sink_config, use_tag_extracted_name, true)) {} + PROTOBUF_GET_WRAPPED_OR_DEFAULT(sink_config, use_tag_extracted_name, true)), + stat_prefix_(sink_config.prefix() != "" ? sink_config.prefix() + "." : "") {} OpenTelemetryGrpcMetricsExporterImpl::OpenTelemetryGrpcMetricsExporterImpl( const OtlpOptionsSharedPtr config, Grpc::RawAsyncClientSharedPtr raw_async_client) @@ -131,7 +132,9 @@ void OtlpMetricsFlusherImpl::setMetricCommon( const Stats::Metric& stat) const { data_point.set_time_unix_nano(snapshot_time_ns); // TODO(ohadvano): support ``start_time_unix_nano`` optional field - metric.set_name(config_->useTagExtractedName() ? stat.tagExtractedName() : stat.name()); + metric.set_name(absl::StrCat(config_->statPrefix(), config_->useTagExtractedName() + ? stat.tagExtractedName() + : stat.name())); if (config_->emitTagsAsAttributes()) { for (const auto& tag : stat.tags()) { @@ -148,7 +151,9 @@ void OtlpMetricsFlusherImpl::setMetricCommon( const Stats::Metric& stat) const { data_point.set_time_unix_nano(snapshot_time_ns); // TODO(ohadvano): support ``start_time_unix_nano optional`` field - metric.set_name(config_->useTagExtractedName() ? stat.tagExtractedName() : stat.name()); + metric.set_name(absl::StrCat(config_->statPrefix(), config_->useTagExtractedName() + ? stat.tagExtractedName() + : stat.name())); if (config_->emitTagsAsAttributes()) { for (const auto& tag : stat.tags()) { diff --git a/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.h b/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.h index 3b944a0ea808..c62114c05891 100644 --- a/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.h +++ b/source/extensions/stat_sinks/open_telemetry/open_telemetry_impl.h @@ -41,12 +41,14 @@ class OtlpOptions { bool reportHistogramsAsDeltas() { return report_histograms_as_deltas_; } bool emitTagsAsAttributes() { return emit_tags_as_attributes_; } bool useTagExtractedName() { return use_tag_extracted_name_; } + const std::string& statPrefix() { return stat_prefix_; } private: const bool report_counters_as_deltas_; const bool report_histograms_as_deltas_; const bool emit_tags_as_attributes_; const bool use_tag_extracted_name_; + const std::string stat_prefix_; }; using OtlpOptionsSharedPtr = std::shared_ptr; diff --git a/test/extensions/stats_sinks/open_telemetry/config_test.cc b/test/extensions/stats_sinks/open_telemetry/config_test.cc index a2c251699847..975e24e9fceb 100644 --- a/test/extensions/stats_sinks/open_telemetry/config_test.cc +++ b/test/extensions/stats_sinks/open_telemetry/config_test.cc @@ -54,18 +54,21 @@ TEST(OpenTelemetryConfigTest, OtlpOptionsTest) { EXPECT_FALSE(options.reportHistogramsAsDeltas()); EXPECT_TRUE(options.emitTagsAsAttributes()); EXPECT_TRUE(options.useTagExtractedName()); + EXPECT_EQ("", options.statPrefix()); } { envoy::extensions::stat_sinks::open_telemetry::v3::SinkConfig sink_config; sink_config.mutable_emit_tags_as_attributes()->set_value(false); sink_config.mutable_use_tag_extracted_name()->set_value(false); + sink_config.set_prefix("prefix"); OtlpOptions options(sink_config); EXPECT_FALSE(options.reportCountersAsDeltas()); EXPECT_FALSE(options.reportHistogramsAsDeltas()); EXPECT_FALSE(options.emitTagsAsAttributes()); EXPECT_FALSE(options.useTagExtractedName()); + EXPECT_EQ("prefix.", options.statPrefix()); } } diff --git a/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc b/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc index 5f5b253b63bc..80de5836b1d1 100644 --- a/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc +++ b/test/extensions/stats_sinks/open_telemetry/open_telemetry_impl_test.cc @@ -38,12 +38,14 @@ class OpenTelemetryStatsSinkTests : public testing::Test { const OtlpOptionsSharedPtr otlpOptions(bool report_counters_as_deltas = false, bool report_histograms_as_deltas = false, bool emit_tags_as_attributes = true, - bool use_tag_extracted_name = true) { + bool use_tag_extracted_name = true, + const std::string& stat_prefix = "") { envoy::extensions::stat_sinks::open_telemetry::v3::SinkConfig sink_config; sink_config.set_report_counters_as_deltas(report_counters_as_deltas); sink_config.set_report_histograms_as_deltas(report_histograms_as_deltas); sink_config.mutable_emit_tags_as_attributes()->set_value(emit_tags_as_attributes); sink_config.mutable_use_tag_extracted_name()->set_value(use_tag_extracted_name); + sink_config.set_prefix(stat_prefix); return std::make_shared(sink_config); } @@ -243,6 +245,20 @@ TEST_F(OtlpMetricsFlusherTests, MetricsWithDefaultOptions) { expectMetricsCount(metrics, 2); } +TEST_F(OtlpMetricsFlusherTests, MetricsWithStatsPrefix) { + OtlpMetricsFlusherImpl flusher(otlpOptions(false, false, true, true, "prefix")); + + addCounterToSnapshot("test_counter", 1, 1); + addGaugeToSnapshot("test_gauge", 1); + addHistogramToSnapshot("test_histogram"); + + MetricsExportRequestSharedPtr metrics = flusher.flush(snapshot_); + expectMetricsCount(metrics, 3); + expectGauge(metricAt(0, metrics), getTagExtractedName("prefix.test_gauge"), 1); + expectSum(metricAt(1, metrics), getTagExtractedName("prefix.test_counter"), 1, false); + expectHistogram(metricAt(2, metrics), getTagExtractedName("prefix.test_histogram"), false); +} + TEST_F(OtlpMetricsFlusherTests, MetricsWithNoTaggedName) { OtlpMetricsFlusherImpl flusher(otlpOptions(false, false, true, false)); diff --git a/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc b/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc index cf0d19b1a410..f170834fdbdb 100644 --- a/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc +++ b/test/extensions/stats_sinks/open_telemetry/open_telemetry_integration_test.cc @@ -35,6 +35,16 @@ class OpenTelemetryGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParam addFakeUpstream(Http::CodecType::HTTP2); } + void setStatPrefix(const std::string& stat_prefix) { stat_prefix_ = stat_prefix; } + + const std::string getFullStatName(const std::string& stat_name) { + if (stat_prefix_ == "") { + return stat_name; + } + + return absl::StrCat(stat_prefix_, ".", stat_name); + } + void initialize() override { config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { auto* otlp_collector_cluster = bootstrap.mutable_static_resources()->add_clusters(); @@ -47,6 +57,7 @@ class OpenTelemetryGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParam envoy::extensions::stat_sinks::open_telemetry::v3::SinkConfig sink_config; setGrpcService(*sink_config.mutable_grpc_service(), "otlp_collector", fake_upstreams_.back()->localAddress()); + sink_config.set_prefix(stat_prefix_); metrics_sink->mutable_typed_config()->PackFrom(sink_config); bootstrap.mutable_stats_flush_interval()->CopyFrom( @@ -77,7 +88,6 @@ class OpenTelemetryGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParam VERIFY_ASSERTION(waitForMetricsServiceConnection()); while (!known_counter_exists || !known_gauge_exists || !known_histogram_exists) { - std::cout << "loop\n\n"; VERIFY_ASSERTION(waitForMetricsStream()); opentelemetry::proto::collector::metrics::v1::ExportMetricsServiceRequest export_request; VERIFY_ASSERTION(otlp_collector_request_->waitForGrpcMessage(*dispatcher_, export_request)); @@ -95,7 +105,7 @@ class OpenTelemetryGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParam long long int previous_time_stamp = 0; for (const opentelemetry::proto::metrics::v1::Metric& metric : metrics) { - if (metric.name() == "cluster.membership_change" && metric.has_sum()) { + if (metric.name() == getFullStatName("cluster.membership_change") && metric.has_sum()) { known_counter_exists = true; EXPECT_EQ(1, metric.sum().data_points().size()); EXPECT_EQ(1, metric.sum().data_points()[0].as_int()); @@ -108,7 +118,7 @@ class OpenTelemetryGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParam previous_time_stamp = metric.sum().data_points()[0].time_unix_nano(); } - if (metric.name() == "cluster.membership_total" && metric.has_gauge()) { + if (metric.name() == getFullStatName("cluster.membership_total") && metric.has_gauge()) { known_gauge_exists = true; EXPECT_EQ(1, metric.gauge().data_points().size()); EXPECT_EQ(1, metric.gauge().data_points()[0].as_int()); @@ -121,7 +131,8 @@ class OpenTelemetryGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParam previous_time_stamp = metric.gauge().data_points()[0].time_unix_nano(); } - if (metric.name() == "cluster.upstream_rq_time" && metric.has_histogram()) { + if (metric.name() == getFullStatName("cluster.upstream_rq_time") && + metric.has_histogram()) { known_histogram_exists = true; EXPECT_EQ(1, metric.histogram().data_points().size()); EXPECT_EQ(metric.histogram().data_points()[0].bucket_counts().size(), @@ -154,6 +165,19 @@ class OpenTelemetryGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParam return AssertionSuccess(); } + void expectUpstreamRequestFinished() { + switch (clientType()) { + case Grpc::ClientType::EnvoyGrpc: + test_server_->waitForGaugeEq("cluster.otlp_collector.upstream_rq_active", 0); + break; + case Grpc::ClientType::GoogleGrpc: + test_server_->waitForCounterGe("grpc.otlp_collector.streams_closed_0", 1); + break; + default: + PANIC("reached unexpected code"); + } + } + void cleanup() { if (fake_metrics_service_connection_ != nullptr) { AssertionResult result = fake_metrics_service_connection_->close(); @@ -165,6 +189,7 @@ class OpenTelemetryGrpcIntegrationTest : public Grpc::GrpcClientIntegrationParam FakeHttpConnectionPtr fake_metrics_service_connection_; FakeStreamPtr otlp_collector_request_; + std::string stat_prefix_; }; INSTANTIATE_TEST_SUITE_P(IpVersionsClientType, OpenTelemetryGrpcIntegrationTest, @@ -181,17 +206,22 @@ TEST_P(OpenTelemetryGrpcIntegrationTest, BasicFlow) { sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); ASSERT_TRUE(waitForMetricsRequest()); - switch (clientType()) { - case Grpc::ClientType::EnvoyGrpc: - test_server_->waitForGaugeEq("cluster.otlp_collector.upstream_rq_active", 0); - break; - case Grpc::ClientType::GoogleGrpc: - test_server_->waitForCounterGe("grpc.otlp_collector.streams_closed_0", 1); - break; - default: - PANIC("reached unexpected code"); - } + expectUpstreamRequestFinished(); + cleanup(); +} + +TEST_P(OpenTelemetryGrpcIntegrationTest, BasicFlowWithStatPrefix) { + setStatPrefix("prefix"); + initialize(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/path"}, {":scheme", "http"}, {":authority", "host"}}; + + sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + ASSERT_TRUE(waitForMetricsRequest()); + expectUpstreamRequestFinished(); cleanup(); } From cda041df9f794061b86cb9e1f16c5ee77581de5b Mon Sep 17 00:00:00 2001 From: Jacob Bohanon <57016439+jbohanon@users.noreply.github.com> Date: Thu, 7 Sep 2023 10:37:32 -0400 Subject: [PATCH 18/55] fix image sha for devcontainer (#29485) Signed-off-by: Jacob Bohanon --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f831a18c5c07..b0f71d3a0cb0 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM gcr.io/envoy-ci/envoy-build:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:6a6a64be3f4e3c4380531ad1624f9419d1fe99dde4e5eeb04e2d538a92f27205 +FROM gcr.io/envoy-ci/envoy-build:1f2f7ee78f894859de0fa7a415b0bedde1f6c560@sha256:9db54410ba9f8216cac84391ebcfb18a297c2217690c7c77bb050f78f56bb629 ARG USERNAME=vscode ARG USER_UID=501 From 1b41133d41c0ae1ba8782d41414162f381083094 Mon Sep 17 00:00:00 2001 From: code Date: Thu, 7 Sep 2023 23:08:06 +0800 Subject: [PATCH 19/55] http: new runtime flag to disable name downgrading in per filter config searching (#29407) * http: new runtime flag to disable name downgrading in per filter config searching Signed-off-by: wbpcode * fix spelling error Signed-off-by: wbpcode * update docs/comments Signed-off-by: wbpcode * minor update Signed-off-by: wbpcode * address comments Signed-off-by: wbpcode --------- Signed-off-by: wbpcode --- api/envoy/config/route/v3/route.proto | 12 ++-- .../config/route/v3/route_components.proto | 37 ++++-------- changelogs/current.yaml | 6 ++ .../intro/arch_overview/http/http_filters.rst | 50 ++++++++++++++++ source/common/http/filter_manager.cc | 33 +++++++++-- source/common/http/filter_manager.h | 7 ++- source/common/runtime/runtime_features.cc | 2 + test/common/http/BUILD | 1 + test/common/http/filter_manager_test.cc | 59 +++++++++++++++++++ 9 files changed, 169 insertions(+), 38 deletions(-) diff --git a/api/envoy/config/route/v3/route.proto b/api/envoy/config/route/v3/route.proto index d7bc85411b30..237bddebdef6 100644 --- a/api/envoy/config/route/v3/route.proto +++ b/api/envoy/config/route/v3/route.proto @@ -142,15 +142,11 @@ message RouteConfiguration { // For users who want to only match path on the "" portion, this option should be true. bool ignore_path_parameters_in_path_matching = 15; - // The typed_per_filter_config field can be used to provide RouteConfiguration level per filter config. - // The key should match the :ref:`filter config name + // This field can be used to provide RouteConfiguration level per filter config. The key should match the + // :ref:`filter config name // `. - // The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also - // be used for the backwards compatibility. If there is no entry referred by the filter config name, the - // entry referred by the canonical filter name will be provided to the filters as fallback. - // - // Use of this field is filter specific; - // see the :ref:`HTTP filter documentation ` for if and how it is utilized. + // See :ref:`Http filter route specific config ` + // for details. // [#comment: An entry's value may be wrapped in a // :ref:`FilterConfig` // message to specify additional options.] diff --git a/api/envoy/config/route/v3/route_components.proto b/api/envoy/config/route/v3/route_components.proto index 014bb0d9261a..1800ee91b5bf 100644 --- a/api/envoy/config/route/v3/route_components.proto +++ b/api/envoy/config/route/v3/route_components.proto @@ -153,15 +153,11 @@ message VirtualHost { // to configure the CORS HTTP filter. CorsPolicy cors = 8 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; - // The per_filter_config field can be used to provide virtual host-specific configurations for filters. - // The key should match the :ref:`filter config name + // This field can be used to provide virtual host level per filter config. The key should match the + // :ref:`filter config name // `. - // The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also - // be used for the backwards compatibility. If there is no entry referred by the filter config name, the - // entry referred by the canonical filter name will be provided to the filters as fallback. - // - // Use of this field is filter specific; - // see the :ref:`HTTP filter documentation ` for if and how it is utilized. + // See :ref:`Http filter route specific config ` + // for details. // [#comment: An entry's value may be wrapped in a // :ref:`FilterConfig` // message to specify additional options.] @@ -292,15 +288,11 @@ message Route { // Decorator for the matched route. Decorator decorator = 5; - // The per_filter_config field can be used to provide route-specific configurations for filters. - // The key should match the :ref:`filter config name + // This field can be used to provide route specific per filter config. The key should match the + // :ref:`filter config name // `. - // The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also - // be used for the backwards compatibility. If there is no entry referred by the filter config name, the - // entry referred by the canonical filter name will be provided to the filters as fallback. - // - // Use of this field is filter specific; - // see the :ref:`HTTP filter documentation ` for if and how it is utilized. + // See :ref:`Http filter route specific config ` + // for details. // [#comment: An entry's value may be wrapped in a // :ref:`FilterConfig` // message to specify additional options.] @@ -451,16 +443,11 @@ message WeightedCluster { items {string {well_known_regex: HTTP_HEADER_NAME strict: false}} }]; - // The per_filter_config field can be used to provide weighted cluster-specific configurations - // for filters. - // The key should match the :ref:`filter config name + // This field can be used to provide weighted cluster specific per filter config. The key should match the + // :ref:`filter config name // `. - // The canonical filter name (e.g., ``envoy.filters.http.buffer`` for the HTTP buffer filter) can also - // be used for the backwards compatibility. If there is no entry referred by the filter config name, the - // entry referred by the canonical filter name will be provided to the filters as fallback. - // - // Use of this field is filter specific; - // see the :ref:`HTTP filter documentation ` for if and how it is utilized. + // See :ref:`Http filter route specific config ` + // for details. // [#comment: An entry's value may be wrapped in a // :ref:`FilterConfig` // message to specify additional options.] diff --git a/changelogs/current.yaml b/changelogs/current.yaml index b5e5b79f9764..f457f119e35b 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -14,6 +14,12 @@ behavior_changes: :ref:`initial_fetch_timeout ` times out, and will then apply the cached assignment and finish updating the warmed cluster. This change is disabled by default, and can be enabled by setting the runtime flag ``envoy.restart_features.use_eds_cache_for_ads`` to true. +- area: http + change: | + Introduced a new runtime flag ``envoy.reloadable_features.no_downgrade_to_canonical_name`` to disable the name downgrading in the + per filter config searching. + See doc :ref:`Http filter route specific config ` or + issue https://github.com/envoyproxy/envoy/issues/29461 for more specific detail and examples. - area: http change: | Switch from http_parser to BalsaParser for handling HTTP/1.1 traffic. See https://github.com/envoyproxy/envoy/issues/21245 for diff --git a/docs/root/intro/arch_overview/http/http_filters.rst b/docs/root/intro/arch_overview/http/http_filters.rst index 5a77045f1c5a..034a2a655287 100644 --- a/docs/root/intro/arch_overview/http/http_filters.rst +++ b/docs/root/intro/arch_overview/http/http_filters.rst @@ -98,3 +98,53 @@ If no other filters in the chain modify the cached route selection (for example, that filters do is ``clearRouteCache()``, and ``setRoute`` will not survive that), this route selection makes its way to the router filter which finalizes the upstream cluster that the request will be forwarded to. + +.. _arch_overview_http_filters_per_filter_config: + +Route specific config +--------------------- + +The per filter config map can be used to provide +:ref:`route ` or +:ref:`virtual host ` or +:ref:`route configuration ` +specific config for http filters. + + +The key of the per filter config map should match the :ref:`filter config name +`. + + +For example, given following http filter config: + +.. code-block:: yaml + + http_filters: + - name: custom-filter-name-for-lua # Custom name be used as filter config name + typed_config: { ... } + - name: envoy.filters.http.buffer # Canonical name be used as filter config name + typed_config: { ... } + +The ``custom-filter-name-for-lua`` and ``envoy.filters.http.buffer`` will be used as the key to lookup +related per filter config. + + +For the first ``custom-filter-name-for-lua`` filter, if no related entry are found by +``custom-filter-name-for-lua``, we will downgrade to try the canonical filter name ``envoy.filters.http.lua``. +This downgrading is for backward compatibility and could be disabled by setting the runtime flag +``envoy.reloadable_features.no_downgrade_to_canonical_name`` to ``true`` explicitly. + + +For the second ``envoy.filters.http.buffer`` filter, if no related entry are found by +``envoy.filters.http.buffer``, we will not try to downgrade because canonical filter name is the same as +the filter config name. + + +.. warning:: + Downgrading to canonical filter name is deprecated and will be removed soon. Please ensure the + key of the per filter config map matches the filter config name exactly and don't rely on the + downgrading behavior. + + +Use of per filter config map is filter specific. See the :ref:`HTTP filter documentation ` +for if and how it is utilized for every filter. diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 4ac5bfd2424f..324e619edb1a 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -284,9 +284,25 @@ ActiveStreamFilterBase::mostSpecificPerFilterConfig() const { auto* result = current_route->mostSpecificPerFilterConfig(filter_context_.config_name); - if (result == nullptr && filter_context_.filter_name != filter_context_.config_name) { - // Fallback to use filter name. + /** + * If: + * 1. no per filter config is found for the filter config name. + * 2. filter config name is different from the filter canonical name. + * 3. downgrade feature is not disabled. + * we fallback to use the filter canonical name. + */ + if (result == nullptr && !parent_.no_downgrade_to_canonical_name_ && + filter_context_.filter_name != filter_context_.config_name) { + // Fallback to use filter canonical name. result = current_route->mostSpecificPerFilterConfig(filter_context_.filter_name); + + if (result != nullptr) { + ENVOY_LOG_FIRST_N(warn, 10, + "No per filter config is found by filter config name and fallback to use " + "filter canonical name. This is deprecated and will be forbidden very " + "soon. Please use the filter config name to index per filter config. See " + "https://github.com/envoyproxy/envoy/issues/29461 for more detail."); + } } return result; } @@ -306,11 +322,20 @@ void ActiveStreamFilterBase::traversePerFilterConfig( cb(config); }); - if (handled || filter_context_.filter_name == filter_context_.config_name) { + if (handled || parent_.no_downgrade_to_canonical_name_ || + filter_context_.filter_name == filter_context_.config_name) { return; } - current_route->traversePerFilterConfig(filter_context_.filter_name, cb); + current_route->traversePerFilterConfig( + filter_context_.filter_name, [&cb](const Router::RouteSpecificFilterConfig& config) { + ENVOY_LOG_FIRST_N(warn, 10, + "No per filter config is found by filter config name and fallback to use " + "filter canonical name. This is deprecated and will be forbidden very " + "soon. Please use the filter config name to index per filter config. See " + "https://github.com/envoyproxy/envoy/issues/29461 for more detail."); + cb(config); + }); } Http1StreamEncoderOptionsOptRef ActiveStreamFilterBase::http1StreamEncoderOptions() { diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index a5016df76e04..5efc602878bc 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -25,6 +25,7 @@ #include "source/common/local_reply/local_reply.h" #include "source/common/matcher/matcher.h" #include "source/common/protobuf/utility.h" +#include "source/common/runtime/runtime_features.h" #include "source/common/stream_info/stream_info_impl.h" namespace Envoy { @@ -610,7 +611,9 @@ class FilterManager : public ScopeTrackedObject, : filter_manager_callbacks_(filter_manager_callbacks), dispatcher_(dispatcher), connection_(connection), stream_id_(stream_id), account_(std::move(account)), proxy_100_continue_(proxy_100_continue), buffer_limit_(buffer_limit), - filter_chain_factory_(filter_chain_factory) {} + filter_chain_factory_(filter_chain_factory), + no_downgrade_to_canonical_name_(Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.no_downgrade_to_canonical_name")) {} ~FilterManager() override { ASSERT(state_.destroyed_); ASSERT(state_.filter_call_state_ == 0); @@ -1045,6 +1048,8 @@ class FilterManager : public ScopeTrackedObject, // clang-format on State state_; + + const bool no_downgrade_to_canonical_name_{}; }; // The DownstreamFilterManager has explicit handling to send local replies. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 008fcdd50e96..f74e1ec37251 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -117,6 +117,8 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_refresh_rtt_after_request); FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_reject_all); // TODO(adisuissa): enable by default once this is tested in prod. FALSE_RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); +// TODO(wbpcode): enable by default after a complete deprecation period. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_no_downgrade_to_canonical_name); // Block of non-boolean flags. Use of int flags is deprecated. Do not add more. ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT diff --git a/test/common/http/BUILD b/test/common/http/BUILD index a90ffa9c0ce9..d3fce341694f 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -131,6 +131,7 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/local_reply:local_reply_mocks", "//test/mocks/network:network_mocks", + "//test/test_common:test_runtime_lib", ], ) diff --git a/test/common/http/filter_manager_test.cc b/test/common/http/filter_manager_test.cc index 42a5602a9006..f272680cedd3 100644 --- a/test/common/http/filter_manager_test.cc +++ b/test/common/http/filter_manager_test.cc @@ -17,6 +17,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/local_reply/mocks.h" #include "test/mocks/network/mocks.h" +#include "test/test_common/test_runtime.h" #include "gtest/gtest.h" @@ -404,6 +405,64 @@ TEST_F(FilterManagerTest, GetRouteLevelFilterConfig) { filter_manager_->destroyFilters(); }; +TEST_F(FilterManagerTest, GetRouteLevelFilterConfigButDisabledDowngrade) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.no_downgrade_to_canonical_name", "true"}}); + + initialize(); + + std::shared_ptr decoder_filter(new NiceMock()); + + EXPECT_CALL(filter_factory_, createFilterChain(_)) + .WillRepeatedly(Invoke([&](FilterChainManager& manager) -> bool { + auto decoder_factory = createDecoderFilterFactoryCb(decoder_filter); + manager.applyFilterFactoryCb({"custom-name", "filter-name"}, decoder_factory); + return true; + })); + filter_manager_->createFilterChain(); + + std::shared_ptr route(new NiceMock()); + auto route_config = std::make_shared(); + + NiceMock downstream_callbacks; + ON_CALL(filter_manager_callbacks_, downstreamCallbacks) + .WillByDefault(Return(OptRef{downstream_callbacks})); + ON_CALL(downstream_callbacks, route(_)).WillByDefault(Return(route)); + + // Get a valid config by the custom filter name. + EXPECT_CALL(*route, mostSpecificPerFilterConfig(testing::Eq("custom-name"))) + .WillOnce(Return(route_config.get())); + EXPECT_EQ(route_config.get(), decoder_filter->callbacks_->mostSpecificPerFilterConfig()); + + // Get nothing by the custom filter name. + EXPECT_CALL(*route, mostSpecificPerFilterConfig(testing::Eq("custom-name"))) + .WillOnce(Return(nullptr)); + EXPECT_EQ(nullptr, decoder_filter->callbacks_->mostSpecificPerFilterConfig()); + + // Get a valid config by the custom filter name. + EXPECT_CALL(*route, traversePerFilterConfig(testing::Eq("custom-name"), _)) + .WillOnce(Invoke([&](const std::string&, + std::function cb) { + cb(*route_config); + })); + decoder_filter->callbacks_->traversePerFilterConfig( + [&](const Router::RouteSpecificFilterConfig& config) { + EXPECT_EQ(route_config.get(), &config); + }); + + // Get nothing by the custom filter name. + EXPECT_CALL(*route, traversePerFilterConfig(testing::Eq("custom-name"), _)) + .WillOnce(Invoke([&](const std::string&, + std::function) {})); + decoder_filter->callbacks_->traversePerFilterConfig( + [&](const Router::RouteSpecificFilterConfig& config) { + EXPECT_EQ(route_config.get(), &config); + }); + + filter_manager_->destroyFilters(); +}; + TEST_F(FilterManagerTest, GetRouteLevelFilterConfigForNullRoute) { initialize(); From d1b67883af754acae4666cd165f0424ed1ebb816 Mon Sep 17 00:00:00 2001 From: doujiang24 Date: Thu, 7 Sep 2023 23:09:19 +0800 Subject: [PATCH 20/55] golang extension: optimization: added noescape and nocallback directives for C functions. (#29396) * golang extension: optimization: added noescape and nocallback directives for C functions. they could prevent the Go compiler from allocating function parameters on the heap. Signed-off-by: doujiang24 * add comment for the magic number. Signed-off-by: doujiang24 --------- Signed-off-by: doujiang24 --- .../http/source/go/pkg/http/capi_impl.go | 41 ++++++++++---- .../http/source/go/pkg/http/cgo_go122.go | 54 +++++++++++++++++++ .../source/go/pkg/network/cgo_go122.go | 26 +++++++++ 3 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 contrib/golang/filters/http/source/go/pkg/http/cgo_go122.go create mode 100644 contrib/golang/filters/network/source/go/pkg/network/cgo_go122.go diff --git a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go index a758af0556d4..8cecb683c5e8 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go +++ b/contrib/golang/filters/http/source/go/pkg/http/capi_impl.go @@ -58,6 +58,15 @@ const ( ValueUpstreamRemoteAddress = 10 ValueUpstreamClusterName = 11 ValueVirtualClusterName = 12 + + // NOTE: this is a trade-off value. + // When the number of header is less this value, we could use the slice on the stack, + // otherwise, we have to allocate a new slice on the heap, + // and the slice on the stack will be wasted. + // So, we choose a value that many requests' number of header is less than this value. + // But also, it should not be too large, otherwise it might be waste stack memory. + maxStackAllocedHeaderSize = 16 + maxStackAllocedSliceLen = maxStackAllocedHeaderSize * 2 ) type httpCApiImpl struct{} @@ -123,10 +132,16 @@ func (c *httpCApiImpl) HttpGetHeader(r unsafe.Pointer, key *string, value *strin } func (c *httpCApiImpl) HttpCopyHeaders(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string { - // TODO: use a memory pool for better performance, - // since these go strings in strs, will be copied into the following map. - strs := make([]string, num*2) - // but, this buffer can not be reused safely, + var strs []string + if num <= maxStackAllocedHeaderSize { + // NOTE: only const length slice may be allocated on stack. + strs = make([]string, maxStackAllocedSliceLen) + } else { + // TODO: maybe we could use a memory pool for better performance, + // since these go strings in strs, will be copied into the following map. + strs = make([]string, num*2) + } + // NOTE: this buffer can not be reused safely, // since strings may refer to this buffer as string data, and string is const in go. // we have to make sure the all strings is not using before reusing, // but strings may be alive beyond the request life. @@ -194,11 +209,19 @@ func (c *httpCApiImpl) HttpSetBufferHelper(r unsafe.Pointer, bufferPtr uint64, v } func (c *httpCApiImpl) HttpCopyTrailers(r unsafe.Pointer, num uint64, bytes uint64) map[string][]string { - // TODO: use a memory pool for better performance, - // but, should be very careful, since string is const in go, - // and we have to make sure the strings is not using before reusing, - // strings may be alive beyond the request life. - strs := make([]string, num*2) + var strs []string + if num <= maxStackAllocedHeaderSize { + // NOTE: only const length slice may be allocated on stack. + strs = make([]string, maxStackAllocedSliceLen) + } else { + // TODO: maybe we could use a memory pool for better performance, + // since these go strings in strs, will be copied into the following map. + strs = make([]string, num*2) + } + // NOTE: this buffer can not be reused safely, + // since strings may refer to this buffer as string data, and string is const in go. + // we have to make sure the all strings is not using before reusing, + // but strings may be alive beyond the request life. buf := make([]byte, bytes) sHeader := (*reflect.SliceHeader)(unsafe.Pointer(&strs)) bHeader := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) diff --git a/contrib/golang/filters/http/source/go/pkg/http/cgo_go122.go b/contrib/golang/filters/http/source/go/pkg/http/cgo_go122.go new file mode 100644 index 000000000000..8e8bec7a0e66 --- /dev/null +++ b/contrib/golang/filters/http/source/go/pkg/http/cgo_go122.go @@ -0,0 +1,54 @@ +//go:build go1.22 + +package http + +/* +// This is a performance optimization. +// The following noescape and nocallback directives are used to +// prevent the Go compiler from allocating function parameters on the heap. + +#cgo noescape envoyGoFilterHttpCopyHeaders +#cgo nocallback envoyGoFilterHttpCopyHeaders +#cgo noescape envoyGoFilterHttpSendPanicReply +#cgo nocallback envoyGoFilterHttpSendPanicReply +#cgo noescape envoyGoFilterHttpGetHeader +#cgo nocallback envoyGoFilterHttpGetHeader +#cgo noescape envoyGoFilterHttpSetHeaderHelper +#cgo nocallback envoyGoFilterHttpSetHeaderHelper +#cgo noescape envoyGoFilterHttpRemoveHeader +#cgo nocallback envoyGoFilterHttpRemoveHeader +#cgo noescape envoyGoFilterHttpGetBuffer +#cgo nocallback envoyGoFilterHttpGetBuffer +#cgo noescape envoyGoFilterHttpSetBufferHelper +#cgo nocallback envoyGoFilterHttpSetBufferHelper +#cgo noescape envoyGoFilterHttpCopyTrailers +#cgo nocallback envoyGoFilterHttpCopyTrailers +#cgo noescape envoyGoFilterHttpSetTrailer +#cgo nocallback envoyGoFilterHttpSetTrailer +#cgo noescape envoyGoFilterHttpRemoveTrailer +#cgo nocallback envoyGoFilterHttpRemoveTrailer +#cgo noescape envoyGoFilterHttpGetStringValue +#cgo nocallback envoyGoFilterHttpGetStringValue +#cgo noescape envoyGoFilterHttpGetIntegerValue +#cgo nocallback envoyGoFilterHttpGetIntegerValue +#cgo noescape envoyGoFilterHttpGetDynamicMetadata +#cgo nocallback envoyGoFilterHttpGetDynamicMetadata +#cgo noescape envoyGoFilterHttpSetDynamicMetadata +#cgo nocallback envoyGoFilterHttpSetDynamicMetadata +#cgo noescape envoyGoFilterLog +#cgo nocallback envoyGoFilterLog +#cgo noescape envoyGoFilterHttpSetStringFilterState +#cgo nocallback envoyGoFilterHttpSetStringFilterState +#cgo noescape envoyGoFilterHttpGetStringFilterState +#cgo nocallback envoyGoFilterHttpGetStringFilterState +#cgo noescape envoyGoFilterHttpGetStringProperty +#cgo nocallback envoyGoFilterHttpGetStringProperty +#cgo noescape envoyGoFilterHttpDefineMetric +#cgo nocallback envoyGoFilterHttpDefineMetric +#cgo noescape envoyGoFilterHttpIncrementMetric +#cgo nocallback envoyGoFilterHttpIncrementMetric +#cgo noescape envoyGoFilterHttpGetMetric +#cgo nocallback envoyGoFilterHttpGetMetric + +*/ +import "C" diff --git a/contrib/golang/filters/network/source/go/pkg/network/cgo_go122.go b/contrib/golang/filters/network/source/go/pkg/network/cgo_go122.go new file mode 100644 index 000000000000..e9047b95b970 --- /dev/null +++ b/contrib/golang/filters/network/source/go/pkg/network/cgo_go122.go @@ -0,0 +1,26 @@ +//go:build go1.22 + +package network + +/* +// This is a performance optimization. +// The following noescape and nocallback directives are used to +// prevent the Go compiler from allocating function parameters on the heap. + +#cgo noescape envoyGoFilterDownstreamWrite +#cgo nocallback envoyGoFilterDownstreamWrite +#cgo noescape envoyGoFilterDownstreamInfo +#cgo nocallback envoyGoFilterDownstreamInfo +#cgo noescape envoyGoFilterUpstreamConnect +#cgo nocallback envoyGoFilterUpstreamConnect +#cgo noescape envoyGoFilterUpstreamWrite +#cgo nocallback envoyGoFilterUpstreamWrite +#cgo noescape envoyGoFilterUpstreamInfo +#cgo nocallback envoyGoFilterUpstreamInfo +#cgo noescape envoyGoFilterSetFilterState +#cgo nocallback envoyGoFilterSetFilterState +#cgo noescape envoyGoFilterGetFilterState +#cgo nocallback envoyGoFilterGetFilterState + +*/ +import "C" From ffddd03ece01d9a542037bbf275e81a714fd6b8c Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Thu, 7 Sep 2023 12:08:14 -0400 Subject: [PATCH 21/55] xds rate limit: fixing lower bound for xDS rate limiting (#28778) Current fill_rate must be above 0.0 (PGV constraint). However, a low double value can cause an infinite value when computing 1/fill_rate and its cast to uint64_t fails. This PR changes the minimal fill_rate to be once-per-year, and if a lower value is given, it is overridden and set to once-per-year. Alternatives considered: changing the PGV value to 3.1709792e-8 (once-per-year). Risk Level: Low - minor change in behavior Testing: Added fuzz test case Docs Changes: Updated API comments Release Notes: Added. Platform Specific Features: N/A Fixes fuzz bug 60974 Signed-off-by: Adi Suissa-Peleg --- api/envoy/config/core/v3/config_source.proto | 3 +- .../network/redis_proxy/v3/redis_proxy.proto | 2 +- changelogs/current.yaml | 9 ++++ source/common/common/token_bucket_impl.cc | 9 +++- test/common/common/token_bucket_impl_test.cc | 16 +++++++ .../network/redis_proxy/config_test.cc | 43 +++++++++++++++++++ test/server/server_corpus/low_fill_rate | 38 ++++++++++++++++ 7 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 test/server/server_corpus/low_fill_rate diff --git a/api/envoy/config/core/v3/config_source.proto b/api/envoy/config/core/v3/config_source.proto index c12930135a1b..70204bad9eba 100644 --- a/api/envoy/config/core/v3/config_source.proto +++ b/api/envoy/config/core/v3/config_source.proto @@ -152,7 +152,8 @@ message RateLimitSettings { google.protobuf.UInt32Value max_tokens = 1; // Rate at which tokens will be filled per second. If not set, a default fill rate of 10 tokens - // per second will be used. + // per second will be used. The minimal fill rate is once per year. Lower + // fill rates will be set to once per year. google.protobuf.DoubleValue fill_rate = 2 [(validate.rules).double = {gt: 0.0}]; } diff --git a/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto b/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto index 3a64c9cc8665..fba1c786f7c3 100644 --- a/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto +++ b/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto @@ -230,7 +230,7 @@ message RedisProxy { // from client reconnection storm. message ConnectionRateLimit { // Reconnection rate per sec. Rate limiting is implemented with TokenBucket. - uint32 connection_rate_limit_per_sec = 1; + uint32 connection_rate_limit_per_sec = 1 [(validate.rules).uint32 = {gt: 0}]; } reserved 2; diff --git a/changelogs/current.yaml b/changelogs/current.yaml index f457f119e35b..6e6d5752fe92 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -59,6 +59,15 @@ minor_behavior_changes: Enable copying response_code from the upstream stream_info onto the downstream stream_info. This behavior can be reverted by setting runtime guard ``envoy.reloadable_features.copy_response_code_to_downstream_stream_info`` to false. +- area: xds + change: | + Set the lower bound of :ref:`fill_rate ` + to once per year. Values lower than once per year will automatically be set to that value. +- area: redis + change: | + The redis network filter :ref:`connection_rate_limit_per_sec + ` + must be greater than 0. A config that sets this value to 0 will be rejected. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/source/common/common/token_bucket_impl.cc b/source/common/common/token_bucket_impl.cc index eaf6f06d0c9e..f813d426d04f 100644 --- a/source/common/common/token_bucket_impl.cc +++ b/source/common/common/token_bucket_impl.cc @@ -4,9 +4,14 @@ namespace Envoy { +namespace { +// The minimal fill rate will be one second every year. +constexpr double kMinFillRate = 1.0 / (365 * 24 * 60 * 60); +} // namespace + TokenBucketImpl::TokenBucketImpl(uint64_t max_tokens, TimeSource& time_source, double fill_rate) - : max_tokens_(max_tokens), fill_rate_(std::abs(fill_rate)), tokens_(max_tokens), - last_fill_(time_source.monotonicTime()), time_source_(time_source) {} + : max_tokens_(max_tokens), fill_rate_(std::max(std::abs(fill_rate), kMinFillRate)), + tokens_(max_tokens), last_fill_(time_source.monotonicTime()), time_source_(time_source) {} uint64_t TokenBucketImpl::consume(uint64_t tokens, bool allow_partial) { if (tokens_ < max_tokens_) { diff --git a/test/common/common/token_bucket_impl_test.cc b/test/common/common/token_bucket_impl_test.cc index c3d25a4c2b27..66308cfded3a 100644 --- a/test/common/common/token_bucket_impl_test.cc +++ b/test/common/common/token_bucket_impl_test.cc @@ -110,4 +110,20 @@ TEST_F(TokenBucketImplTest, ConsumeAndNextToken) { EXPECT_EQ(time_to_next_token, token_bucket.nextTokenAvailable()); } +// Validate that a minimal refresh time is 1 year. +TEST_F(TokenBucketImplTest, YearlyMinRefillRate) { + constexpr uint64_t seconds_per_year = 365 * 24 * 60 * 60; + // Set the fill rate to be 2 years. + TokenBucketImpl token_bucket{1, time_system_, 1.0 / (seconds_per_year * 2)}; + + // Consume first token. + EXPECT_EQ(1, token_bucket.consume(1, false)); + + // Less than a year should still have no tokens. + time_system_.setMonotonicTime(std::chrono::seconds(seconds_per_year - 1)); + EXPECT_EQ(0, token_bucket.consume(1, false)); + time_system_.setMonotonicTime(std::chrono::seconds(seconds_per_year)); + EXPECT_EQ(1, token_bucket.consume(1, false)); +} + } // namespace Envoy diff --git a/test/extensions/filters/network/redis_proxy/config_test.cc b/test/extensions/filters/network/redis_proxy/config_test.cc index 5bfe03cd6985..f0d220af674c 100644 --- a/test/extensions/filters/network/redis_proxy/config_test.cc +++ b/test/extensions/filters/network/redis_proxy/config_test.cc @@ -147,6 +147,49 @@ stat_prefix: foo cb(connection); } +// Validates that a value of connection_rate_limit above 0 isn't rejected. +TEST(RedisProxyFilterConfigFactoryTest, ValidConnectionRateLimit) { + const std::string yaml = R"EOF( +prefix_routes: + catch_all_route: + cluster: fake_cluster +stat_prefix: foo +settings: + op_timeout: 0.02s + connection_rate_limit: + connection_rate_limit_per_sec: 1 + )EOF"; + + envoy::extensions::filters::network::redis_proxy::v3::RedisProxy proto_config; + TestUtility::loadFromYamlAndValidate(yaml, proto_config); + NiceMock context; + RedisProxyFilterConfigFactory factory; + Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, context); + EXPECT_TRUE(factory.isTerminalFilterByProto(proto_config, context.getServerFactoryContext())); + Network::MockConnection connection; + EXPECT_CALL(connection, addReadFilter(_)); + cb(connection); +} + +// Validates that a value of connection_rate_limit 0 is rejected. +TEST(RedisProxyFilterConfigFactoryTest, InvalidConnectionRateLimit) { + const std::string yaml = R"EOF( +prefix_routes: + catch_all_route: + cluster: fake_cluster +stat_prefix: foo +settings: + op_timeout: 0.02s + connection_rate_limit: + connection_rate_limit_per_sec: 0 + )EOF"; + + envoy::extensions::filters::network::redis_proxy::v3::RedisProxy proto_config; + EXPECT_THROW_WITH_REGEX(TestUtility::loadFromYamlAndValidate(yaml, proto_config), + ProtoValidationException, + "ConnectionRateLimitPerSec: value must be greater than 0"); +} + } // namespace RedisProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/test/server/server_corpus/low_fill_rate b/test/server/server_corpus/low_fill_rate new file mode 100644 index 000000000000..a0bfde7ab283 --- /dev/null +++ b/test/server/server_corpus/low_fill_rate @@ -0,0 +1,38 @@ +node { + id: "\00096\000@" + cluster: "@" +} +dynamic_resources { + ads_config { + api_type: GRPC + grpc_services { + google_grpc { + target_uri: "127_7.0.1" + stat_prefix: "\t`" + } + } + rate_limit_settings { + max_tokens { + } + fill_rate { + value: 2.47032822920623e-323 + } + } + transport_api_version: V3 + } +} +flags_path: "`" +layered_runtime { + layers { + name: "\337h\177\177\177\177\177\177\177~>" + rtds_layer { + name: "\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\0018592863541252882453177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177?\177\177\177\177\177\177\177\177Q177\177" + rtds_config { + ads { + } + resource_api_version: V3 + } + } + } +} +header_prefix: "`" From e6ee1cfb83d1c100530ac1e7b4a1db88cc8be8d0 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 7 Sep 2023 12:44:03 -0400 Subject: [PATCH 22/55] changing xDS APIs to take abs::Satus (#29439) Unlike many of the throw->status PRs I'm doing xDS in two parts - one is a pure API change, and the follow-up(s) will change the throw over to returning non-Ok stauts. This PR is large enough already I didn't want to do them all at once. Risk Level: low (but will cause downstream churn, sorry) Testing: updated tests Docs Changes: n/a Release Notes: n/a envoyproxy/envoy-mobile#176 Signed-off-by: Alyssa Wilk --- .../filters/network/test/config_test.cc | 6 +- .../filters/network/test/proxy_test.cc | 2 +- contrib/sxg/filters/http/test/filter_test.cc | 7 +- envoy/common/exception.h | 8 ++ .../dynamic_extension_config_provider.h | 6 +- envoy/config/subscription.h | 24 +++--- envoy/rds/route_config_provider.h | 3 +- source/common/config/config_provider_impl.h | 3 +- source/common/config/xds_context_params.cc | 2 +- source/common/filter/config_discovery_impl.cc | 50 +++++++----- source/common/filter/config_discovery_impl.h | 22 ++--- .../rds/rds_route_config_provider_impl.cc | 3 +- .../rds/rds_route_config_provider_impl.h | 2 +- .../rds/rds_route_config_subscription.cc | 12 +-- .../rds/rds_route_config_subscription.h | 10 +-- .../rds/static_route_config_provider_impl.h | 2 +- source/common/router/config_impl.cc | 18 ++--- source/common/router/rds_impl.cc | 10 ++- source/common/router/rds_impl.h | 4 +- source/common/router/scoped_rds.cc | 33 +++++--- source/common/router/scoped_rds.h | 20 ++--- source/common/router/vhds.cc | 5 +- source/common/router/vhds.h | 11 ++- source/common/runtime/runtime_impl.cc | 46 +++++++---- source/common/runtime/runtime_impl.h | 14 ++-- source/common/secret/sds_api.cc | 12 +-- source/common/secret/sds_api.h | 10 +-- source/common/upstream/cds_api_impl.cc | 14 ++-- source/common/upstream/cds_api_impl.h | 10 +-- source/common/upstream/od_cds_api_impl.cc | 12 +-- source/common/upstream/od_cds_api_impl.h | 10 +-- source/extensions/clusters/eds/eds.cc | 19 +++-- source/extensions/clusters/eds/eds.h | 10 +-- source/extensions/clusters/eds/leds.cc | 10 ++- source/extensions/clusters/eds/leds.h | 10 ++- .../filesystem_subscription_impl.cc | 4 +- .../config_subscription/grpc/grpc_mux_impl.cc | 6 +- .../grpc/grpc_subscription_impl.cc | 20 +++-- .../grpc/grpc_subscription_impl.h | 10 +-- .../config_subscription/grpc/watch_map.cc | 15 ++-- .../rest/http_subscription_impl.cc | 2 +- .../listener_manager/lds_api.cc | 14 ++-- .../listener_manager/lds_api.h | 10 +-- source/server/admin/admin.h | 2 +- .../config/config_provider_impl_test.cc | 47 ++++++----- .../common/config/subscription_test_harness.h | 1 + .../filter/config_discovery_impl_test.cc | 59 +++++++------- test/common/rds/rds_test.cc | 5 +- test/common/router/config_impl_test.cc | 3 + test/common/router/rds_impl_test.cc | 65 +++++++++------ test/common/router/scoped_rds_test.cc | 81 ++++++++++--------- test/common/router/vhds_test.cc | 10 ++- test/common/runtime/runtime_impl_test.cc | 33 ++++---- test/common/secret/sds_api_test.cc | 44 +++++----- .../common/secret/secret_manager_impl_test.cc | 35 ++++---- test/common/upstream/cds_api_impl_test.cc | 38 +++++---- .../deferred_cluster_initialization_test.cc | 2 +- test/common/upstream/od_cds_api_impl_test.cc | 23 +++--- test/extensions/clusters/eds/eds_test.cc | 45 ++++++----- test/extensions/clusters/eds/leds_test.cc | 22 +++-- .../grpc/grpc_mux_impl_test.cc | 29 ++++++- .../grpc/new_grpc_mux_impl_test.cc | 9 +++ .../grpc/watch_map_test.cc | 27 ++++--- .../grpc/xds_grpc_mux_impl_test.cc | 29 ++++++- .../filters/http/oauth2/filter_test.cc | 6 +- .../network/dubbo_proxy/config_test.cc | 5 +- .../network/thrift_proxy/config_test.cc | 5 +- .../listener_manager/lds_api_test.cc | 49 ++++++----- test/mocks/config/mocks.h | 4 +- test/mocks/router/mocks.h | 2 +- test/server/admin/admin_test.cc | 2 +- tools/code_format/config.yaml | 2 + 72 files changed, 715 insertions(+), 490 deletions(-) diff --git a/contrib/generic_proxy/filters/network/test/config_test.cc b/contrib/generic_proxy/filters/network/test/config_test.cc index f0705ffba751..276666182325 100644 --- a/contrib/generic_proxy/filters/network/test/config_test.cc +++ b/contrib/generic_proxy/filters/network/test/config_test.cc @@ -124,8 +124,10 @@ version_info: "1" TestUtility::parseYaml(response_yaml); const auto decoded_resources = TestUtility::decodeResources< envoy::extensions::filters::network::generic_proxy::v3::RouteConfiguration>(response); - factory_context.server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ - ->onConfigUpdate(decoded_resources.refvec_, response.version_info()); + EXPECT_TRUE( + factory_context.server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .ok()); auto message_ptr = factory_context.admin_.config_tracker_.config_tracker_callbacks_["genericrds_routes"]( universal_name_matcher); diff --git a/contrib/generic_proxy/filters/network/test/proxy_test.cc b/contrib/generic_proxy/filters/network/test/proxy_test.cc index d75797f9c90e..10253829422a 100644 --- a/contrib/generic_proxy/filters/network/test/proxy_test.cc +++ b/contrib/generic_proxy/filters/network/test/proxy_test.cc @@ -38,7 +38,7 @@ class MockRouteConfigProvider : public Rds::RouteConfigProvider { MOCK_METHOD(Rds::ConfigConstSharedPtr, config, (), (const)); MOCK_METHOD(const absl::optional&, configInfo, (), (const)); MOCK_METHOD(SystemTime, lastUpdated, (), (const)); - MOCK_METHOD(void, onConfigUpdate, ()); + MOCK_METHOD(absl::Status, onConfigUpdate, ()); std::shared_ptr> route_config_{new NiceMock()}; }; diff --git a/contrib/sxg/filters/http/test/filter_test.cc b/contrib/sxg/filters/http/test/filter_test.cc index 92bf1fa92f9e..933198e6f6c7 100644 --- a/contrib/sxg/filters/http/test/filter_test.cc +++ b/contrib/sxg/filters/http/test/filter_test.cc @@ -395,7 +395,7 @@ name: certificate TestUtility::loadFromYaml(yaml_client, typed_secret); const auto decoded_resources_client = TestUtility::decodeResources({typed_secret}); - certificate_callback->onConfigUpdate(decoded_resources_client.refvec_, ""); + EXPECT_TRUE(certificate_callback->onConfigUpdate(decoded_resources_client.refvec_, "").ok()); EXPECT_EQ(secret_reader.certificate(), "certificate_test"); EXPECT_EQ(secret_reader.privateKey(), ""); @@ -408,7 +408,7 @@ name: private_key TestUtility::loadFromYaml(yaml_token, typed_secret); const auto decoded_resources_token = TestUtility::decodeResources({typed_secret}); - private_key_callback->onConfigUpdate(decoded_resources_token.refvec_, ""); + EXPECT_TRUE(private_key_callback->onConfigUpdate(decoded_resources_token.refvec_, "").ok()); EXPECT_EQ(secret_reader.certificate(), "certificate_test"); EXPECT_EQ(secret_reader.privateKey(), "private_key_test"); @@ -421,7 +421,8 @@ name: certificate TestUtility::loadFromYaml(yaml_client_recheck, typed_secret); const auto decoded_resources_client_recheck = TestUtility::decodeResources({typed_secret}); - certificate_callback->onConfigUpdate(decoded_resources_client_recheck.refvec_, ""); + EXPECT_TRUE( + certificate_callback->onConfigUpdate(decoded_resources_client_recheck.refvec_, "").ok()); EXPECT_EQ(secret_reader.certificate(), "certificate_test_recheck"); EXPECT_EQ(secret_reader.privateKey(), "private_key_test"); } diff --git a/envoy/common/exception.h b/envoy/common/exception.h index 823fd7021ebd..13cf32bda7cd 100644 --- a/envoy/common/exception.h +++ b/envoy/common/exception.h @@ -12,6 +12,14 @@ class EnvoyException : public std::runtime_error { EnvoyException(const std::string& message) : std::runtime_error(message) {} }; +#define THROW_IF_NOT_OK(status_fn) \ + { \ + absl::Status status = status_fn; \ + if (!status.ok()) { \ + throw EnvoyException(std::string(status.message())); \ + } \ + } + // Simple macro to handle bridging functions which return absl::StatusOr, and // functions which throw errors. // diff --git a/envoy/config/dynamic_extension_config_provider.h b/envoy/config/dynamic_extension_config_provider.h index 6d0b92764d65..a557ad5fc300 100644 --- a/envoy/config/dynamic_extension_config_provider.h +++ b/envoy/config/dynamic_extension_config_provider.h @@ -26,9 +26,11 @@ class DynamicExtensionConfigProviderBase { * validated. * @param version_info is the version of the new extension configuration. * @param cb the continuation callback for a completed configuration application on all threads. + * @return an absl status indicating if a non-exception-throwing error was encountered. */ - virtual void onConfigUpdate(const Protobuf::Message& config, const std::string& version_info, - ConfigAppliedCb applied_on_all_threads) PURE; + virtual absl::Status onConfigUpdate(const Protobuf::Message& config, + const std::string& version_info, + ConfigAppliedCb applied_on_all_threads) PURE; /** * Removes the current configuration from the provider. diff --git a/envoy/config/subscription.h b/envoy/config/subscription.h index 0c4d61a924c8..4bcd3e56b4e5 100644 --- a/envoy/config/subscription.h +++ b/envoy/config/subscription.h @@ -103,28 +103,30 @@ class SubscriptionCallbacks { * everything other than delta gRPC - filesystem, HTTP, non-delta gRPC). * @param resources vector of fetched resources corresponding to the configuration update. * @param version_info supplies the version information as supplied by the xDS discovery response. - * @throw EnvoyException with reason if the configuration is rejected. Otherwise the configuration - * is accepted. Accepted configurations have their version_info reflected in subsequent - * requests. + * @return an absl status indicating if a non-exception-throwing error was encountered. + * @throw EnvoyException with reason if the configuration is rejected for legacy reasons, + * Accepted configurations have their version_info reflected in subsequent requests. */ - virtual void onConfigUpdate(const std::vector& resources, - const std::string& version_info) PURE; + virtual absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) PURE; /** * Called when a delta configuration update is received. * @param added_resources resources newly added since the previous fetch. * @param removed_resources names of resources that this fetch instructed to be removed. * @param system_version_info aggregate response data "version", for debugging. - * @throw EnvoyException with reason if the config changes are rejected. Otherwise the changes - * are accepted. Accepted changes have their version_info reflected in subsequent requests. + * @return an absl status indicating if a non-exception-throwing error was encountered. + * @throw EnvoyException with reason if the configuration is rejected for legacy reasons, + * Accepted configurations have their version_info reflected in subsequent requests. */ - virtual void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) PURE; + virtual absl::Status + onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) PURE; /** * Called when either the Subscription is unable to fetch a config update or when onConfigUpdate - * invokes an exception. + * returns a failure or invokes an exception. * @param reason supplies the update failure reason. * @param e supplies any exception data on why the fetch failed. May be nullptr. */ diff --git a/envoy/rds/route_config_provider.h b/envoy/rds/route_config_provider.h index 9b60e3f9780a..dd220bc99c10 100644 --- a/envoy/rds/route_config_provider.h +++ b/envoy/rds/route_config_provider.h @@ -50,8 +50,9 @@ class RouteConfigProvider { /** * Callback used to notify RouteConfigProvider about configuration changes. + * @return Status indicating if the call was successful or had graceful error handling. */ - virtual void onConfigUpdate() PURE; + virtual absl::Status onConfigUpdate() PURE; }; using RouteConfigProviderPtr = std::unique_ptr; diff --git a/source/common/config/config_provider_impl.h b/source/common/config/config_provider_impl.h index 9c5d844a4b5d..b4a04faaaecc 100644 --- a/source/common/config/config_provider_impl.h +++ b/source/common/config/config_provider_impl.h @@ -172,9 +172,10 @@ class ConfigSubscriptionCommonBase : protected Logger::Loggable& params, UNREFERENCED_PARAMETER(params); UNREFERENCED_PARAMETER(metadata); UNREFERENCED_PARAMETER(prefix); - throw EnvoyException("JSON/YAML support compiled out"); + IS_ENVOY_BUG("JSON/YAML support compiled out"); #endif } diff --git a/source/common/filter/config_discovery_impl.cc b/source/common/filter/config_discovery_impl.cc index 6bcf095d0d5c..e9ce25cd6e86 100644 --- a/source/common/filter/config_discovery_impl.cc +++ b/source/common/filter/config_discovery_impl.cc @@ -17,12 +17,14 @@ namespace Envoy { namespace Filter { namespace { -void validateTypeUrlHelper(const std::string& type_url, - const absl::flat_hash_set require_type_urls) { +absl::Status validateTypeUrlHelper(const std::string& type_url, + const absl::flat_hash_set require_type_urls) { if (!require_type_urls.contains(type_url)) { - throw EnvoyException(fmt::format("Error: filter config has type URL {} but expect {}.", - type_url, absl::StrJoin(require_type_urls, ", "))); + return absl::InvalidArgumentError( + fmt::format("Error: filter config has type URL {} but expect {}.", type_url, + absl::StrJoin(require_type_urls, ", "))); } + return absl::OkStatus(); } } // namespace @@ -50,8 +52,9 @@ DynamicFilterConfigProviderImplBase::~DynamicFilterConfigProviderImplBase() { subscription_->filter_config_providers_.erase(this); } -void DynamicFilterConfigProviderImplBase::validateTypeUrl(const std::string& type_url) const { - validateTypeUrlHelper(type_url, require_type_urls_); +absl::Status +DynamicFilterConfigProviderImplBase::validateTypeUrl(const std::string& type_url) const { + return validateTypeUrlHelper(type_url, require_type_urls_); } const std::string& DynamicFilterConfigProviderImplBase::name() { return subscription_->name(); } @@ -86,33 +89,37 @@ void FilterConfigSubscription::start() { } } -void FilterConfigSubscription::onConfigUpdate( - const std::vector& resources, const std::string& version_info) { +absl::Status +FilterConfigSubscription::onConfigUpdate(const std::vector& resources, + const std::string& version_info) { ConfigVersionSharedPtr next = std::make_shared(version_info, factory_context_.timeSource().systemTime()); if (resources.size() != 1) { - throw EnvoyException(fmt::format( + return absl::InvalidArgumentError(fmt::format( "Unexpected number of resources in ExtensionConfigDS response: {}", resources.size())); } const auto& filter_config = dynamic_cast( resources[0].get().resource()); if (filter_config.name() != filter_config_name_) { - throw EnvoyException(fmt::format("Unexpected resource name in ExtensionConfigDS response: {}", - filter_config.name())); + return absl::InvalidArgumentError(fmt::format( + "Unexpected resource name in ExtensionConfigDS response: {}", filter_config.name())); } // Skip update if hash matches next->config_hash_ = MessageUtil::hash(filter_config.typed_config()); if (next->config_hash_ == last_->config_hash_) { // Initial hash is 0, so this branch happens only after a config was already applied, and // there is no need to mark the init target ready. - return; + return absl::OkStatus(); } // Ensure that the filter config is valid in the filter chain context once the proto is // processed. Validation happens before updating to prevent a partial update application. It // might be possible that the providers have distinct type URL constraints. next->type_url_ = Config::Utility::getFactoryType(filter_config.typed_config()); for (auto* provider : filter_config_providers_) { - provider->validateTypeUrl(next->type_url_); + absl::Status status = provider->validateTypeUrl(next->type_url_); + if (!status.ok()) { + return status; + } } std::tie(next->config_, next->factory_name_) = filter_config_provider_manager_.getMessage(filter_config, factory_context_); @@ -127,16 +134,18 @@ void FilterConfigSubscription::onConfigUpdate( filter_config_providers_, [last = last_](DynamicFilterConfigProviderImplBase* provider, std::shared_ptr cleanup) { - provider->onConfigUpdate(*last->config_, last->version_info_, [cleanup] {}); + THROW_IF_NOT_OK( + provider->onConfigUpdate(*last->config_, last->version_info_, [cleanup] {})); }, [me = shared_from_this()]() { me->updateComplete(); }); // The filter configs are created and published to worker queues at this point, so it // is safe to mark the subscription as ready and publish the warmed parent resources. ENVOY_LOG(debug, "Updated filter config {} created, warming done", filter_config_name_); init_target_.ready(); + return absl::OkStatus(); } -void FilterConfigSubscription::onConfigUpdate( +absl::Status FilterConfigSubscription::onConfigUpdate( const std::vector& added_resources, const Protobuf::RepeatedPtrField& removed_resources, const std::string&) { if (!removed_resources.empty()) { @@ -151,8 +160,9 @@ void FilterConfigSubscription::onConfigUpdate( [me = shared_from_this()]() { me->updateComplete(); }); } else if (!added_resources.empty()) { ASSERT(added_resources.size() == 1); - onConfigUpdate(added_resources, added_resources[0].get().version()); + return onConfigUpdate(added_resources, added_resources[0].get().version()); } + return absl::OkStatus(); } void FilterConfigSubscription::onConfigUpdateFailed(Config::ConfigUpdateFailureReason reason, @@ -215,7 +225,7 @@ void FilterConfigProviderManagerImplBase::applyLastOrDefaultConfig( bool last_config_valid = false; if (subscription->lastConfig()) { TRY_ASSERT_MAIN_THREAD { - provider.validateTypeUrl(subscription->lastTypeUrl()); + THROW_IF_NOT_OK(provider.validateTypeUrl(subscription->lastTypeUrl())) provider.validateMessage(filter_config_name, *subscription->lastConfig(), subscription->lastFactoryName()); last_config_valid = true; @@ -227,8 +237,8 @@ void FilterConfigProviderManagerImplBase::applyLastOrDefaultConfig( }); if (last_config_valid) { - provider.onConfigUpdate(*subscription->lastConfig(), subscription->lastVersionInfo(), - nullptr); + THROW_IF_NOT_OK(provider.onConfigUpdate(*subscription->lastConfig(), + subscription->lastVersionInfo(), nullptr)); } } @@ -250,7 +260,7 @@ void FilterConfigProviderManagerImplBase::validateProtoConfigDefaultFactory( void FilterConfigProviderManagerImplBase::validateProtoConfigTypeUrl( const std::string& type_url, const absl::flat_hash_set& require_type_urls) const { - validateTypeUrlHelper(type_url, require_type_urls); + THROW_IF_NOT_OK(validateTypeUrlHelper(type_url, require_type_urls)); } } // namespace Filter diff --git a/source/common/filter/config_discovery_impl.h b/source/common/filter/config_discovery_impl.h index 44afdb315926..9085b874c825 100644 --- a/source/common/filter/config_discovery_impl.h +++ b/source/common/filter/config_discovery_impl.h @@ -44,7 +44,7 @@ class DynamicFilterConfigProviderImplBase : public Config::DynamicExtensionConfi ~DynamicFilterConfigProviderImplBase() override; const Init::Target& initTarget() const { return init_target_; } - void validateTypeUrl(const std::string& type_url) const; + absl::Status validateTypeUrl(const std::string& type_url) const; virtual void validateMessage(const std::string& config_name, const Protobuf::Message& message, const std::string& factory_name) const PURE; @@ -108,10 +108,11 @@ class DynamicFilterConfigProviderImpl : public DynamicFilterConfigProviderImplBa } // Config::DynamicExtensionConfigProviderBase - void onConfigUpdate(const Protobuf::Message& message, const std::string&, - Config::ConfigAppliedCb applied_on_all_threads) override { + absl::Status onConfigUpdate(const Protobuf::Message& message, const std::string&, + Config::ConfigAppliedCb applied_on_all_threads) override { const FactoryCb config = instantiateFilterFactory(message); update(config, applied_on_all_threads); + return absl::OkStatus(); } void onConfigRemoved(Config::ConfigAppliedCb applied_on_all_threads) override { @@ -124,7 +125,10 @@ class DynamicFilterConfigProviderImpl : public DynamicFilterConfigProviderImplBa void applyDefaultConfiguration() override { if (default_configuration_) { - onConfigUpdate(*default_configuration_, "", nullptr); + auto status = onConfigUpdate(*default_configuration_, "", nullptr); + if (!status.ok()) { + throw EnvoyException(std::string(status.message())); + } } } const Network::ListenerFilterMatcherSharedPtr& getListenerFilterMatcher() override { @@ -406,11 +410,11 @@ class FilterConfigSubscription void start(); // Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override; - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string&) override; + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string&) override; void onConfigUpdateFailed(Config::ConfigUpdateFailureReason reason, const EnvoyException*) override; void updateComplete(); diff --git a/source/common/rds/rds_route_config_provider_impl.cc b/source/common/rds/rds_route_config_provider_impl.cc index 6e6789275617..92ac88a332f5 100644 --- a/source/common/rds/rds_route_config_provider_impl.cc +++ b/source/common/rds/rds_route_config_provider_impl.cc @@ -29,9 +29,10 @@ RdsRouteConfigProviderImpl::configInfo() const { return config_update_info_->configInfo(); } -void RdsRouteConfigProviderImpl::onConfigUpdate() { +absl::Status RdsRouteConfigProviderImpl::onConfigUpdate() { tls_.runOnAllThreads([new_config = config_update_info_->parsedConfiguration()]( OptRef tls) { tls->config_ = new_config; }); + return absl::OkStatus(); } } // namespace Rds diff --git a/source/common/rds/rds_route_config_provider_impl.h b/source/common/rds/rds_route_config_provider_impl.h index e3ed7c2cc7b5..80ea7da26227 100644 --- a/source/common/rds/rds_route_config_provider_impl.h +++ b/source/common/rds/rds_route_config_provider_impl.h @@ -32,7 +32,7 @@ class RdsRouteConfigProviderImpl : public RouteConfigProvider, const absl::optional& configInfo() const override; SystemTime lastUpdated() const override { return config_update_info_->lastUpdated(); } - void onConfigUpdate() override; + absl::Status onConfigUpdate() override; private: struct ThreadLocalConfig : public ThreadLocal::ThreadLocalObject { diff --git a/source/common/rds/rds_route_config_subscription.cc b/source/common/rds/rds_route_config_subscription.cc index b551363b7160..5a155af697f7 100644 --- a/source/common/rds/rds_route_config_subscription.cc +++ b/source/common/rds/rds_route_config_subscription.cc @@ -50,11 +50,11 @@ RdsRouteConfigSubscription::~RdsRouteConfigSubscription() { route_config_provider_manager_.eraseDynamicProvider(manager_identifier_); } -void RdsRouteConfigSubscription::onConfigUpdate( +absl::Status RdsRouteConfigSubscription::onConfigUpdate( const std::vector& resources, const std::string& version_info) { if (!validateUpdateSize(resources.size())) { - return; + return absl::OkStatus(); } const auto& route_config = resources[0].get().resource(); Protobuf::ReflectableMessage reflectable_config = createReflectableMessage(route_config); @@ -83,16 +83,17 @@ void RdsRouteConfigSubscription::onConfigUpdate( config_update_info_->configHash()); if (route_config_provider_ != nullptr) { - route_config_provider_->onConfigUpdate(); + THROW_IF_NOT_OK(route_config_provider_->onConfigUpdate()); } afterProviderUpdate(); } local_init_target_.ready(); + return absl::OkStatus(); } -void RdsRouteConfigSubscription::onConfigUpdate( +absl::Status RdsRouteConfigSubscription::onConfigUpdate( const std::vector& added_resources, const Protobuf::RepeatedPtrField& removed_resources, const std::string&) { if (!removed_resources.empty()) { @@ -103,8 +104,9 @@ void RdsRouteConfigSubscription::onConfigUpdate( rds_type_, removed_resources[0]); } if (!added_resources.empty()) { - onConfigUpdate(added_resources, added_resources[0].get().version()); + return onConfigUpdate(added_resources, added_resources[0].get().version()); } + return absl::OkStatus(); } void RdsRouteConfigSubscription::onConfigUpdateFailed( diff --git a/source/common/rds/rds_route_config_subscription.h b/source/common/rds/rds_route_config_subscription.h index 7f360f01bbac..6c31198748df 100644 --- a/source/common/rds/rds_route_config_subscription.h +++ b/source/common/rds/rds_route_config_subscription.h @@ -62,12 +62,12 @@ class RdsRouteConfigSubscription : Envoy::Config::SubscriptionCallbacks, private: // Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override; + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override; - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string&) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string&) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException*) override; diff --git a/source/common/rds/static_route_config_provider_impl.h b/source/common/rds/static_route_config_provider_impl.h index 196f7a9efb11..ad02c78d1a17 100644 --- a/source/common/rds/static_route_config_provider_impl.h +++ b/source/common/rds/static_route_config_provider_impl.h @@ -25,7 +25,7 @@ class StaticRouteConfigProviderImpl : public RouteConfigProvider { ConfigConstSharedPtr config() const override { return config_; } const absl::optional& configInfo() const override { return config_info_; } SystemTime lastUpdated() const override { return last_updated_; } - void onConfigUpdate() override {} + absl::Status onConfigUpdate() override { return absl::OkStatus(); } private: ProtobufTypes::MessagePtr route_config_proto_; diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index f5dd4c521a29..ca3b7e91b845 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -82,39 +82,33 @@ RouteEntryImplBaseConstSharedPtr createAndValidateRoute( RouteEntryImplBaseConstSharedPtr route; switch (route_config.match().path_specifier_case()) { - case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPrefix: { + case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPrefix: route = std::make_shared(vhost, route_config, optional_http_filters, factory_context, validator); break; - } - case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPath: { + case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPath: route = std::make_shared(vhost, route_config, optional_http_filters, factory_context, validator); break; - } - case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kSafeRegex: { + case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kSafeRegex: route = std::make_shared(vhost, route_config, optional_http_filters, factory_context, validator); break; - } - case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kConnectMatcher: { + case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kConnectMatcher: route = std::make_shared(vhost, route_config, optional_http_filters, factory_context, validator); break; - case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPathSeparatedPrefix: { + case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPathSeparatedPrefix: route = std::make_shared( vhost, route_config, optional_http_filters, factory_context, validator); break; - } - case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPathMatchPolicy: { + case envoy::config::route::v3::RouteMatch::PathSpecifierCase::kPathMatchPolicy: route = std::make_shared( vhost, route_config, optional_http_filters, factory_context, validator); break; - } case envoy::config::route::v3::RouteMatch::PathSpecifierCase::PATH_SPECIFIER_NOT_SET: break; // throw the error below. } - } if (!route) { throw EnvoyException("Invalid route config"); } diff --git a/source/common/router/rds_impl.cc b/source/common/router/rds_impl.cc index 633de10ca6ca..fe4cc2ba5bae 100644 --- a/source/common/router/rds_impl.cc +++ b/source/common/router/rds_impl.cc @@ -157,13 +157,16 @@ RdsRouteConfigSubscription& RdsRouteConfigProviderImpl::subscription() { return static_cast(base_.subscription()); } -void RdsRouteConfigProviderImpl::onConfigUpdate() { - base_.onConfigUpdate(); +absl::Status RdsRouteConfigProviderImpl::onConfigUpdate() { + auto status = base_.onConfigUpdate(); + if (!status.ok()) { + return status; + } const auto aliases = config_update_info_->resourceIdsInLastVhdsUpdate(); // Regular (non-VHDS) RDS updates don't populate aliases fields in resources. if (aliases.empty()) { - return; + return absl::OkStatus(); } const auto config = @@ -189,6 +192,7 @@ void RdsRouteConfigProviderImpl::onConfigUpdate() { it++; } } + return absl::OkStatus(); } ConfigConstSharedPtr RdsRouteConfigProviderImpl::configCast() const { diff --git a/source/common/router/rds_impl.h b/source/common/router/rds_impl.h index 340bfe6e0575..643c06ff720e 100644 --- a/source/common/router/rds_impl.h +++ b/source/common/router/rds_impl.h @@ -80,7 +80,7 @@ class StaticRouteConfigProviderImpl : public RouteConfigProvider { Rds::ConfigConstSharedPtr config() const override { return base_.config(); } const absl::optional& configInfo() const override { return base_.configInfo(); } SystemTime lastUpdated() const override { return base_.lastUpdated(); } - void onConfigUpdate() override { base_.onConfigUpdate(); } + absl::Status onConfigUpdate() override { return base_.onConfigUpdate(); } ConfigConstSharedPtr configCast() const override; void requestVirtualHostsUpdate(const std::string&, Event::Dispatcher&, std::weak_ptr) override {} @@ -154,7 +154,7 @@ class RdsRouteConfigProviderImpl : public RouteConfigProvider, const absl::optional& configInfo() const override { return base_.configInfo(); } SystemTime lastUpdated() const override { return base_.lastUpdated(); } - void onConfigUpdate() override; + absl::Status onConfigUpdate() override; ConfigConstSharedPtr configCast() const override; void requestVirtualHostsUpdate( const std::string& for_domain, Event::Dispatcher& thread_local_dispatcher, diff --git a/source/common/router/scoped_rds.cc b/source/common/router/scoped_rds.cc index 4ad5fc00f3a7..d241be70a9b1 100644 --- a/source/common/router/scoped_rds.cc +++ b/source/common/router/scoped_rds.cc @@ -271,7 +271,7 @@ void ScopedRdsConfigSubscription::RdsRouteConfigProviderHelper::maybeInitRdsConf parent_.onRdsConfigUpdate(scope_name_, route_provider_->configCast()); } -bool ScopedRdsConfigSubscription::addOrUpdateScopes( +absl::StatusOr ScopedRdsConfigSubscription::addOrUpdateScopes( const std::vector& resources, Init::Manager& init_manager, const std::string& version_info) { bool any_applied = false; @@ -284,7 +284,7 @@ bool ScopedRdsConfigSubscription::addOrUpdateScopes( dynamic_cast( resource.get().resource()); if (scoped_route_config.route_configuration_name().empty()) { - throw EnvoyException("route_configuration_name is empty."); + return absl::InvalidArgumentError("route_configuration_name is empty."); } const std::string scope_name = scoped_route_config.name(); if (const auto& scope_info_iter = scoped_route_map_.find(scope_name); @@ -369,7 +369,7 @@ ScopedRdsConfigSubscription::removeScopes( return to_be_removed_rds_providers; } -void ScopedRdsConfigSubscription::onConfigUpdate( +absl::Status ScopedRdsConfigSubscription::onConfigUpdate( const std::vector& added_resources, const Protobuf::RepeatedPtrField& removed_resources, const std::string& version_info) { @@ -415,7 +415,8 @@ void ScopedRdsConfigSubscription::onConfigUpdate( Protobuf::RepeatedPtrField clean_removed_resources = detectUpdateConflictAndCleanupRemoved(added_resources, removed_resources, exception_msg); if (!exception_msg.empty()) { - throw EnvoyException(fmt::format("Error adding/updating scoped route(s): {}", exception_msg)); + return absl::InvalidArgumentError( + fmt::format("Error adding/updating scoped route(s): {}", exception_msg)); } // Do not delete RDS config providers just yet, in case the to be deleted RDS subscriptions could @@ -423,18 +424,24 @@ void ScopedRdsConfigSubscription::onConfigUpdate( std::list to_be_removed_rds_providers = removeScopes(clean_removed_resources, version_info); - bool any_applied = - addOrUpdateScopes(added_resources, - (srds_init_mgr == nullptr ? localInitManager() : *srds_init_mgr), - version_info) || - !to_be_removed_rds_providers.empty(); - ConfigSubscriptionCommonBase::onConfigUpdate(); - if (any_applied) { + auto status_or_applied = addOrUpdateScopes( + added_resources, (srds_init_mgr == nullptr ? localInitManager() : *srds_init_mgr), + version_info); + if (!status_or_applied.status().ok()) { + return status_or_applied.status(); + } + bool any_applied = status_or_applied.value(); + auto status = ConfigSubscriptionCommonBase::onConfigUpdate(); + if (!status.ok()) { + return status; + } + if (any_applied || !to_be_removed_rds_providers.empty()) { setLastConfigInfo(absl::optional({absl::nullopt, version_info})); } stats_.all_scopes_.set(scoped_route_map_.size()); stats_.config_reload_.inc(); stats_.config_reload_time_ms_.set(DateUtil::nowToMilliseconds(factory_context_.timeSource())); + return absl::OkStatus(); } void ScopedRdsConfigSubscription::onRdsConfigUpdate(const std::string& scope_name, @@ -459,14 +466,14 @@ void ScopedRdsConfigSubscription::onRdsConfigUpdate(const std::string& scope_nam // TODO(stevenzzzz): see issue #7508, consider generalizing this function as it overlaps with // CdsApiImpl::onConfigUpdate. -void ScopedRdsConfigSubscription::onConfigUpdate( +absl::Status ScopedRdsConfigSubscription::onConfigUpdate( const std::vector& resources, const std::string& version_info) { Protobuf::RepeatedPtrField to_remove_repeated; for (const auto& scoped_route : scoped_route_map_) { *to_remove_repeated.Add() = scoped_route.first; } - onConfigUpdate(resources, to_remove_repeated, version_info); + return onConfigUpdate(resources, to_remove_repeated, version_info); } Protobuf::RepeatedPtrField diff --git a/source/common/router/scoped_rds.h b/source/common/router/scoped_rds.h index 6b76b0594e64..3dc1b7e615ae 100644 --- a/source/common/router/scoped_rds.h +++ b/source/common/router/scoped_rds.h @@ -184,9 +184,11 @@ class ScopedRdsConfigSubscription // Adds or updates scopes, create a new RDS provider for each resource, if an exception is thrown // during updating, the exception message is collected via the exception messages vector. - // Returns true if any scope updated, false otherwise. - bool addOrUpdateScopes(const std::vector& resources, - Init::Manager& init_manager, const std::string& version_info); + // Returns a failed status if the operation was unsuccessful. If successful, + // returns a boolean indicating if any scopes were applied. + absl::StatusOr + addOrUpdateScopes(const std::vector& resources, + Init::Manager& init_manager, const std::string& version_info); // Removes given scopes from the managed set of scopes. // Returns a list of to be removed helpers which is temporally held in the onConfigUpdate method, // to make sure new scopes sharing the same RDS source configs could reuse the subscriptions. @@ -208,12 +210,12 @@ class ScopedRdsConfigSubscription // Envoy::Config::SubscriptionCallbacks // NOTE: both delta form and state-of-the-world form onConfigUpdate(resources, version_info) will - // throw an EnvoyException on any error and essentially reject an update. - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override; - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) override; + // throw an EnvoyException or return failure on any error and essentially reject an update. + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException*) override { ASSERT(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure != reason); diff --git a/source/common/router/vhds.cc b/source/common/router/vhds.cc index c0df530ef90d..939852a50fc6 100644 --- a/source/common/router/vhds.cc +++ b/source/common/router/vhds.cc @@ -66,7 +66,7 @@ void VhdsSubscription::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureRe init_target_.ready(); } -void VhdsSubscription::onConfigUpdate( +absl::Status VhdsSubscription::onConfigUpdate( const std::vector& added_resources, const Protobuf::RepeatedPtrField& removed_resources, const std::string& version_info) { @@ -91,11 +91,12 @@ void VhdsSubscription::onConfigUpdate( config_update_info_->protobufConfigurationCast().name(), config_update_info_->configHash()); if (route_config_provider_ != nullptr) { - route_config_provider_->onConfigUpdate(); + THROW_IF_NOT_OK(route_config_provider_->onConfigUpdate()); } } init_target_.ready(); + return absl::OkStatus(); } } // namespace Router diff --git a/source/common/router/vhds.h b/source/common/router/vhds.h index 8c0513082ab8..6e3f397b76a8 100644 --- a/source/common/router/vhds.h +++ b/source/common/router/vhds.h @@ -58,10 +58,13 @@ class VhdsSubscription : Envoy::Config::SubscriptionBase&, - const std::string&) override {} - void onConfigUpdate(const std::vector&, - const Protobuf::RepeatedPtrField&, const std::string&) override; + absl::Status onConfigUpdate(const std::vector&, + const std::string&) override { + return absl::OkStatus(); + } + absl::Status onConfigUpdate(const std::vector&, + const Protobuf::RepeatedPtrField&, + const std::string&) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; diff --git a/source/common/runtime/runtime_impl.cc b/source/common/runtime/runtime_impl.cc index 2a4deeee8b2e..8368c3fa11c6 100644 --- a/source/common/runtime/runtime_impl.cc +++ b/source/common/runtime/runtime_impl.cc @@ -594,32 +594,41 @@ void RtdsSubscription::createSubscription() { {}); } -void RtdsSubscription::onConfigUpdate(const std::vector& resources, - const std::string&) { - validateUpdateSize(resources.size(), 0); +absl::Status +RtdsSubscription::onConfigUpdate(const std::vector& resources, + const std::string&) { + absl::Status valid = validateUpdateSize(resources.size(), 0); + if (!valid.ok()) { + return valid; + } const auto& runtime = dynamic_cast(resources[0].get().resource()); if (runtime.name() != resource_name_) { - throw EnvoyException( + return absl::InvalidArgumentError( fmt::format("Unexpected RTDS runtime (expecting {}): {}", resource_name_, runtime.name())); } ENVOY_LOG(debug, "Reloading RTDS snapshot for onConfigUpdate"); proto_.CopyFrom(runtime.layer()); parent_.loadNewSnapshot(); init_target_.ready(); + return absl::OkStatus(); } -void RtdsSubscription::onConfigUpdate( - const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, const std::string&) { - validateUpdateSize(added_resources.size(), removed_resources.size()); +absl::Status +RtdsSubscription::onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string&) { + absl::Status valid = validateUpdateSize(added_resources.size(), removed_resources.size()); + if (!valid.ok()) { + return valid; + } // This is a singleton subscription, so we can only have the subscribed resource added or removed, // but not both. if (!added_resources.empty()) { - onConfigUpdate(added_resources, added_resources[0].get().version()); + return onConfigUpdate(added_resources, added_resources[0].get().version()); } else { - onConfigRemoved(removed_resources); + return onConfigRemoved(removed_resources); } } @@ -633,20 +642,22 @@ void RtdsSubscription::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureRe void RtdsSubscription::start() { subscription_->start({resource_name_}); } -void RtdsSubscription::validateUpdateSize(uint32_t added_resources_num, - uint32_t removed_resources_num) { +absl::Status RtdsSubscription::validateUpdateSize(uint32_t added_resources_num, + uint32_t removed_resources_num) { if (added_resources_num + removed_resources_num != 1) { init_target_.ready(); - throw EnvoyException(fmt::format("Unexpected RTDS resource length, number of added recources " - "{}, number of removed recources {}", - added_resources_num, removed_resources_num)); + return absl::InvalidArgumentError( + fmt::format("Unexpected RTDS resource length, number of added recources " + "{}, number of removed recources {}", + added_resources_num, removed_resources_num)); } + return absl::OkStatus(); } -void RtdsSubscription::onConfigRemoved( +absl::Status RtdsSubscription::onConfigRemoved( const Protobuf::RepeatedPtrField& removed_resources) { if (removed_resources[0] != resource_name_) { - throw EnvoyException( + return absl::InvalidArgumentError( fmt::format("Unexpected removal of unknown RTDS runtime layer {}, expected {}", removed_resources[0], resource_name_)); } @@ -654,6 +665,7 @@ void RtdsSubscription::onConfigRemoved( proto_.Clear(); parent_.loadNewSnapshot(); init_target_.ready(); + return absl::OkStatus(); } void LoaderImpl::loadNewSnapshot() { diff --git a/source/common/runtime/runtime_impl.h b/source/common/runtime/runtime_impl.h index 393228f3323c..e2ebde9e0c8b 100644 --- a/source/common/runtime/runtime_impl.h +++ b/source/common/runtime/runtime_impl.h @@ -178,18 +178,18 @@ struct RtdsSubscription : Envoy::Config::SubscriptionBase& resources, - const std::string& version_info) override; - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string&) override; + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string&) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; void start(); - void validateUpdateSize(uint32_t added_resources_num, uint32_t removed_resources_num); - void onConfigRemoved(const Protobuf::RepeatedPtrField& removed_resources); + absl::Status validateUpdateSize(uint32_t added_resources_num, uint32_t removed_resources_num); + absl::Status onConfigRemoved(const Protobuf::RepeatedPtrField& removed_resources); void createSubscription(); LoaderImpl& parent_; diff --git a/source/common/secret/sds_api.cc b/source/common/secret/sds_api.cc index effcac5a1a62..ebb6ed1c3d05 100644 --- a/source/common/secret/sds_api.cc +++ b/source/common/secret/sds_api.cc @@ -79,8 +79,8 @@ void SdsApi::onWatchUpdate() { }); } -void SdsApi::onConfigUpdate(const std::vector& resources, - const std::string& version_info) { +absl::Status SdsApi::onConfigUpdate(const std::vector& resources, + const std::string& version_info) { validateUpdateSize(resources.size()); const auto& secret = dynamic_cast( resources[0].get().resource()); @@ -129,12 +129,14 @@ void SdsApi::onConfigUpdate(const std::vector& resou secret_data_.last_updated_ = time_source_.systemTime(); secret_data_.version_info_ = version_info; init_target_.ready(); + return absl::OkStatus(); } -void SdsApi::onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField&, const std::string&) { +absl::Status SdsApi::onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField&, + const std::string&) { validateUpdateSize(added_resources.size()); - onConfigUpdate(added_resources, added_resources[0].get().version()); + return onConfigUpdate(added_resources, added_resources[0].get().version()); } void SdsApi::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, diff --git a/source/common/secret/sds_api.h b/source/common/secret/sds_api.h index 2c5a08bc5281..dbe410ae177e 100644 --- a/source/common/secret/sds_api.h +++ b/source/common/secret/sds_api.h @@ -74,11 +74,11 @@ class SdsApi : public Envoy::Config::SubscriptionBase< Common::CallbackManager<> update_callback_manager_; // Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override; - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) override; + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; virtual std::vector getDataSourceFilenames() PURE; diff --git a/source/common/upstream/cds_api_impl.cc b/source/common/upstream/cds_api_impl.cc index ecb1d082a4b7..b9a43923f30f 100644 --- a/source/common/upstream/cds_api_impl.cc +++ b/source/common/upstream/cds_api_impl.cc @@ -33,8 +33,8 @@ CdsApiImpl::CdsApiImpl(const envoy::config::core::v3::ConfigSource& cds_config, } } -void CdsApiImpl::onConfigUpdate(const std::vector& resources, - const std::string& version_info) { +absl::Status CdsApiImpl::onConfigUpdate(const std::vector& resources, + const std::string& version_info) { auto all_existing_clusters = cm_.clusters(); // Exclude the clusters which CDS wants to add. for (const auto& resource : resources) { @@ -53,12 +53,13 @@ void CdsApiImpl::onConfigUpdate(const std::vector& r *to_remove_repeated.Add() = cluster_name; } } - onConfigUpdate(resources, to_remove_repeated, version_info); + return onConfigUpdate(resources, to_remove_repeated, version_info); } -void CdsApiImpl::onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) { +absl::Status +CdsApiImpl::onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) { auto exception_msgs = helper_.onConfigUpdate(added_resources, removed_resources, system_version_info); runInitializeCallbackIfAny(); @@ -66,6 +67,7 @@ void CdsApiImpl::onConfigUpdate(const std::vector& a throw EnvoyException( fmt::format("Error adding/updating cluster(s) {}", absl::StrJoin(exception_msgs, ", "))); } + return absl::OkStatus(); } void CdsApiImpl::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, diff --git a/source/common/upstream/cds_api_impl.h b/source/common/upstream/cds_api_impl.h index b38c6686c40a..aa24feaa8fbe 100644 --- a/source/common/upstream/cds_api_impl.h +++ b/source/common/upstream/cds_api_impl.h @@ -39,11 +39,11 @@ class CdsApiImpl : public CdsApi, private: // Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override; - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) override; + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; CdsApiImpl(const envoy::config::core::v3::ConfigSource& cds_config, diff --git a/source/common/upstream/od_cds_api_impl.cc b/source/common/upstream/od_cds_api_impl.cc index 17c69424ad99..c976b1000f5e 100644 --- a/source/common/upstream/od_cds_api_impl.cc +++ b/source/common/upstream/od_cds_api_impl.cc @@ -38,17 +38,18 @@ OdCdsApiImpl::OdCdsApiImpl(const envoy::config::core::v3::ConfigSource& odcds_co } } -void OdCdsApiImpl::onConfigUpdate(const std::vector& resources, - const std::string& version_info) { +absl::Status OdCdsApiImpl::onConfigUpdate(const std::vector& resources, + const std::string& version_info) { UNREFERENCED_PARAMETER(resources); UNREFERENCED_PARAMETER(version_info); // On-demand cluster updates are only supported for delta, not sotw. PANIC("not supported"); } -void OdCdsApiImpl::onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) { +absl::Status +OdCdsApiImpl::onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) { auto exception_msgs = helper_.onConfigUpdate(added_resources, removed_resources, system_version_info); sendAwaiting(); @@ -65,6 +66,7 @@ void OdCdsApiImpl::onConfigUpdate(const std::vector& throw EnvoyException( fmt::format("Error adding/updating cluster(s) {}", absl::StrJoin(exception_msgs, ", "))); } + return absl::OkStatus(); } void OdCdsApiImpl::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, diff --git a/source/common/upstream/od_cds_api_impl.h b/source/common/upstream/od_cds_api_impl.h index 43f03d7d6db5..11686be76465 100644 --- a/source/common/upstream/od_cds_api_impl.h +++ b/source/common/upstream/od_cds_api_impl.h @@ -70,11 +70,11 @@ class OdCdsApiImpl : public OdCdsApi, private: // Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override; - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) override; + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; diff --git a/source/extensions/clusters/eds/eds.cc b/source/extensions/clusters/eds/eds.cc index d8445d0f2c95..21eccdf5b42e 100644 --- a/source/extensions/clusters/eds/eds.cc +++ b/source/extensions/clusters/eds/eds.cc @@ -160,10 +160,11 @@ void EdsClusterImpl::BatchUpdateHelper::updateLocalityEndpoints( all_new_hosts.emplace(address_as_string); } -void EdsClusterImpl::onConfigUpdate(const std::vector& resources, - const std::string&) { +absl::Status +EdsClusterImpl::onConfigUpdate(const std::vector& resources, + const std::string&) { if (!validateUpdateSize(resources.size())) { - return; + return absl::OkStatus(); } envoy::config::endpoint::v3::ClusterLoadAssignment cluster_load_assignment = dynamic_cast( @@ -218,6 +219,7 @@ void EdsClusterImpl::onConfigUpdate(const std::vectorremoveCallback(edsServiceName(), this); using_cached_resource_ = false; } + return absl::OkStatus(); } void EdsClusterImpl::update( @@ -280,15 +282,16 @@ void EdsClusterImpl::update( BatchUpdateHelper helper(*this, *used_load_assignment); priority_set_.batchHostUpdate(helper); + return; } -void EdsClusterImpl::onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField&, - const std::string&) { +absl::Status +EdsClusterImpl::onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField&, const std::string&) { if (!validateUpdateSize(added_resources.size())) { - return; + return absl::OkStatus(); } - onConfigUpdate(added_resources, added_resources[0].get().version()); + return onConfigUpdate(added_resources, added_resources[0].get().version()); } bool EdsClusterImpl::validateUpdateSize(int num_resources) { diff --git a/source/extensions/clusters/eds/eds.h b/source/extensions/clusters/eds/eds.h index 8a1a32a0349a..b20ec27bf0bb 100644 --- a/source/extensions/clusters/eds/eds.h +++ b/source/extensions/clusters/eds/eds.h @@ -42,11 +42,11 @@ class EdsClusterImpl private: // Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override; - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) override; + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; using LocalityWeightsMap = absl::node_hash_mapstart({}); } -void LedsSubscription::onConfigUpdate( - const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, const std::string&) { +absl::Status +LedsSubscription::onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string&) { // At least one resource must be added or removed. if (added_resources.empty() && removed_resources.empty()) { ENVOY_LOG(debug, "No added or removed LbEndpoint entries for cluster {} in onConfigUpdate()", @@ -44,7 +45,7 @@ void LedsSubscription::onConfigUpdate( initial_update_attempt_complete_ = true; callback_(); } - return; + return absl::OkStatus(); } ENVOY_LOG(info, "{}: add {} endpoint(s), remove {} endpoints(s)", cluster_name_, @@ -70,6 +71,7 @@ void LedsSubscription::onConfigUpdate( // Notify the callbacks that the host list has been modified. initial_update_attempt_complete_ = true; callback_(); + return absl::OkStatus(); } void LedsSubscription::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, diff --git a/source/extensions/clusters/eds/leds.h b/source/extensions/clusters/eds/leds.h index d3893f5da184..dab7a5bff389 100644 --- a/source/extensions/clusters/eds/leds.h +++ b/source/extensions/clusters/eds/leds.h @@ -54,11 +54,13 @@ class LedsSubscription private: // Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector&, const std::string&) override { + absl::Status onConfigUpdate(const std::vector&, + const std::string&) override { + return absl::OkStatus(); } - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string&) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string&) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; diff --git a/source/extensions/config_subscription/filesystem/filesystem_subscription_impl.cc b/source/extensions/config_subscription/filesystem/filesystem_subscription_impl.cc index 15ed6e0793c4..bc33cecee33e 100644 --- a/source/extensions/config_subscription/filesystem/filesystem_subscription_impl.cc +++ b/source/extensions/config_subscription/filesystem/filesystem_subscription_impl.cc @@ -70,7 +70,7 @@ std::string FilesystemSubscriptionImpl::refreshInternal(ProtobufTypes::MessagePt *config_update = std::move(owned_message); const auto decoded_resources = DecodedResourcesWrapper(*resource_decoder_, message.resources(), message.version_info()); - callbacks_.onConfigUpdate(decoded_resources.refvec_, message.version_info()); + THROW_IF_NOT_OK(callbacks_.onConfigUpdate(decoded_resources.refvec_, message.version_info())); return message.version_info(); } @@ -154,7 +154,7 @@ FilesystemCollectionSubscriptionImpl::refreshInternal(ProtobufTypes::MessagePtr* } } *config_update = std::move(owned_resource_message); - callbacks_.onConfigUpdate(decoded_resources.refvec_, resource_message.version()); + THROW_IF_NOT_OK(callbacks_.onConfigUpdate(decoded_resources.refvec_, resource_message.version())); return resource_message.version(); } diff --git a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc index 640609f5af04..435c98f53c0c 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc @@ -389,7 +389,7 @@ void GrpcMuxImpl::processDiscoveryResources(const std::vectorresources_.empty()) { - watch->callbacks_.onConfigUpdate(all_resource_refs, version_info); + THROW_IF_NOT_OK(watch->callbacks_.onConfigUpdate(all_resource_refs, version_info)); continue; } std::vector found_resources; @@ -416,7 +416,7 @@ void GrpcMuxImpl::processDiscoveryResources(const std::vectorcallbacks_.onConfigUpdate(found_resources, version_info); + THROW_IF_NOT_OK(watch->callbacks_.onConfigUpdate(found_resources, version_info)); // Resource cache is only used for EDS resources. if (eds_resources_cache_ && (type_url == Config::getTypeUrl())) { @@ -513,7 +513,7 @@ void GrpcMuxImpl::expiryCallback(absl::string_view type_url, } } - watch->callbacks_.onConfigUpdate({}, found_resources_for_watch, ""); + THROW_IF_NOT_OK(watch->callbacks_.onConfigUpdate({}, found_resources_for_watch, "")); } } diff --git a/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc b/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc index d32adfa13160..54b4da8b31d3 100644 --- a/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc +++ b/source/extensions/config_subscription/grpc/grpc_subscription_impl.cc @@ -64,15 +64,19 @@ void GrpcSubscriptionImpl::requestOnDemandUpdate( } // Config::SubscriptionCallbacks -void GrpcSubscriptionImpl::onConfigUpdate(const std::vector& resources, - const std::string& version_info) { +absl::Status +GrpcSubscriptionImpl::onConfigUpdate(const std::vector& resources, + const std::string& version_info) { disableInitFetchTimeoutTimer(); // TODO(mattklein123): In the future if we start tracking per-resource versions, we need to // supply those versions to onConfigUpdate() along with the xDS response ("system") // version_info. This way, both types of versions can be tracked and exposed for debugging by // the configuration update targets. auto start = dispatcher_.timeSource().monotonicTime(); - callbacks_.onConfigUpdate(resources, version_info); + absl::Status status = callbacks_.onConfigUpdate(resources, version_info); + if (!status.ok()) { + return status; + } std::chrono::milliseconds update_duration = std::chrono::duration_cast( dispatcher_.timeSource().monotonicTime() - start); stats_.update_success_.inc(); @@ -88,16 +92,21 @@ void GrpcSubscriptionImpl::onConfigUpdate(const std::vector& added_resources, const Protobuf::RepeatedPtrField& removed_resources, const std::string& system_version_info) { disableInitFetchTimeoutTimer(); stats_.update_attempt_.inc(); auto start = dispatcher_.timeSource().monotonicTime(); - callbacks_.onConfigUpdate(added_resources, removed_resources, system_version_info); + absl::Status status = + callbacks_.onConfigUpdate(added_resources, removed_resources, system_version_info); + if (!status.ok()) { + return status; + } std::chrono::milliseconds update_duration = std::chrono::duration_cast( dispatcher_.timeSource().monotonicTime() - start); stats_.update_success_.inc(); @@ -105,6 +114,7 @@ void GrpcSubscriptionImpl::onConfigUpdate( stats_.version_.set(HashUtil::xxHash64(system_version_info)); stats_.version_text_.set(system_version_info); stats_.update_duration_.recordValue(update_duration.count()); + return absl::OkStatus(); } void GrpcSubscriptionImpl::onConfigUpdateFailed(ConfigUpdateFailureReason reason, diff --git a/source/extensions/config_subscription/grpc/grpc_subscription_impl.h b/source/extensions/config_subscription/grpc/grpc_subscription_impl.h index 80690e629bb2..5039449ba9b7 100644 --- a/source/extensions/config_subscription/grpc/grpc_subscription_impl.h +++ b/source/extensions/config_subscription/grpc/grpc_subscription_impl.h @@ -33,11 +33,11 @@ class GrpcSubscriptionImpl : public Subscription, updateResourceInterest(const absl::flat_hash_set& update_to_these_names) override; void requestOnDemandUpdate(const absl::flat_hash_set& add_these_names) override; // Config::SubscriptionCallbacks (all pass through to callbacks_!) - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override; - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) override; + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) override; void onConfigUpdateFailed(ConfigUpdateFailureReason reason, const EnvoyException* e) override; GrpcMuxSharedPtr grpcMux() { return grpc_mux_; } diff --git a/source/extensions/config_subscription/grpc/watch_map.cc b/source/extensions/config_subscription/grpc/watch_map.cc index 9c16a4182e45..ea00f4e0d5af 100644 --- a/source/extensions/config_subscription/grpc/watch_map.cc +++ b/source/extensions/config_subscription/grpc/watch_map.cc @@ -171,11 +171,11 @@ void WatchMap::onConfigUpdate(const std::vector& resources, // 3) Otherwise, we can skip onConfigUpdate for this watch. if (map_is_single_wildcard || !watch->state_of_the_world_empty_) { watch->state_of_the_world_empty_ = true; - watch->callbacks_.onConfigUpdate({}, version_info); + THROW_IF_NOT_OK(watch->callbacks_.onConfigUpdate({}, version_info)); } } else { watch->state_of_the_world_empty_ = false; - watch->callbacks_.onConfigUpdate(this_watch_updates->second, version_info); + THROW_IF_NOT_OK(watch->callbacks_.onConfigUpdate(this_watch_updates->second, version_info)); } } @@ -257,10 +257,12 @@ void WatchMap::onConfigUpdate( const auto removed = per_watch_removed.find(cur_watch); if (removed == per_watch_removed.end()) { // additions only, no removals - cur_watch->callbacks_.onConfigUpdate(resource_to_add, {}, system_version_info); + THROW_IF_NOT_OK( + cur_watch->callbacks_.onConfigUpdate(resource_to_add, {}, system_version_info)); } else { // both additions and removals - cur_watch->callbacks_.onConfigUpdate(resource_to_add, removed->second, system_version_info); + THROW_IF_NOT_OK(cur_watch->callbacks_.onConfigUpdate(resource_to_add, removed->second, + system_version_info)); // Drop the removals now, so the final removals-only pass won't use them. per_watch_removed.erase(removed); } @@ -270,12 +272,13 @@ void WatchMap::onConfigUpdate( if (deferred_removed_during_update_->count(cur_watch) > 0) { continue; } - cur_watch->callbacks_.onConfigUpdate({}, resource_to_remove, system_version_info); + THROW_IF_NOT_OK( + cur_watch->callbacks_.onConfigUpdate({}, resource_to_remove, system_version_info)); } // notify empty update if (added_resources.empty() && removed_resources.empty()) { for (auto& cur_watch : wildcard_watches_) { - cur_watch->callbacks_.onConfigUpdate({}, {}, system_version_info); + THROW_IF_NOT_OK(cur_watch->callbacks_.onConfigUpdate({}, {}, system_version_info)); } } diff --git a/source/extensions/config_subscription/rest/http_subscription_impl.cc b/source/extensions/config_subscription/rest/http_subscription_impl.cc index 4c7a911e708e..2f641d3b9e7b 100644 --- a/source/extensions/config_subscription/rest/http_subscription_impl.cc +++ b/source/extensions/config_subscription/rest/http_subscription_impl.cc @@ -92,7 +92,7 @@ void HttpSubscriptionImpl::parseResponse(const Http::ResponseMessage& response) TRY_ASSERT_MAIN_THREAD { const auto decoded_resources = DecodedResourcesWrapper(*resource_decoder_, message.resources(), message.version_info()); - callbacks_.onConfigUpdate(decoded_resources.refvec_, message.version_info()); + THROW_IF_NOT_OK(callbacks_.onConfigUpdate(decoded_resources.refvec_, message.version_info())); request_.set_version_info(message.version_info()); stats_.update_time_.set(DateUtil::nowToMilliseconds(dispatcher_.timeSource())); stats_.version_.set(HashUtil::xxHash64(request_.version_info())); diff --git a/source/extensions/listener_managers/listener_manager/lds_api.cc b/source/extensions/listener_managers/listener_manager/lds_api.cc index 321ec3d3cc3d..5d556b31f8f7 100644 --- a/source/extensions/listener_managers/listener_manager/lds_api.cc +++ b/source/extensions/listener_managers/listener_manager/lds_api.cc @@ -40,9 +40,10 @@ LdsApiImpl::LdsApiImpl(const envoy::config::core::v3::ConfigSource& lds_config, init_manager.add(init_target_); } -void LdsApiImpl::onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) { +absl::Status +LdsApiImpl::onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) { Config::ScopedResume maybe_resume_rds_sds; if (cm_.adsMux()) { const std::vector paused_xds_types{ @@ -107,10 +108,11 @@ void LdsApiImpl::onConfigUpdate(const std::vector& a if (!message.empty()) { throw EnvoyException(fmt::format("Error adding/updating listener(s) {}", message)); } + return absl::OkStatus(); } -void LdsApiImpl::onConfigUpdate(const std::vector& resources, - const std::string& version_info) { +absl::Status LdsApiImpl::onConfigUpdate(const std::vector& resources, + const std::string& version_info) { // We need to keep track of which listeners need to remove. // Specifically, it's [listeners we currently have] - [listeners found in the response]. absl::node_hash_set listeners_to_remove; @@ -127,7 +129,7 @@ void LdsApiImpl::onConfigUpdate(const std::vector& r for (const auto& listener : listeners_to_remove) { *to_remove_repeated.Add() = listener; } - onConfigUpdate(resources, to_remove_repeated, version_info); + return onConfigUpdate(resources, to_remove_repeated, version_info); } void LdsApiImpl::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, diff --git a/source/extensions/listener_managers/listener_manager/lds_api.h b/source/extensions/listener_managers/listener_manager/lds_api.h index 72d441b80164..473fd8c85345 100644 --- a/source/extensions/listener_managers/listener_manager/lds_api.h +++ b/source/extensions/listener_managers/listener_manager/lds_api.h @@ -36,11 +36,11 @@ class LdsApiImpl : public LdsApi, private: // Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override; - void onConfigUpdate(const std::vector& added_resources, - const Protobuf::RepeatedPtrField& removed_resources, - const std::string& system_version_info) override; + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override; + absl::Status onConfigUpdate(const std::vector& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& system_version_info) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index f20736fb16a8..8af764932030 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -275,7 +275,7 @@ class AdminImpl : public Admin, Rds::ConfigConstSharedPtr config() const override { return config_; } const absl::optional& configInfo() const override { return config_info_; } SystemTime lastUpdated() const override { return time_source_.systemTime(); } - void onConfigUpdate() override {} + absl::Status onConfigUpdate() override { return absl::OkStatus(); } Router::ConfigConstSharedPtr configCast() const override { return config_; } void requestVirtualHostsUpdate(const std::string&, Event::Dispatcher&, std::weak_ptr) override {} diff --git a/test/common/config/config_provider_impl_test.cc b/test/common/config/config_provider_impl_test.cc index 43d2c7b31585..47549b986b76 100644 --- a/test/common/config/config_provider_impl_test.cc +++ b/test/common/config/config_provider_impl_test.cc @@ -80,19 +80,20 @@ class DummyConfigSubscription : public ConfigSubscriptionInstance, } // Envoy::Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override { + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override { const auto& config = dynamic_cast(resources[0].get().resource()); if (checkAndApplyConfigUpdate(config, "dummy_config", version_info)) { config_proto_ = config; } - ConfigSubscriptionCommonBase::onConfigUpdate(); + return ConfigSubscriptionCommonBase::onConfigUpdate(); } // Envoy::Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector&, - const Protobuf::RepeatedPtrField&, const std::string&) override { + absl::Status onConfigUpdate(const std::vector&, + const Protobuf::RepeatedPtrField&, + const std::string&) override { PANIC("not implemented"); } @@ -273,7 +274,7 @@ TEST_F(ConfigProviderImplTest, SharedOwnership) { DummyConfigSubscription& subscription = dynamic_cast(*provider1).subscription(); const auto decoded_resources = TestUtility::decodeResources({dummy_config}, "a"); - subscription.onConfigUpdate(decoded_resources.refvec_, "1"); + ASSERT_TRUE(subscription.onConfigUpdate(decoded_resources.refvec_, "1").ok()); // Check that a newly created provider with the same config source will share // the subscription, config proto and resulting ConfigProvider::Config. @@ -300,9 +301,10 @@ TEST_F(ConfigProviderImplTest, SharedOwnership) { EXPECT_NE(provider1->config().get(), provider3->config().get()); - dynamic_cast(*provider3) - .subscription() - .onConfigUpdate(decoded_resources.refvec_, "provider3"); + ASSERT_TRUE(dynamic_cast(*provider3) + .subscription() + .onConfigUpdate(decoded_resources.refvec_, "provider3") + .ok()); EXPECT_EQ(2UL, static_cast( provider_manager_->dumpConfigs().get()) @@ -370,13 +372,13 @@ TEST_F(ConfigProviderImplTest, DuplicateConfigProto) { // First time issuing a configUpdate(). A new ConfigProvider::Config should be created. const auto dummy_config = parseDummyConfigFromYaml("a: a dynamic dummy config"); const auto decoded_resources = TestUtility::decodeResources({dummy_config}, "a"); - subscription.onConfigUpdate(decoded_resources.refvec_, "1"); + ASSERT_TRUE(subscription.onConfigUpdate(decoded_resources.refvec_, "1").ok()); EXPECT_NE(subscription.getConfig(), nullptr); auto config_ptr = subscription.getConfig(); EXPECT_EQ(typed_provider->config().get(), config_ptr.get()); // Second time issuing the configUpdate(), this time with a duplicate proto. A new // ConfigProvider::Config _should not_ be created. - subscription.onConfigUpdate(decoded_resources.refvec_, "2"); + ASSERT_TRUE(subscription.onConfigUpdate(decoded_resources.refvec_, "2").ok()); EXPECT_EQ(config_ptr, subscription.getConfig()); EXPECT_EQ(typed_provider->config().get(), config_ptr.get()); } @@ -461,7 +463,7 @@ TEST_F(ConfigProviderImplTest, ConfigDump) { DummyConfigSubscription& subscription = dynamic_cast(*dynamic_provider).subscription(); const auto decoded_resources = TestUtility::decodeResources({dummy_config}, "a"); - subscription.onConfigUpdate(decoded_resources.refvec_, "v1"); + ASSERT_TRUE(subscription.onConfigUpdate(decoded_resources.refvec_, "v1").ok()); message_ptr = server_factory_context_.admin_.config_tracker_.config_tracker_callbacks_["dummy"]( Matchers::UniversalStringMatcher()); @@ -535,10 +537,10 @@ class DeltaDummyConfigSubscription : public DeltaConfigSubscriptionInstance, void start() override {} // Envoy::Config::SubscriptionCallbacks - void onConfigUpdate(const std::vector& resources, - const std::string& version_info) override { + absl::Status onConfigUpdate(const std::vector& resources, + const std::string& version_info) override { if (resources.empty()) { - return; + return absl::OkStatus(); } // For simplicity, there is no logic here to track updates and/or removals to the existing @@ -559,11 +561,16 @@ class DeltaDummyConfigSubscription : public DeltaConfigSubscriptionInstance, }); } - ConfigSubscriptionCommonBase::onConfigUpdate(); + absl::Status status = ConfigSubscriptionCommonBase::onConfigUpdate(); + if (!status.ok()) { + return status; + } setLastConfigInfo(absl::optional({absl::nullopt, version_info})); + return absl::OkStatus(); } - void onConfigUpdate(const std::vector&, - const Protobuf::RepeatedPtrField&, const std::string&) override { + absl::Status onConfigUpdate(const std::vector&, + const Protobuf::RepeatedPtrField&, + const std::string&) override { PANIC("not implemented"); } void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason, @@ -705,7 +712,7 @@ TEST_F(DeltaConfigProviderImplTest, MultipleDeltaSubscriptions) { DeltaDummyConfigSubscription& subscription = dynamic_cast(*provider1).subscription(); - subscription.onConfigUpdate(decoded_resources.refvec_, "1"); + ASSERT_TRUE(subscription.onConfigUpdate(decoded_resources.refvec_, "1").ok()); ConfigProviderPtr provider2 = provider_manager_->createXdsConfigProvider( config_source_proto, server_factory_context_, init_manager_, "dummy_prefix", @@ -726,7 +733,7 @@ TEST_F(DeltaConfigProviderImplTest, MultipleDeltaSubscriptions) { // Issue a second config update to validate that having multiple providers bound to the // subscription causes a single update to the underlying shared config implementation. - subscription.onConfigUpdate(decoded_resources.refvec_, "2"); + ASSERT_TRUE(subscription.onConfigUpdate(decoded_resources.refvec_, "2").ok()); // NOTE: the config implementation is append only and _does not_ track updates/removals to the // config proto set, so the expectation is to double the size of the set. EXPECT_EQ(provider1->config().get(), diff --git a/test/common/config/subscription_test_harness.h b/test/common/config/subscription_test_harness.h index 87db8d899cdd..3f043377f460 100644 --- a/test/common/config/subscription_test_harness.h +++ b/test/common/config/subscription_test_harness.h @@ -127,6 +127,7 @@ ACTION_P(ThrowOnRejectedConfig, accept) { if (!accept) { throw EnvoyException("bad config"); } + return absl::OkStatus(); } } // namespace Config diff --git a/test/common/filter/config_discovery_impl_test.cc b/test/common/filter/config_discovery_impl_test.cc index e930fdf71059..059e1e1dde75 100644 --- a/test/common/filter/config_discovery_impl_test.cc +++ b/test/common/filter/config_discovery_impl_test.cc @@ -226,7 +226,8 @@ class FilterConfigDiscoveryImplTest : public FilterConfigDiscoveryTestBase { TestUtility::decodeResources(response); EXPECT_CALL(init_watcher_, ready()); - callbacks_->onConfigUpdate(decoded_resources.refvec_, response.version_info()); + ASSERT_TRUE( + callbacks_->onConfigUpdate(decoded_resources.refvec_, response.version_info()).ok()); EXPECT_NE(absl::nullopt, provider_->config()); EXPECT_EQ(1UL, store_.counter(getConfigReloadCounter()).value()); EXPECT_EQ(0UL, store_.counter(getConfigFailCounter()).value()); @@ -234,7 +235,7 @@ class FilterConfigDiscoveryImplTest : public FilterConfigDiscoveryTestBase { // Ensure that we honor resource removals. Protobuf::RepeatedPtrField remove; *remove.Add() = "foo"; - callbacks_->onConfigUpdate({}, remove, "1"); + ASSERT_TRUE(callbacks_->onConfigUpdate({}, remove, "1").ok()); EXPECT_EQ(2UL, store_.counter(getConfigReloadCounter()).value()); EXPECT_EQ(0UL, store_.counter(getConfigFailCounter()).value()); } @@ -397,8 +398,9 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, Basic) { .WillOnce(Invoke([&config_discovery_test]() { EXPECT_TRUE(config_discovery_test.filter_factory_.created_); })); - config_discovery_test.callbacks_->onConfigUpdate(decoded_resources.refvec_, - response.version_info()); + ASSERT_TRUE(config_discovery_test.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .ok()); EXPECT_NE(absl::nullopt, config_discovery_test.provider_->config()); EXPECT_EQ(1UL, config_discovery_test.store_.counter(config_discovery_test.getConfigReloadCounter()) @@ -413,8 +415,9 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, Basic) { const auto response = config_discovery_test.createResponse("2", "foo"); const auto decoded_resources = TestUtility::decodeResources(response); - config_discovery_test.callbacks_->onConfigUpdate(decoded_resources.refvec_, - response.version_info()); + ASSERT_TRUE(config_discovery_test.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .ok()); EXPECT_EQ(1UL, config_discovery_test.store_.counter(config_discovery_test.getConfigReloadCounter()) @@ -453,10 +456,10 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, TooManyResources) { const auto decoded_resources = TestUtility::decodeResources(response); EXPECT_CALL(config_discovery_test.init_watcher_, ready()); - EXPECT_THROW_WITH_MESSAGE(config_discovery_test.callbacks_->onConfigUpdate( - decoded_resources.refvec_, response.version_info()), - EnvoyException, - "Unexpected number of resources in ExtensionConfigDS response: 2"); + EXPECT_EQ(config_discovery_test.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .message(), + "Unexpected number of resources in ExtensionConfigDS response: 2"); EXPECT_EQ( 0UL, config_discovery_test.store_.counter(config_discovery_test.getConfigReloadCounter()).value()); @@ -470,10 +473,10 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, WrongName) { const auto decoded_resources = TestUtility::decodeResources(response); EXPECT_CALL(config_discovery_test.init_watcher_, ready()); - EXPECT_THROW_WITH_MESSAGE(config_discovery_test.callbacks_->onConfigUpdate( - decoded_resources.refvec_, response.version_info()), - EnvoyException, - "Unexpected resource name in ExtensionConfigDS response: bar"); + EXPECT_EQ(config_discovery_test.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .message(), + "Unexpected resource name in ExtensionConfigDS response: bar"); EXPECT_EQ( 0UL, config_discovery_test.store_.counter(config_discovery_test.getConfigReloadCounter()).value()); @@ -526,8 +529,9 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, DualProviders) { const auto decoded_resources = TestUtility::decodeResources(response); EXPECT_CALL(config_discovery_test.init_watcher_, ready()); - config_discovery_test.callbacks_->onConfigUpdate(decoded_resources.refvec_, - response.version_info()); + ASSERT_TRUE(config_discovery_test.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .ok()); EXPECT_NE(absl::nullopt, config_discovery_test.provider_->config()); EXPECT_NE(absl::nullopt, provider2->config()); EXPECT_EQ( @@ -554,13 +558,12 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, DualProvidersInvalid) { const auto decoded_resources = TestUtility::decodeResources(response); EXPECT_CALL(config_discovery_test.init_watcher_, ready()); - EXPECT_THROW_WITH_MESSAGE( - config_discovery_test.callbacks_->onConfigUpdate(decoded_resources.refvec_, - response.version_info()), - EnvoyException, - "Error: filter config has type URL test.integration.filters.AddBodyFilterConfig but " - "expect " + - config_discovery_test.getTypeUrl() + "."); + ASSERT_EQ(config_discovery_test.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .message(), + "Error: filter config has type URL test.integration.filters.AddBodyFilterConfig but " + "expect " + + config_discovery_test.getTypeUrl() + "."); EXPECT_EQ( 0UL, config_discovery_test.store_.counter(config_discovery_test.getConfigReloadCounter()).value()); @@ -610,14 +613,16 @@ TYPED_TEST(FilterConfigDiscoveryImplTestParameter, TerminalFilterInvalid) { if (config_discovery_test.getFilterType() == "listener" || config_discovery_test.getFilterType() == "upstream_network") { - EXPECT_NO_THROW(config_discovery_test.callbacks_->onConfigUpdate(decoded_resources.refvec_, - response.version_info())); + ASSERT_TRUE(config_discovery_test.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .ok()); return; } EXPECT_THROW_WITH_MESSAGE( - config_discovery_test.callbacks_->onConfigUpdate(decoded_resources.refvec_, - response.version_info()), + EXPECT_TRUE(config_discovery_test.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .ok()), EnvoyException, "Error: terminal filter named foo of type envoy.test.filter must be the last filter " "in a " + diff --git a/test/common/rds/rds_test.cc b/test/common/rds/rds_test.cc index beb835fd78fd..eb2b50b9722d 100644 --- a/test/common/rds/rds_test.cc +++ b/test/common/rds/rds_test.cc @@ -175,8 +175,9 @@ virtual_hosts: null auto response = TestUtility::parseYaml(response_json); const auto decoded_resources = TestUtility::decodeResources(response); - server_factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, response.version_info()); + EXPECT_TRUE(server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .ok()); } NiceMock outer_init_manager_; diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index be0e83df6328..549700fd2615 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -6667,6 +6667,9 @@ TEST(NullConfigImplTest, All) { EXPECT_EQ(nullptr, config.route(headers, stream_info, 0)); EXPECT_EQ(0UL, config.internalOnlyHeaders().size()); EXPECT_EQ("", config.name()); + EXPECT_FALSE(config.usesVhds()); + EXPECT_FALSE(config.mostSpecificHeaderMutationsWins()); + EXPECT_EQ(0Ul, config.maxDirectResponseBodySizeBytes()); } class BadHttpRouteConfigurationsTest : public testing::Test, public ConfigImplTestBase {}; diff --git a/test/common/router/rds_impl_test.cc b/test/common/router/rds_impl_test.cc index 981094f1b32d..3a7304dfd867 100644 --- a/test/common/router/rds_impl_test.cc +++ b/test/common/router/rds_impl_test.cc @@ -276,11 +276,13 @@ TEST_F(RdsImplTest, Basic) { TestUtility::decodeResources(response1); EXPECT_CALL(init_watcher_, ready()); - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); EXPECT_EQ(nullptr, route(Http::TestRequestHeaderMapImpl{{":authority", "foo"}})); // 2nd request with same response. Based on hash should not reload config. - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); EXPECT_EQ(nullptr, route(Http::TestRequestHeaderMapImpl{{":authority", "foo"}})); // Load the config and verified shared count. @@ -326,7 +328,8 @@ TEST_F(RdsImplTest, Basic) { // Make sure we don't lookup/verify clusters. EXPECT_CALL(server_factory_context_.cluster_manager_, getThreadLocalCluster(Eq("bar"))).Times(0); - rds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()); + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()).ok()); EXPECT_EQ("foo", route(Http::TestRequestHeaderMapImpl{{":authority", "foo"}, {":path", "/foo"}}) ->routeEntry() ->clusterName()); @@ -385,7 +388,8 @@ TEST_F(RdsImplTest, UnknownFacotryForPerVirtualHostTypedConfig) { EXPECT_CALL(init_watcher_, ready()); EXPECT_THROW_WITH_MESSAGE( - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()), + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()), EnvoyException, "Didn't find a registered implementation for 'filter.unknown' with type URL: " "'google.protobuf.Struct'"); @@ -457,7 +461,8 @@ stat_prefix: foo TestUtility::decodeResources(response1); EXPECT_CALL(init_watcher_, ready()); - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); } // Validate the optional unknown factory will be ignored for per virtualhost typed config. @@ -525,7 +530,8 @@ stat_prefix: foo TestUtility::decodeResources(response1); EXPECT_CALL(init_watcher_, ready()); - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); } // validate there will be exception throw when unknown factory found for per route typed config. @@ -575,7 +581,8 @@ TEST_F(RdsImplTest, UnknownFacotryForPerRouteTypedConfig) { EXPECT_CALL(init_watcher_, ready()); EXPECT_THROW_WITH_MESSAGE( - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()), + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()), EnvoyException, "Didn't find a registered implementation for 'filter.unknown' with type URL: " "'google.protobuf.Struct'"); @@ -647,7 +654,8 @@ stat_prefix: foo TestUtility::decodeResources(response1); EXPECT_CALL(init_watcher_, ready()); - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); } // Validates behavior when the config is delivered but it fails PGV validation. @@ -675,8 +683,8 @@ TEST_F(RdsImplTest, FailureInvalidConfig) { TestUtility::parseYaml(valid_json); const auto decoded_resources = TestUtility::decodeResources(response1); - EXPECT_NO_THROW( - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info())); + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); // Sadly the RdsRouteConfigSubscription privately inherited from // SubscriptionCallbacks, so we has to use reinterpret_cast here. RdsRouteConfigSubscription* rds_subscription = @@ -703,7 +711,9 @@ TEST_F(RdsImplTest, FailureInvalidConfig) { TestUtility::decodeResources(response2); EXPECT_THROW_WITH_MESSAGE( - rds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()), + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()) + .ok()), EnvoyException, "Unexpected RDS configuration (expecting foo_route_config): " "INVALID_NAME_FOR_route_config"); @@ -765,7 +775,8 @@ TEST_F(RdsImplTest, VHDSandRDSupdateTogether) { TestUtility::decodeResources(response1); EXPECT_CALL(init_watcher_, ready()); - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); EXPECT_TRUE(rds_->configCast()->usesVhds()); EXPECT_EQ("foo", route(Http::TestRequestHeaderMapImpl{{":authority", "foo"}, {":path", "/foo"}}) @@ -1001,7 +1012,8 @@ name: foo TestUtility::decodeResources(response1); EXPECT_CALL(init_watcher_, ready()); - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); message_ptr = server_factory_context_.admin_.config_tracker_.config_tracker_callbacks_["routes"]( universal_name_matcher); const auto& route_config_dump3 = @@ -1098,8 +1110,9 @@ name: foo_route_config )EOF"); const auto decoded_resources = TestUtility::decodeResources({route_config}); - server_factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, "1"); + EXPECT_TRUE(server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, "1") + .ok()); RouteConfigProviderSharedPtr provider2 = route_config_provider_manager_->createRdsRouteConfigProvider( @@ -1122,8 +1135,9 @@ name: foo_route_config route_config_provider_manager_->createRdsRouteConfigProvider( rds2, OptionalHttpFilters(), server_factory_context_, "foo_prefix", outer_init_manager_); EXPECT_NE(provider3, provider_); - server_factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, "provider3"); + EXPECT_TRUE(server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, "provider3") + .ok()); UniversalStringMatcher universal_name_matcher; EXPECT_EQ(2UL, route_config_provider_manager_->dumpRouteConfigs(universal_name_matcher) ->dynamic_route_configs() @@ -1183,8 +1197,9 @@ name: foo_route_config )EOF"); const auto decoded_resources = TestUtility::decodeResources({route_config}); - server_factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, "1"); + EXPECT_TRUE(server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, "1") + .ok()); EXPECT_TRUE(provider_->configInfo().has_value()); EXPECT_TRUE(provider2->configInfo().has_value()); @@ -1198,7 +1213,9 @@ TEST_F(RouteConfigProviderManagerImplTest, OnConfigUpdateEmpty) { start(_)); outer_init_manager_.initialize(init_watcher_); EXPECT_CALL(init_watcher_, ready()); - server_factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate({}, ""); + EXPECT_TRUE(server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate({}, "") + .ok()); } TEST_F(RouteConfigProviderManagerImplTest, OnConfigUpdateWrongSize) { @@ -1210,8 +1227,9 @@ TEST_F(RouteConfigProviderManagerImplTest, OnConfigUpdateWrongSize) { const auto decoded_resources = TestUtility::decodeResources({route_config, route_config}); EXPECT_CALL(init_watcher_, ready()); EXPECT_THROW_WITH_MESSAGE( - server_factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, ""), + EXPECT_TRUE(server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, "") + .ok()), EnvoyException, "Unexpected RDS resource length: 2"); } @@ -1272,7 +1290,8 @@ version_info: '1' EXPECT_CALL(init_watcher_, ready()); EXPECT_THROW_WITH_MESSAGE( - rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()), + EXPECT_TRUE( + rds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()), EnvoyException, "Only a single wildcard domain is permitted in route foo_route_config"); message_ptr = server_factory_context_.admin_.config_tracker_.config_tracker_callbacks_["routes"]( diff --git a/test/common/router/scoped_rds_test.cc b/test/common/router/scoped_rds_test.cc index f392bdd26d60..21bda9810dc8 100644 --- a/test/common/router/scoped_rds_test.cc +++ b/test/common/router/scoped_rds_test.cc @@ -413,7 +413,7 @@ name: foo_scoped_routes resources.push_back(parseScopedRouteConfigurationFromYaml(config_yaml)); } const auto decoded_resources = TestUtility::decodeResources(resources); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, version)); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, version).ok()); } // Helper function which pushes an update to given RDS subscription, the start(_) of the @@ -441,7 +441,8 @@ name: foo_scoped_routes if (rds_subscription_by_name_.find(name) == rds_subscription_by_name_.end()) { continue; } - rds_subscription_by_name_[name]->onConfigUpdate(decoded_resources.refvec_, version); + EXPECT_TRUE( + rds_subscription_by_name_[name]->onConfigUpdate(decoded_resources.refvec_, version).ok()); } } @@ -487,8 +488,8 @@ name: foo_scope TestUtility::decodeResources({resource}); context_init_manager_.initialize(init_watcher_); - EXPECT_THROW_WITH_MESSAGE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1"), - EnvoyException, "route_configuration_name is empty."); + EXPECT_EQ(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").message(), + "route_configuration_name is empty."); } // Test an exception will be throw when unknown factory in the per-virtualhost typed config. @@ -507,7 +508,7 @@ route_configuration_name: foo_routes const auto decoded_resources = TestUtility::decodeResources({resource}); context_init_manager_.initialize(init_watcher_); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").ok()); constexpr absl::string_view route_config_tmpl = R"EOF( name: {} @@ -551,7 +552,7 @@ route_configuration_name: foo_routes // Delta API. const auto decoded_resources = TestUtility::decodeResources({resource, resource_2}); context_init_manager_.initialize(init_watcher_); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, {}, "v1")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, {}, "v1").ok()); EXPECT_EQ(1UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") .value()); @@ -562,7 +563,7 @@ route_configuration_name: foo_routes ASSERT_NE(srds_delta_subscription, nullptr); ASSERT_EQ("v1", srds_delta_subscription->configInfo()->last_config_version_); // Push again the same set of config with different version number, the config will be skipped. - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, {}, "123")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, {}, "123").ok()); ASSERT_EQ("v1", srds_delta_subscription->configInfo()->last_config_version_); EXPECT_EQ(2UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") @@ -592,7 +593,7 @@ route_configuration_name: foo_routes const auto decoded_resources = TestUtility::decodeResources({resource}); context_init_manager_.initialize(init_watcher_); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").ok()); constexpr absl::string_view route_config_tmpl = R"EOF( name: {} @@ -633,7 +634,7 @@ route_configuration_name: foo_routes init_watcher_.expectReady(); // Only the SRDS parent_init_target_. context_init_manager_.initialize(init_watcher_); const auto decoded_resources = TestUtility::decodeResources({resource, resource_2}); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").ok()); EXPECT_EQ(1UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") .value()); @@ -673,7 +674,7 @@ route_configuration_name: foo_routes // Delete foo_scope2. const auto decoded_resources_2 = TestUtility::decodeResources({resource}); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, "3")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, "3").ok()); EXPECT_EQ(1UL, all_scopes_.value()); EXPECT_EQ(getScopedRouteMap().count("foo_scope"), 1); EXPECT_EQ(2UL, @@ -719,7 +720,7 @@ route_configuration_name: foo_routes // Delta API. const auto decoded_resources = TestUtility::decodeResources({resource, resource_2}); context_init_manager_.initialize(init_watcher_); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, {}, "1")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, {}, "1").ok()); EXPECT_EQ(1UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") .value()); @@ -760,7 +761,7 @@ route_configuration_name: foo_routes Protobuf::RepeatedPtrField deletes; *deletes.Add() = "foo_scope2"; const auto decoded_resources_2 = TestUtility::decodeResources({resource}); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, deletes, "2")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, deletes, "2").ok()); EXPECT_EQ(1UL, all_scopes_.value()); EXPECT_EQ(getScopedRouteMap().count("foo_scope"), 1); EXPECT_EQ(2UL, @@ -802,9 +803,10 @@ route_configuration_name: foo_routes init_watcher_.expectReady().Times(0); // The onConfigUpdate will simply throw an exception. context_init_manager_.initialize(init_watcher_); const auto decoded_resources = TestUtility::decodeResources({resource, resource_2}); - EXPECT_THROW_WITH_REGEX( - srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1"), EnvoyException, - ".*scope key conflict found, first scope is 'foo_scope', second scope is 'foo_scope2'"); + EXPECT_THAT( + srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").message(), + testing::MatchesRegex( + ".*scope key conflict found, first scope is 'foo_scope', second scope is 'foo_scope2'")); EXPECT_EQ( // Fully rejected. 0UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") @@ -844,9 +846,10 @@ route_configuration_name: foo_routes context_init_manager_.initialize(init_watcher_); const auto decoded_resources = TestUtility::decodeResources({resource, resource_2}); - EXPECT_THROW_WITH_REGEX( - srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1"), EnvoyException, - ".*scope key conflict found, first scope is 'foo_scope', second scope is 'foo_scope2'"); + EXPECT_THAT( + srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").message(), + testing::MatchesRegex( + ".*scope key conflict found, first scope is 'foo_scope', second scope is 'foo_scope2'")); EXPECT_EQ( // Fully rejected. 0UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") @@ -885,7 +888,7 @@ route_configuration_name: bar_routes const auto decoded_resources = TestUtility::decodeResources({resource, resource_2}); init_watcher_.expectReady(); context_init_manager_.initialize(init_watcher_); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").ok()); EXPECT_EQ(1UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") .value()); @@ -922,7 +925,7 @@ route_configuration_name: foo_routes // Remove foo_scope1 and add a new scope3 reuses the same scope_key. const auto resource_3 = parseScopedRouteConfigurationFromYaml(config_yaml3); const auto decoded_resources_2 = TestUtility::decodeResources({resource_2, resource_3}); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, "2")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, "2").ok()); EXPECT_EQ(2UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") .value()); @@ -951,9 +954,10 @@ route_configuration_name: foo_routes const auto resource_4 = parseScopedRouteConfigurationFromYaml(config_yaml4); const auto decoded_resources_3 = TestUtility::decodeResources({resource_2, resource_3, resource_4}); - EXPECT_THROW_WITH_REGEX( - srds_subscription_->onConfigUpdate(decoded_resources_3.refvec_, "3"), EnvoyException, - "scope key conflict found, first scope is 'foo_scope2', second scope is 'foo_scope4'"); + EXPECT_THAT( + srds_subscription_->onConfigUpdate(decoded_resources_3.refvec_, "3").message(), + testing::MatchesRegex( + ".*scope key conflict found, first scope is 'foo_scope2', second scope is 'foo_scope4'")); EXPECT_EQ(2UL, all_scopes_.value()); EXPECT_EQ(getScopedRouteMap().count("foo_scope1"), 0); EXPECT_EQ(getScopedRouteMap().count("foo_scope2"), 1); @@ -967,7 +971,7 @@ route_configuration_name: foo_routes // Delete foo_scope2, and push a new foo_scope4 with the same scope key but different route-table. const auto decoded_resources_4 = TestUtility::decodeResources({resource_3, resource_4}); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources_4.refvec_, "4")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources_4.refvec_, "4").ok()); EXPECT_EQ(server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") .value(), 3UL); @@ -1004,10 +1008,9 @@ route_configuration_name: foo_routes )EOF"; const auto resource = parseScopedRouteConfigurationFromYaml(config_yaml); const auto decoded_resources = TestUtility::decodeResources({resource, resource}); - EXPECT_THROW_WITH_MESSAGE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1"), - EnvoyException, - "Error adding/updating scoped route(s): duplicate scoped route " - "configuration 'foo_scope' found"); + EXPECT_EQ(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").message(), + "Error adding/updating scoped route(s): duplicate scoped route " + "configuration 'foo_scope' found"); } // Tests duplicate resources in the same update, should be fully rejected. @@ -1025,8 +1028,8 @@ route_configuration_name: foo_routes )EOF"; const auto resource = parseScopedRouteConfigurationFromYaml(config_yaml); const auto decoded_resources = TestUtility::decodeResources({resource, resource}); - EXPECT_THROW_WITH_MESSAGE( - srds_subscription_->onConfigUpdate(decoded_resources.refvec_, {}, "1"), EnvoyException, + EXPECT_EQ( + srds_subscription_->onConfigUpdate(decoded_resources.refvec_, {}, "1").message(), "Error adding/updating scoped route(s): duplicate scoped route configuration 'foo_scope' " "found"); EXPECT_EQ( @@ -1109,7 +1112,7 @@ route_configuration_name: dynamic-foo-route-config )EOF"); timeSystem().setSystemTime(std::chrono::milliseconds(1234567891567)); const auto decoded_resources = TestUtility::decodeResources({resource}); - srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1"); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").ok()); TestUtility::loadFromYaml(R"EOF( inline_scoped_route_configs: @@ -1180,7 +1183,7 @@ route_configuration_name: dynamic-foo-route-config *message_ptr); EXPECT_THAT(expected_config_dump, ProtoEq(scoped_routes_config_dump5)); - srds_subscription_->onConfigUpdate({}, "2"); + EXPECT_TRUE(srds_subscription_->onConfigUpdate({}, "2").ok()); TestUtility::loadFromYaml(R"EOF( inline_scoped_route_configs: dynamic_scoped_route_configs: @@ -1293,7 +1296,7 @@ route_configuration_name: foo_routes // Delta API. const auto decoded_resources = TestUtility::decodeResources({resource, resource_2}); context_init_manager_.initialize(init_watcher_); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, {}, "1")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, {}, "1").ok()); EXPECT_EQ(1UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") .value()); @@ -1316,7 +1319,7 @@ route_configuration_name: foo_routes )EOF"; const auto resource_4 = parseScopedRouteConfigurationFromYaml(config_yaml2); const auto decoded_resources_2 = TestUtility::decodeResources({resource_3, resource_4}); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, {}, "2")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, {}, "2").ok()); EXPECT_EQ(2UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") .value()); @@ -1346,7 +1349,7 @@ route_configuration_name: foo_routes // Delta API. const auto decoded_resources = TestUtility::decodeResources({resource, resource_2}); context_init_manager_.initialize(init_watcher_); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").ok()); EXPECT_EQ(1UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") .value()); @@ -1369,7 +1372,7 @@ route_configuration_name: foo_routes )EOF"; const auto resource_4 = parseScopedRouteConfigurationFromYaml(config_yaml2); const auto decoded_resources_2 = TestUtility::decodeResources({resource_3, resource_4}); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, "2")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources_2.refvec_, "2").ok()); EXPECT_EQ(2UL, server_factory_context_.store_.counter("foo.scoped_rds.foo_scoped_routes.config_reload") .value()); @@ -1740,7 +1743,7 @@ TEST_F(ScopedRdsTest, DanglingSubscriptionOnDemandUpdate) { // Destroy the scoped_rds subscription by destroying its only config provider. provider_.reset(); EXPECT_CALL(event_dispatcher_, post(_)); - EXPECT_NO_THROW(temp_post_cb()); + temp_post_cb(); } // Delete the on demand scope before on demand update in main thread. @@ -1778,7 +1781,7 @@ on_demand: true ScopeKeyPtr scope_key = scope_key_builder_->computeScopeKey( TestRequestHeaderMapImpl{{"Addr", "x-foo-key;x-foo-key"}}); // Delete the scope route. - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate({}, "2")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate({}, "2").ok()); EXPECT_EQ(0UL, all_scopes_.value()); EXPECT_CALL(event_dispatcher_, post(_)); // Scope no longer exists after srds update. @@ -1826,7 +1829,7 @@ route_configuration_name: foo_routes init_watcher_.expectReady(); // Only the SRDS parent_init_target_. context_init_manager_.initialize(init_watcher_); const auto decoded_resources = TestUtility::decodeResources({resource}); - EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1")); + EXPECT_TRUE(srds_subscription_->onConfigUpdate(decoded_resources.refvec_, "1").ok()); pushRdsConfig({"foo_routes"}, "111"); diff --git a/test/common/router/vhds_test.cc b/test/common/router/vhds_test.cc index 0c6a843e7e19..4b72efce6331 100644 --- a/test/common/router/vhds_test.cc +++ b/test/common/router/vhds_test.cc @@ -136,8 +136,9 @@ TEST_F(VhdsTest, VhdsAddsVirtualHosts) { const auto decoded_resources = TestUtility::decodeResources(added_resources); const Protobuf::RepeatedPtrField removed_resources; - factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, removed_resources, "1"); + EXPECT_TRUE(factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, removed_resources, "1") + .ok()); EXPECT_EQ(1UL, config_update_info->protobufConfigurationCast().virtual_hosts_size()); EXPECT_TRUE(messageDifferencer_.Equals( @@ -196,8 +197,9 @@ name: my_route const auto decoded_resources = TestUtility::decodeResources(added_resources); const Protobuf::RepeatedPtrField removed_resources; - factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, removed_resources, "1"); + EXPECT_TRUE(factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, removed_resources, "1") + .ok()); EXPECT_EQ(2UL, config_update_info->protobufConfigurationCast().virtual_hosts_size()); config_update_info->onRdsUpdate(updated_route_config, "2"); diff --git a/test/common/runtime/runtime_impl_test.cc b/test/common/runtime/runtime_impl_test.cc index 98084cb871c7..f040aedfbcf8 100644 --- a/test/common/runtime/runtime_impl_test.cc +++ b/test/common/runtime/runtime_impl_test.cc @@ -960,19 +960,19 @@ class RtdsLoaderImplTest : public LoaderImplTest { void doOnConfigUpdateVerifyNoThrow(const envoy::service::runtime::v3::Runtime& runtime, uint32_t callback_index = 0) { const auto decoded_resources = TestUtility::decodeResources({runtime}); - VERBOSE_EXPECT_NO_THROW( - rtds_callbacks_[callback_index]->onConfigUpdate(decoded_resources.refvec_, "")); + EXPECT_TRUE( + rtds_callbacks_[callback_index]->onConfigUpdate(decoded_resources.refvec_, "").ok()); } void doDeltaOnConfigUpdateVerifyNoThrow(const envoy::service::runtime::v3::Runtime& runtime) { const auto decoded_resources = TestUtility::decodeResources({runtime}); - VERBOSE_EXPECT_NO_THROW(rtds_callbacks_[0]->onConfigUpdate(decoded_resources.refvec_, {}, "")); + EXPECT_TRUE(rtds_callbacks_[0]->onConfigUpdate(decoded_resources.refvec_, {}, "").ok()); } void doDeltaOnConfigRemovalVerifyNoThrow(const std::string& resource_name) { Protobuf::RepeatedPtrField removed_resources; *removed_resources.Add() = resource_name; - VERBOSE_EXPECT_NO_THROW(rtds_callbacks_[0]->onConfigUpdate({}, removed_resources, "")); + EXPECT_TRUE(rtds_callbacks_[0]->onConfigUpdate({}, removed_resources, "").ok()); } std::vector layers_{"some_resource"}; @@ -986,9 +986,9 @@ TEST_F(RtdsLoaderImplTest, UnexpectedSizeEmpty) { setup(); EXPECT_CALL(rtds_init_callback_, Call()); - EXPECT_THROW_WITH_MESSAGE(rtds_callbacks_[0]->onConfigUpdate({}, ""), EnvoyException, - "Unexpected RTDS resource length, number of added recources 0, number " - "of removed recources 0"); + EXPECT_EQ(rtds_callbacks_[0]->onConfigUpdate({}, "").message(), + "Unexpected RTDS resource length, number of added recources 0, number " + "of removed recources 0"); EXPECT_EQ(0, store_.counter("runtime.load_error").value()); EXPECT_EQ(1, store_.counter("runtime.load_success").value()); @@ -1004,10 +1004,9 @@ TEST_F(RtdsLoaderImplTest, UnexpectedSizeTooMany) { const auto decoded_resources = TestUtility::decodeResources({runtime, runtime}); EXPECT_CALL(rtds_init_callback_, Call()); - EXPECT_THROW_WITH_MESSAGE(rtds_callbacks_[0]->onConfigUpdate(decoded_resources.refvec_, ""), - EnvoyException, - "Unexpected RTDS resource length, number of added recources 2, number " - "of removed recources 0"); + EXPECT_EQ(rtds_callbacks_[0]->onConfigUpdate(decoded_resources.refvec_, "").message(), + "Unexpected RTDS resource length, number of added recources 2, number " + "of removed recources 0"); EXPECT_EQ(0, store_.counter("runtime.load_error").value()); EXPECT_EQ(1, store_.counter("runtime.load_success").value()); @@ -1041,9 +1040,8 @@ TEST_F(RtdsLoaderImplTest, WrongResourceName) { baz: meh )EOF"); const auto decoded_resources = TestUtility::decodeResources({runtime}); - EXPECT_THROW_WITH_MESSAGE(rtds_callbacks_[0]->onConfigUpdate(decoded_resources.refvec_, ""), - EnvoyException, - "Unexpected RTDS runtime (expecting some_resource): other_resource"); + EXPECT_EQ(rtds_callbacks_[0]->onConfigUpdate(decoded_resources.refvec_, "").message(), + "Unexpected RTDS runtime (expecting some_resource): other_resource"); EXPECT_EQ("whatevs", loader_->snapshot().get("foo").value().get()); EXPECT_EQ("yar", loader_->snapshot().get("bar").value().get()); @@ -1192,10 +1190,9 @@ TEST_F(RtdsLoaderImplTest, DeltaOnConfigUpdateWithRemovalFailure) { Protobuf::RepeatedPtrField removed_resources; *removed_resources.Add() = "some_wrong_resource_name"; - EXPECT_THROW_WITH_MESSAGE(rtds_callbacks_[0]->onConfigUpdate({}, removed_resources, ""), - EnvoyException, - "Unexpected removal of unknown RTDS runtime layer " - "some_wrong_resource_name, expected some_resource"); + EXPECT_EQ(rtds_callbacks_[0]->onConfigUpdate({}, removed_resources, "").message(), + "Unexpected removal of unknown RTDS runtime layer " + "some_wrong_resource_name, expected some_resource"); // Removal failed, the keys point to the same value before the removal call. EXPECT_EQ("bar", loader_->snapshot().get("foo").value().get()); diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc index e0fb476c3155..9e92060f114e 100644 --- a/test/common/secret/sds_api_test.cc +++ b/test/common/secret/sds_api_test.cc @@ -176,7 +176,7 @@ TEST_F(SdsApiTest, DynamicTlsCertificateUpdateSuccess) { const auto decoded_resources = TestUtility::decodeResources({typed_secret}); EXPECT_CALL(secret_callback, onAddOrUpdateSecret()); - subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + EXPECT_TRUE(subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); testing::NiceMock ctx; Ssl::TlsCertificateConfigImpl tls_config(*sds_api.secret(), ctx, *api_); @@ -265,7 +265,8 @@ class TlsCertificateSdsRotationApiTest : public testing::TestWithParam, watch_cbs_.push_back(cb); })); } - subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + EXPECT_TRUE( + subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); } const bool watched_directory_; @@ -323,7 +324,8 @@ class CertificateValidationContextSdsRotationApiTest : public testing::TestWithP .WillOnce(Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { watch_cbs_.push_back(cb); })); - subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + EXPECT_TRUE( + subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); } std::unique_ptr sds_api_; @@ -513,12 +515,12 @@ class PartialMockSds : public SdsApi { init_manager.add(init_target_); } - MOCK_METHOD(void, onConfigUpdate, + MOCK_METHOD(absl::Status, onConfigUpdate, (const std::vector&, const std::string&)); - void onConfigUpdate(const std::vector& added, - const Protobuf::RepeatedPtrField& removed, - const std::string& version) override { - SdsApi::onConfigUpdate(added, removed, version); + absl::Status onConfigUpdate(const std::vector& added, + const Protobuf::RepeatedPtrField& removed, + const std::string& version) override { + return SdsApi::onConfigUpdate(added, removed, version); } void setSecret(const envoy::extensions::transport_sockets::tls::v3::Secret&) override {} void validateConfig(const envoy::extensions::transport_sockets::tls::v3::Secret&) override {} @@ -543,7 +545,7 @@ TEST_F(SdsApiTest, Delta) { *dispatcher_, *api_); initialize(); EXPECT_CALL(sds, onConfigUpdate(DecodedResourcesEq(resources), "version1")); - subscription_factory_.callbacks_->onConfigUpdate(resources, {}, "ignored"); + EXPECT_TRUE(subscription_factory_.callbacks_->onConfigUpdate(resources, {}, "ignored").ok()); // An attempt to remove a resource logs an error, but otherwise just carries on (ignoring the // removal attempt). @@ -554,7 +556,8 @@ TEST_F(SdsApiTest, Delta) { EXPECT_CALL(sds, onConfigUpdate(DecodedResourcesEq(resources_v2), "version2")); Protobuf::RepeatedPtrField removals; *removals.Add() = "route_0"; - subscription_factory_.callbacks_->onConfigUpdate(resources_v2, removals, "ignored"); + EXPECT_TRUE( + subscription_factory_.callbacks_->onConfigUpdate(resources_v2, removals, "ignored").ok()); } // Tests SDS's use of the delta variant of onConfigUpdate(). @@ -585,7 +588,8 @@ TEST_F(SdsApiTest, DeltaUpdateSuccess) { EXPECT_CALL(secret_callback, onAddOrUpdateSecret()); initialize(); - subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, ""); + EXPECT_TRUE( + subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "").ok()); testing::NiceMock ctx; Ssl::TlsCertificateConfigImpl tls_config(*sds_api.secret(), ctx, *api_); @@ -627,7 +631,7 @@ TEST_F(SdsApiTest, DynamicCertificateValidationContextUpdateSuccess) { const auto decoded_resources = TestUtility::decodeResources({typed_secret}); EXPECT_CALL(secret_callback, onAddOrUpdateSecret()); initialize(); - subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + EXPECT_TRUE(subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); auto cvc_config = Ssl::CertificateValidationContextConfigImpl::create(*sds_api.secret(), *api_).value(); @@ -691,7 +695,7 @@ TEST_F(SdsApiTest, DefaultCertificateValidationContextTest) { const auto decoded_resources = TestUtility::decodeResources({typed_secret}); initialize(); - subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + EXPECT_TRUE(subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); const std::string default_verify_certificate_hash = "0000000000000000000000000000000000000000000000000000000000000000"; @@ -781,7 +785,7 @@ name: "encryption_key" EXPECT_CALL(secret_callback, onAddOrUpdateSecret()); EXPECT_CALL(validation_callback, validateGenericSecret(_)); initialize(); - subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + EXPECT_TRUE(subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); const envoy::extensions::transport_sockets::tls::v3::GenericSecret generic_secret( *sds_api.secret()); @@ -801,9 +805,9 @@ TEST_F(SdsApiTest, EmptyResource) { init_manager_.add(*sds_api.initTarget()); initialize(); - EXPECT_THROW_WITH_MESSAGE(subscription_factory_.callbacks_->onConfigUpdate({}, ""), - EnvoyException, - "Missing SDS resources for abc.com in onConfigUpdate()"); + EXPECT_THROW_WITH_MESSAGE( + EXPECT_TRUE(subscription_factory_.callbacks_->onConfigUpdate({}, "").ok()), EnvoyException, + "Missing SDS resources for abc.com in onConfigUpdate()"); } // Validate that SdsApi throws exception if multiple secrets are passed to onConfigUpdate(). @@ -831,7 +835,8 @@ TEST_F(SdsApiTest, SecretUpdateWrongSize) { initialize(); EXPECT_THROW_WITH_MESSAGE( - subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), + EXPECT_TRUE( + subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), EnvoyException, "Unexpected SDS secrets length: 2"); } @@ -861,7 +866,8 @@ TEST_F(SdsApiTest, SecretUpdateWrongSecretName) { initialize(); EXPECT_THROW_WITH_MESSAGE( - subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), + EXPECT_TRUE( + subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), EnvoyException, "Unexpected SDS secret (expecting abc.com): wrong.name.com"); } diff --git a/test/common/secret/secret_manager_impl_test.cc b/test/common/secret/secret_manager_impl_test.cc index 87a706c9ec4b..b6335026065e 100644 --- a/test/common/secret/secret_manager_impl_test.cc +++ b/test/common/secret/secret_manager_impl_test.cc @@ -383,8 +383,9 @@ name: "abc.com" TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), typed_secret); const auto decoded_resources = TestUtility::decodeResources({typed_secret}); init_target_handle->initialize(init_watcher); - secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, ""); + EXPECT_TRUE(secret_context.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, "") + .ok()); testing::NiceMock ctx; Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), ctx, *api_); const std::string cert_pem = @@ -433,8 +434,9 @@ name: "encryption_key" TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), typed_secret); const auto decoded_resources = TestUtility::decodeResources({typed_secret}); init_target_handle->initialize(init_watcher); - secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, ""); + EXPECT_TRUE(secret_context.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, "") + .ok()); const envoy::extensions::transport_sockets::tls::v3::GenericSecret generic_secret( *secret_provider->secret()); @@ -481,8 +483,9 @@ name: "abc.com" TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), typed_secret); const auto decoded_resources = TestUtility::decodeResources({typed_secret}); init_target_handle->initialize(init_watcher); - secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, "keycert-v1"); + EXPECT_TRUE(secret_context.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, "keycert-v1") + .ok()); testing::NiceMock ctx; Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), ctx, *api_); EXPECT_EQ("DUMMY_INLINE_BYTES_FOR_CERT_CHAIN", tls_config.certificateChain()); @@ -527,8 +530,9 @@ name: "abc.com.validation" const auto decoded_resources_2 = TestUtility::decodeResources({typed_secret}); init_target_handle->initialize(init_watcher); - secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources_2.refvec_, "validation-context-v1"); + EXPECT_TRUE(secret_context.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources_2.refvec_, "validation-context-v1") + .ok()); auto cert_validation_context = Ssl::CertificateValidationContextConfigImpl::create(*context_secret_provider->secret(), *api_) .value(); @@ -582,8 +586,9 @@ name: "abc.com.stek" const auto decoded_resources_3 = TestUtility::decodeResources({typed_secret}); init_target_handle->initialize(init_watcher); - secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources_3.refvec_, "stek-context-v1"); + EXPECT_TRUE(secret_context.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources_3.refvec_, "stek-context-v1") + .ok()); EXPECT_EQ(stek_secret_provider->secret()->keys()[1].inline_string(), "DUMMY_INLINE_STRING"); const std::string updated_once_more_config_dump = R"EOF( @@ -646,8 +651,9 @@ name: "signing_key" TestUtility::loadFromYaml(TestEnvironment::substitute(generic_secret_yaml), typed_secret); const auto decoded_resources_4 = TestUtility::decodeResources({typed_secret}); init_target_handle->initialize(init_watcher); - secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources_4.refvec_, "signing-key-v1"); + EXPECT_TRUE(secret_context.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources_4.refvec_, "signing-key-v1") + .ok()); const envoy::extensions::transport_sockets::tls::v3::GenericSecret generic_secret( *generic_secret_provider->secret()); @@ -1104,8 +1110,9 @@ name: "abc.com" EXPECT_FALSE(typed_secret.tls_certificate().has_private_key()); const auto decoded_resources = TestUtility::decodeResources({typed_secret}); init_target_handle->initialize(init_watcher); - secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( - decoded_resources.refvec_, ""); + EXPECT_TRUE(secret_context.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, "") + .ok()); EXPECT_TRUE(secret_provider->secret()->has_private_key_provider()); EXPECT_FALSE(secret_provider->secret()->has_private_key()); diff --git a/test/common/upstream/cds_api_impl_test.cc b/test/common/upstream/cds_api_impl_test.cc index e8f31860a8c1..d0ebb8bcaaa9 100644 --- a/test/common/upstream/cds_api_impl_test.cc +++ b/test/common/upstream/cds_api_impl_test.cc @@ -105,7 +105,8 @@ version_info: '0' const auto decoded_resources = TestUtility::decodeResources(response1); - cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); EXPECT_EQ("0", cds_->versionInfo()); const std::string response2_yaml = R"EOF( @@ -118,7 +119,8 @@ version_info: '1' EXPECT_CALL(cm_, removeCluster("cluster1")).WillOnce(Return(true)); const auto decoded_resources_2 = TestUtility::decodeResources(response2); - cds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()); + EXPECT_TRUE( + cds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()).ok()); EXPECT_EQ("1", cds_->versionInfo()); } @@ -134,10 +136,11 @@ TEST_F(CdsApiImplTest, ValidateDuplicateClusters) { EXPECT_CALL(cm_, clusters()).WillRepeatedly(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); - EXPECT_THROW_WITH_MESSAGE(cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), - EnvoyException, - "Error adding/updating cluster(s) duplicate_cluster: duplicate cluster " - "duplicate_cluster found"); + EXPECT_THROW_WITH_MESSAGE( + EXPECT_TRUE(cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), + EnvoyException, + "Error adding/updating cluster(s) duplicate_cluster: duplicate cluster " + "duplicate_cluster found"); } TEST_F(CdsApiImplTest, EmptyConfigUpdate) { @@ -148,7 +151,7 @@ TEST_F(CdsApiImplTest, EmptyConfigUpdate) { EXPECT_CALL(cm_, clusters()).WillOnce(Return(makeClusterInfoMaps({}))); EXPECT_CALL(initialized_, ready()); - cds_callbacks_->onConfigUpdate({}, ""); + EXPECT_TRUE(cds_callbacks_->onConfigUpdate({}, "").ok()); } TEST_F(CdsApiImplTest, ConfigUpdateWith2ValidClusters) { @@ -169,7 +172,7 @@ TEST_F(CdsApiImplTest, ConfigUpdateWith2ValidClusters) { expectAdd("cluster_2"); const auto decoded_resources = TestUtility::decodeResources({cluster_1, cluster_2}); - cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + EXPECT_TRUE(cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); } TEST_F(CdsApiImplTest, DeltaConfigUpdate) { @@ -201,7 +204,7 @@ TEST_F(CdsApiImplTest, DeltaConfigUpdate) { } const auto decoded_resources = TestUtility::decodeResources(resources); - cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "v1"); + EXPECT_TRUE(cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "v1").ok()); } { @@ -220,7 +223,7 @@ TEST_F(CdsApiImplTest, DeltaConfigUpdate) { EXPECT_CALL(cm_, removeCluster(StrEq("cluster_1"))).WillOnce(Return(true)); const auto decoded_resources = TestUtility::decodeResources(resources); - cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, removed, "v2"); + EXPECT_TRUE(cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, removed, "v2").ok()); } } @@ -247,7 +250,8 @@ TEST_F(CdsApiImplTest, ConfigUpdateAddsSecondClusterEvenIfFirstThrows) { const auto decoded_resources = TestUtility::decodeResources({cluster_1, cluster_2, cluster_3}); EXPECT_THROW_WITH_MESSAGE( - cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), EnvoyException, + EXPECT_TRUE(cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), + EnvoyException, "Error adding/updating cluster(s) cluster_1: An exception, cluster_3: Another exception"); } @@ -286,7 +290,8 @@ version_info: '0' EXPECT_EQ("", cds_->versionInfo()); const auto decoded_resources = TestUtility::decodeResources(response1); - cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); EXPECT_EQ("0", cds_->versionInfo()); const std::string response2_yaml = R"EOF( @@ -318,7 +323,8 @@ version_info: '1' EXPECT_CALL(cm_, removeCluster("cluster2")); const auto decoded_resources_2 = TestUtility::decodeResources(response2); - cds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()); + EXPECT_TRUE( + cds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()).ok()); EXPECT_EQ("1", cds_->versionInfo()); } @@ -356,8 +362,10 @@ version_info: '0' EXPECT_CALL(initialized_, ready()); const auto decoded_resources = TestUtility::decodeResources(response1); - EXPECT_THROW(cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()), - EnvoyException); + EXPECT_THROW( + EXPECT_TRUE( + cds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()), + EnvoyException); EXPECT_EQ("", cds_->versionInfo()); } diff --git a/test/common/upstream/deferred_cluster_initialization_test.cc b/test/common/upstream/deferred_cluster_initialization_test.cc index ff0824bf0e71..30da60f9b69c 100644 --- a/test/common/upstream/deferred_cluster_initialization_test.cc +++ b/test/common/upstream/deferred_cluster_initialization_test.cc @@ -426,7 +426,7 @@ class EdsTest : public DeferredClusterInitializationTest { const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment) { const auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); - VERBOSE_EXPECT_NO_THROW(callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "")); + EXPECT_TRUE(callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "").ok()); } void addEndpoint(envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment, diff --git a/test/common/upstream/od_cds_api_impl_test.cc b/test/common/upstream/od_cds_api_impl_test.cc index 4f6d2f22e1cc..ffd925bb5234 100644 --- a/test/common/upstream/od_cds_api_impl_test.cc +++ b/test/common/upstream/od_cds_api_impl_test.cc @@ -71,7 +71,7 @@ TEST_F(OdCdsApiImplTest, AwaitingListIsProcessedOnConfigUpdate) { EXPECT_CALL( *cm_.subscription_factory_.subscription_, requestOnDemandUpdate(UnorderedElementsAre("another_cluster_1", "another_cluster_2"))); - odcds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "0"); + ASSERT_TRUE(odcds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "0").ok()); } // Check that the awaiting list is processed when we receive a failure response for the initial @@ -128,7 +128,7 @@ TEST_F(OdCdsApiImplTest, OnDemandUpdateIsRequestedAfterInitialFetch) { envoy::config::cluster::v3::Cluster cluster; cluster.set_name("fake_cluster"); const auto decoded_resources = TestUtility::decodeResources({cluster}); - odcds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "0"); + ASSERT_TRUE(odcds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "0").ok()); EXPECT_CALL(*cm_.subscription_factory_.subscription_, requestOnDemandUpdate(UnorderedElementsAre("another_cluster"))); odcds_->updateOnDemand("another_cluster"); @@ -142,10 +142,11 @@ TEST_F(OdCdsApiImplTest, ValidateDuplicateClusters) { cluster_1.set_name("duplicate_cluster"); const auto decoded_resources = TestUtility::decodeResources({cluster_1, cluster_1}); - EXPECT_THROW_WITH_MESSAGE(odcds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, ""), - EnvoyException, - "Error adding/updating cluster(s) duplicate_cluster: duplicate cluster " - "duplicate_cluster found"); + EXPECT_THROW_WITH_MESSAGE( + ASSERT_TRUE(odcds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "").ok()), + EnvoyException, + "Error adding/updating cluster(s) duplicate_cluster: duplicate cluster " + "duplicate_cluster found"); } // Check that notifier gets a message about potentially missing cluster. @@ -156,7 +157,7 @@ TEST_F(OdCdsApiImplTest, NotifierGetsUsed) { EXPECT_CALL(notifier_, notifyMissingCluster("missing_cluster")); std::vector v{"missing_cluster"}; Protobuf::RepeatedPtrField removed(v.begin(), v.end()); - odcds_callbacks_->onConfigUpdate({}, removed, ""); + ASSERT_TRUE(odcds_callbacks_->onConfigUpdate({}, removed, "").ok()); } // Check that notifier won't be used for a requested cluster that did @@ -178,10 +179,10 @@ TEST_F(OdCdsApiImplTest, NotifierNotUsed) { odcds_->updateOnDemand("cluster"); EXPECT_CALL(notifier_, notifyMissingCluster(_)).Times(2); EXPECT_CALL(notifier_, notifyMissingCluster("cluster")).Times(0); - odcds_callbacks_->onConfigUpdate(some_cluster_resource.refvec_, {}, ""); - odcds_callbacks_->onConfigUpdate({}, removed, ""); - odcds_callbacks_->onConfigUpdate({}, {}, ""); - odcds_callbacks_->onConfigUpdate(some_cluster2_resource.refvec_, removed2, ""); + ASSERT_TRUE(odcds_callbacks_->onConfigUpdate(some_cluster_resource.refvec_, {}, "").ok()); + ASSERT_TRUE(odcds_callbacks_->onConfigUpdate({}, removed, "").ok()); + ASSERT_TRUE(odcds_callbacks_->onConfigUpdate({}, {}, "").ok()); + ASSERT_TRUE(odcds_callbacks_->onConfigUpdate(some_cluster2_resource.refvec_, removed2, "").ok()); } } // namespace diff --git a/test/extensions/clusters/eds/eds_test.cc b/test/extensions/clusters/eds/eds_test.cc index f7f392a79dfa..02495fb926c8 100644 --- a/test/extensions/clusters/eds/eds_test.cc +++ b/test/extensions/clusters/eds/eds_test.cc @@ -148,7 +148,7 @@ class EdsTest : public testing::Test, public Event::TestUsingSimulatedTime { const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment) { const auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); - VERBOSE_EXPECT_NO_THROW(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "")); + EXPECT_TRUE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); } NiceMock server_context_; @@ -258,7 +258,7 @@ TEST_F(EdsTest, OnConfigUpdateWrongName) { TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); initialize(); try { - eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + EXPECT_TRUE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); } catch (const EnvoyException& e) { eds_callbacks_->onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::UpdateRejected, &e); @@ -269,9 +269,9 @@ TEST_F(EdsTest, OnConfigUpdateWrongName) { // Validate that onConfigUpdate() with empty cluster vector size ignores config. TEST_F(EdsTest, OnConfigUpdateEmpty) { initialize(); - eds_callbacks_->onConfigUpdate({}, ""); + EXPECT_TRUE(eds_callbacks_->onConfigUpdate({}, "").ok()); Protobuf::RepeatedPtrField removed_resources; - eds_callbacks_->onConfigUpdate({}, removed_resources, ""); + EXPECT_TRUE(eds_callbacks_->onConfigUpdate({}, removed_resources, "").ok()); EXPECT_EQ(2UL, stats_.findCounterByString("cluster.name.update_empty").value().get().value()); EXPECT_TRUE(initialized_); } @@ -284,7 +284,7 @@ TEST_F(EdsTest, OnConfigUpdateWrongSize) { const auto decoded_resources = TestUtility::decodeResources( {cluster_load_assignment, cluster_load_assignment}, "cluster_name"); try { - eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""); + EXPECT_TRUE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); } catch (const EnvoyException& e) { eds_callbacks_->onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::UpdateRejected, &e); @@ -316,7 +316,7 @@ TEST_F(EdsTest, DeltaOnConfigUpdateSuccess) { const auto decoded_resources = TestUtility::decodeResources( resources, "cluster_name"); - VERBOSE_EXPECT_NO_THROW(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "v1")); + EXPECT_TRUE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, {}, "v1").ok()); EXPECT_TRUE(initialized_); EXPECT_EQ(1UL, @@ -756,8 +756,9 @@ TEST_F(EdsTest, MalformedIPForHealthChecks) { initialize(); const auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); - EXPECT_THROW_WITH_MESSAGE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), - EnvoyException, "malformed IP address: foo.bar.com"); + EXPECT_THROW_WITH_MESSAGE( + EXPECT_TRUE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), + EnvoyException, "malformed IP address: foo.bar.com"); } // Verify that a host is removed if it is removed from discovery, stabilized, and then later @@ -2368,9 +2369,9 @@ TEST_F(EdsTest, NoPriorityForLocalCluster) { initialize(); const auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); - EXPECT_THROW_WITH_MESSAGE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), - EnvoyException, - "Unexpected non-zero priority for local cluster 'name'."); + EXPECT_THROW_WITH_MESSAGE( + EXPECT_TRUE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), + EnvoyException, "Unexpected non-zero priority for local cluster 'name'."); // Try an update which only has endpoints with P=0. This should go through. cluster_load_assignment.clear_endpoints(); @@ -2661,10 +2662,11 @@ TEST_F(EdsTest, MalformedIP) { initialize(); const auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); - EXPECT_THROW_WITH_MESSAGE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), - EnvoyException, - "malformed IP address: foo.bar.com. Consider setting resolver_name or " - "setting cluster type to 'STRICT_DNS' or 'LOGICAL_DNS'"); + EXPECT_THROW_WITH_MESSAGE( + EXPECT_TRUE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), + EnvoyException, + "malformed IP address: foo.bar.com. Consider setting resolver_name or " + "setting cluster type to 'STRICT_DNS' or 'LOGICAL_DNS'"); } class EdsAssignmentTimeoutTest : public EdsTest { @@ -2828,10 +2830,11 @@ TEST_F(EdsTest, OnConfigUpdateLedsAndEndpoints) { const auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); - EXPECT_THROW_WITH_MESSAGE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), - EnvoyException, - "A ClusterLoadAssignment for cluster fare cannot include both LEDS " - "(resource: xdstp://foo/leds/collection) and a list of endpoints."); + EXPECT_THROW_WITH_MESSAGE( + EXPECT_TRUE(eds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), + EnvoyException, + "A ClusterLoadAssignment for cluster fare cannot include both LEDS " + "(resource: xdstp://foo/leds/collection) and a list of endpoints."); } class EdsCachedAssignmentTest : public testing::Test { @@ -2894,14 +2897,14 @@ class EdsCachedAssignmentTest : public testing::Test { const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment) { const auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); - VERBOSE_EXPECT_NO_THROW(eds_callbacks_pre_->onConfigUpdate(decoded_resources.refvec_, "")); + EXPECT_TRUE(eds_callbacks_pre_->onConfigUpdate(decoded_resources.refvec_, "").ok()); } void doOnConfigUpdateVerifyNoThrowPost( const envoy::config::endpoint::v3::ClusterLoadAssignment& cluster_load_assignment) { const auto decoded_resources = TestUtility::decodeResources({cluster_load_assignment}, "cluster_name"); - VERBOSE_EXPECT_NO_THROW(eds_callbacks_post_->onConfigUpdate(decoded_resources.refvec_, "")); + EXPECT_TRUE(eds_callbacks_post_->onConfigUpdate(decoded_resources.refvec_, "").ok()); } // Emulates a CDS update that creates a new cluster object with the same name, // that waits for EDS response. diff --git a/test/extensions/clusters/eds/leds_test.cc b/test/extensions/clusters/eds/leds_test.cc index 14a0fc5c0958..2487a414ca09 100644 --- a/test/extensions/clusters/eds/leds_test.cc +++ b/test/extensions/clusters/eds/leds_test.cc @@ -172,7 +172,8 @@ TEST_F(LedsTest, OnConfigUpdateSuccess) { const auto decoded_resources = TestUtility::decodeResources(added_resources); const Protobuf::RepeatedPtrField removed_resources; - leds_callbacks_->onConfigUpdate(decoded_resources.refvec_, removed_resources, ""); + EXPECT_TRUE( + leds_callbacks_->onConfigUpdate(decoded_resources.refvec_, removed_resources, "").ok()); EXPECT_EQ(1UL, callbacks_called_counter_); EXPECT_TRUE(leds_subscription_->isUpdated()); const auto& all_endpoints_map = leds_subscription_->getEndpointsMap(); @@ -185,14 +186,14 @@ TEST_F(LedsTest, OnConfigUpdateEmpty) { initialize(); EXPECT_FALSE(leds_subscription_->isUpdated()); const Protobuf::RepeatedPtrField removed_resources; - leds_callbacks_->onConfigUpdate({}, removed_resources, ""); + EXPECT_TRUE(leds_callbacks_->onConfigUpdate({}, removed_resources, "").ok()); EXPECT_EQ(1UL, stats_.counter("cluster.xds_cluster.leds.update_empty").value()); // Verify that the callback was called even after an empty update. EXPECT_EQ(1UL, callbacks_called_counter_); EXPECT_TRUE(leds_subscription_->isUpdated()); // Verify that the second time an empty update arrives, the callback isn't called. - leds_callbacks_->onConfigUpdate({}, removed_resources, ""); + EXPECT_TRUE(leds_callbacks_->onConfigUpdate({}, removed_resources, "").ok()); EXPECT_EQ(2UL, stats_.counter("cluster.xds_cluster.leds.update_empty").value()); EXPECT_EQ(1UL, callbacks_called_counter_); } @@ -220,7 +221,8 @@ TEST_F(LedsTest, OnConfigUpdateFailedEndpoints) { const auto decoded_resources = TestUtility::decodeResources(added_resources); const Protobuf::RepeatedPtrField removed_resources; - leds_callbacks_->onConfigUpdate(decoded_resources.refvec_, removed_resources, ""); + EXPECT_TRUE( + leds_callbacks_->onConfigUpdate(decoded_resources.refvec_, removed_resources, "").ok()); EXPECT_EQ(1UL, callbacks_called_counter_); // Verify there's an endpoint. @@ -255,7 +257,8 @@ TEST_F(LedsTest, UpdateEndpoint) { const auto decoded_resources = TestUtility::decodeResources(added_resources); const Protobuf::RepeatedPtrField removed_resources; - leds_callbacks_->onConfigUpdate(decoded_resources.refvec_, removed_resources, ""); + EXPECT_TRUE( + leds_callbacks_->onConfigUpdate(decoded_resources.refvec_, removed_resources, "").ok()); EXPECT_EQ(1UL, callbacks_called_counter_); EXPECT_TRUE(leds_subscription_->isUpdated()); const auto& all_endpoints_map = leds_subscription_->getEndpointsMap(); @@ -268,7 +271,9 @@ TEST_F(LedsTest, UpdateEndpoint) { const auto& updated_resources = buildAddedResources({lb_endpoint1_update}, {lb_endpoint1_name}); const auto decoded_resources_update = TestUtility::decodeResources(updated_resources); - leds_callbacks_->onConfigUpdate(decoded_resources_update.refvec_, removed_resources, ""); + EXPECT_TRUE( + leds_callbacks_->onConfigUpdate(decoded_resources_update.refvec_, removed_resources, "") + .ok()); EXPECT_EQ(2UL, callbacks_called_counter_); EXPECT_TRUE(leds_subscription_->isUpdated()); const auto& all_endpoints_update = leds_subscription_->getEndpointsMap(); @@ -292,7 +297,8 @@ TEST_F(LedsTest, RemoveEndpoint) { const auto decoded_resources = TestUtility::decodeResources(added_resources); const Protobuf::RepeatedPtrField removed_resources; - leds_callbacks_->onConfigUpdate(decoded_resources.refvec_, removed_resources, ""); + EXPECT_TRUE( + leds_callbacks_->onConfigUpdate(decoded_resources.refvec_, removed_resources, "").ok()); EXPECT_EQ(1UL, callbacks_called_counter_); EXPECT_TRUE(leds_subscription_->isUpdated()); const auto& all_endpoints_map = leds_subscription_->getEndpointsMap(); @@ -302,7 +308,7 @@ TEST_F(LedsTest, RemoveEndpoint) { // Remove the first endpoint. const auto& removed_resources_update = buildRemovedResources({lb_endpoint1_name}); - leds_callbacks_->onConfigUpdate({}, removed_resources_update, ""); + EXPECT_TRUE(leds_callbacks_->onConfigUpdate({}, removed_resources_update, "").ok()); EXPECT_EQ(2UL, callbacks_called_counter_); EXPECT_TRUE(leds_subscription_->isUpdated()); const auto& all_endpoints_update = leds_subscription_->getEndpointsMap(); diff --git a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc index 115c91ce1c0a..5dfb9a7afa57 100644 --- a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc @@ -432,6 +432,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*ttl_timer, enabled()); EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(1000), _)); @@ -452,6 +453,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*ttl_timer, enabled()); EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(10000), _)); @@ -487,6 +489,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*ttl_timer, disableTimer()); expectSendMessage(type_url, {"x"}, "1"); @@ -506,6 +509,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*ttl_timer, enabled()); EXPECT_CALL(*ttl_timer, enableTimer(std::chrono::milliseconds(10000), _)); @@ -519,6 +523,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { .WillOnce(Invoke([](auto, const auto& removed, auto) { EXPECT_EQ(1, removed.size()); EXPECT_EQ("x", removed.Get(0)); + return absl::OkStatus(); })); // Fire the TTL timer. EXPECT_CALL(*ttl_timer, disableTimer()); @@ -590,6 +595,7 @@ TEST_F(GrpcMuxImplTest, WildcardWatch) { dynamic_cast( resources[0].get().resource()); EXPECT_TRUE(TestUtility::protoEqual(expected_assignment, load_assignment)); + return absl::OkStatus(); })); expectSendMessage(type_url, {}, "1"); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); @@ -629,6 +635,7 @@ TEST_F(GrpcMuxImplTest, WatchDemux) { dynamic_cast( resources[0].get().resource()); EXPECT_TRUE(TestUtility::protoEqual(expected_assignment, load_assignment)); + return absl::OkStatus(); })); expectSendMessage(type_url, {"y", "z", "x"}, "1"); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); @@ -659,6 +666,7 @@ TEST_F(GrpcMuxImplTest, WatchDemux) { dynamic_cast( resources[1].get().resource()); EXPECT_TRUE(TestUtility::protoEqual(expected_assignment_1, load_assignment_z)); + return absl::OkStatus(); })); EXPECT_CALL(foo_callbacks, onConfigUpdate(_, "2")) .WillOnce(Invoke([&load_assignment_x, &load_assignment_y]( @@ -672,6 +680,7 @@ TEST_F(GrpcMuxImplTest, WatchDemux) { dynamic_cast( resources[1].get().resource()); EXPECT_TRUE(TestUtility::protoEqual(expected_assignment_1, load_assignment_y)); + return absl::OkStatus(); })); expectSendMessage(type_url, {"y", "z", "x"}, "2"); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); @@ -722,6 +731,7 @@ TEST_F(GrpcMuxImplTest, SingleWatcherWithEmptyUpdates) { EXPECT_CALL(foo_callbacks, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_TRUE(resources.empty()); + return absl::OkStatus(); })); expectSendMessage(type_url, {}, "1"); grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); @@ -1041,6 +1051,7 @@ TEST_F(GrpcMuxImplTest, CacheEdsResource) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {"x"}, "1"); @@ -1082,6 +1093,7 @@ TEST_F(GrpcMuxImplTest, UpdateCacheEdsResource) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {"x"}, "1"); @@ -1129,8 +1141,11 @@ TEST_F(GrpcMuxImplTest, AddRemoveSubscriptions) { response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) - .WillOnce(Invoke([](const std::vector& resources, - const std::string&) { EXPECT_EQ(1, resources.size()); })); + .WillOnce( + Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); + })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {"x"}, "1"); // Ack. grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); @@ -1157,8 +1172,11 @@ TEST_F(GrpcMuxImplTest, AddRemoveSubscriptions) { response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, "2")) - .WillOnce(Invoke([](const std::vector& resources, - const std::string&) { EXPECT_EQ(1, resources.size()); })); + .WillOnce( + Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); + })); EXPECT_CALL(*eds_resources_cache_, setResource("y", ProtoEq(load_assignment))); expectSendMessage(type_url, {"y"}, "2"); // Ack. grpc_mux_->grpcStreamForTest().onReceiveMessage(std::move(response)); @@ -1203,6 +1221,7 @@ TEST_F(GrpcMuxImplTest, RemoveCachedResourceOnLastSubscription) { EXPECT_CALL(eds_sub1_callbacks, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {"x"}, "1"); // Ack. @@ -1229,11 +1248,13 @@ TEST_F(GrpcMuxImplTest, RemoveCachedResourceOnLastSubscription) { EXPECT_CALL(eds_sub2_callbacks, onConfigUpdate(_, "2")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); EXPECT_CALL(eds_sub1_callbacks, onConfigUpdate(_, "2")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {"x"}, "2"); // Ack. diff --git a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc index 3d978df55080..9888a26b6e45 100644 --- a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc @@ -310,6 +310,7 @@ TEST_P(NewGrpcMuxImplTest, ReconnectionResetsWildcardSubscription) { EXPECT_EQ(1, added_resources.size()); EXPECT_TRUE( TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + return absl::OkStatus(); })); // Expect an ack with the nonce. expectSendMessage(type_url, {}, {}, "111"); @@ -326,6 +327,7 @@ TEST_P(NewGrpcMuxImplTest, ReconnectionResetsWildcardSubscription) { EXPECT_EQ(1, added_resources.size()); EXPECT_TRUE( TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + return absl::OkStatus(); })); // No ack reply is expected in this case, as EDS is suspended. onDiscoveryResponse(std::move(response)); @@ -377,6 +379,7 @@ TEST_P(NewGrpcMuxImplTest, DiscoveryResponseNonexistentSub) { EXPECT_EQ(1, added_resources.size()); EXPECT_TRUE( TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + return absl::OkStatus(); })); onDiscoveryResponse(std::move(response)); } @@ -476,6 +479,7 @@ TEST_P(NewGrpcMuxImplTest, XdsTpGlobCollection) { const std::string&) { EXPECT_EQ(1, added_resources.size()); EXPECT_TRUE(TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + return absl::OkStatus(); })); onDiscoveryResponse(std::move(response)); } @@ -533,6 +537,7 @@ TEST_P(NewGrpcMuxImplTest, XdsTpSingleton) { EXPECT_TRUE(TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); EXPECT_TRUE(TestUtility::protoEqual(added_resources[1].get().resource(), load_assignment)); EXPECT_TRUE(TestUtility::protoEqual(added_resources[2].get().resource(), load_assignment)); + return absl::OkStatus(); })); onDiscoveryResponse(std::move(response)); } @@ -610,6 +615,7 @@ TEST_P(NewGrpcMuxImplTest, CacheEdsResource) { EXPECT_EQ(1, added_resources.size()); EXPECT_TRUE( TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {}, {}); // Ack. @@ -654,6 +660,7 @@ TEST_P(NewGrpcMuxImplTest, UpdateCacheEdsResource) { EXPECT_EQ(1, added_resources.size()); EXPECT_TRUE( TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {}, {}); // Ack. @@ -705,6 +712,7 @@ TEST_P(NewGrpcMuxImplTest, AddRemoveSubscriptions) { EXPECT_EQ(1, added_resources.size()); EXPECT_TRUE( TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {}, {}); // Ack. @@ -741,6 +749,7 @@ TEST_P(NewGrpcMuxImplTest, AddRemoveSubscriptions) { EXPECT_EQ(1, added_resources.size()); EXPECT_TRUE( TestUtility::protoEqual(added_resources[0].get().resource(), load_assignment)); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("y", ProtoEq(load_assignment))); expectSendMessage(type_url, {}, {}); // Ack. diff --git a/test/extensions/config_subscription/grpc/watch_map_test.cc b/test/extensions/config_subscription/grpc/watch_map_test.cc index 771c26cea73c..4186b3d8b8d7 100644 --- a/test/extensions/config_subscription/grpc/watch_map_test.cc +++ b/test/extensions/config_subscription/grpc/watch_map_test.cc @@ -45,6 +45,7 @@ void expectDeltaUpdate( for (size_t i = 0; i < expected_removals.size(); i++) { EXPECT_EQ(expected_removals[i], removed_resources[i]); } + return absl::OkStatus(); })); } @@ -65,6 +66,7 @@ void expectDeltaAndSotwUpdate( EXPECT_TRUE( TestUtility::protoEqual(gotten_resources[i].get().resource(), expected_resources[i])); } + return absl::OkStatus(); })); expectDeltaUpdate(callbacks, expected_resources, expected_removals, version); } @@ -77,8 +79,11 @@ void expectNoUpdate(MockSubscriptionCallbacks& callbacks, const std::string& ver void expectEmptySotwNoDeltaUpdate(MockSubscriptionCallbacks& callbacks, const std::string& version) { EXPECT_CALL(callbacks, onConfigUpdate(_, version)) - .WillOnce(Invoke([](const std::vector& gotten_resources, - const std::string&) { EXPECT_EQ(gotten_resources.size(), 0); })); + .WillOnce( + Invoke([](const std::vector& gotten_resources, const std::string&) { + EXPECT_EQ(gotten_resources.size(), 0); + return absl::OkStatus(); + })); EXPECT_CALL(callbacks, onConfigUpdate(_, _, version)).Times(0); } @@ -364,11 +369,12 @@ class SameWatchRemoval : public testing::Test { watch_map_.updateWatchInterest(watch2_, {"alice"}); } - void removeAllInterest() { - ASSERT_FALSE(watch_cb_invoked_); + absl::Status removeAllInterest() { + EXPECT_FALSE(watch_cb_invoked_); watch_cb_invoked_ = true; watch_map_.removeWatch(watch1_); watch_map_.removeWatch(watch2_); + return absl::OkStatus(); } TestUtility::TestOpaqueResourceDecoderImpl @@ -386,10 +392,10 @@ class SameWatchRemoval : public testing::Test { TEST_F(SameWatchRemoval, SameWatchRemovalSotw) { EXPECT_CALL(callbacks1_, onConfigUpdate(_, _)) .Times(AtMost(1)) - .WillRepeatedly(InvokeWithoutArgs([this] { removeAllInterest(); })); + .WillRepeatedly(InvokeWithoutArgs([this] { return removeAllInterest(); })); EXPECT_CALL(callbacks2_, onConfigUpdate(_, _)) .Times(AtMost(1)) - .WillRepeatedly(InvokeWithoutArgs([this] { removeAllInterest(); })); + .WillRepeatedly(InvokeWithoutArgs([this] { return removeAllInterest(); })); watch_map_.onConfigUpdate(updated_resources_, "version1"); } @@ -400,10 +406,10 @@ TEST_F(SameWatchRemoval, SameWatchRemovalDeltaAdd) { EXPECT_CALL(callbacks1_, onConfigUpdate(_, _, _)) .Times(AtMost(1)) - .WillRepeatedly(InvokeWithoutArgs([this] { removeAllInterest(); })); + .WillRepeatedly(InvokeWithoutArgs([this] { return removeAllInterest(); })); EXPECT_CALL(callbacks2_, onConfigUpdate(_, _, _)) .Times(AtMost(1)) - .WillRepeatedly(InvokeWithoutArgs([this] { removeAllInterest(); })); + .WillRepeatedly(InvokeWithoutArgs([this] { return removeAllInterest(); })); watch_map_.onConfigUpdate(delta_resources, removed_names_proto, "version1"); } @@ -412,10 +418,10 @@ TEST_F(SameWatchRemoval, SameWatchRemovalDeltaRemove) { *removed_names_proto.Add() = "alice"; EXPECT_CALL(callbacks1_, onConfigUpdate(_, _, _)) .Times(AtMost(1)) - .WillRepeatedly(InvokeWithoutArgs([this] { removeAllInterest(); })); + .WillRepeatedly(InvokeWithoutArgs([this] { return removeAllInterest(); })); EXPECT_CALL(callbacks2_, onConfigUpdate(_, _, _)) .Times(AtMost(1)) - .WillRepeatedly(InvokeWithoutArgs([this] { removeAllInterest(); })); + .WillRepeatedly(InvokeWithoutArgs([this] { return removeAllInterest(); })); watch_map_.onConfigUpdate({}, removed_names_proto, "version1"); } @@ -757,6 +763,7 @@ TEST(WatchMapTest, OnConfigUpdateUsingNamespaces) { EXPECT_FALSE(gotten_resources[0].get().hasResource()); EXPECT_EQ(gotten_resources[0].get().name(), not_resolved); EXPECT_EQ(gotten_resources[0].get().aliases(), std::vector{not_resolved}); + return absl::OkStatus(); })); Protobuf::RepeatedPtrField removed_names_proto; diff --git a/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc index 9924eaef77cf..819c55381c20 100644 --- a/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc @@ -382,6 +382,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); expectSendMessage(type_url, {"x"}, "1"); grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); @@ -402,6 +403,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); // No update, just a change in TTL. expectSendMessage(type_url, {"x"}, "1"); @@ -420,6 +422,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_TRUE(resources.empty()); + return absl::OkStatus(); })); // No update, just a change in TTL. @@ -440,6 +443,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); expectSendMessage(type_url, {"x"}, "1"); grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); @@ -460,6 +464,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); // No update, just a change in TTL. expectSendMessage(type_url, {"x"}, "1"); @@ -471,6 +476,7 @@ TEST_F(GrpcMuxImplTest, ResourceTTL) { .WillOnce(Invoke([](auto, const auto& removed, auto) { EXPECT_EQ(1, removed.size()); EXPECT_EQ("x", removed.Get(0)); + return absl::OkStatus(); })); // Fire the TTL timer. EXPECT_CALL(*ttl_timer, disableTimer()); @@ -538,6 +544,7 @@ TEST_F(GrpcMuxImplTest, WildcardWatch) { dynamic_cast( resources[0].get().resource()); EXPECT_TRUE(TestUtility::protoEqual(expected_assignment, load_assignment)); + return absl::OkStatus(); })); expectSendMessage(type_url, {}, "1"); grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); @@ -577,6 +584,7 @@ TEST_F(GrpcMuxImplTest, WatchDemux) { dynamic_cast( resources[0].get().resource()); EXPECT_TRUE(TestUtility::protoEqual(expected_assignment, load_assignment)); + return absl::OkStatus(); })); expectSendMessage(type_url, {"y", "z", "x"}, "1"); grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); @@ -609,6 +617,7 @@ TEST_F(GrpcMuxImplTest, WatchDemux) { dynamic_cast( resources[1].get().resource()); EXPECT_TRUE(TestUtility::protoEqual(expected_assignment_1, load_assignment_z)); + return absl::OkStatus(); })); EXPECT_CALL(foo_callbacks, onConfigUpdate(_, "2")) .WillOnce(Invoke([&load_assignment_x, &load_assignment_y]( @@ -622,6 +631,7 @@ TEST_F(GrpcMuxImplTest, WatchDemux) { dynamic_cast( resources[1].get().resource()); EXPECT_TRUE(TestUtility::protoEqual(expected_assignment_1, load_assignment_y)); + return absl::OkStatus(); })); expectSendMessage(type_url, {"y", "z", "x"}, "2"); grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); @@ -672,6 +682,7 @@ TEST_F(GrpcMuxImplTest, SingleWatcherWithEmptyUpdates) { EXPECT_CALL(foo_callbacks, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_TRUE(resources.empty()); + return absl::OkStatus(); })); expectSendMessage(type_url, {}, "1"); grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); @@ -980,6 +991,7 @@ TEST_F(GrpcMuxImplTest, ValidResourceDecoderAfterRemoval) { dynamic_cast( resources[0].get().resource()); EXPECT_TRUE(TestUtility::protoEqual(expected_assignment, load_assignment)); + return absl::OkStatus(); })); expectSendMessage(type_url, {"x"}, "1"); grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); @@ -1014,6 +1026,7 @@ TEST_F(GrpcMuxImplTest, ValidResourceDecoderAfterRemoval) { dynamic_cast( resources[0].get().resource()); EXPECT_TRUE(TestUtility::protoEqual(expected_assignment, load_assignment)); + return absl::OkStatus(); })); expectSendMessage(type_url, {"y"}, "2"); grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); @@ -1113,6 +1126,7 @@ TEST_F(GrpcMuxImplTest, CacheEdsResource) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {"x"}, "1"); @@ -1154,6 +1168,7 @@ TEST_F(GrpcMuxImplTest, UpdateCacheEdsResource) { EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) .WillOnce(Invoke([](const std::vector& resources, const std::string&) { EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {"x"}, "1"); @@ -1200,8 +1215,11 @@ TEST_F(GrpcMuxImplTest, AddRemoveSubscriptions) { response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, "1")) - .WillOnce(Invoke([](const std::vector& resources, - const std::string&) { EXPECT_EQ(1, resources.size()); })); + .WillOnce( + Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); + })); EXPECT_CALL(*eds_resources_cache_, setResource("x", ProtoEq(load_assignment))); expectSendMessage(type_url, {"x"}, "1"); // Ack. grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); @@ -1228,8 +1246,11 @@ TEST_F(GrpcMuxImplTest, AddRemoveSubscriptions) { response->add_resources()->PackFrom(load_assignment); EXPECT_CALL(callbacks_, onConfigUpdate(_, "2")) - .WillOnce(Invoke([](const std::vector& resources, - const std::string&) { EXPECT_EQ(1, resources.size()); })); + .WillOnce( + Invoke([](const std::vector& resources, const std::string&) { + EXPECT_EQ(1, resources.size()); + return absl::OkStatus(); + })); EXPECT_CALL(*eds_resources_cache_, setResource("y", ProtoEq(load_assignment))); expectSendMessage(type_url, {"y"}, "2"); // Ack. grpc_mux_->onDiscoveryResponse(std::move(response), control_plane_stats_); diff --git a/test/extensions/filters/http/oauth2/filter_test.cc b/test/extensions/filters/http/oauth2/filter_test.cc index a613b12a5010..97038bff5be4 100644 --- a/test/extensions/filters/http/oauth2/filter_test.cc +++ b/test/extensions/filters/http/oauth2/filter_test.cc @@ -241,7 +241,7 @@ name: client TestUtility::loadFromYaml(yaml_client, typed_secret); const auto decoded_resources_client = TestUtility::decodeResources({typed_secret}); - client_callback->onConfigUpdate(decoded_resources_client.refvec_, ""); + EXPECT_TRUE(client_callback->onConfigUpdate(decoded_resources_client.refvec_, "").ok()); EXPECT_EQ(secret_reader.clientSecret(), "client_test"); EXPECT_EQ(secret_reader.tokenSecret(), ""); @@ -254,7 +254,7 @@ name: token TestUtility::loadFromYaml(yaml_token, typed_secret); const auto decoded_resources_token = TestUtility::decodeResources({typed_secret}); - token_callback->onConfigUpdate(decoded_resources_token.refvec_, ""); + EXPECT_TRUE(token_callback->onConfigUpdate(decoded_resources_token.refvec_, "").ok()); EXPECT_EQ(secret_reader.clientSecret(), "client_test"); EXPECT_EQ(secret_reader.tokenSecret(), "token_test"); @@ -267,7 +267,7 @@ name: client TestUtility::loadFromYaml(yaml_client_recheck, typed_secret); const auto decoded_resources_client_recheck = TestUtility::decodeResources({typed_secret}); - client_callback->onConfigUpdate(decoded_resources_client_recheck.refvec_, ""); + EXPECT_TRUE(client_callback->onConfigUpdate(decoded_resources_client_recheck.refvec_, "").ok()); EXPECT_EQ(secret_reader.clientSecret(), "client_test_recheck"); EXPECT_EQ(secret_reader.tokenSecret(), "token_test"); } diff --git a/test/extensions/filters/network/dubbo_proxy/config_test.cc b/test/extensions/filters/network/dubbo_proxy/config_test.cc index 848998377578..a6c7bcf71710 100644 --- a/test/extensions/filters/network/dubbo_proxy/config_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/config_test.cc @@ -203,8 +203,9 @@ version_info: "1" TestUtility::parseYaml(response_yaml); const auto decoded_resources = TestUtility::decodeResources< envoy::extensions::filters::network::dubbo_proxy::v3::MultipleRouteConfiguration>(response); - context_.server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ - ->onConfigUpdate(decoded_resources.refvec_, response.version_info()); + EXPECT_TRUE(context_.server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .ok()); auto message_ptr = context_.admin_.config_tracker_.config_tracker_callbacks_["drds_routes"]( universal_name_matcher); const auto& dump = diff --git a/test/extensions/filters/network/thrift_proxy/config_test.cc b/test/extensions/filters/network/thrift_proxy/config_test.cc index d048f12bf70a..14128f7dd4ed 100644 --- a/test/extensions/filters/network/thrift_proxy/config_test.cc +++ b/test/extensions/filters/network/thrift_proxy/config_test.cc @@ -275,8 +275,9 @@ version_info: "1" TestUtility::parseYaml(response_yaml); const auto decoded_resources = TestUtility::decodeResources< envoy::extensions::filters::network::thrift_proxy::v3::RouteConfiguration>(response); - context_.server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ - ->onConfigUpdate(decoded_resources.refvec_, response.version_info()); + EXPECT_TRUE(context_.server_factory_context_.cluster_manager_.subscription_factory_.callbacks_ + ->onConfigUpdate(decoded_resources.refvec_, response.version_info()) + .ok()); auto message_ptr = context_.admin_.config_tracker_.config_tracker_callbacks_["trds_routes"]( universal_name_matcher); const auto& dump = diff --git a/test/extensions/listener_managers/listener_manager/lds_api_test.cc b/test/extensions/listener_managers/listener_manager/lds_api_test.cc index 17b7e4b56c97..562b82212443 100644 --- a/test/extensions/listener_managers/listener_manager/lds_api_test.cc +++ b/test/extensions/listener_managers/listener_manager/lds_api_test.cc @@ -132,8 +132,8 @@ TEST_F(LdsApiTest, MisconfiguredListenerNameIsPresentInException) { const auto decoded_resources = TestUtility::decodeResources({listener}); EXPECT_THROW_WITH_MESSAGE( - lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), EnvoyException, - "Error adding/updating listener(s) invalid-listener: something is wrong\n"); + EXPECT_TRUE(lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), + EnvoyException, "Error adding/updating listener(s) invalid-listener: something is wrong\n"); } TEST_F(LdsApiTest, EmptyListenersUpdate) { @@ -151,7 +151,7 @@ TEST_F(LdsApiTest, EmptyListenersUpdate) { ; EXPECT_CALL(init_watcher_, ready()); - lds_callbacks_->onConfigUpdate({}, ""); + EXPECT_TRUE(lds_callbacks_->onConfigUpdate({}, "").ok()); } TEST_F(LdsApiTest, ListenerCreationContinuesEvenAfterException) { @@ -182,10 +182,11 @@ TEST_F(LdsApiTest, ListenerCreationContinuesEvenAfterException) { const auto decoded_resources = TestUtility::decodeResources({listener_0, listener_1, listener_2, listener_3}); - EXPECT_THROW_WITH_MESSAGE(lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), - EnvoyException, - "Error adding/updating listener(s) invalid-listener-1: something is " - "wrong\ninvalid-listener-2: something else is wrong\n"); + EXPECT_THROW_WITH_MESSAGE( + EXPECT_TRUE(lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), + EnvoyException, + "Error adding/updating listener(s) invalid-listener-1: something is " + "wrong\ninvalid-listener-2: something else is wrong\n"); } // Validate onConfigUpdate throws EnvoyException with duplicate listeners. @@ -207,10 +208,11 @@ TEST_F(LdsApiTest, ValidateDuplicateListeners) { EXPECT_CALL(init_watcher_, ready()); const auto decoded_resources = TestUtility::decodeResources({listener, listener}); - EXPECT_THROW_WITH_MESSAGE(lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, ""), - EnvoyException, - "Error adding/updating listener(s) duplicate_listener: duplicate " - "listener duplicate_listener found\n"); + EXPECT_THROW_WITH_MESSAGE( + EXPECT_TRUE(lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()), + EnvoyException, + "Error adding/updating listener(s) duplicate_listener: duplicate " + "listener duplicate_listener found\n"); } TEST_F(LdsApiTest, Basic) { @@ -247,7 +249,8 @@ TEST_F(LdsApiTest, Basic) { EXPECT_CALL(init_watcher_, ready()); const auto decoded_resources = TestUtility::decodeResources(response1); - lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); EXPECT_EQ("0", lds_->versionInfo()); @@ -280,7 +283,8 @@ TEST_F(LdsApiTest, Basic) { EXPECT_CALL(listener_manager_, endListenerUpdate(_)); const auto decoded_resources_2 = TestUtility::decodeResources(response2); - lds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()); + EXPECT_TRUE( + lds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()).ok()); EXPECT_EQ("1", lds_->versionInfo()); } @@ -313,7 +317,8 @@ TEST_F(LdsApiTest, UpdateVersionOnListenerRemove) { EXPECT_CALL(init_watcher_, ready()); const auto decoded_resources = TestUtility::decodeResources(response1); - lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); EXPECT_EQ("0", lds_->versionInfo()); @@ -331,7 +336,8 @@ TEST_F(LdsApiTest, UpdateVersionOnListenerRemove) { EXPECT_CALL(listener_manager_, endListenerUpdate(_)); const auto decoded_resources_2 = TestUtility::decodeResources(response2); - lds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()); + EXPECT_TRUE( + lds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()).ok()); EXPECT_EQ("1", lds_->versionInfo()); } @@ -363,7 +369,8 @@ version_info: '1' EXPECT_CALL(init_watcher_, ready()); const auto decoded_resources = TestUtility::decodeResources(response1); - lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); constexpr absl::string_view response2_basic = R"EOF( version_info: '1' @@ -402,8 +409,8 @@ version_info: '1' EXPECT_CALL(listener_manager_, endListenerUpdate(_)); const auto decoded_resources_2 = TestUtility::decodeResources(response2); - EXPECT_NO_THROW( - lds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info())); + EXPECT_NO_THROW(EXPECT_TRUE( + lds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()).ok())); } // Validate behavior when the config fails delivery at the subscription level. @@ -451,7 +458,8 @@ TEST_F(LdsApiTest, ReplacingListenerWithSameAddress) { EXPECT_CALL(init_watcher_, ready()); const auto decoded_resources = TestUtility::decodeResources(response1); - lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()); + EXPECT_TRUE( + lds_callbacks_->onConfigUpdate(decoded_resources.refvec_, response1.version_info()).ok()); EXPECT_EQ("0", lds_->versionInfo()); @@ -484,7 +492,8 @@ TEST_F(LdsApiTest, ReplacingListenerWithSameAddress) { EXPECT_CALL(listener_manager_, endListenerUpdate(_)); const auto decoded_resources_2 = TestUtility::decodeResources(response2); - lds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()); + EXPECT_TRUE( + lds_callbacks_->onConfigUpdate(decoded_resources_2.refvec_, response2.version_info()).ok()); } } // namespace diff --git a/test/mocks/config/mocks.h b/test/mocks/config/mocks.h index d6fe23e3c3f9..ef9897f9cb2b 100644 --- a/test/mocks/config/mocks.h +++ b/test/mocks/config/mocks.h @@ -25,9 +25,9 @@ class MockSubscriptionCallbacks : public SubscriptionCallbacks { MockSubscriptionCallbacks(); ~MockSubscriptionCallbacks() override; - MOCK_METHOD(void, onConfigUpdate, + MOCK_METHOD(absl::Status, onConfigUpdate, (const std::vector& resources, const std::string& version_info)); - MOCK_METHOD(void, onConfigUpdate, + MOCK_METHOD(absl::Status, onConfigUpdate, (const std::vector& added_resources, const Protobuf::RepeatedPtrField& removed_resources, const std::string& system_version_info)); diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index 931aaab8d50d..e7e3e7af36bb 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -557,7 +557,7 @@ class MockRouteConfigProvider : public RouteConfigProvider { MOCK_METHOD(Rds::ConfigConstSharedPtr, config, (), (const)); MOCK_METHOD(const absl::optional&, configInfo, (), (const)); MOCK_METHOD(SystemTime, lastUpdated, (), (const)); - MOCK_METHOD(void, onConfigUpdate, ()); + MOCK_METHOD(absl::Status, onConfigUpdate, ()); MOCK_METHOD(ConfigConstSharedPtr, configCast, (), (const)); MOCK_METHOD(void, requestVirtualHostsUpdate, (const std::string&, Event::Dispatcher&, diff --git a/test/server/admin/admin_test.cc b/test/server/admin/admin_test.cc index 6ce194505fa3..e8c83b9a9f3c 100644 --- a/test/server/admin/admin_test.cc +++ b/test/server/admin/admin_test.cc @@ -323,7 +323,7 @@ TEST_P(AdminInstanceTest, Overrides) { peer.routeConfigProvider().config(); peer.routeConfigProvider().configInfo(); peer.routeConfigProvider().lastUpdated(); - peer.routeConfigProvider().onConfigUpdate(); + ASSERT_TRUE(peer.routeConfigProvider().onConfigUpdate().ok()); peer.scopedRouteConfigProvider().lastUpdated(); peer.scopedRouteConfigProvider().getConfigProto(); diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 951db5311eb5..29be5380afed 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -86,6 +86,8 @@ paths: # Header files that can throw exceptions. These should be limited; the only # valid situation identified so far is template functions used for config # processing. + - envoy/common/exception.h + - source/common/filter/config_discovery_impl.h - source/common/config/utility.h - source/common/matcher/map_matcher.h - source/common/matcher/field_matcher.h From 83a7d934cdb98ca8779a9c72795ef2da0345f61c Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Thu, 7 Sep 2023 19:48:18 +0300 Subject: [PATCH 23/55] udp_session_filters: add API for session filters and apply in UDP proxy (#29366) Signed-off-by: ohadvano --- .../filters/udp/udp_proxy/v3/udp_proxy.proto | 18 +- changelogs/current.yaml | 4 + .../listeners/udp_filters/udp_proxy.rst | 8 + source/extensions/filters/udp/udp_proxy/BUILD | 3 + .../filters/udp/udp_proxy/config.cc | 14 + .../extensions/filters/udp/udp_proxy/config.h | 13 +- .../udp/udp_proxy/session_filters/filter.h | 18 +- .../filters/udp/udp_proxy/udp_proxy_filter.cc | 122 +++++-- .../filters/udp/udp_proxy/udp_proxy_filter.h | 79 ++++- test/extensions/filters/udp/udp_proxy/BUILD | 4 + test/extensions/filters/udp/udp_proxy/mocks.h | 1 + .../udp/udp_proxy/session_filters/BUILD | 20 ++ .../session_filters/drainer_filter.h | 177 ++++++++++ .../session_filters/drainer_filter.proto | 24 ++ .../udp_proxy/udp_proxy_integration_test.cc | 311 ++++++++++++++++-- 15 files changed, 749 insertions(+), 67 deletions(-) create mode 100644 test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.h create mode 100644 test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.proto diff --git a/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto b/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto index c9eb7316b60e..4e4ecfc60ff3 100644 --- a/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto +++ b/api/envoy/extensions/filters/udp/udp_proxy/v3/udp_proxy.proto @@ -5,6 +5,7 @@ package envoy.extensions.filters.udp.udp_proxy.v3; import "envoy/config/accesslog/v3/accesslog.proto"; import "envoy/config/core/v3/udp_socket_config.proto"; +import "google/protobuf/any.proto"; import "google/protobuf/duration.proto"; import "xds/annotations/v3/status.proto"; @@ -26,7 +27,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.filters.udp_listener.udp_proxy] // Configuration for the UDP proxy filter. -// [#next-free-field: 11] +// [#next-free-field: 12] message UdpProxyConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.udp.udp_proxy.v2alpha.UdpProxyConfig"; @@ -49,6 +50,18 @@ message UdpProxyConfig { } } + // Configuration for UDP session filters. + message SessionFilter { + // The name of the filter configuration. + string name = 1 [(validate.rules).string = {min_len: 1}]; + + oneof config_type { + // Filter specific configuration which depends on the filter being + // instantiated. See the supported filters for further documentation. + google.protobuf.Any typed_config = 2; + } + } + // The stat prefix used when emitting UDP proxy filter stats. string stat_prefix = 1 [(validate.rules).string = {min_len: 1}]; @@ -110,4 +123,7 @@ message UdpProxyConfig { // Configuration for proxy access logs emitted by the UDP proxy. Note that certain UDP specific data is emitted as :ref:`Dynamic Metadata `. repeated config.accesslog.v3.AccessLog proxy_access_log = 10; + + // Optional session filters that will run for each UDP session. + repeated SessionFilter session_filters = 11; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 6e6d5752fe92..2091179036be 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -197,6 +197,10 @@ new_features: change: | added :ref:`custom_sink ` type to enable writing tap data out to a custom sink extension. +- area: udp_proxy + change: | + added :ref:`session_filters ` config to + support optional filters that will run for each upstream UDP session. More information can be found in the UDP proxy documentation. - area: otlp_stats_sink change: | added :ref:` stats prefix option` diff --git a/docs/root/configuration/listeners/udp_filters/udp_proxy.rst b/docs/root/configuration/listeners/udp_filters/udp_proxy.rst index 9de4896fbc24..c4f0f040af08 100644 --- a/docs/root/configuration/listeners/udp_filters/udp_proxy.rst +++ b/docs/root/configuration/listeners/udp_filters/udp_proxy.rst @@ -74,6 +74,14 @@ remaining datagrams to different clusters according to their source ports. :lines: 14-53 :caption: :download:`udp-proxy-router.yaml <_include/udp-proxy-router.yaml>` +Session filters +--------------- + +The UDP proxy is able to apply :ref:`session filters `. +These kinds of filters run seprately on each upstream UDP session and support a more granular API that allows running operations only +at the start of an upstream UDP session, when a UDP datagram is received from the downstream and when a UDP datagram is received from the +upstream, similar to network filters. + Example configuration --------------------- diff --git a/source/extensions/filters/udp/udp_proxy/BUILD b/source/extensions/filters/udp/udp_proxy/BUILD index 15cd9b4f0cdd..f0f070f85836 100644 --- a/source/extensions/filters/udp/udp_proxy/BUILD +++ b/source/extensions/filters/udp/udp_proxy/BUILD @@ -36,6 +36,7 @@ envoy_cc_library( "//source/common/access_log:access_log_lib", "//source/common/api:os_sys_calls_lib", "//source/common/common:empty_string", + "//source/common/common:linked_object", "//source/common/common:random_generator_lib", "//source/common/network:socket_lib", "//source/common/network:socket_option_factory_lib", @@ -43,6 +44,8 @@ envoy_cc_library( "//source/common/stream_info:stream_info_lib", "//source/common/upstream:load_balancer_lib", "//source/extensions/filters/udp/udp_proxy/router:router_lib", + "//source/extensions/filters/udp/udp_proxy/session_filters:filter_config_interface", + "//source/extensions/filters/udp/udp_proxy/session_filters:filter_interface", "@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/udp/udp_proxy/v3:pkg_cc_proto", ], diff --git a/source/extensions/filters/udp/udp_proxy/config.cc b/source/extensions/filters/udp/udp_proxy/config.cc index 945de66fe9df..da1fcb56419f 100644 --- a/source/extensions/filters/udp/udp_proxy/config.cc +++ b/source/extensions/filters/udp/udp_proxy/config.cc @@ -38,6 +38,20 @@ UdpProxyFilterConfigImpl::UdpProxyFilterConfigImpl( if (!config.hash_policies().empty()) { hash_policy_ = std::make_unique(config.hash_policies()); } + + for (const auto& filter : config.session_filters()) { + ENVOY_LOG(debug, " UDP session filter #{}", filter_factories_.size()); + ENVOY_LOG(debug, " name: {}", filter.name()); + ENVOY_LOG(debug, " config: {}", + MessageUtil::getJsonStringFromMessageOrError( + static_cast(filter.typed_config()), true)); + + auto& factory = Config::Utility::getAndCheckFactory(filter); + ProtobufTypes::MessagePtr message = Envoy::Config::Utility::translateToFactoryConfig( + filter, context.messageValidationVisitor(), factory); + FilterFactoryCb callback = factory.createFilterFactoryFromProto(*message, context); + filter_factories_.push_back(callback); + } } static Registry::RegisterFactory { +class UdpProxyFilterConfigImpl : public UdpProxyFilterConfig, + public FilterChainFactory, + Logger::Loggable { public: UdpProxyFilterConfigImpl( Server::Configuration::ListenerFactoryContext& context, @@ -42,6 +44,14 @@ class UdpProxyFilterConfigImpl : public UdpProxyFilterConfig, Logger::Loggable& proxyAccessLogs() const override { return proxy_access_logs_; } + const FilterChainFactory& sessionFilterFactory() const override { return *this; }; + + // FilterChainFactory + void createFilterChain(FilterChainFactoryCallbacks& callbacks) const override { + for (const FilterFactoryCb& factory : filter_factories_) { + factory(callbacks); + } + }; private: static UdpProxyDownstreamStats generateStats(const std::string& stat_prefix, @@ -63,6 +73,7 @@ class UdpProxyFilterConfigImpl : public UdpProxyFilterConfig, Logger::Loggable session_access_logs_; std::vector proxy_access_logs_; Random::RandomGenerator& random_; + std::list filter_factories_; }; /** diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/filter.h b/source/extensions/filters/udp/udp_proxy/session_filters/filter.h index 3710631356b9..a3f434a8c245 100644 --- a/source/extensions/filters/udp/udp_proxy/session_filters/filter.h +++ b/source/extensions/filters/udp/udp_proxy/session_filters/filter.h @@ -28,7 +28,17 @@ class FilterCallbacks { virtual StreamInfo::StreamInfo& streamInfo() PURE; }; -class ReadFilterCallbacks : public FilterCallbacks {}; +class ReadFilterCallbacks : public FilterCallbacks { +public: + ~ReadFilterCallbacks() override = default; + + /** + * If a read filter stopped filter iteration, continueFilterChain() can be called to continue the + * filter chain. It will have onNewSession() called if it was not previously called. + */ + virtual void continueFilterChain() PURE; +}; + class WriteFilterCallbacks : public FilterCallbacks {}; /** @@ -50,7 +60,7 @@ class ReadFilter { /** * Called when a new UDP session is first established. Filters should do one time long term - * processing that needs to be done when a connection is established. Filter chain iteration + * processing that needs to be done when a session is established. Filter chain iteration * can be stopped if needed. * @return status used by the filter manager to manage further filter iteration. */ @@ -68,7 +78,7 @@ class ReadFilter { * called by the filter manager a single time when the filter is first registered. * * IMPORTANT: No outbound networking or complex processing should be done in this function. - * That should be done in the context of onNewConnection() if needed. + * That should be done in the context of onNewSession() if needed. * * @param callbacks supplies the callbacks. */ @@ -106,7 +116,7 @@ class WriteFilter { * called by the filter manager a single time when the filter is first registered. * * IMPORTANT: No outbound networking or complex processing should be done in this function. - * That should be done in the context of ReadFilter::onNewConnection() if needed. + * That should be done in the context of ReadFilter::onNewSession() if needed. * * @param callbacks supplies the callbacks. */ diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc index 259823e7967f..b5c1697f1944 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc @@ -156,6 +156,8 @@ UdpProxyFilter::ActiveSession* UdpProxyFilter::ClusterInfo::createSessionWithHos const Upstream::HostConstSharedPtr& host) { ASSERT(host); auto new_session = std::make_unique(*this, std::move(addresses), host); + new_session->createFilterChain(); + new_session->onNewSession(); auto new_session_ptr = new_session.get(); sessions_.emplace(std::move(new_session)); host_to_sessions_[host.get()].emplace(new_session_ptr); @@ -202,7 +204,7 @@ Network::FilterStatus UdpProxyFilter::StickySessionClusterInfo::onData(Network:: } } - active_session->write(*data.buffer_); + active_session->onData(data); return Network::FilterStatus::StopIteration; } @@ -238,11 +240,13 @@ UdpProxyFilter::PerPacketLoadBalancingClusterInfo::onData(Network::UdpRecvData& active_session->host().address()->asStringView()); } - active_session->write(*data.buffer_); + active_session->onData(data); return Network::FilterStatus::StopIteration; } +std::atomic UdpProxyFilter::ActiveSession::next_global_session_id_; + UdpProxyFilter::ActiveSession::ActiveSession(ClusterInfo& cluster, Network::UdpRecvData::LocalPeerAddresses&& addresses, const Upstream::HostConstSharedPtr& host) @@ -254,7 +258,8 @@ UdpProxyFilter::ActiveSession::ActiveSession(ClusterInfo& cluster, // is bound until the first packet is sent to the upstream host. socket_(cluster.filter_.createSocket(host)), udp_session_info_( - StreamInfo::StreamInfoImpl(cluster_.filter_.config_->timeSource(), nullptr)) { + StreamInfo::StreamInfoImpl(cluster_.filter_.config_->timeSource(), nullptr)), + session_id_(next_global_session_id_++) { socket_->ioHandle().initializeFileEvent( cluster.filter_.read_callbacks_->udpListener().dispatcher(), @@ -375,13 +380,30 @@ void UdpProxyFilter::ActiveSession::onReadReady() { cluster_.filter_.read_callbacks_->udpListener().flush(); } -void UdpProxyFilter::ActiveSession::write(const Buffer::Instance& buffer) { - ENVOY_LOG(trace, "writing {} byte datagram upstream: downstream={} local={} upstream={}", - buffer.length(), addresses_.peer_->asStringView(), addresses_.local_->asStringView(), - host_->address()->asStringView()); - const uint64_t buffer_length = buffer.length(); - cluster_.filter_.config_->stats().downstream_sess_rx_bytes_.add(buffer_length); - session_stats_.downstream_sess_rx_bytes_ += buffer_length; +void UdpProxyFilter::ActiveSession::onNewSession() { + for (auto& active_read_filter : read_filters_) { + if (active_read_filter->initialized_) { + // The filter may call continueFilterChain() in onNewSession(), causing next + // filters to iterate onNewSession(), so check that it was not called before. + continue; + } + + active_read_filter->initialized_ = true; + auto status = active_read_filter->read_filter_->onNewSession(); + if (status == ReadFilterStatus::StopIteration) { + return; + } + } +} + +void UdpProxyFilter::ActiveSession::onData(Network::UdpRecvData& data) { + ENVOY_LOG(trace, "received {} byte datagram from downstream: downstream={} local={} upstream={}", + data.buffer_->length(), addresses_.peer_->asStringView(), + addresses_.local_->asStringView(), host_->address()->asStringView()); + + const uint64_t rx_buffer_length = data.buffer_->length(); + cluster_.filter_.config_->stats().downstream_sess_rx_bytes_.add(rx_buffer_length); + session_stats_.downstream_sess_rx_bytes_ += rx_buffer_length; cluster_.filter_.config_->stats().downstream_sess_rx_datagrams_.inc(); ++session_stats_.downstream_sess_rx_datagrams_; @@ -395,7 +417,6 @@ void UdpProxyFilter::ActiveSession::write(const Buffer::Instance& buffer) { // NOTE: We do not specify the local IP to use for the sendmsg call if use_original_src_ip_ is not // set. We allow the OS to select the right IP based on outbound routing rules if // use_original_src_ip_ is not set, else use downstream peer IP as local IP. - const Network::Address::Ip* local_ip = use_original_src_ip_ ? addresses_.peer_->ip() : nullptr; if (!use_original_src_ip_ && !connected_) { Api::SysCallIntResult rc = socket_->ioHandle().connect(host_->address()); if (SOCKET_FAILURE(rc.return_value_)) { @@ -406,35 +427,88 @@ void UdpProxyFilter::ActiveSession::write(const Buffer::Instance& buffer) { connected_ = true; } - Api::IoCallUint64Result rc = - Network::Utility::writeToSocket(socket_->ioHandle(), buffer, local_ip, *host_->address()); + + for (auto& active_read_filter : read_filters_) { + auto status = active_read_filter->read_filter_->onData(data); + if (status == ReadFilterStatus::StopIteration) { + return; + } + } + + writeUpstream(data); +} + +void UdpProxyFilter::ActiveSession::writeUpstream(Network::UdpRecvData& data) { + ASSERT(connected_ || use_original_src_ip_); + + const uint64_t tx_buffer_length = data.buffer_->length(); + ENVOY_LOG(trace, "writing {} byte datagram upstream: downstream={} local={} upstream={}", + tx_buffer_length, addresses_.peer_->asStringView(), addresses_.local_->asStringView(), + host_->address()->asStringView()); + + const Network::Address::Ip* local_ip = use_original_src_ip_ ? addresses_.peer_->ip() : nullptr; + Api::IoCallUint64Result rc = Network::Utility::writeToSocket(socket_->ioHandle(), *data.buffer_, + local_ip, *host_->address()); + if (!rc.ok()) { cluster_.cluster_stats_.sess_tx_errors_.inc(); } else { cluster_.cluster_stats_.sess_tx_datagrams_.inc(); - cluster_.cluster_.info()->trafficStats()->upstream_cx_tx_bytes_total_.add(buffer_length); + cluster_.cluster_.info()->trafficStats()->upstream_cx_tx_bytes_total_.add(tx_buffer_length); } } -void UdpProxyFilter::ActiveSession::processPacket(Network::Address::InstanceConstSharedPtr, - Network::Address::InstanceConstSharedPtr, - Buffer::InstancePtr buffer, MonotonicTime) { - ENVOY_LOG(trace, "writing {} byte datagram downstream: downstream={} local={} upstream={}", - buffer->length(), addresses_.peer_->asStringView(), addresses_.local_->asStringView(), +void UdpProxyFilter::ActiveSession::onContinueFilterChain(ActiveReadFilter* filter) { + ASSERT(filter != nullptr); + + std::list::iterator entry = std::next(filter->entry()); + for (; entry != read_filters_.end(); entry++) { + if (!(*entry)->read_filter_ || (*entry)->initialized_) { + continue; + } + + (*entry)->initialized_ = true; + auto status = (*entry)->read_filter_->onNewSession(); + if (status == ReadFilterStatus::StopIteration) { + break; + } + } +} + +void UdpProxyFilter::ActiveSession::processPacket( + Network::Address::InstanceConstSharedPtr local_address, + Network::Address::InstanceConstSharedPtr peer_address, Buffer::InstancePtr buffer, + MonotonicTime receive_time) { + const uint64_t rx_buffer_length = buffer->length(); + ENVOY_LOG(trace, "received {} byte datagram from upstream: downstream={} local={} upstream={}", + rx_buffer_length, addresses_.peer_->asStringView(), addresses_.local_->asStringView(), host_->address()->asStringView()); - const uint64_t buffer_length = buffer->length(); cluster_.cluster_stats_.sess_rx_datagrams_.inc(); - cluster_.cluster_.info()->trafficStats()->upstream_cx_rx_bytes_total_.add(buffer_length); + cluster_.cluster_.info()->trafficStats()->upstream_cx_rx_bytes_total_.add(rx_buffer_length); + + Network::UdpRecvData recv_data{ + {std::move(local_address), std::move(peer_address)}, std::move(buffer), receive_time}; + for (auto& active_write_filter : write_filters_) { + auto status = active_write_filter->write_filter_->onWrite(recv_data); + if (status == WriteFilterStatus::StopIteration) { + return; + } + } + + const uint64_t tx_buffer_length = recv_data.buffer_->length(); + ENVOY_LOG(trace, "writing {} byte datagram downstream: downstream={} local={} upstream={}", + tx_buffer_length, addresses_.peer_->asStringView(), addresses_.local_->asStringView(), + host_->address()->asStringView()); - Network::UdpSendData data{addresses_.local_->ip(), *addresses_.peer_, *buffer}; + Network::UdpSendData data{addresses_.local_->ip(), *addresses_.peer_, *recv_data.buffer_}; const Api::IoCallUint64Result rc = cluster_.filter_.read_callbacks_->udpListener().send(data); if (!rc.ok()) { cluster_.filter_.config_->stats().downstream_sess_tx_errors_.inc(); ++session_stats_.downstream_sess_tx_errors_; } else { - cluster_.filter_.config_->stats().downstream_sess_tx_bytes_.add(buffer_length); - session_stats_.downstream_sess_tx_bytes_ += buffer_length; + cluster_.filter_.config_->stats().downstream_sess_tx_bytes_.add(tx_buffer_length); + session_stats_.downstream_sess_tx_bytes_ += tx_buffer_length; cluster_.filter_.config_->stats().downstream_sess_tx_datagrams_.inc(); ++session_stats_.downstream_sess_tx_datagrams_; } diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h index f7f840149fe6..5b3c38bdc24d 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h @@ -12,6 +12,7 @@ #include "source/common/access_log/access_log_impl.h" #include "source/common/api/os_sys_calls_impl.h" #include "source/common/common/empty_string.h" +#include "source/common/common/linked_object.h" #include "source/common/common/random_generator.h" #include "source/common/network/socket_impl.h" #include "source/common/network/socket_interface.h" @@ -21,6 +22,8 @@ #include "source/common/upstream/load_balancer_impl.h" #include "source/extensions/filters/udp/udp_proxy/hash_policy_impl.h" #include "source/extensions/filters/udp/udp_proxy/router/router_impl.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/filter.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/filter_config.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" @@ -30,6 +33,8 @@ namespace Extensions { namespace UdpFilters { namespace UdpProxy { +using namespace UdpProxy::SessionFilters; + /** * All UDP proxy downstream stats. @see stats_macros.h */ @@ -87,6 +92,7 @@ class UdpProxyFilterConfig { virtual const Network::ResolvedUdpSocketConfig& upstreamSocketConfig() const PURE; virtual const std::vector& sessionAccessLogs() const PURE; virtual const std::vector& proxyAccessLogs() const PURE; + virtual const FilterChainFactory& sessionFilterFactory() const PURE; }; using UdpProxyFilterConfigSharedPtr = std::shared_ptr; @@ -122,8 +128,39 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, Network::FilterStatus onReceiveError(Api::IoError::IoErrorCode error_code) override; private: + class ActiveSession; class ClusterInfo; + struct ActiveReadFilter : public virtual ReadFilterCallbacks, LinkedObject { + ActiveReadFilter(ActiveSession& parent, ReadFilterSharedPtr filter) + : parent_(parent), read_filter_(std::move(filter)) {} + + // SessionFilters::ReadFilterCallbacks + uint64_t sessionId() const override { return parent_.sessionId(); }; + StreamInfo::StreamInfo& streamInfo() override { return parent_.streamInfo(); }; + void continueFilterChain() override { parent_.onContinueFilterChain(this); } + + ActiveSession& parent_; + ReadFilterSharedPtr read_filter_; + bool initialized_{false}; + }; + + using ActiveReadFilterPtr = std::unique_ptr; + + struct ActiveWriteFilter : public virtual WriteFilterCallbacks, LinkedObject { + ActiveWriteFilter(ActiveSession& parent, WriteFilterSharedPtr filter) + : parent_(parent), write_filter_(std::move(filter)) {} + + // SessionFilters::WriteFilterCallbacks + uint64_t sessionId() const override { return parent_.sessionId(); }; + StreamInfo::StreamInfo& streamInfo() override { return parent_.streamInfo(); }; + + ActiveSession& parent_; + WriteFilterSharedPtr write_filter_; + }; + + using ActiveWriteFilterPtr = std::unique_ptr; + /** * An active session is similar to a TCP connection. It binds a 4-tuple (downstream IP/port, local * IP/port) to a selected upstream host for the purpose of packet forwarding. Unlike a TCP @@ -132,14 +169,47 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, * will be hashed to the same session and will be forwarded to the same upstream, using the same * local ephemeral IP/port. */ - class ActiveSession : public Network::UdpPacketProcessor { + class ActiveSession : public Network::UdpPacketProcessor, public FilterChainFactoryCallbacks { public: ActiveSession(ClusterInfo& parent, Network::UdpRecvData::LocalPeerAddresses&& addresses, const Upstream::HostConstSharedPtr& host); ~ActiveSession() override; const Network::UdpRecvData::LocalPeerAddresses& addresses() const { return addresses_; } const Upstream::Host& host() const { return *host_; } - void write(const Buffer::Instance& buffer); + void onNewSession(); + void onData(Network::UdpRecvData& data); + void writeUpstream(Network::UdpRecvData& data); + + void createFilterChain() { + cluster_.filter_.config_->sessionFilterFactory().createFilterChain(*this); + } + + uint64_t sessionId() const { return session_id_; }; + StreamInfo::StreamInfo& streamInfo() { return udp_session_info_; }; + void onContinueFilterChain(ActiveReadFilter* filter); + + // SessionFilters::FilterChainFactoryCallbacks + void addReadFilter(ReadFilterSharedPtr filter) override { + ActiveReadFilterPtr wrapper = std::make_unique(*this, filter); + filter->initializeReadFilterCallbacks(*wrapper); + LinkedList::moveIntoListBack(std::move(wrapper), read_filters_); + }; + + void addWriteFilter(WriteFilterSharedPtr filter) override { + ActiveWriteFilterPtr wrapper = std::make_unique(*this, filter); + filter->initializeWriteFilterCallbacks(*wrapper); + LinkedList::moveIntoList(std::move(wrapper), write_filters_); + }; + + void addFilter(FilterSharedPtr filter) override { + ActiveReadFilterPtr read_wrapper = std::make_unique(*this, filter); + filter->initializeReadFilterCallbacks(*read_wrapper); + LinkedList::moveIntoListBack(std::move(read_wrapper), read_filters_); + + ActiveWriteFilterPtr write_wrapper = std::make_unique(*this, filter); + filter->initializeWriteFilterCallbacks(*write_wrapper); + LinkedList::moveIntoList(std::move(write_wrapper), write_filters_); + }; private: void onIdleTimer(); @@ -173,6 +243,8 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, uint64_t downstream_sess_rx_datagrams_; }; + static std::atomic next_global_session_id_; + ClusterInfo& cluster_; const bool use_original_src_ip_; const Network::UdpRecvData::LocalPeerAddresses addresses_; @@ -192,6 +264,9 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, UdpProxySessionStats session_stats_{}; StreamInfo::StreamInfoImpl udp_session_info_; + uint64_t session_id_; + std::list read_filters_; + std::list write_filters_; }; using ActiveSessionPtr = std::unique_ptr; diff --git a/test/extensions/filters/udp/udp_proxy/BUILD b/test/extensions/filters/udp/udp_proxy/BUILD index 886dd26c5cba..9ee76b603005 100644 --- a/test/extensions/filters/udp/udp_proxy/BUILD +++ b/test/extensions/filters/udp/udp_proxy/BUILD @@ -87,6 +87,10 @@ envoy_extension_cc_test( "//envoy/network:filter_interface", "//envoy/server:filter_config_interface", "//source/extensions/filters/udp/udp_proxy:config", + "//source/extensions/filters/udp/udp_proxy/session_filters:filter_config_interface", + "//source/extensions/filters/udp/udp_proxy/session_filters:filter_interface", + "//test/extensions/filters/udp/udp_proxy/session_filters:drainer_filter_config_lib", + "//test/extensions/filters/udp/udp_proxy/session_filters:drainer_filter_proto_cc_proto", "//test/integration:integration_lib", "//test/test_common:registry_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", diff --git a/test/extensions/filters/udp/udp_proxy/mocks.h b/test/extensions/filters/udp/udp_proxy/mocks.h index 74ee29a397dc..9f24496564a5 100644 --- a/test/extensions/filters/udp/udp_proxy/mocks.h +++ b/test/extensions/filters/udp/udp_proxy/mocks.h @@ -21,6 +21,7 @@ class MockReadFilterCallbacks : public ReadFilterCallbacks { MOCK_METHOD(uint64_t, sessionId, (), (const)); MOCK_METHOD(StreamInfo::StreamInfo&, streamInfo, ()); + MOCK_METHOD(void, continueFilterChain, ()); uint64_t session_id_{1}; NiceMock stream_info_; diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/BUILD b/test/extensions/filters/udp/udp_proxy/session_filters/BUILD index 0107bab83bc8..c6feadb8a612 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/BUILD +++ b/test/extensions/filters/udp/udp_proxy/session_filters/BUILD @@ -1,6 +1,8 @@ load( "//bazel:envoy_build_system.bzl", + "envoy_cc_test_library", "envoy_package", + "envoy_proto_library", ) load( "//test/extensions:extensions_build_system.bzl", @@ -20,3 +22,21 @@ envoy_extension_cc_test( "//test/extensions/filters/udp/udp_proxy:mocks", ], ) + +envoy_proto_library( + name = "drainer_filter_proto", + srcs = ["drainer_filter.proto"], +) + +envoy_cc_test_library( + name = "drainer_filter_config_lib", + srcs = ["drainer_filter.h"], + deps = [ + ":drainer_filter_proto_cc_proto", + "//envoy/registry", + "//source/extensions/filters/udp/udp_proxy/session_filters:factory_base_lib", + "//source/extensions/filters/udp/udp_proxy/session_filters:filter_interface", + "//test/test_common:utility_lib", + ], + alwayslink = 1, +) diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.h b/test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.h new file mode 100644 index 000000000000..22c940f8f1cd --- /dev/null +++ b/test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.h @@ -0,0 +1,177 @@ +#pragma once + +#include "envoy/registry/registry.h" + +#include "source/common/config/utility.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/factory_base.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/filter.h" + +#include "test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.pb.h" +#include "test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.pb.validate.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Extensions { +namespace UdpFilters { +namespace UdpProxy { +namespace SessionFilters { + +using ReadDrainerConfig = + test::extensions::filters::udp::udp_proxy::session_filters::DrainerUdpSessionReadFilterConfig; +using WriteDrainerConfig = + test::extensions::filters::udp::udp_proxy::session_filters::DrainerUdpSessionWriteFilterConfig; +using DrainerConfig = + test::extensions::filters::udp::udp_proxy::session_filters::DrainerUdpSessionFilterConfig; + +class DrainerUdpSessionReadFilter : public virtual ReadFilter { +public: + DrainerUdpSessionReadFilter(int downstream_bytes_to_drain, bool stop_iteration_on_new_session, + bool stop_iteration_on_first_read, bool continue_filter_chain) + : downstream_bytes_to_drain_(downstream_bytes_to_drain), + stop_iteration_on_new_session_(stop_iteration_on_new_session), + stop_iteration_on_first_read_(stop_iteration_on_first_read), + continue_filter_chain_(continue_filter_chain) {} + + ReadFilterStatus onNewSession() override { + if (stop_iteration_on_new_session_) { + // We can count how many times onNewSession was called on a filter chain + // by increasing the original bytes to drain value. + downstream_bytes_to_drain_++; + return ReadFilterStatus::StopIteration; + } else { + return ReadFilterStatus::Continue; + } + } + + ReadFilterStatus onData(Network::UdpRecvData& data) override { + data.buffer_->drain(downstream_bytes_to_drain_); + if (stop_iteration_on_first_read_) { + stop_iteration_on_first_read_ = false; + return ReadFilterStatus::StopIteration; + } else { + if (continue_filter_chain_) { + read_callbacks_->continueFilterChain(); + // Calling twice, to make sure that the second call does not have effect. + read_callbacks_->continueFilterChain(); + } + return ReadFilterStatus::Continue; + } + } + + void initializeReadFilterCallbacks(ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + session_id_ = callbacks.sessionId(); + } + +private: + int downstream_bytes_to_drain_; + bool stop_iteration_on_new_session_{false}; + bool stop_iteration_on_first_read_{false}; + bool continue_filter_chain_{false}; + uint64_t session_id_; + ReadFilterCallbacks* read_callbacks_; +}; + +class DrainerUdpSessionReadFilterConfigFactory : public FactoryBase { +public: + DrainerUdpSessionReadFilterConfigFactory() : FactoryBase("test.udp_session.drainer_read") {} + +private: + FilterFactoryCb + createFilterFactoryFromProtoTyped(const ReadDrainerConfig& config, + Server::Configuration::FactoryContext&) override { + return [config](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addReadFilter(std::make_unique( + config.downstream_bytes_to_drain(), config.stop_iteration_on_new_session(), + config.stop_iteration_on_first_read(), config.continue_filter_chain())); + }; + } +}; + +static Registry::RegisterFactory + register_drainer_udp_session_read_filter_; + +class DrainerUdpSessionWriteFilter : public virtual WriteFilter { +public: + DrainerUdpSessionWriteFilter(int upstream_bytes_to_drain, bool stop_iteration_on_first_write) + : upstream_bytes_to_drain_(upstream_bytes_to_drain), + stop_iteration_on_first_write_(stop_iteration_on_first_write) {} + + WriteFilterStatus onWrite(Network::UdpRecvData& data) override { + data.buffer_->drain(upstream_bytes_to_drain_); + if (stop_iteration_on_first_write_) { + stop_iteration_on_first_write_ = false; + return WriteFilterStatus::StopIteration; + } else { + return WriteFilterStatus::Continue; + } + } + + void initializeWriteFilterCallbacks(WriteFilterCallbacks& callbacks) override { + session_id_ = callbacks.sessionId(); + } + +private: + int upstream_bytes_to_drain_; + bool stop_iteration_on_first_write_{false}; + uint64_t session_id_; +}; + +class DrainerUdpSessionWriteFilterConfigFactory : public FactoryBase { +public: + DrainerUdpSessionWriteFilterConfigFactory() : FactoryBase("test.udp_session.drainer_write") {} + +private: + FilterFactoryCb + createFilterFactoryFromProtoTyped(const WriteDrainerConfig& config, + Server::Configuration::FactoryContext&) override { + return [config](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addWriteFilter(std::make_unique( + config.upstream_bytes_to_drain(), config.stop_iteration_on_first_write())); + }; + } +}; + +static Registry::RegisterFactory + register_drainer_udp_session_write_filter_; + +class DrainerUdpSessionFilter : public Filter, + public DrainerUdpSessionReadFilter, + public DrainerUdpSessionWriteFilter { +public: + DrainerUdpSessionFilter(int downstream_bytes_to_drain, int upstream_bytes_to_drain, + bool stop_iteration_on_new_session, bool stop_iteration_on_first_read, + bool continue_filter_chain, bool stop_iteration_on_first_write) + : DrainerUdpSessionReadFilter(downstream_bytes_to_drain, stop_iteration_on_new_session, + stop_iteration_on_first_read, continue_filter_chain), + DrainerUdpSessionWriteFilter(upstream_bytes_to_drain, stop_iteration_on_first_write) {} +}; + +class DrainerUdpSessionFilterConfigFactory : public FactoryBase { +public: + DrainerUdpSessionFilterConfigFactory() : FactoryBase("test.udp_session.drainer") {} + +private: + FilterFactoryCb + createFilterFactoryFromProtoTyped(const DrainerConfig& config, + Server::Configuration::FactoryContext&) override { + return [config](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addFilter(std::make_shared( + config.downstream_bytes_to_drain(), config.upstream_bytes_to_drain(), + config.stop_iteration_on_new_session(), config.stop_iteration_on_first_read(), + config.continue_filter_chain(), config.stop_iteration_on_first_write())); + }; + } +}; + +static Registry::RegisterFactory + register_drainer_udp_session_filter_; + +} // namespace SessionFilters +} // namespace UdpProxy +} // namespace UdpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.proto b/test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.proto new file mode 100644 index 000000000000..ca4f68e97fa3 --- /dev/null +++ b/test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package test.extensions.filters.udp.udp_proxy.session_filters; + +message DrainerUdpSessionReadFilterConfig { + uint32 downstream_bytes_to_drain = 1; + bool stop_iteration_on_new_session = 2; + bool stop_iteration_on_first_read = 3; + bool continue_filter_chain = 4; +} + +message DrainerUdpSessionWriteFilterConfig { + uint32 upstream_bytes_to_drain = 1; + bool stop_iteration_on_first_write = 2; +} + +message DrainerUdpSessionFilterConfig { + uint32 downstream_bytes_to_drain = 1; + uint32 upstream_bytes_to_drain = 2; + bool stop_iteration_on_new_session = 3; + bool stop_iteration_on_first_read = 4; + bool continue_filter_chain = 5; + bool stop_iteration_on_first_write = 6; +} diff --git a/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc b/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc index bc245e79435d..c917713475b7 100644 --- a/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc +++ b/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc @@ -4,12 +4,15 @@ #include "envoy/network/filter.h" #include "envoy/server/filter_config.h" +#include "test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.h" +#include "test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.pb.h" #include "test/integration/integration.h" #include "test/test_common/network_utility.h" #include "test/test_common/registry.h" namespace Envoy { namespace { + class UdpReverseFilter : public Network::UdpListenerReadFilter { public: UdpReverseFilter(Network::UdpReadFilterCallbacks& callbacks) : UdpListenerReadFilter(callbacks) {} @@ -55,10 +58,10 @@ class UdpProxyIntegrationTest : public testing::TestWithParam max_rx_datagram_size = absl::nullopt) { + void setup(uint32_t upstream_count, absl::optional max_rx_datagram_size = absl::nullopt, + const std::string& session_filters_config = "") { FakeUpstreamConfig::UdpConfig config; config.max_rx_datagram_size_ = max_rx_datagram_size; setUdpFakeUpstream(config); @@ -80,37 +83,26 @@ class UdpProxyIntegrationTest : public testing::TestWithParammutable_listeners(0) - ->mutable_udp_listener_config() - ->mutable_downstream_socket_config() - ->mutable_max_rx_datagram_size() - ->set_value(max_rx_datagram_size.value()); - }); - } - - config_helper_.addListenerFilter(fmt::format(R"EOF( -name: udp_proxy -typed_config: - '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig - stat_prefix: foo - matcher: - on_no_match: - action: - name: route - typed_config: - '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route - cluster: cluster_0 + max_datagram_config = fmt::format(R"EOF( upstream_socket_config: max_rx_datagram_size: {} )EOF", - max_rx_datagram_size.value())); - } else { - config_helper_.addListenerFilter(R"EOF( + max_rx_datagram_size.value()); + + config_helper_.addConfigModifier( + [max_rx_datagram_size](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_udp_listener_config() + ->mutable_downstream_socket_config() + ->mutable_max_rx_datagram_size() + ->set_value(max_rx_datagram_size.value()); + }); + } + + config_helper_.addListenerFilter(R"EOF( name: udp_proxy typed_config: '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.UdpProxyConfig @@ -122,12 +114,72 @@ name: udp_proxy typed_config: '@type': type.googleapis.com/envoy.extensions.filters.udp.udp_proxy.v3.Route cluster: cluster_0 -)EOF"); - } +)EOF" + max_datagram_config + session_filters_config); BaseIntegrationTest::initialize(); } + struct DrainFilterConfig { + std::string type_; + int downstream_bytes_to_drain_; + int upstream_bytes_to_drain_; + bool stop_iteration_on_new_session_{false}; + bool stop_iteration_on_first_read_{false}; + bool continue_filter_chain_{false}; + bool stop_iteration_on_first_write_{false}; + }; + + std::string getDrainerSessionFilterConfig(std::list session_filters_configs) { + std::string session_filters = R"EOF( + session_filters: +)EOF"; + + for (auto config : session_filters_configs) { + if (config.type_ == "read") { + session_filters += fmt::format( + R"EOF( + - name: {} + typed_config: + '@type': type.googleapis.com/test.extensions.filters.udp.udp_proxy.session_filters.DrainerUdpSessionReadFilterConfig + downstream_bytes_to_drain: {} + stop_iteration_on_new_session: {} + stop_iteration_on_first_read: {} + continue_filter_chain: {} +)EOF", + config.type_, config.downstream_bytes_to_drain_, config.stop_iteration_on_new_session_, + config.stop_iteration_on_first_read_, config.continue_filter_chain_); + } else if (config.type_ == "write") { + session_filters += fmt::format(R"EOF( + - name: {} + typed_config: + '@type': type.googleapis.com/test.extensions.filters.udp.udp_proxy.session_filters.DrainerUdpSessionWriteFilterConfig + upstream_bytes_to_drain: {} + stop_iteration_on_first_write: {} +)EOF", + config.type_, config.upstream_bytes_to_drain_, + config.stop_iteration_on_first_write_); + } else if (config.type_ == "read_write") { + session_filters += fmt::format( + R"EOF( + - name: {} + typed_config: + '@type': type.googleapis.com/test.extensions.filters.udp.udp_proxy.session_filters.DrainerUdpSessionFilterConfig + downstream_bytes_to_drain: {} + upstream_bytes_to_drain: {} + stop_iteration_on_new_session: {} + stop_iteration_on_first_read: {} + continue_filter_chain: {} + stop_iteration_on_first_write: {} +)EOF", + config.type_, config.downstream_bytes_to_drain_, config.upstream_bytes_to_drain_, + config.stop_iteration_on_new_session_, config.stop_iteration_on_first_read_, + config.continue_filter_chain_, config.stop_iteration_on_first_write_); + } + } + + return session_filters; + } + void setupMultiple() { FakeUpstreamConfig::UdpConfig config; config.max_rx_datagram_size_ = absl::nullopt; @@ -177,14 +229,13 @@ name: test.udp_listener.reverse EXPECT_EQ(expected_response, response_datagram.buffer_->toString()); EXPECT_EQ(listener_address.asString(), response_datagram.addresses_.peer_->asString()); - EXPECT_EQ(expected_request.size(), - test_server_->counter("udp.foo.downstream_sess_rx_bytes")->value()); + EXPECT_EQ(request.size(), test_server_->counter("udp.foo.downstream_sess_rx_bytes")->value()); EXPECT_EQ(1, test_server_->counter("udp.foo.downstream_sess_rx_datagrams")->value()); EXPECT_EQ(expected_request.size(), test_server_->counter("cluster.cluster_0.upstream_cx_tx_bytes_total")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.udp.sess_tx_datagrams")->value()); - EXPECT_EQ(expected_response.size(), + EXPECT_EQ(response.size(), test_server_->counter("cluster.cluster_0.upstream_cx_rx_bytes_total")->value()); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.udp.sess_rx_datagrams")->value()); // The stat is incremented after the send so there is a race condition and we must wait for @@ -198,6 +249,11 @@ name: test.udp_listener.reverse UdpReverseFilterConfigFactory factory_; Registry::InjectFactory registration_; + Extensions::UdpFilters::UdpProxy::SessionFilters::DrainerUdpSessionFilterConfigFactory + session_filter_factory_; + Registry::InjectFactory< + Extensions::UdpFilters::UdpProxy::SessionFilters::NamedUdpSessionFilterConfigFactory> + session_filter_registration_; }; INSTANTIATE_TEST_SUITE_P(IpVersions, UdpProxyIntegrationTest, @@ -387,5 +443,190 @@ TEST_P(UdpProxyIntegrationTest, MultipleFilters) { requestResponseWithListenerAddress(*listener_address, "hello", "olleh"); } +TEST_P(UdpProxyIntegrationTest, ReadSessionFilter) { + setup(1, absl::nullopt, getDrainerSessionFilterConfig({{"read", 3, 0}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + requestResponseWithListenerAddress(*listener_address, "hello", "lo", "world1", "world1"); +} + +TEST_P(UdpProxyIntegrationTest, TwoReadSessionFilters) { + setup(1, absl::nullopt, getDrainerSessionFilterConfig({{"read", 3, 0}, {"read", 1, 0}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + requestResponseWithListenerAddress(*listener_address, "hello", "o", "world1", "world1"); +} + +TEST_P(UdpProxyIntegrationTest, WriteSessionFilter) { + setup(1, absl::nullopt, getDrainerSessionFilterConfig({{"write", 0, 3}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + requestResponseWithListenerAddress(*listener_address, "hello", "hello", "world1", "ld1"); +} + +TEST_P(UdpProxyIntegrationTest, TwoWriteSessionFilters) { + setup(1, absl::nullopt, getDrainerSessionFilterConfig({{"write", 0, 3}, {"write", 0, 1}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + requestResponseWithListenerAddress(*listener_address, "hello", "hello", "world1", "d1"); +} + +TEST_P(UdpProxyIntegrationTest, ReadAndWriteSessionFilters) { + setup(1, absl::nullopt, getDrainerSessionFilterConfig({{"read", 3, 0}, {"write", 0, 3}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + requestResponseWithListenerAddress(*listener_address, "hello", "lo", "world1", "ld1"); +} + +TEST_P(UdpProxyIntegrationTest, TwoReadAndWriteSessionFilters) { + setup(1, absl::nullopt, + getDrainerSessionFilterConfig( + {{"read", 3, 0}, {"write", 0, 3}, {"read", 1, 0}, {"write", 0, 1}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + requestResponseWithListenerAddress(*listener_address, "hello", "o", "world1", "d1"); +} + +TEST_P(UdpProxyIntegrationTest, BidirectionalSessionFilter) { + setup(1, absl::nullopt, getDrainerSessionFilterConfig({{"read_write", 3, 3}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + requestResponseWithListenerAddress(*listener_address, "hello", "lo", "world1", "ld1"); +} + +TEST_P(UdpProxyIntegrationTest, ReadSessionFilterStopOnNewConnection) { + // In the test filter, the onNewSession() call will increase the amount of bytes to drain by 1, + // if it's set to return StopIteration. + // Therefore we have two read filters where the first one should return StopIteration, + // so we expect the overall amount of bytes to drain to increase by 1, instead of 2. + setup(1, absl::nullopt, + getDrainerSessionFilterConfig({{"read", 2, 0, true, false, false, false}, + {"read", 0, 0, true, false, false, false}})); + + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + + std::string request = "hello"; + std::string expected_request = "lo"; // We expect 3 bytes to drain. + std::string response = "world"; + std::string expected_response = "world"; + + // Send datagram to be proxied. + Network::Test::UdpSyncPeer client(version_, Network::DEFAULT_UDP_MAX_DATAGRAM_SIZE); + client.write(request, *listener_address); + + // Wait for the upstream datagram. + Network::UdpRecvData request_datagram; + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ(expected_request, request_datagram.buffer_->toString()); + + // Respond from the upstream. + fake_upstreams_[0]->sendUdpDatagram(response, request_datagram.addresses_.peer_); + Network::UdpRecvData response_datagram; + client.recv(response_datagram); + EXPECT_EQ(expected_response, response_datagram.buffer_->toString()); +} + +TEST_P(UdpProxyIntegrationTest, ReadSessionFilterStopOnRead) { + setup(1, absl::nullopt, + getDrainerSessionFilterConfig({{"read", 0, 0, false, true, false, false}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + + std::string request = "hello"; + std::string expected_request = "hello"; + std::string response = "world"; + std::string expected_response = "world"; + + // Send two datagrams but see that only the second one arrived upstream. + Network::Test::UdpSyncPeer client(version_, Network::DEFAULT_UDP_MAX_DATAGRAM_SIZE); + client.write("hello1", *listener_address); + client.write(request, *listener_address); + + // Wait for the upstream datagram. + Network::UdpRecvData request_datagram; + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ(expected_request, request_datagram.buffer_->toString()); + + // Respond from the upstream. + fake_upstreams_[0]->sendUdpDatagram(response, request_datagram.addresses_.peer_); + Network::UdpRecvData response_datagram; + client.recv(response_datagram); + EXPECT_EQ(expected_response, response_datagram.buffer_->toString()); +} + +TEST_P(UdpProxyIntegrationTest, ReadSessionFilterStopOnNewConnectionAndLaterContinue) { + // In the test filter, the onNewSession() call will increase the amount of bytes to drain by 1, + // if it's set to return StopIteration. + // The first filter will StopIteration in onNewSession(), so the count will increase by 1. Then, + // onData will call to continueFilterChain(), so the next onNewSession() will also increase the + // count by 1. We expect overall that 2 bytes will be drained. + setup(1, absl::nullopt, + getDrainerSessionFilterConfig( + {{"read", 0, 0, true, false, true, false}, {"read", 0, 0, true, false, true, false}})); + + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + + std::string request = "hello"; + std::string expected_request = "llo"; // We expect 2 bytes to drain. + std::string response = "world"; + std::string expected_response = "world"; + + // Send datagram to be proxied. + Network::Test::UdpSyncPeer client(version_, Network::DEFAULT_UDP_MAX_DATAGRAM_SIZE); + client.write(request, *listener_address); + + // Wait for the upstream datagram. + Network::UdpRecvData request_datagram; + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ(expected_request, request_datagram.buffer_->toString()); + + // Respond from the upstream. + fake_upstreams_[0]->sendUdpDatagram(response, request_datagram.addresses_.peer_); + Network::UdpRecvData response_datagram; + client.recv(response_datagram); + EXPECT_EQ(expected_response, response_datagram.buffer_->toString()); +} + +TEST_P(UdpProxyIntegrationTest, WriteSessionFilterStopOnWrite) { + setup(1, absl::nullopt, + getDrainerSessionFilterConfig({{"write", 0, 0, false, false, false, true}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + + std::string request = "hello"; + std::string expected_request = "hello"; + std::string response = "world"; + std::string expected_response = "world"; + + // Send datagram to be proxied. + Network::Test::UdpSyncPeer client(version_, Network::DEFAULT_UDP_MAX_DATAGRAM_SIZE); + client.write(request, *listener_address); + + // Wait for the upstream datagram. + Network::UdpRecvData request_datagram; + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ(expected_request, request_datagram.buffer_->toString()); + + // Send two datagrams but see that only the second one arrived downstream. + fake_upstreams_[0]->sendUdpDatagram("response1", request_datagram.addresses_.peer_); + fake_upstreams_[0]->sendUdpDatagram(response, request_datagram.addresses_.peer_); + Network::UdpRecvData response_datagram; + client.recv(response_datagram); + EXPECT_EQ(expected_response, response_datagram.buffer_->toString()); +} + } // namespace } // namespace Envoy From d5a1d6e21aefee95af2b9743027ac1f404ab6a45 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 7 Sep 2023 18:29:43 +0100 Subject: [PATCH 24/55] ci/cache: Use `.bazelrc` for docker cache key (#29492) Signed-off-by: Ryan Northey --- .azure-pipelines/bazel.yml | 2 +- .azure-pipelines/pipelines.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index 6c8756314512..6bad103d3bc1 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -11,7 +11,7 @@ parameters: # caching - name: cacheKeyDocker type: string - default: ".devcontainer/Dockerfile" + default: ".bazelrc" - name: cacheKeyVersion type: string default: $(cacheKeyVersion) diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 8d766d900c1a..6fe40d65f65f 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -51,7 +51,7 @@ variables: - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker - value: ".devcontainer/Dockerfile" + value: ".bazelrc" # Docker build uses separate docker cache - name: cacheKeyDockerBuild # VERSION.txt is included to refresh Docker images for release From fc8717ba484915b01f5010620cb4371e3aec9d40 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 7 Sep 2023 19:33:20 +0100 Subject: [PATCH 25/55] ci/cache: Fix cache priming for Docker only (#29494) Signed-off-by: Ryan Northey --- .azure-pipelines/docker/prime_cache.sh | 13 ++++++++++++- .azure-pipelines/pipelines.yml | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.azure-pipelines/docker/prime_cache.sh b/.azure-pipelines/docker/prime_cache.sh index 8f8a0af6190c..2ec003d16d95 100755 --- a/.azure-pipelines/docker/prime_cache.sh +++ b/.azure-pipelines/docker/prime_cache.sh @@ -33,6 +33,18 @@ fi echo "===================================================" echo +echo +echo "================ Docker fetch ======================" +if [[ "$DOCKER_RESTORED" != "true" ]]; then + echo "Fetching Docker" + ./ci/run_envoy_docker.sh uname -a + docker images +else + echo "Not fetching Docker as it was restored" +fi +echo "===================================================" +echo + echo echo "================ Bazel fetch ======================" # Fetch bazel dependencies @@ -45,7 +57,6 @@ fi echo "===================================================" echo -docker images df -h echo diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 6fe40d65f65f..2325dbe4fc1b 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -47,7 +47,7 @@ variables: - name: cacheKeyName value: envoy - name: cacheKeyVersion - value: v1 + value: v2 - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker From dde11f20487262bc6228335604f07b7862156cc8 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 7 Sep 2023 20:18:46 +0100 Subject: [PATCH 26/55] deps/tooling: Bump `envoy.code.check` -> 0.5.5 (#29488) Signed-off-by: Ryan Northey --- source/extensions/extensions_metadata.yaml | 4 ++++ tools/base/requirements.in | 2 +- tools/base/requirements.txt | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 069ad9b4d075..3f5b1f615782 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -184,6 +184,7 @@ envoy.filters.http.admission_control: - envoy.filters.http.upstream security_posture: unknown status: stable + status_upstream: alpha type_urls: - envoy.extensions.filters.http.admission_control.v3.AdmissionControl envoy.filters.http.alternate_protocols_cache: @@ -222,6 +223,7 @@ envoy.filters.http.buffer: - envoy.filters.http.upstream security_posture: robust_to_untrusted_downstream status: stable + status_upstream: stable type_urls: - envoy.extensions.filters.http.buffer.v3.Buffer - envoy.extensions.filters.http.buffer.v3.BufferPerRoute @@ -251,6 +253,7 @@ envoy.filters.http.upstream_codec: - envoy.filters.http.upstream security_posture: robust_to_untrusted_downstream_and_upstream status: stable + status_upstream: stable type_urls: - envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec envoy.filters.http.composite: @@ -532,6 +535,7 @@ envoy.filters.http.header_mutation: - envoy.filters.http.upstream security_posture: unknown status: alpha + status_upstream: alpha type_urls: - envoy.extensions.filters.http.header_mutation.v3.HeaderMutation - envoy.extensions.filters.http.header_mutation.v3.HeaderMutationPerRoute diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 86532c679c85..81e3d3926be2 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -7,7 +7,7 @@ coloredlogs cryptography>=41.0.1 dependatool>=0.2.2 envoy.base.utils>=0.4.11 -envoy.code.check>=0.5.4 +envoy.code.check>=0.5.7 envoy.dependency.check>=0.1.7 envoy.distribution.release>=0.0.9 envoy.distribution.repo>=0.0.8 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 833e96839b05..b970aed196c6 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -439,9 +439,9 @@ envoy-base-utils==0.4.11 \ # envoy-docs-sphinx-runner # envoy-github-release # envoy-gpg-sign -envoy-code-check==0.5.4 \ - --hash=sha256:b3c338a0e607960ea75eb8298e786548d317655ac4c89d89b259395684eaf134 \ - --hash=sha256:ec919ea1e5523c5ad669f6601bb58c8da77bc1891c8846950add3b563c629ac5 +envoy-code-check==0.5.7 \ + --hash=sha256:5cd2c5a6a9e4b85bc3342cea58f1b6200ebf5ef926e7b8320a1f73bd49145811 \ + --hash=sha256:628b8ff787278e130cc576302a9f383c3a0a645841e5f7323c72359dc59c2935 # via -r requirements.in envoy-dependency-check==0.1.8 \ --hash=sha256:ac9820e446bb44e05121e5c93c210f40ca37076580b0d082da2c63e7784c338a \ @@ -1235,7 +1235,7 @@ sphinxcontrib-httpdomain==1.8.1 \ --hash=sha256:21eefe1270e4d9de8d717cc89ee92cc4871b8736774393bafc5e38a6bb77b1d5 \ --hash=sha256:6c2dfe6ca282d75f66df333869bb0ce7331c01b475db6809ff9d107b7cdfe04b # via envoy-docs-sphinx-runner -sphinxcontrib-jquery @ https://github.com/sphinx-contrib/jquery/archive/refs/tags/v3.0.0.zip \ +sphinxcontrib.jquery @ https://github.com/sphinx-contrib/jquery/archive/refs/tags/v3.0.0.zip \ --hash=sha256:562ad9ac0ac3d8f04a363eb3507ae4b2b856aa04aabab6df7543530fafb849ca # via # -r requirements.in From 7cf204d96bc2fd67b781594b3186d8428ec700bd Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Thu, 7 Sep 2023 13:18:25 -0700 Subject: [PATCH 27/55] quic: Use name() instead of DebugString() (#29343) quic: Use name() instead of DebugString() when checking that the QUIC transport socket is configured Risk Level: Low Testing: Existing Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Ryan Hamilton --- source/common/upstream/BUILD | 1 + source/common/upstream/upstream_impl.cc | 24 +++++- .../transport_sockets/http_11_proxy/BUILD | 3 + test/common/upstream/BUILD | 1 + test/common/upstream/upstream_impl_test.cc | 82 +++++++++++++++++++ .../http_11_proxy/connect_integration_test.cc | 2 +- 6 files changed, 108 insertions(+), 5 deletions(-) diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index 51bfad374dc5..f93085e1b096 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -403,6 +403,7 @@ envoy_cc_library( "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/upstream_codec/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/transport_sockets/raw_buffer/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/http_11_proxy/v3:pkg_cc_proto", "//envoy/event:dispatcher_interface", "//envoy/event:timer_interface", "//envoy/network:dns_interface", diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index ca4ba1b4c532..298bb47ec4c9 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -18,6 +18,7 @@ #include "envoy/event/dispatcher.h" #include "envoy/event/timer.h" #include "envoy/extensions/filters/http/upstream_codec/v3/upstream_codec.pb.h" +#include "envoy/extensions/transport_sockets/http_11_proxy/v3/upstream_http_11_connect.pb.h" #include "envoy/extensions/transport_sockets/raw_buffer/v3/raw_buffer.pb.h" #include "envoy/init/manager.h" #include "envoy/network/dns.h" @@ -1401,6 +1402,22 @@ ClusterInfoImpl::upstreamHttpProtocol(absl::optional downstream_ : Http::Protocol::Http11}; } +bool validateTransportSocketSupportsQuic( + const envoy::config::core::v3::TransportSocket& transport_socket) { + // The transport socket is valid for QUIC if it is either a QUIC transport socket, + // or if it is a QUIC transport socket wrapped in an HTTP/1.1 proxy socket. + if (transport_socket.name() == "envoy.transport_sockets.quic") { + return true; + } + if (transport_socket.name() != "envoy.transport_sockets.http_11_proxy") { + return false; + } + envoy::extensions::transport_sockets::http_11_proxy::v3::Http11ProxyUpstreamTransport + http11_socket; + MessageUtil::unpackTo(transport_socket.typed_config(), http11_socket); + return http11_socket.transport_socket().name() == "envoy.transport_sockets.quic"; +} + ClusterImplBase::ClusterImplBase(const envoy::config::cluster::v3::Cluster& cluster, ClusterFactoryContext& cluster_context) : init_manager_(fmt::format("Cluster {}", cluster.name())), @@ -1457,11 +1474,10 @@ ClusterImplBase::ClusterImplBase(const envoy::config::cluster::v3::Cluster& clus if (info_->features() & ClusterInfoImpl::Features::HTTP3) { #if defined(ENVOY_ENABLE_QUIC) - if (cluster.transport_socket().DebugString().find("envoy.transport_sockets.quic") == - std::string::npos) { + if (!validateTransportSocketSupportsQuic(cluster.transport_socket())) { throw EnvoyException( - fmt::format("HTTP3 requires a QuicUpstreamTransport transport socket: {}", cluster.name(), - cluster.DebugString())); + fmt::format("HTTP3 requires a QuicUpstreamTransport transport socket: {} {}", + cluster.name(), cluster.transport_socket().DebugString())); } #else throw EnvoyException("HTTP3 configured but not enabled in the build."); diff --git a/source/extensions/transport_sockets/http_11_proxy/BUILD b/source/extensions/transport_sockets/http_11_proxy/BUILD index 66b49fc2baa1..f6a709461b7c 100644 --- a/source/extensions/transport_sockets/http_11_proxy/BUILD +++ b/source/extensions/transport_sockets/http_11_proxy/BUILD @@ -13,6 +13,9 @@ envoy_cc_extension( name = "upstream_config", srcs = ["config.cc"], hdrs = ["config.h"], + extra_visibility = [ + "//test/common/upstream:__subpackages__", + ], deps = [ ":connect", "//envoy/network:transport_socket_interface", diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 09d6bfa0b899..9ff58a05dcd6 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -554,6 +554,7 @@ envoy_cc_test( "//source/extensions/clusters/static:static_cluster_lib", "//source/extensions/clusters/strict_dns:strict_dns_cluster_lib", "//source/extensions/transport_sockets/raw_buffer:config", + "//source/extensions/transport_sockets/http_11_proxy:upstream_config", "//source/extensions/transport_sockets/tls:config", "//source/extensions/upstreams/http:config", "//source/extensions/upstreams/tcp:config", diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index e054932d2663..a03a6c0586a2 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -4550,6 +4550,88 @@ TEST_F(ClusterInfoImplTest, Http3) { downstream_h3->info()->http3Options().quic_protocol_options().has_max_concurrent_streams()); } +TEST_F(ClusterInfoImplTest, Http3WithHttp11WrappedSocket) { + const std::string yaml = TestEnvironment::substitute(R"EOF( + name: name + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: MAGLEV + load_assignment: + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: foo.bar.com + port_value: 443 + transport_socket: + name: envoy.transport_sockets.http_11_proxy + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.http_11_proxy.v3.Http11ProxyUpstreamTransport + transport_socket: + name: envoy.transport_sockets.quic + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.quic.v3.QuicUpstreamTransport + upstream_tls_context: + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_key.pem" + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + match_typed_subject_alt_names: + - matcher: + exact: localhost + san_type: URI + - matcher: + exact: 127.0.0.1 + san_type: IP_ADDRESS + )EOF", + Network::Address::IpVersion::v4); + + auto cluster1 = makeCluster(yaml); + ASSERT_TRUE(cluster1->info()->idleTimeout().has_value()); + EXPECT_EQ(std::chrono::hours(1), cluster1->info()->idleTimeout().value()); + + const std::string explicit_http3 = R"EOF( + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http3_protocol_options: + quic_protocol_options: + max_concurrent_streams: 2 + common_http_protocol_options: + idle_timeout: 1s + )EOF"; + + const std::string downstream_http3 = R"EOF( + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + use_downstream_protocol_config: + http3_protocol_options: {} + common_http_protocol_options: + idle_timeout: 1s + )EOF"; + + auto explicit_h3 = makeCluster(yaml + explicit_http3); + EXPECT_EQ(Http::Protocol::Http3, + explicit_h3->info()->upstreamHttpProtocol({Http::Protocol::Http10})[0]); + EXPECT_EQ( + explicit_h3->info()->http3Options().quic_protocol_options().max_concurrent_streams().value(), + 2); + + auto downstream_h3 = makeCluster(yaml + downstream_http3); + EXPECT_EQ(Http::Protocol::Http3, + downstream_h3->info()->upstreamHttpProtocol({Http::Protocol::Http3})[0]); + EXPECT_FALSE( + downstream_h3->info()->http3Options().quic_protocol_options().has_max_concurrent_streams()); +} + TEST_F(ClusterInfoImplTest, Http3BadConfig) { const std::string yaml = TestEnvironment::substitute(R"EOF( name: name diff --git a/test/extensions/transport_sockets/http_11_proxy/connect_integration_test.cc b/test/extensions/transport_sockets/http_11_proxy/connect_integration_test.cc index e8a6cf77e676..cab98ed863c2 100644 --- a/test/extensions/transport_sockets/http_11_proxy/connect_integration_test.cc +++ b/test/extensions/transport_sockets/http_11_proxy/connect_integration_test.cc @@ -77,7 +77,7 @@ name: envoy.clusters.dynamic_forward_proxy if (inner_socket.name().empty()) { inner_socket.set_name("envoy.transport_sockets.raw_buffer"); } - transport_socket->set_name("envoy.transport_sockets.upstream_http_11_proxy"); + transport_socket->set_name("envoy.transport_sockets.http_11_proxy"); envoy::extensions::transport_sockets::http_11_proxy::v3::Http11ProxyUpstreamTransport transport; transport.mutable_transport_socket()->MergeFrom(inner_socket); From c2a4ff7ca229d48f3b4a4bc2ab431693adde1992 Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Thu, 7 Sep 2023 17:53:25 -0500 Subject: [PATCH 28/55] mobile: Remove the use of TestEngineBuilder from the lifetimes_test and send_headers_test. (#29499) This updates the lifetimes_test and send_headers_test to use EngineBuilder directly instead of TestEngineBuilder. TestEngineBuilder will soon be removed. Risk Level: low (clean up) Testing: existing tests Docs Changes: n/a Release Notes: n/a Signed-off-by: Fredy Wijaya --- mobile/test/cc/integration/lifetimes_test.cc | 70 +++----------- .../test/cc/integration/send_headers_test.cc | 96 ++++++------------- 2 files changed, 42 insertions(+), 124 deletions(-) diff --git a/mobile/test/cc/integration/lifetimes_test.cc b/mobile/test/cc/integration/lifetimes_test.cc index b5fc701c0825..9b00ac89a9ec 100644 --- a/mobile/test/cc/integration/lifetimes_test.cc +++ b/mobile/test/cc/integration/lifetimes_test.cc @@ -1,61 +1,16 @@ -#include "test/common/integration/test_engine_builder.h" #include "test/test_common/utility.h" #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine.h" +#include "library/cc/engine_builder.h" #include "library/cc/envoy_error.h" #include "library/cc/log_level.h" #include "library/cc/request_headers_builder.h" #include "library/cc/request_method.h" -#include "library/cc/response_headers.h" namespace Envoy { namespace { -const static std::string CONFIG = R"( -listener_manager: - name: envoy.listener_manager_impl.api - typed_config: - "@type": type.googleapis.com/envoy.config.listener.v3.ApiListenerManager -static_resources: - listeners: - - name: base_api_listener - address: - socket_address: - protocol: TCP - address: 0.0.0.0 - port_value: 10000 - api_listener: - api_listener: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.EnvoyMobileHttpConnectionManager - config: - stat_prefix: hcm - route_config: - name: api_router - virtual_hosts: - - name: api - domains: - - "*" - routes: - - match: - prefix: "/" - direct_response: - status: 200 - http_filters: - - name: envoy.filters.http.assertion - typed_config: - "@type": type.googleapis.com/envoymobile.extensions.filters.http.assertion.Assertion - match_config: - http_request_headers_match: - headers: - - name: ":authority" - exact_match: example.com - - name: envoy.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router -)"; - struct Status { int status_code; bool end_stream; @@ -70,14 +25,12 @@ void sendRequest(Platform::EngineSharedPtr engine, Status& status, status.status_code = headers->httpStatus(); status.end_stream = end_stream; }) + .setOnData([&](envoy_data, bool end_stream) { status.end_stream = end_stream; }) .setOnComplete([&](envoy_stream_intel, envoy_final_stream_intel) { stream_complete.Notify(); }) - .setOnError([&](Platform::EnvoyErrorSharedPtr envoy_error, envoy_stream_intel, - envoy_final_stream_intel) { - (void)envoy_error; - stream_complete.Notify(); - }) + .setOnError([&](Platform::EnvoyErrorSharedPtr, envoy_stream_intel, + envoy_final_stream_intel) { stream_complete.Notify(); }) .setOnCancel([&](envoy_stream_intel, envoy_final_stream_intel) { stream_complete.Notify(); }) @@ -90,10 +43,17 @@ void sendRequest(Platform::EngineSharedPtr engine, Status& status, } void sendRequestEndToEnd() { - TestEngineBuilder engine_builder; - auto bootstrap = std::make_unique(); - TestUtility::loadFromYaml(CONFIG, *bootstrap); - Platform::EngineSharedPtr engine = engine_builder.createEngine(std::move(bootstrap)); + Platform::EngineBuilder engine_builder; + engine_builder.addNativeFilter( + "test_remote_response", + "{'@type': " + "type.googleapis.com/" + "envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse}"); + absl::Notification engine_running; + Platform::EngineSharedPtr engine = engine_builder.addLogLevel(Platform::LogLevel::debug) + .setOnEngineRunning([&]() { engine_running.Notify(); }) + .build(); + engine_running.WaitForNotification(); Status status; absl::Notification stream_complete; diff --git a/mobile/test/cc/integration/send_headers_test.cc b/mobile/test/cc/integration/send_headers_test.cc index b17ec5f9f650..c55b1126c2b5 100644 --- a/mobile/test/cc/integration/send_headers_test.cc +++ b/mobile/test/cc/integration/send_headers_test.cc @@ -1,93 +1,51 @@ -#include "test/common/integration/test_engine_builder.h" #include "test/test_common/utility.h" #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -#include "library/cc/engine.h" +#include "library/cc/engine_builder.h" #include "library/cc/envoy_error.h" -#include "library/cc/log_level.h" #include "library/cc/request_headers_builder.h" #include "library/cc/request_method.h" -#include "library/cc/response_headers.h" namespace Envoy { namespace { -const static std::string CONFIG = R"( -listener_manager: - name: envoy.listener_manager_impl.api - typed_config: - "@type": type.googleapis.com/envoy.config.listener.v3.ApiListenerManager -static_resources: - listeners: - - name: base_api_listener - address: - socket_address: - protocol: TCP - address: 0.0.0.0 - port_value: 10000 - api_listener: - api_listener: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.EnvoyMobileHttpConnectionManager - config: - stat_prefix: hcm - route_config: - name: api_router - virtual_hosts: - - name: api - domains: - - "*" - routes: - - match: - prefix: "/" - direct_response: - status: 200 - http_filters: - - name: envoy.filters.http.assertion - typed_config: - "@type": type.googleapis.com/envoymobile.extensions.filters.http.assertion.Assertion - match_config: - http_request_headers_match: - headers: - - name: ":authority" - exact_match: example.com - - name: envoy.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router -)"; - struct Status { int status_code; bool end_stream; }; TEST(SendHeadersTest, CanSendHeaders) { - TestEngineBuilder engine_builder; - auto bootstrap = std::make_unique(); - TestUtility::loadFromYaml(CONFIG, *bootstrap); - Platform::EngineSharedPtr engine = engine_builder.createEngine(std::move(bootstrap)); + Platform::EngineBuilder engine_builder; + engine_builder.addNativeFilter( + "test_remote_response", + "{'@type': " + "type.googleapis.com/" + "envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse}"); + absl::Notification engine_running; + Platform::EngineSharedPtr engine = engine_builder.addLogLevel(Platform::LogLevel::debug) + .setOnEngineRunning([&]() { engine_running.Notify(); }) + .build(); + engine_running.WaitForNotification(); Status status; absl::Notification stream_complete; - Platform::StreamSharedPtr stream; auto stream_prototype = engine->streamClient()->newStreamPrototype(); - - stream_prototype->setOnHeaders( - [&](Platform::ResponseHeadersSharedPtr headers, bool end_stream, envoy_stream_intel) { - status.status_code = headers->httpStatus(); - status.end_stream = end_stream; - }); - stream_prototype->setOnComplete( - [&](envoy_stream_intel, envoy_final_stream_intel) { stream_complete.Notify(); }); - stream_prototype->setOnError( - [&](Platform::EnvoyErrorSharedPtr envoy_error, envoy_stream_intel, envoy_final_stream_intel) { - (void)envoy_error; - stream_complete.Notify(); - }); - stream_prototype->setOnCancel( - [&](envoy_stream_intel, envoy_final_stream_intel) { stream_complete.Notify(); }); - - stream = stream_prototype->start(); + Platform::StreamSharedPtr stream = + (*stream_prototype) + .setOnHeaders( + [&](Platform::ResponseHeadersSharedPtr headers, bool end_stream, envoy_stream_intel) { + status.status_code = headers->httpStatus(); + status.end_stream = end_stream; + }) + .setOnData([&](envoy_data, bool end_stream) { status.end_stream = end_stream; }) + .setOnComplete( + [&](envoy_stream_intel, envoy_final_stream_intel) { stream_complete.Notify(); }) + .setOnError([&](Platform::EnvoyErrorSharedPtr, envoy_stream_intel, + envoy_final_stream_intel) { stream_complete.Notify(); }) + .setOnCancel( + [&](envoy_stream_intel, envoy_final_stream_intel) { stream_complete.Notify(); }) + .start(); Platform::RequestHeadersBuilder request_headers_builder(Platform::RequestMethod::GET, "https", "example.com", "/"); From f0b867c07c59cc81223224afa9624100624d6815 Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 7 Sep 2023 19:27:19 -0400 Subject: [PATCH 29/55] Update QUICHE from aac16c3f2 to f326e9346 (#29498) * Update QUICHE from aac16c3f2 to f326e9346 https://github.com/google/quiche/compare/aac16c3f2..f326e9346 ``` $ git log aac16c3f2..f326e9346 --date=short --no-merges --format="%ad %al %s" 2023-09-07 vasilvv Streamline the use of QUICHE_EXPORT macros in quic/core. 2023-09-07 vasilvv Move quic_client_factory.h into cross-platform tools target. 2023-09-06 bnc Deprecate --gfe2_reloadable_flag_quic_clear_body_manager. 2023-09-06 bnc Fix typo. 2023-09-05 elburrito Add BlindSignAuthOptions proto to BlindSignAuth constructor. This options proto will allow callers to configure BlindSignAuth with new features. 2023-09-05 quiche-dev Remove redundant call to OnDataFrameSent() 2023-09-05 wub Deprecate --gfe2_reloadable_flag_quic_bbr2_ignore_bad_rtt_sample. 2023-09-01 elburrito Set do_not_use_rsa_public_exponent to true in AnonymousTokensRsaBssaClient, and set its value correctly in BlindSignAuth and PPN Krypton. 2023-09-01 renjietang [quic]Add client connection option MPQM to control the migration when multi-port is enabled. 2023-09-01 martinduke Update QuicDispatcher interfaces to explicitly take ConnectionIdGeneratorInterface. 2023-08-30 quiche-dev Add `--qbone_max_pacing_kbps` flag to the QBONE bonnet. 2023-08-30 fayang In QUIC, let server send error code QUIC_HANDSHAKE_FAILED_PACKETS_BUFFERED_TOO_LONG (rather than QUIC_HANDSHAKE_FAILED) when handshake fails due to packets buffered for too long. 2023-08-30 birenroy Adds a unit test demonstrating round-trip Set-Cookie header handling in oghttp2. 2023-08-29 elburrito Fix Tricorder errors in BlindSignAuth 2023-08-29 quiche-dev Remove DEBUG datapol annotations from fields which shouldn't have it 2023-08-28 vasilvv Clean up filelists.bara.sky to make it usable in Chromium. 2023-08-28 diannahu Enabling rolled out flags. 2023-08-28 vasilvv Add a missing IWYU pragma for third_party/http2/adapter/nghttp2.h. 2023-08-28 quiche-dev Fix new -Wmissing-field-initializers warning 2023-08-28 danzh Change LOG(ERROR) log in DLOG(ERROR) for QUIC invalid request headers. 2023-08-25 quiche-dev Add do_not_use_rsa_public_exponent to auth and sign 2023-08-25 birenroy Verifies Set-Cookie handling within Http2HeaderBlock. 2023-08-25 birenroy Invokes the invalid frame callback for some Content-Length errors encountered by OgHttp2Session. ``` Signed-off-by: Dan Zhang --- bazel/repository_locations.bzl | 6 +++--- source/common/quic/envoy_quic_dispatcher.cc | 5 +++-- source/common/quic/envoy_quic_dispatcher.h | 3 ++- test/integration/multiplexed_integration_test.cc | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 440d2afaafc9..bae213c32e5e 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1124,12 +1124,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://github.com/google/quiche", - version = "aac16c3f27808315c731dd779054207eb0aa29ab", - sha256 = "abb090b13122af18f6cc1a617cffa143265f1ee0d639be6522d017c74b27599a", + version = "f326e9346dc7d3d4365c1d2c0ebbd285bccd794f", + sha256 = "93eb1970888bbb98f1e4f80768241e657074c6097534fbc5acf3aced51c7f84c", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["controlplane", "dataplane_core"], - release_date = "2023-08-25", + release_date = "2023-09-07", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", diff --git a/source/common/quic/envoy_quic_dispatcher.cc b/source/common/quic/envoy_quic_dispatcher.cc index 4da0e6a13201..79558797784a 100644 --- a/source/common/quic/envoy_quic_dispatcher.cc +++ b/source/common/quic/envoy_quic_dispatcher.cc @@ -84,7 +84,8 @@ quic::QuicTimeWaitListManager* EnvoyQuicDispatcher::CreateQuicTimeWaitListManage std::unique_ptr EnvoyQuicDispatcher::CreateQuicSession( quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& self_address, const quic::QuicSocketAddress& peer_address, absl::string_view /*alpn*/, - const quic::ParsedQuicVersion& version, const quic::ParsedClientHello& parsed_chlo) { + const quic::ParsedQuicVersion& version, const quic::ParsedClientHello& parsed_chlo, + quic::ConnectionIdGeneratorInterface& connection_id_generator) { quic::QuicConfig quic_config = config(); // TODO(danzh) use passed-in ALPN instead of hard-coded h3 after proof source interfaces takes in // ALPN. @@ -98,7 +99,7 @@ std::unique_ptr EnvoyQuicDispatcher::CreateQuicSession( auto quic_connection = std::make_unique( server_connection_id, self_address, peer_address, *helper(), *alarm_factory(), writer(), /*owns_writer=*/false, quic::ParsedQuicVersionVector{version}, std::move(connection_socket), - connection_id_generator()); + connection_id_generator); auto quic_session = std::make_unique( quic_config, quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this, session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_, diff --git a/source/common/quic/envoy_quic_dispatcher.h b/source/common/quic/envoy_quic_dispatcher.h index f6f88eee69cf..5a71c322d153 100644 --- a/source/common/quic/envoy_quic_dispatcher.h +++ b/source/common/quic/envoy_quic_dispatcher.h @@ -86,7 +86,8 @@ class EnvoyQuicDispatcher : public quic::QuicDispatcher { std::unique_ptr CreateQuicSession( quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& self_address, const quic::QuicSocketAddress& peer_address, absl::string_view alpn, - const quic::ParsedQuicVersion& version, const quic::ParsedClientHello& parsed_chlo) override; + const quic::ParsedQuicVersion& version, const quic::ParsedClientHello& parsed_chlo, + quic::ConnectionIdGeneratorInterface& connection_id_generator) override; // quic::QuicDispatcher // Sets current_packet_dispatch_success_ to false for processPacket's return value, diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index 190e57065d1c..b678090bec3e 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -2242,7 +2242,7 @@ TEST_P(MultiplexedIntegrationTest, InconsistentContentLength) { EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("inconsistent_content_length")); } else if (GetParam().http2_implementation == Http2Impl::Oghttp2) { EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason()); - EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("http2.remote_reset")); + EXPECT_THAT(waitForAccessLog(access_log_name_), "http2.violation.of.messaging.rule"); } else { EXPECT_EQ(Http::StreamResetReason::ConnectionTermination, response->resetReason()); // http2.violation.of.messaging.rule From ab3ef829094144e41a812509c6f069b2bb9b88e8 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Thu, 7 Sep 2023 23:29:51 -0400 Subject: [PATCH 30/55] stream out histogram fragments to HTML (#29356) Commit Message: When rendering detailed histograms for HTML, it's friendlier to browsers if we enable them to parse and render each histogram as it comes out. The histogram data can be very large, so by putting each call to render a histogram in its own script tag, we (at low cost to the C++ server) can reduce the memory stored in the browser and also enable it to begin rendering before the server finishes streaming. Risk Level: Testing: /test/common/json/... test/common/buffer //test/server/admin/... /test/integration/admin_html/web_test.thml Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Joshua Marantz --- source/common/json/json_streamer.cc | 5 +++ source/common/json/json_streamer.h | 20 ++++++--- source/server/admin/stats_html_render.cc | 47 +++++++++++++-------- source/server/admin/stats_html_render.h | 6 +-- source/server/admin/stats_render.cc | 31 +++++++------- source/server/admin/stats_render.h | 32 +++++++++++--- test/common/json/json_streamer_test.cc | 8 ++++ test/server/admin/stats_html_render_test.cc | 36 +++++++++++++--- 8 files changed, 132 insertions(+), 53 deletions(-) diff --git a/source/common/json/json_streamer.cc b/source/common/json/json_streamer.cc index ca2d4ee26e78..1d6498b4fe63 100644 --- a/source/common/json/json_streamer.cc +++ b/source/common/json/json_streamer.cc @@ -48,6 +48,11 @@ Streamer::MapPtr Streamer::makeRootMap() { return std::make_unique(*this); } +Streamer::ArrayPtr Streamer::makeRootArray() { + ASSERT_LEVELS_EMPTY; + return std::make_unique(*this); +} + Streamer::MapPtr Streamer::Level::addMap() { ASSERT_THIS_IS_TOP_LEVEL; nextField(); diff --git a/source/common/json/json_streamer.h b/source/common/json/json_streamer.h index 7c9cab05bcf6..c4def6aafb5e 100644 --- a/source/common/json/json_streamer.h +++ b/source/common/json/json_streamer.h @@ -5,9 +5,7 @@ #include #include "envoy/buffer/buffer.h" -#include "envoy/common/optref.h" -#include "absl/container/flat_hash_map.h" #include "absl/strings/string_view.h" #include "absl/types/variant.h" @@ -169,15 +167,23 @@ class Streamer { }; /** - * Makes a root map for the streamer. A similar function can be added - * easily if this class is to be used for a JSON structure with a - * top-level array. + * Makes a root map for the streamer. * - * You must create a root map before any of the JSON population functions - * can be called, as those are only available on Map and Array objects. + * You must create a root map or array before any of the JSON population + * functions can be called, as those are only available on Map and Array + * objects. */ MapPtr makeRootMap(); + /** + * Makes a root array for the streamer. + * + * You must create a root map or array before any of the JSON population + * functions can be called, as those are only available on Map and Array + * objects. + */ + ArrayPtr makeRootArray(); + private: friend Level; friend Map; diff --git a/source/server/admin/stats_html_render.cc b/source/server/admin/stats_html_render.cc index 3b4d73d2018f..f86c84861f88 100644 --- a/source/server/admin/stats_html_render.cc +++ b/source/server/admin/stats_html_render.cc @@ -21,25 +21,15 @@ namespace Server { StatsHtmlRender::StatsHtmlRender(Http::ResponseHeaderMap& response_headers, Buffer::Instance& response, const StatsParams& params) - : StatsTextRender(params), active_(params.format_ == StatsFormat::ActiveHtml) { + : StatsTextRender(params), active_(params.format_ == StatsFormat::ActiveHtml), + json_histograms_(!active_ && + params.histogram_buckets_mode_ == Utility::HistogramBucketsMode::Detailed) { AdminHtmlUtil::renderHead(response_headers, response); - if (!active_ && params.histogram_buckets_mode_ == Utility::HistogramBucketsMode::Detailed) { - StatsParams json_params(params); - json_params.histogram_buckets_mode_ = params.histogram_buckets_mode_; - json_response_headers_ = Http::ResponseHeaderMapImpl::create(); - histogram_json_render_ = - std::make_unique(*json_response_headers_, json_data_, json_params); - } } void StatsHtmlRender::finalize(Buffer::Instance& response) { - // Render all the histograms here using the JSON data we've accumulated - // for them. - if (histogram_json_render_ != nullptr) { - histogram_json_render_->finalize(json_data_); - response.add("\n
\n\n"); } else { StatsTextRender::generate(response, name, histogram); } diff --git a/source/server/admin/stats_html_render.h b/source/server/admin/stats_html_render.h index 886488195858..92001c6e868e 100644 --- a/source/server/admin/stats_html_render.h +++ b/source/server/admin/stats_html_render.h @@ -41,10 +41,8 @@ class StatsHtmlRender : public StatsTextRender { private: const bool active_{false}; - Buffer::OwnedImpl json_data_; - std::unique_ptr histogram_json_render_; - Http::ResponseHeaderMapPtr json_response_headers_; // ignored. - std::unique_ptr json_headers_; + bool json_histograms_{false}; + bool first_histogram_{true}; }; } // namespace Server diff --git a/source/server/admin/stats_render.cc b/source/server/admin/stats_render.cc index f3a9f672a6d6..d1e5ce04efb6 100644 --- a/source/server/admin/stats_render.cc +++ b/source/server/admin/stats_render.cc @@ -176,21 +176,22 @@ void StatsJsonRender::generate(Buffer::Instance& response, const std::string& na collectBuckets(name, histogram, interval_buckets, cumulative_buckets); break; } - case Utility::HistogramBucketsMode::Detailed: - generateHistogramDetail(name, histogram); + case Utility::HistogramBucketsMode::Detailed: { + generateHistogramDetail(name, histogram, *json_->histogram_array_->addMap()); break; } + } drainIfNeeded(response); } -void StatsJsonRender::populateSupportedPercentiles(Json::Streamer::Map& map) { +void StatsJsonRender::populateSupportedPercentiles(Json::Streamer::Array& array) { Stats::HistogramStatisticsImpl empty_statistics; std::vector supported = empty_statistics.supportedQuantiles(); std::vector views(supported.size()); for (uint32_t i = 0, n = supported.size(); i < n; ++i) { views[i] = supported[i] * 100; } - map.addArray()->addEntries(views); + array.addEntries(views); } void StatsJsonRender::populatePercentiles(const Stats::ParentHistogram& histogram, @@ -214,14 +215,14 @@ void StatsJsonRender::renderHistogramStart() { case Utility::HistogramBucketsMode::Detailed: json_->histogram_map2_ = json_->histogram_map1_->addMap(); json_->histogram_map2_->addKey("supported_percentiles"); - populateSupportedPercentiles(*json_->histogram_map2_); + { populateSupportedPercentiles(*json_->histogram_map2_->addArray()); } json_->histogram_map2_->addKey("details"); json_->histogram_array_ = json_->histogram_map2_->addArray(); break; case Utility::HistogramBucketsMode::NoBuckets: json_->histogram_map2_ = json_->histogram_map1_->addMap(); json_->histogram_map2_->addKey("supported_quantiles"); - populateSupportedPercentiles(*json_->histogram_map2_); + { populateSupportedPercentiles(*json_->histogram_map2_->addArray()); } json_->histogram_map2_->addKey("computed_quantiles"); json_->histogram_array_ = json_->histogram_map2_->addArray(); break; @@ -233,17 +234,17 @@ void StatsJsonRender::renderHistogramStart() { } void StatsJsonRender::generateHistogramDetail(const std::string& name, - const Stats::ParentHistogram& histogram) { + const Stats::ParentHistogram& histogram, + Json::Streamer::Map& map) { // Now we produce the stream-able histogram records, without using the json intermediate // representation or serializer. - Json::Streamer::MapPtr map = json_->histogram_array_->addMap(); - map->addEntries({{"name", name}}); - map->addKey("totals"); - populateBucketsVerbose(histogram.detailedTotalBuckets(), *map); - map->addKey("intervals"); - populateBucketsVerbose(histogram.detailedIntervalBuckets(), *map); - map->addKey("percentiles"); - populatePercentiles(histogram, *map); + map.addEntries({{"name", name}}); + map.addKey("totals"); + populateBucketsVerbose(histogram.detailedTotalBuckets(), map); + map.addKey("intervals"); + populateBucketsVerbose(histogram.detailedIntervalBuckets(), map); + map.addKey("percentiles"); + populatePercentiles(histogram, map); } void StatsJsonRender::populateBucketsVerbose( diff --git a/source/server/admin/stats_render.h b/source/server/admin/stats_render.h index af819a0d1cb5..8894c9ee3d13 100644 --- a/source/server/admin/stats_render.h +++ b/source/server/admin/stats_render.h @@ -77,18 +77,40 @@ class StatsJsonRender : public StatsRender { const Stats::ParentHistogram& histogram) override; void finalize(Buffer::Instance& response) override; + /** + * Streams the supported percentiles into a JSON array. Note that no histogram + * context is provided for this; this is a static property of the binary. + * + * @param array the json streaming array array to stream into. + */ + static void populateSupportedPercentiles(Json::Streamer::Array& array); + + /** + * Streams detail about the provided histogram into the provided JSON map. + * + * @param name the name of the histogram (usually the same as histogram.name(), + * but is passed in explicitly because it may already have been + * computed when filtering stats, and it is somewhat expensive + * to compute the name. + * @param histogram the histogram to stream + * @param map the json map to stream into. + * + */ + static void generateHistogramDetail(const std::string& name, + const Stats::ParentHistogram& histogram, + Json::Streamer::Map& map); + private: // Collects the buckets from the specified histogram. void collectBuckets(const std::string& name, const Stats::ParentHistogram& histogram, const std::vector& interval_buckets, const std::vector& cumulative_buckets); - void generateHistogramDetail(const std::string& name, const Stats::ParentHistogram& histogram); - void populateBucketsVerbose(const std::vector& buckets, - Json::Streamer::Map& map); + static void populateBucketsVerbose(const std::vector& buckets, + Json::Streamer::Map& map); void renderHistogramStart(); - void populateSupportedPercentiles(Json::Streamer::Map& map); - void populatePercentiles(const Stats::ParentHistogram& histogram, Json::Streamer::Map& map); + static void populatePercentiles(const Stats::ParentHistogram& histogram, + Json::Streamer::Map& map); // This function irons out an API mistake made when defining the StatsRender // interface. The issue is that callers can provide a response buffer when diff --git a/test/common/json/json_streamer_test.cc b/test/common/json/json_streamer_test.cc index a6dbd19f1721..1dd699d6f00f 100644 --- a/test/common/json/json_streamer_test.cc +++ b/test/common/json/json_streamer_test.cc @@ -96,6 +96,14 @@ TEST_F(JsonStreamerTest, SubArray) { EXPECT_EQ(R"EOF({"a":[1,"two",3.5,null],"embedded\"quote":"value"})EOF", buffer_.toString()); } +TEST_F(JsonStreamerTest, TopArray) { + { + Streamer::ArrayPtr array = streamer_.makeRootArray(); + array->addEntries({1.0, "two", 3.5, std::nan("")}); + } + EXPECT_EQ(R"EOF([1,"two",3.5,null])EOF", buffer_.toString()); +} + TEST_F(JsonStreamerTest, SubMap) { Streamer::MapPtr map = streamer_.makeRootMap(); map->addKey("a"); diff --git a/test/server/admin/stats_html_render_test.cc b/test/server/admin/stats_html_render_test.cc index 974c7908b2cd..03a38cbde865 100644 --- a/test/server/admin/stats_html_render_test.cc +++ b/test/server/admin/stats_html_render_test.cc @@ -6,7 +6,9 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::EndsWith; using testing::HasSubstr; +using testing::StartsWith; namespace Envoy { namespace Server { @@ -47,13 +49,37 @@ TEST_F(StatsHtmlRenderTest, HistogramNoBuckets) { HasSubstr(expected)); } -TEST_F(StatsHtmlRenderTest, HistogramDetailed) { - // The goal of this test is to show that we have embedded the histogram as a json fragment. +TEST_F(StatsHtmlRenderTest, HistogramStreamingDetailed) { + // The goal of this test is to show that we have embedded the histogram as a + // json fragment, which gets streamed out to HTML one histogram at a time, + // rather than buffered. params_.histogram_buckets_mode_ = Utility::HistogramBucketsMode::Detailed; + StatsHtmlRender renderer{response_headers_, response_, params_}; - constexpr absl::string_view expected = "const json = \n{\"stats\":[{\"histograms\":"; - EXPECT_THAT(render<>(renderer, "h1", populateHistogram("h1", {200, 300, 300})), - HasSubstr(expected)); + renderer.generate(response_, "scalar", 42); + EXPECT_THAT(response_.toString(), StartsWith("\n\n\n")); + EXPECT_THAT(response_.toString(), EndsWith("\nscalar: 42\n")); + response_.drain(response_.length()); + + renderer.generate(response_, "h1", populateHistogram("h1", {200, 300, 300})); + EXPECT_THAT(response_.toString(), StartsWith("\n
\n" + "\n")); + response_.drain(response_.length()); + + renderer.generate(response_, "h2", populateHistogram("h2", {200, 300, 300})); + EXPECT_THAT(response_.toString(), + StartsWith("\n")); + response_.drain(response_.length()); + + renderer.finalize(response_); + EXPECT_EQ("\n\n", response_.toString()); } TEST_F(StatsHtmlRenderTest, RenderActive) { From 40f2d296bb561b8f89786a671e6c682dc37eebfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 11:14:34 +0100 Subject: [PATCH 31/55] build(deps): bump gitpython from 3.1.34 to 3.1.35 in /tools/base (#29510) Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.34 to 3.1.35. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.34...3.1.35) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index b970aed196c6..c43f4b5b701f 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -626,9 +626,9 @@ gitdb==4.0.10 \ --hash=sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a \ --hash=sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7 # via gitpython -gitpython==3.1.34 \ - --hash=sha256:5d3802b98a3bae1c2b8ae0e1ff2e4aa16bcdf02c145da34d092324f599f01395 \ - --hash=sha256:85f7d365d1f6bf677ae51039c1ef67ca59091c7ebd5a3509aa399d4eda02d6dd +gitpython==3.1.35 \ + --hash=sha256:9cbefbd1789a5fe9bcf621bb34d3f441f3a90c8461d377f84eda73e721d9b06b \ + --hash=sha256:c19b4292d7a1d3c0f653858db273ff8a6614100d1eb1528b014ec97286193c09 # via -r requirements.in google-api-core==2.11.1 \ --hash=sha256:25d29e05a0058ed5f19c61c0a78b1b53adea4d9364b464d014fbda941f6d1c9a \ @@ -1235,7 +1235,7 @@ sphinxcontrib-httpdomain==1.8.1 \ --hash=sha256:21eefe1270e4d9de8d717cc89ee92cc4871b8736774393bafc5e38a6bb77b1d5 \ --hash=sha256:6c2dfe6ca282d75f66df333869bb0ce7331c01b475db6809ff9d107b7cdfe04b # via envoy-docs-sphinx-runner -sphinxcontrib.jquery @ https://github.com/sphinx-contrib/jquery/archive/refs/tags/v3.0.0.zip \ +sphinxcontrib-jquery @ https://github.com/sphinx-contrib/jquery/archive/refs/tags/v3.0.0.zip \ --hash=sha256:562ad9ac0ac3d8f04a363eb3507ae4b2b856aa04aabab6df7543530fafb849ca # via # -r requirements.in From 79bbaf39034a1008a22b39eb96217406db9b7165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:28:51 +0000 Subject: [PATCH 32/55] build(deps): bump orjson from 3.9.5 to 3.9.6 in /tools/base (#29512) Bumps [orjson](https://github.com/ijl/orjson) from 3.9.5 to 3.9.6. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.9.5...3.9.6) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 122 ++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index c43f4b5b701f..fd2edcee938d 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -922,67 +922,67 @@ oauth2client==4.1.3 \ # via # gcs-oauth2-boto-plugin # google-apitools -orjson==3.9.5 \ - --hash=sha256:0abcd039f05ae9ab5b0ff11624d0b9e54376253b7d3217a358d09c3edf1d36f7 \ - --hash=sha256:0eefb7cfdd9c2bc65f19f974a5d1dfecbac711dae91ed635820c6b12da7a3c11 \ - --hash=sha256:10cc8ad5ff7188efcb4bec196009d61ce525a4e09488e6d5db41218c7fe4f001 \ - --hash=sha256:1225d2d5ee76a786bda02f8c5e15017462f8432bb960de13d7c2619dba6f0275 \ - --hash=sha256:15df211469625fa27eced4aa08dc03e35f99c57d45a33855cc35f218ea4071b8 \ - --hash=sha256:17404333c40047888ac40bd8c4d49752a787e0a946e728a4e5723f111b6e55a5 \ - --hash=sha256:1a7aa5573a949760d6161d826d34dc36db6011926f836851fe9ccb55b5a7d8e8 \ - --hash=sha256:2493f1351a8f0611bc26e2d3d407efb873032b4f6b8926fed8cfed39210ca4ba \ - --hash=sha256:25b81aca8c7be61e2566246b6a0ca49f8aece70dd3f38c7f5c837f398c4cb142 \ - --hash=sha256:2bcec0b1024d0031ab3eab7a8cb260c8a4e4a5e35993878a2da639d69cdf6a65 \ - --hash=sha256:385c1c713b1e47fd92e96cf55fd88650ac6dfa0b997e8aa7ecffd8b5865078b1 \ - --hash=sha256:4449f84bbb13bcef493d8aa669feadfced0f7c5eea2d0d88b5cc21f812183af8 \ - --hash=sha256:4a3943234342ab37d9ed78fb0a8f81cd4b9532f67bf2ac0d3aa45fa3f0a339f3 \ - --hash=sha256:50ced24a7b23058b469ecdb96e36607fc611cbaee38b58e62a55c80d1b3ad4e1 \ - --hash=sha256:5793a21a21bf34e1767e3d61a778a25feea8476dcc0bdf0ae1bc506dc34561ea \ - --hash=sha256:591ad7d9e4a9f9b104486ad5d88658c79ba29b66c5557ef9edf8ca877a3f8d11 \ - --hash=sha256:5bfa79916ef5fef75ad1f377e54a167f0de334c1fa4ebb8d0224075f3ec3d8c0 \ - --hash=sha256:664cff27f85939059472afd39acff152fbac9a091b7137092cb651cf5f7747b5 \ - --hash=sha256:68c78b2a3718892dc018adbc62e8bab6ef3c0d811816d21e6973dee0ca30c152 \ - --hash=sha256:6900f0248edc1bec2a2a3095a78a7e3ef4e63f60f8ddc583687eed162eedfd69 \ - --hash=sha256:6cc2cbf302fbb2d0b2c3c142a663d028873232a434d89ce1b2604ebe5cc93ce8 \ - --hash=sha256:6daf5ee0b3cf530b9978cdbf71024f1c16ed4a67d05f6ec435c6e7fe7a52724c \ - --hash=sha256:83c9939073281ef7dd7c5ca7f54cceccb840b440cec4b8a326bda507ff88a0a6 \ - --hash=sha256:8547b95ca0e2abd17e1471973e6d676f1d8acedd5f8fb4f739e0612651602d66 \ - --hash=sha256:86127bf194f3b873135e44ce5dc9212cb152b7e06798d5667a898a00f0519be4 \ - --hash=sha256:87ce174d6a38d12b3327f76145acbd26f7bc808b2b458f61e94d83cd0ebb4d76 \ - --hash=sha256:88e18a74d916b74f00d0978d84e365c6bf0e7ab846792efa15756b5fb2f7d49d \ - --hash=sha256:89670fe2732e3c0c54406f77cad1765c4c582f67b915c74fda742286809a0cdc \ - --hash=sha256:89c9332695b838438ea4b9a482bce8ffbfddde4df92750522d928fb00b7b8dce \ - --hash=sha256:8b2852afca17d7eea85f8e200d324e38c851c96598ac7b227e4f6c4e59fbd3df \ - --hash=sha256:9006b1eb645ecf460da067e2dd17768ccbb8f39b01815a571bfcfab7e8da5e52 \ - --hash=sha256:91dda66755795ac6100e303e206b636568d42ac83c156547634256a2e68de694 \ - --hash=sha256:a26fafe966e9195b149950334bdbe9026eca17fe8ffe2d8fa87fdc30ca925d30 \ - --hash=sha256:a461dc9fb60cac44f2d3218c36a0c1c01132314839a0e229d7fb1bba69b810d8 \ - --hash=sha256:a7cb961efe013606913d05609f014ad43edfaced82a576e8b520a5574ce3b2b9 \ - --hash=sha256:a960bb1bc9a964d16fcc2d4af5a04ce5e4dfddca84e3060c35720d0a062064fe \ - --hash=sha256:aa185959c082475288da90f996a82e05e0c437216b96f2a8111caeb1d54ef926 \ - --hash=sha256:ad6845912a71adcc65df7c8a7f2155eba2096cf03ad2c061c93857de70d699ad \ - --hash=sha256:b1b74ea2a3064e1375da87788897935832e806cc784de3e789fd3c4ab8eb3fa5 \ - --hash=sha256:b26b5aa5e9ee1bad2795b925b3adb1b1b34122cb977f30d89e0a1b3f24d18450 \ - --hash=sha256:bd19bc08fa023e4c2cbf8294ad3f2b8922f4de9ba088dbc71e6b268fdf54591c \ - --hash=sha256:c74df28749c076fd6e2157190df23d43d42b2c83e09d79b51694ee7315374ad5 \ - --hash=sha256:ca6b96659c7690773d8cebb6115c631f4a259a611788463e9c41e74fa53bf33f \ - --hash=sha256:d28514b5b6dfaf69097be70d0cf4f1407ec29d0f93e0b4131bf9cc8fd3f3e374 \ - --hash=sha256:d748cc48caf5a91c883d306ab648df1b29e16b488c9316852844dd0fd000d1c2 \ - --hash=sha256:d9f17c59fe6c02bc5f89ad29edb0253d3059fe8ba64806d789af89a45c35269a \ - --hash=sha256:dedf1a6173748202df223aea29de814b5836732a176b33501375c66f6ab7d822 \ - --hash=sha256:e174cc579904a48ee1ea3acb7045e8a6c5d52c17688dfcb00e0e842ec378cabf \ - --hash=sha256:e298e0aacfcc14ef4476c3f409e85475031de24e5b23605a465e9bf4b2156273 \ - --hash=sha256:e6762755470b5c82f07b96b934af32e4d77395a11768b964aaa5eb092817bc31 \ - --hash=sha256:e87dfa6ac0dae764371ab19b35eaaa46dfcb6ef2545dfca03064f21f5d08239f \ - --hash=sha256:ebfdbf695734b1785e792a1315e41835ddf2a3e907ca0e1c87a53f23006ce01d \ - --hash=sha256:ef84724f7d29dcfe3aafb1fc5fc7788dca63e8ae626bb9298022866146091a3e \ - --hash=sha256:f13d61c0c7414ddee1ef4d0f303e2222f8cced5a2e26d9774751aecd72324c9e \ - --hash=sha256:f39f4b99199df05c7ecdd006086259ed25886cdbd7b14c8cdb10c7675cfcca7d \ - --hash=sha256:f8d51702f42c785b115401e1d64a27a2ea767ae7cf1fb8edaa09c7cf1571c660 \ - --hash=sha256:f9850c03a8e42fba1a508466e6a0f99472fd2b4a5f30235ea49b2a1b32c04c11 \ - --hash=sha256:fa504082f53efcbacb9087cc8676c163237beb6e999d43e72acb4bb6f0db11e6 \ - --hash=sha256:ff27e98532cb87379d1a585837d59b187907228268e7b0a87abe122b2be6968e \ - --hash=sha256:ffc544e0e24e9ae69301b9a79df87a971fa5d1c20a6b18dca885699709d01be0 +orjson==3.9.6 \ + --hash=sha256:018f85b53e5c7a8bd1b5ce900358760dddb8a7b9b2da1545c9a17cf42ae99cc6 \ + --hash=sha256:03977a50b682b546c03b7d6e4f39d41cd0568cae533aabd339c853ff33c44a35 \ + --hash=sha256:0520473680d24290558d26aeb8b7d8ba6835955e01ff09a9d0ea866049a0d9c3 \ + --hash=sha256:0664ad3c14dfb61ec794e469525556367a0d9bdc4246a64a6f0b3f8140f89d87 \ + --hash=sha256:08cc162e221105e195301030b3d98e668335da6020424cc61e4ea85fd0d49456 \ + --hash=sha256:0bd7b491ae93221b38c4a6a99a044e0f84a99fba36321e22cf94a38ac7f517d8 \ + --hash=sha256:0ee1664ccc7bdd6de64b6f3f04633837391e2c8e8e04bfd8b3a3270597de2e22 \ + --hash=sha256:118171ed986d71f8201571911d6ec8c8e8e498afd8a8dd038ac55d642d8246b8 \ + --hash=sha256:15e4442fea9aae10074a06e9e486373b960ef61d5735836cb026dd4d104f511d \ + --hash=sha256:181e56cbd94149a721fdbc5417b6283c668e9995a320e6279a87ac8c736d0c6f \ + --hash=sha256:1c7d9a4db055d8febdf949273bc9bc7a15179ea92cdc7c77d0f992fdbf52cfa4 \ + --hash=sha256:1daedb551d3a71873caad350b2b824c56d38e6f03381d7d2d516b9eb01196cdf \ + --hash=sha256:20c7bad91dabf327fb7d034bb579e7d613d1a003f5ed773a3324acc038ae5f9a \ + --hash=sha256:212e6ec66d0bcc9882f9bd0e1870b486a6ead115975108fe17e5e87d0666044e \ + --hash=sha256:212f0524ecd04f217f023bb9f2226f8ff41805cfc69f02d1cbd57300b13cd644 \ + --hash=sha256:2ec2b39c4a38a763e18b93a70ce2114fa322b88ce1896769332271af4f5b33b6 \ + --hash=sha256:2fd1771f0f41569734a85d572735aa47c89b2d0e98b0aa89edc5db849cffd1ef \ + --hash=sha256:32e3e1cc335b1d4539e131fb3a361953b9d7b499e27f81c3648359c0e70ed7aa \ + --hash=sha256:418202b229b00d628e52bc5883a06d43aeecd0449962ad5b4f68113a7fd741a6 \ + --hash=sha256:4601ff8efd8cc45b21a23e0d70bc6f6f67270e95bf8bf4746c4960f696114f47 \ + --hash=sha256:48761464611a333a83686f21f70b483951eb11c6136d7ab46848da03ac90beb1 \ + --hash=sha256:496c1515b6b4a1435667035a955e4531cbea341b0a50e86db42b4b6d0b9c78b0 \ + --hash=sha256:49ecdeb3ae767e6abefd5711c75052692d53a65cce00d6d8caabb5a9b756fcb1 \ + --hash=sha256:49f2f632c8e2db6e9e024d3ea5b9b1343fb5bc4e52d3139c2c724d84f952fae8 \ + --hash=sha256:517a48ddb9684d69002e1ee16d9eb5213be338837936b5dad4bccde61ac4c2ef \ + --hash=sha256:593a939aa8cf0c39a9f8f706681439a172ce98d679bc2b387130bcc219be1ee4 \ + --hash=sha256:62f8d96904024620edd73d0b2d72321ba5fd499ee3a459dd8691d44252db3310 \ + --hash=sha256:66c9e0b728a1e0b1c4acb1f9c728800176a86a4c5b3e3bdb0c00d9dba8823ef0 \ + --hash=sha256:676f037a3ef590f6664d70be956659c7c164daa91b652504cf54d59c252cf29c \ + --hash=sha256:72f6ef36a66a7a2e98d1e247c7a5b7e92d26731c9e9e9a3de627e82a56d1aee6 \ + --hash=sha256:8ba3f11b0197508c4c5e99d11a088182360fd1d4177fe281824105b0cf452137 \ + --hash=sha256:8cfa39f734dac4f5e64d79e5735355d09e6c6a8ade1312daab63efeac325da8a \ + --hash=sha256:93de6166da3ee5523d25acbae6d77f5a76525a1a81b69966a3091a3497f8f9ee \ + --hash=sha256:9b2dd0042fc1527960ddb3e7e81376df9cb799e9c0d31931befd14dc77a4f422 \ + --hash=sha256:9c8ae23b3cde20c2d5472cd9efe35623ebf3c7648e62c8e534082528394078fb \ + --hash=sha256:a1c9987b3e9920c90456c879d9f2ec030f1f7417a1c8ea53badbaceb7a8dcab6 \ + --hash=sha256:a3cb03b1aaf94633d78b389d4110ed5cfd4fc6c09c99a1c61ba418f512b92de7 \ + --hash=sha256:a6843d59c882608da5a026d54e04016924c279f29ead28db9e99d55613326687 \ + --hash=sha256:a6c8702fbb658cb3eb2ac88d50e0c921782a4041012f9138e737341288abe817 \ + --hash=sha256:aeec3598ad7e6b5f0267fa0e57ebc27f140eed7d8e4c68a193d814af3973e1a3 \ + --hash=sha256:b0032c152f29688f84d0660de992df3d76163c45b2ba7ba1aa9bc1f770e84316 \ + --hash=sha256:b077ec427eb805264ab9606406819cb745bef6be0a3d903613c9fa8421547a46 \ + --hash=sha256:b9466e66982ddf18bc0e96e383be5fecc867f659aee3cd621a70de0af0154ac1 \ + --hash=sha256:c0cdbf3b293a11f33aa1b164783b2df8a368bf5a5ec0d46a5f241f00927f3df8 \ + --hash=sha256:cefad5742f0ee2cfae795756eefcedabf4f6e4910fc530cc06f72df2d1ada781 \ + --hash=sha256:dd7b8cf05ed44c79c4a74de128ee481adb1b2446939d565fc7611d34b07d0b3b \ + --hash=sha256:de609af7958f0f9010de04356edb4f58f0cfadbb17103c198561a721981fba74 \ + --hash=sha256:df0545fc5a5f699d7693498847064df56a94990f5a779276549622083e1e850b \ + --hash=sha256:df35c8c0b1a0dad33dc679375d9e6464b0100f1899f72d6b8f9938d877d6f67e \ + --hash=sha256:e2f394a2c112080b8ccec2cc24cc196375980914afa943446df46b6cc133f0ab \ + --hash=sha256:e46ea20dcc8b9d6e5377e125a8101dc59da06086f08e924b6b3c45322709c484 \ + --hash=sha256:e898a5150c3375512f76820bd9a009aab717ffde551f60f381aa8bad9f503bda \ + --hash=sha256:ea3be461eb2aa2299399445001a1d9a53d170efc7dbe39087f163f40733cd9c1 \ + --hash=sha256:f00458dd180d332820545009afca77f3dc4658526995e1aab4127357f473693d \ + --hash=sha256:f19579ba3dbf069b77ebeb70f9628571c9969e51a558cdda7eace9d1885f379f \ + --hash=sha256:f5ff143d42c4a7e6ef0ecdaeca41348eb0ab730a60e3e9927fd0153ba5d4bb60 \ + --hash=sha256:f742af5b28fa153a89e6d87a13bae0ac94bf5c8ac56335102a0e1d9267ed1bc7 \ + --hash=sha256:f994f52901814cf70cc68b835da8394ea50a5464426d122275ac96a0bc39ba20 \ + --hash=sha256:fad6866871411ee9737d4b26fbc7dbe1f66f371ce8a9fffc329bb76805752c4f \ + --hash=sha256:fcb3921062e495a3df770517b5ad9db18a7e0db70d42453bdbb545d8fceb0f85 # via # -r requirements.in # envoy-base-utils From 6ba0ef69686011e1fb8d92471f8c839e34973f98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Sep 2023 10:33:31 +0000 Subject: [PATCH 33/55] build(deps): bump actions/dependency-review-action from 3.0.8 to 3.1.0 (#29513) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 3.0.8 to 3.1.0. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/f6fff72a3217f580d5afd49a46826795305b63c7...6c5ccdad469c9f8a2996bfecaec55a631a347034) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/depsreview.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/depsreview.yml b/.github/workflows/depsreview.yml index c794091f9d61..3890070d58d5 100644 --- a/.github/workflows/depsreview.yml +++ b/.github/workflows/depsreview.yml @@ -13,4 +13,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v4 - name: 'Dependency Review' - uses: actions/dependency-review-action@f6fff72a3217f580d5afd49a46826795305b63c7 + uses: actions/dependency-review-action@6c5ccdad469c9f8a2996bfecaec55a631a347034 From 2cb9401202cc1fb6ac7c33317c7197754462da46 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Fri, 8 Sep 2023 09:42:47 -0400 Subject: [PATCH 34/55] buffer: use std::to_chars for serializing doubles (#29500) A performance regression was found in a late commit as part of #28988 where we switched from a low-precision fast serialization of doubles (absl::StrCat) to a high-precision slow serialization of doubles (absl::StrFormat("%.15g", ...). This resolves that issue using std::to_chars which is faster than absl::StrCat and is also high precision. The downside is it doesn't work on all platforms, so an intermediate high-precision medium-speed solution was found for Apple and GCC. The serialize-double-to-buffer functionality is moved out of Json::Streamer and into a new Buffer::Util namespace. Latest Json histogram serialization performance: ``` ------------------------------------------------------------------------ Benchmark Time CPU Iterations ------------------------------------------------------------------------ BM_HistogramsJson 17.3 ms 17.3 ms 41 Signed-off-by: Joshua Marantz --- source/common/buffer/BUILD | 10 ++++++ source/common/buffer/buffer_util.cc | 43 ++++++++++++++++++++++++++ source/common/buffer/buffer_util.h | 27 ++++++++++++++++ source/common/json/BUILD | 1 + source/common/json/json_streamer.cc | 5 ++- source/server/admin/stats_render.cc | 4 +-- test/common/buffer/BUILD | 10 ++++++ test/common/buffer/buffer_util_test.cc | 29 +++++++++++++++++ 8 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 source/common/buffer/buffer_util.cc create mode 100644 source/common/buffer/buffer_util.h create mode 100644 test/common/buffer/buffer_util_test.cc diff --git a/source/common/buffer/BUILD b/source/common/buffer/BUILD index c5ea9bd23730..b1cd4bb68dfc 100644 --- a/source/common/buffer/BUILD +++ b/source/common/buffer/BUILD @@ -42,3 +42,13 @@ envoy_cc_library( "//source/common/protobuf", ], ) + +envoy_cc_library( + name = "buffer_util_lib", + srcs = ["buffer_util.cc"], + hdrs = ["buffer_util.h"], + deps = [ + "//envoy/buffer:buffer_interface", + "//source/common/common:assert_lib", + ], +) diff --git a/source/common/buffer/buffer_util.cc b/source/common/buffer/buffer_util.cc new file mode 100644 index 000000000000..4f4856165039 --- /dev/null +++ b/source/common/buffer/buffer_util.cc @@ -0,0 +1,43 @@ +#include "source/common/buffer/buffer_util.h" + +#include + +#include "source/common/common/macros.h" + +namespace Envoy { +namespace Buffer { + +void Util::serializeDouble(double number, Buffer::Instance& buffer) { + // Converting a double to a string: who would think it would be so complex? + // It's easy if you don't care about speed or accuracy :). Here we are measuring + // the speed with test/server/admin/stats_handler_speed_test --benchmark_filter=BM_HistogramsJson + // Here are some options: + // * absl::StrCat(number) -- fast (19ms on speed test) but loses precision (drops decimals). + // * absl::StrFormat("%.15g") -- works great but a bit slow (24ms on speed test) + // * `snprintf`(buf, sizeof(buf), "%.15g", ...) -- works but slow as molasses: 30ms. + // * fmt::format("{}") -- works great and is a little faster than absl::StrFormat: 21ms. + // * fmt::to_string -- works great and is a little faster than fmt::format: 19ms. + // * std::to_chars -- fast (16ms) and precise, but requires a few lines to + // generate the string_view, and does not work on all platforms yet. + // + // The accuracy is checked in buffer_util_test. +#if defined(__APPLE__) || defined(GCC_COMPILER) + // On Apple and gcc, std::to_chars does not work with 'double', so we revert + // to the next fastest correct implementation. + buffer.addFragments({fmt::to_string(number)}); +#else + // This version is awkward, and doesn't work on Apple as of August 2023, but + // it is the fastest correct option on other platforms. + char buf[100]; + std::to_chars_result result = std::to_chars(buf, buf + sizeof(buf), number); + ENVOY_BUG(result.ec == std::errc{}, std::make_error_code(result.ec).message()); + buffer.addFragments({absl::string_view(buf, result.ptr - buf)}); + + // Note: there is room to speed this up further by serializing the number directly + // into the buffer. However, buffer does not currently make it easy and fast + // to get (say) 100 characters of raw buffer to serialize into. +#endif +} + +} // namespace Buffer +} // namespace Envoy diff --git a/source/common/buffer/buffer_util.h b/source/common/buffer/buffer_util.h new file mode 100644 index 000000000000..9e8227fa1238 --- /dev/null +++ b/source/common/buffer/buffer_util.h @@ -0,0 +1,27 @@ +#pragma once + +#include "envoy/buffer/buffer.h" + +namespace Envoy { +namespace Buffer { + +class Util { +public: + /** + * Serializes double to a buffer with high precision and high performance. + * + * This helper function is defined on Buffer rather than working with + * intermediate string constructs because, depending on the platform, a + * different sort of intermediate char buffer is chosen for maximum + * performance. It's fastest to then directly append the serialized + * char-buffer into the Buffer::Instance, without defining the intermediate + * char-buffer as part of the API. + * + * @param number the number to convert. + * @param buffer the buffer in which to write the double. + */ + static void serializeDouble(double number, Buffer::Instance& buffer); +}; + +} // namespace Buffer +} // namespace Envoy diff --git a/source/common/json/BUILD b/source/common/json/BUILD index 7daf7c9448c4..ade0a5b3c126 100644 --- a/source/common/json/BUILD +++ b/source/common/json/BUILD @@ -53,6 +53,7 @@ envoy_cc_library( deps = [ ":json_sanitizer_lib", "//envoy/buffer:buffer_interface", + "//source/common/buffer:buffer_util_lib", "//source/common/common:assert_lib", ], ) diff --git a/source/common/json/json_streamer.cc b/source/common/json/json_streamer.cc index 1d6498b4fe63..53fb28dbc1cb 100644 --- a/source/common/json/json_streamer.cc +++ b/source/common/json/json_streamer.cc @@ -1,9 +1,8 @@ #include "source/common/json/json_streamer.h" +#include "source/common/buffer/buffer_util.h" #include "source/common/json/json_sanitizer.h" -#include "absl/strings/str_format.h" - namespace Envoy { namespace Json { @@ -159,7 +158,7 @@ void Streamer::addNumber(double number) { if (std::isnan(number)) { response_.addFragments({"null"}); } else { - response_.addFragments({absl::StrFormat("%.15g", number)}); + Buffer::Util::serializeDouble(number, response_); } } diff --git a/source/server/admin/stats_render.cc b/source/server/admin/stats_render.cc index d1e5ce04efb6..4694b27b7abe 100644 --- a/source/server/admin/stats_render.cc +++ b/source/server/admin/stats_render.cc @@ -53,8 +53,8 @@ void StatsTextRender::addDetail(const std::vector Date: Fri, 8 Sep 2023 11:17:18 -0400 Subject: [PATCH 35/55] Allow custom local address resolvers. (#27705) * Allow custom local address resolvers. #27881 introduces the concept of EDS clusters with hosts that have multiple (potentially > 2) IP addresses. The current implementation of UpstreamLocalAddressSelector limits the number of source addresses in BindConfig artificially to 2, and further requires that the addresses be of different address families. The workaround for this (if we need to specify more than 2 source addresses or have multiple addresses from the same family) is to use a custom address resolver that resolves the bind config address to nullptr (and therefore ignore it) and call bind in a customised SocketInterfaceImpl to a local source address determined by the SocketInterfaceImpl specialisation. This PR makes it possible to define a custom local address selector, that makes it easy to work with a custom address resolver to pick the right source address based on the upstream address selected by HappyEyeballsConnectionImpl Signed-off-by: pcrao --- api/BUILD | 1 + api/envoy/config/core/v3/address.proto | 19 +- .../upstream/local_address_selector/v3/BUILD | 9 + .../v3/default_local_address_selector.proto | 31 ++ api/versioning/BUILD | 1 + changelogs/current.yaml | 5 + docs/root/api-v3/config/upstream/upstream.rst | 1 + envoy/upstream/upstream.h | 49 ++- mobile/envoy_build_config/BUILD | 1 + .../envoy_build_config/extension_registry.cc | 4 + .../network/happy_eyeballs_connection_impl.cc | 3 +- .../network/happy_eyeballs_connection_impl.h | 21 +- source/common/protobuf/BUILD | 1 + .../protobuf/create_reflectable_message.cc | 4 + source/common/upstream/BUILD | 37 ++ .../default_local_address_selector.cc | 36 ++ .../upstream/default_local_address_selector.h | 34 ++ .../default_local_address_selector_factory.cc | 75 ++++ .../default_local_address_selector_factory.h | 34 ++ source/common/upstream/upstream_impl.cc | 322 ++++++++---------- source/common/upstream/upstream_impl.h | 38 +-- source/extensions/extensions_metadata.yaml | 7 + test/common/http/http3/conn_pool_test.cc | 29 +- test/common/tcp/conn_pool_test.cc | 4 +- test/common/upstream/BUILD | 44 +++ .../default_local_address_selector_test.cc | 38 +++ ...local_address_selector_integration_test.cc | 249 ++++++++++++++ .../upstream/test_local_address_selector.h | 62 ++++ test/common/upstream/upstream_impl_test.cc | 96 ++++-- test/mocks/upstream/cluster_info.cc | 5 +- test/mocks/upstream/cluster_info.h | 22 +- tools/code_format/config.yaml | 1 + tools/extensions/extensions_schema.yaml | 2 + 33 files changed, 999 insertions(+), 286 deletions(-) create mode 100644 api/envoy/config/upstream/local_address_selector/v3/BUILD create mode 100644 api/envoy/config/upstream/local_address_selector/v3/default_local_address_selector.proto create mode 100644 source/common/upstream/default_local_address_selector.cc create mode 100644 source/common/upstream/default_local_address_selector.h create mode 100644 source/common/upstream/default_local_address_selector_factory.cc create mode 100644 source/common/upstream/default_local_address_selector_factory.h create mode 100644 test/common/upstream/default_local_address_selector_test.cc create mode 100644 test/common/upstream/local_address_selector_integration_test.cc create mode 100644 test/common/upstream/test_local_address_selector.h diff --git a/api/BUILD b/api/BUILD index 0cfe5040f790..3ebfa5f8d57d 100644 --- a/api/BUILD +++ b/api/BUILD @@ -123,6 +123,7 @@ proto_library( "//envoy/config/route/v3:pkg", "//envoy/config/tap/v3:pkg", "//envoy/config/trace/v3:pkg", + "//envoy/config/upstream/local_address_selector/v3:pkg", "//envoy/data/accesslog/v3:pkg", "//envoy/data/cluster/v3:pkg", "//envoy/data/core/v3:pkg", diff --git a/api/envoy/config/core/v3/address.proto b/api/envoy/config/core/v3/address.proto index 09b13969e85d..3bd9b4cd3dc1 100644 --- a/api/envoy/config/core/v3/address.proto +++ b/api/envoy/config/core/v3/address.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package envoy.config.core.v3; +import "envoy/config/core/v3/extension.proto"; import "envoy/config/core/v3/socket_option.proto"; import "google/protobuf/wrappers.proto"; @@ -130,7 +131,7 @@ message ExtraSourceAddress { SocketOptionsOverride socket_options = 2; } -// [#next-free-field: 6] +// [#next-free-field: 7] message BindConfig { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.BindConfig"; @@ -151,19 +152,21 @@ message BindConfig { repeated SocketOption socket_options = 3; // Extra source addresses appended to the address specified in the `source_address` - // field. This enables to specify multiple source addresses. Currently, only one extra - // address can be supported, and the extra address should have a different IP version - // with the address in the `source_address` field. The address which has the same IP - // version with the target host's address IP version will be used as bind address. If more - // than one extra address specified, only the first address matched IP version will be - // returned. If there is no same IP version address found, the address in the `source_address` - // will be returned. + // field. This enables to specify multiple source addresses. + // The source address selection is determined by :ref:`local_address_selector + // `. repeated ExtraSourceAddress extra_source_addresses = 5; // Deprecated by // :ref:`extra_source_addresses ` repeated SocketAddress additional_source_addresses = 4 [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + + // Custom local address selector to override the default (i.e. + // :ref:`DefaultLocalAddressSelector + // `). + // [#extension-category: envoy.upstream.local_address_selector] + TypedExtensionConfig local_address_selector = 6; } // Addresses specify either a logical or physical address and port, which are diff --git a/api/envoy/config/upstream/local_address_selector/v3/BUILD b/api/envoy/config/upstream/local_address_selector/v3/BUILD new file mode 100644 index 000000000000..ee92fb652582 --- /dev/null +++ b/api/envoy/config/upstream/local_address_selector/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], +) diff --git a/api/envoy/config/upstream/local_address_selector/v3/default_local_address_selector.proto b/api/envoy/config/upstream/local_address_selector/v3/default_local_address_selector.proto new file mode 100644 index 000000000000..4ecd27d1fe09 --- /dev/null +++ b/api/envoy/config/upstream/local_address_selector/v3/default_local_address_selector.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package envoy.config.upstream.local_address_selector.v3; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.config.upstream.local_address_selector.v3"; +option java_outer_classname = "DefaultLocalAddressSelectorProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/config/upstream/local_address_selector/v3;local_address_selectorv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Default Local Address Selector] +// [#extension: envoy.upstream.local_address_selector.default_local_address_selector] + +// Default implementation of a local address selector. This implementation is +// used if :ref:`local_address_selector +// ` is not +// specified. +// This implementation supports the specification of only one address in +// :ref:`extra_source_addresses +// ` which +// is appended to the address specified in the +// :ref:`source_address ` +// field. The extra address should have a different IP version than the address in the +// `source_address` field. The address which has the same IP +// version with the target host's address IP version will be used as bind address. +// If there is no same IP version address found, the address in the `source_address` field will +// be returned. +message DefaultLocalAddressSelector { +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 8f97a336d71f..72e6de52b0af 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -61,6 +61,7 @@ proto_library( "//envoy/config/route/v3:pkg", "//envoy/config/tap/v3:pkg", "//envoy/config/trace/v3:pkg", + "//envoy/config/upstream/local_address_selector/v3:pkg", "//envoy/data/accesslog/v3:pkg", "//envoy/data/cluster/v3:pkg", "//envoy/data/core/v3:pkg", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2091179036be..598143a28934 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -210,6 +210,11 @@ new_features: added :ref:`record_headers_received_time ` to control writing request and response headers received time in trace output. +- area: upstream + change: | + Added the ability to specify a custom upstream local address selector using + :ref:`local_address_selector:`. + deprecated: - area: tracing change: | diff --git a/docs/root/api-v3/config/upstream/upstream.rst b/docs/root/api-v3/config/upstream/upstream.rst index 5b8489ad10b2..d97a7eb898a2 100644 --- a/docs/root/api-v3/config/upstream/upstream.rst +++ b/docs/root/api-v3/config/upstream/upstream.rst @@ -9,3 +9,4 @@ Upstream configuration ../../extensions/upstreams/http/*/v3/** ../../extensions/upstreams/tcp/v3/** ../../extensions/upstreams/tcp/*/v3/** + local_address_selector/v3/** diff --git a/envoy/upstream/upstream.h b/envoy/upstream/upstream.h index cc43c573c0f2..85c3bafe5007 100644 --- a/envoy/upstream/upstream.h +++ b/envoy/upstream/upstream.h @@ -50,7 +50,7 @@ struct UpstreamLocalAddress { }; /** - * Used to select upstream local address based on the endpoint address. + * Interface to select upstream local address based on the endpoint address. */ class UpstreamLocalAddressSelector { public: @@ -63,9 +63,47 @@ class UpstreamLocalAddressSelector { * @return UpstreamLocalAddress which includes the selected upstream local address and socket * options. */ - virtual UpstreamLocalAddress getUpstreamLocalAddress( - const Network::Address::InstanceConstSharedPtr& endpoint_address, - const Network::ConnectionSocket::OptionsSharedPtr& socket_options) const PURE; + UpstreamLocalAddress + getUpstreamLocalAddress(const Network::Address::InstanceConstSharedPtr& endpoint_address, + const Network::ConnectionSocket::OptionsSharedPtr& socket_options) const { + UpstreamLocalAddress local_address = getUpstreamLocalAddressImpl(endpoint_address); + Network::ConnectionSocket::OptionsSharedPtr connection_options = + std::make_shared( + socket_options ? *socket_options + : std::vector{}); + return {local_address.address_, + local_address.socket_options_ != nullptr + ? Network::Socket::appendOptions(connection_options, local_address.socket_options_) + : connection_options}; + } + +private: + /* + * The implementation is responsible for picking the ``UpstreamLocalAddress`` + * based on the ``endpoint_address``. However adding the connection socket + * options is the responsibility of the base class. + */ + virtual UpstreamLocalAddress getUpstreamLocalAddressImpl( + const Network::Address::InstanceConstSharedPtr& endpoint_address) const PURE; +}; + +using UpstreamLocalAddressSelectorConstSharedPtr = + std::shared_ptr; + +class UpstreamLocalAddressSelectorFactory : public Config::TypedFactory { +public: + ~UpstreamLocalAddressSelectorFactory() override = default; + + /** + * @param cluster_name is set to the name of the cluster if ``bind_config`` is + * from cluster config. If the bind config from the cluster manager, the param + * is empty. + */ + virtual UpstreamLocalAddressSelectorConstSharedPtr + createLocalAddressSelector(std::vector upstream_local_addresses, + absl::optional cluster_name) const PURE; + + std::string category() const override { return "envoy.upstream.local_address_selector"; } }; /** @@ -1134,8 +1172,7 @@ class ClusterInfo : public Http::FilterChainFactory { /** * @return std::shared_ptr as upstream local address selector. */ - virtual std::shared_ptr - getUpstreamLocalAddressSelector() const PURE; + virtual UpstreamLocalAddressSelectorConstSharedPtr getUpstreamLocalAddressSelector() const PURE; /** * @return the configuration for load balancer subsets. diff --git a/mobile/envoy_build_config/BUILD b/mobile/envoy_build_config/BUILD index d9c8ecfc7cc4..d83d85bedc2d 100644 --- a/mobile/envoy_build_config/BUILD +++ b/mobile/envoy_build_config/BUILD @@ -16,6 +16,7 @@ envoy_cc_library( "@envoy//source/common/http/matching:inputs_lib", "@envoy//source/common/network:socket_lib", "@envoy//source/common/router:upstream_codec_filter_lib", + "@envoy//source/common/upstream:default_local_address_selector_factory", "@envoy//source/common/watchdog:abort_action_config", "@envoy//source/extensions/clusters/dynamic_forward_proxy:cluster", "@envoy//source/extensions/compression/brotli/decompressor:config", diff --git a/mobile/envoy_build_config/extension_registry.cc b/mobile/envoy_build_config/extension_registry.cc index e5c399b5d352..af1922af939b 100644 --- a/mobile/envoy_build_config/extension_registry.cc +++ b/mobile/envoy_build_config/extension_registry.cc @@ -13,6 +13,7 @@ #include "source/common/network/default_client_connection_factory.h" #include "source/common/network/socket_interface_impl.h" #include "source/common/router/upstream_codec_filter.h" +#include "source/common/upstream/default_local_address_selector_factory.h" #include "source/common/watchdog/abort_action_config.h" #include "source/extensions/clusters/dynamic_forward_proxy/cluster.h" #include "source/extensions/compression/brotli/decompressor/config.h" @@ -174,6 +175,9 @@ void ExtensionRegistry::registerFactories() { // Mobile compiles out watchdog support. Watchdog::forceRegisterAbortActionFactory(); + // This is required for the default upstream local address selector. + Upstream::forceRegisterDefaultUpstreamLocalAddressSelectorFactory(); + #ifdef ENVOY_MOBILE_STATS_REPORTING Network::Address::forceRegisterIpResolver(); Upstream::forceRegisterLogicalDnsClusterFactory(); diff --git a/source/common/network/happy_eyeballs_connection_impl.cc b/source/common/network/happy_eyeballs_connection_impl.cc index 67cf720343a6..a268b5801289 100644 --- a/source/common/network/happy_eyeballs_connection_impl.cc +++ b/source/common/network/happy_eyeballs_connection_impl.cc @@ -7,7 +7,8 @@ namespace Network { HappyEyeballsConnectionProvider::HappyEyeballsConnectionProvider( Event::Dispatcher& dispatcher, const std::vector& address_list, - const std::shared_ptr& upstream_local_address_selector, + const std::shared_ptr& + upstream_local_address_selector, UpstreamTransportSocketFactory& socket_factory, TransportSocketOptionsConstSharedPtr transport_socket_options, const Upstream::HostDescriptionConstSharedPtr& host, diff --git a/source/common/network/happy_eyeballs_connection_impl.h b/source/common/network/happy_eyeballs_connection_impl.h index 55516d199a8e..1b9684d5a2d5 100644 --- a/source/common/network/happy_eyeballs_connection_impl.h +++ b/source/common/network/happy_eyeballs_connection_impl.h @@ -15,14 +15,15 @@ namespace Network { class HappyEyeballsConnectionProvider : public ConnectionProvider, Logger::Loggable { public: - HappyEyeballsConnectionProvider(Event::Dispatcher& dispatcher, - const std::vector& address_list, - const std::shared_ptr& - upstream_local_address_selector, - UpstreamTransportSocketFactory& socket_factory, - TransportSocketOptionsConstSharedPtr transport_socket_options, - const Upstream::HostDescriptionConstSharedPtr& host, - const ConnectionSocket::OptionsSharedPtr options); + HappyEyeballsConnectionProvider( + Event::Dispatcher& dispatcher, + const std::vector& address_list, + const std::shared_ptr& + upstream_local_address_selector, + UpstreamTransportSocketFactory& socket_factory, + TransportSocketOptionsConstSharedPtr transport_socket_options, + const Upstream::HostDescriptionConstSharedPtr& host, + const ConnectionSocket::OptionsSharedPtr options); bool hasNextConnection() override; ClientConnectionPtr createNextConnection(const uint64_t id) override; size_t nextConnection() override; @@ -39,7 +40,7 @@ class HappyEyeballsConnectionProvider : public ConnectionProvider, Event::Dispatcher& dispatcher_; // List of addresses to attempt to connect to. const std::vector address_list_; - const std::shared_ptr upstream_local_address_selector_; + const Upstream::UpstreamLocalAddressSelectorConstSharedPtr upstream_local_address_selector_; UpstreamTransportSocketFactory& socket_factory_; TransportSocketOptionsConstSharedPtr transport_socket_options_; const Upstream::HostDescriptionConstSharedPtr host_; @@ -70,7 +71,7 @@ class HappyEyeballsConnectionImpl : public MultiConnectionBaseImpl, public: HappyEyeballsConnectionImpl(Event::Dispatcher& dispatcher, const std::vector& address_list, - const std::shared_ptr& + const std::shared_ptr& upstream_local_address_selector, UpstreamTransportSocketFactory& socket_factory, TransportSocketOptionsConstSharedPtr transport_socket_options, diff --git a/source/common/protobuf/BUILD b/source/common/protobuf/BUILD index ed6a758a807c..ba96f6cb5c95 100644 --- a/source/common/protobuf/BUILD +++ b/source/common/protobuf/BUILD @@ -121,6 +121,7 @@ envoy_cc_library( "@envoy_api//envoy/config/overload/v3:pkg_cc_proto_descriptor", "@envoy_api//envoy/config/route/v3:pkg_cc_proto_descriptor", "@envoy_api//envoy/config/trace/v3:pkg_cc_proto_descriptor", + "@envoy_api//envoy/config/upstream/local_address_selector/v3:pkg_cc_proto_descriptor", "@envoy_api//envoy/data/accesslog/v3:pkg_cc_proto_descriptor", "@envoy_api//envoy/data/cluster/v3:pkg_cc_proto_descriptor", "@envoy_api//envoy/data/core/v3:pkg_cc_proto_descriptor", diff --git a/source/common/protobuf/create_reflectable_message.cc b/source/common/protobuf/create_reflectable_message.cc index 5470e6d58c6b..41b82e0cdf36 100644 --- a/source/common/protobuf/create_reflectable_message.cc +++ b/source/common/protobuf/create_reflectable_message.cc @@ -77,6 +77,7 @@ Protobuf::ReflectableMessage createReflectableMessage(const Protobuf::Message& m #include "envoy/config/trace/v3/trace_descriptor.pb.h" #include "envoy/config/trace/v3/xray_descriptor.pb.h" #include "envoy/config/trace/v3/zipkin_descriptor.pb.h" +#include "envoy/config/upstream/local_address_selector/v3/default_local_address_selector_descriptor.pb.h" #include "envoy/data/accesslog/v3/accesslog_descriptor.pb.h" #include "envoy/data/cluster/v3/outlier_detection_event_descriptor.pb.h" #include "envoy/data/core/v3/health_check_event_descriptor.pb.h" @@ -255,6 +256,9 @@ std::unique_ptr createTranscoder() { protobuf::reflection::envoy_config_trace_v3_trace::kFileDescriptorInfo, protobuf::reflection::envoy_config_trace_v3_xray::kFileDescriptorInfo, protobuf::reflection::envoy_config_trace_v3_zipkin::kFileDescriptorInfo, + protobuf::reflection:: + envoy_config_upstream_local_address_selector_v3_default_local_address_selector:: + kFileDescriptorInfo, protobuf::reflection::envoy_data_accesslog_v3_accesslog::kFileDescriptorInfo, protobuf::reflection::envoy_data_cluster_v3_outlier_detection_event::kFileDescriptorInfo, protobuf::reflection::envoy_data_core_v3_health_check_event::kFileDescriptorInfo, diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index f93085e1b096..a8499ce61f4b 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -393,6 +393,7 @@ envoy_cc_library( srcs = ["upstream_impl.cc"], deps = [ ":cluster_factory_lib", + ":default_local_address_selector_factory", ":health_checker_lib", # TODO(mattklein123): Move the clusters to extensions so they can be compiled out. ":upstream_includes", @@ -401,6 +402,7 @@ envoy_cc_library( "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "//source/common/stats:deferred_creation", "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", + "@envoy_api//envoy/config/upstream/local_address_selector/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/upstream_codec/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/transport_sockets/raw_buffer/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/transport_sockets/http_11_proxy/v3:pkg_cc_proto", @@ -408,6 +410,7 @@ envoy_cc_library( "//envoy/event:timer_interface", "//envoy/network:dns_interface", "//envoy/network:listen_socket_interface", + "//envoy/registry", "//envoy/ssl:context_interface", "//envoy/upstream:health_checker_interface", "//source/common/common:dns_utils_lib", @@ -566,3 +569,37 @@ envoy_cc_library( "//envoy/stats:stats_interface", ], ) + +envoy_cc_library( + name = "default_local_address_selector", + srcs = [ + "default_local_address_selector.cc", + ], + hdrs = [ + "default_local_address_selector.h", + ], + external_deps = ["abseil_optional"], + deps = [ + "//envoy/upstream:upstream_interface", + ], +) + +envoy_cc_library( + name = "default_local_address_selector_factory", + srcs = [ + "default_local_address_selector_factory.cc", + ], + hdrs = [ + "default_local_address_selector_factory.h", + ], + external_deps = ["abseil_optional"], + deps = [ + ":default_local_address_selector", + "//envoy/network:address_interface", + "//envoy/network:socket_interface", + "//envoy/registry", + "//envoy/upstream:upstream_interface", + "//source/common/network:resolver_lib", + "@envoy_api//envoy/config/upstream/local_address_selector/v3:pkg_cc_proto", + ], +) diff --git a/source/common/upstream/default_local_address_selector.cc b/source/common/upstream/default_local_address_selector.cc new file mode 100644 index 000000000000..8108116e3bc4 --- /dev/null +++ b/source/common/upstream/default_local_address_selector.cc @@ -0,0 +1,36 @@ +#include "source/common/upstream/default_local_address_selector.h" + +#include + +namespace Envoy { +namespace Upstream { + +DefaultUpstreamLocalAddressSelector::DefaultUpstreamLocalAddressSelector( + std::vector<::Envoy::Upstream::UpstreamLocalAddress>&& upstream_local_addresses) + : upstream_local_addresses_(std::move(upstream_local_addresses)) { + // If bind config is not provided, we insert at least one + // ``UpstreamLocalAddress`` with null address. + ASSERT(upstream_local_addresses_.size() > 0); +} + +UpstreamLocalAddress DefaultUpstreamLocalAddressSelector::getUpstreamLocalAddressImpl( + const Network::Address::InstanceConstSharedPtr& endpoint_address) const { + for (auto& local_address : upstream_local_addresses_) { + if (local_address.address_ == nullptr) { + continue; + } + + // Invalid addresses should have been rejected while parsing the bind + // config. + ASSERT(local_address.address_->ip() != nullptr); + if (endpoint_address->ip() != nullptr && + local_address.address_->ip()->version() == endpoint_address->ip()->version()) { + return local_address; + } + } + + return upstream_local_addresses_[0]; +} + +} // namespace Upstream +} // namespace Envoy diff --git a/source/common/upstream/default_local_address_selector.h b/source/common/upstream/default_local_address_selector.h new file mode 100644 index 000000000000..4c4540f172d7 --- /dev/null +++ b/source/common/upstream/default_local_address_selector.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "envoy/upstream/upstream.h" + +#include "absl/types/optional.h" + +namespace Envoy { +namespace Upstream { + +/** + * Default implementation of UpstreamLocalAddressSelector. + * + * See :ref:`DefaultLocalAddressSelector + * ` + * for a description of the behavior of this implementation. + */ +class DefaultUpstreamLocalAddressSelector : public UpstreamLocalAddressSelector { +public: + DefaultUpstreamLocalAddressSelector( + std::vector<::Envoy::Upstream::UpstreamLocalAddress>&& upstream_local_addresses); + + // UpstreamLocalAddressSelector + UpstreamLocalAddress getUpstreamLocalAddressImpl( + const Network::Address::InstanceConstSharedPtr& endpoint_address) const override; + +private: + std::vector upstream_local_addresses_; +}; + +} // namespace Upstream +} // namespace Envoy diff --git a/source/common/upstream/default_local_address_selector_factory.cc b/source/common/upstream/default_local_address_selector_factory.cc new file mode 100644 index 000000000000..64db72d67c33 --- /dev/null +++ b/source/common/upstream/default_local_address_selector_factory.cc @@ -0,0 +1,75 @@ +#include "source/common/upstream/default_local_address_selector_factory.h" + +#include "source/common/upstream/default_local_address_selector.h" + +namespace Envoy { +namespace Upstream { + +namespace { +constexpr absl::string_view kDefaultLocalAddressSelectorName = + "envoy.upstream.local_address_selector.default_local_address_selector"; + +void validate(const std::vector<::Envoy::Upstream::UpstreamLocalAddress>& upstream_local_addresses, + absl::optional cluster_name) { + + if (upstream_local_addresses.size() == 0) { + throw EnvoyException(fmt::format("{}'s upstream binding config has no valid source address.", + !(cluster_name.has_value()) + ? "Bootstrap" + : fmt::format("Cluster {}", cluster_name.value()))); + } + + if (upstream_local_addresses.size() > 2) { + throw EnvoyException(fmt::format( + "{}'s upstream binding config has more than one extra/additional source addresses. Only " + "one extra/additional source can be supported in BindConfig's " + "extra_source_addresses/additional_source_addresses field", + !(cluster_name.has_value()) ? "Bootstrap" + : fmt::format("Cluster {}", cluster_name.value()))); + } + // If we have exactly one upstream address, it needs to have a valid IP + // version if non-null. This is enforced by the fact that BindConfig only + // allows socket address. + ASSERT(upstream_local_addresses.size() != 1 || upstream_local_addresses[0].address_ == nullptr || + upstream_local_addresses[0].address_->ip() != nullptr); + + // If we have more than one upstream address, they need to have different IP versions. + if (upstream_local_addresses.size() == 2) { + ASSERT(upstream_local_addresses[0].address_ != nullptr && + upstream_local_addresses[1].address_ != nullptr && + upstream_local_addresses[0].address_->ip() != nullptr && + upstream_local_addresses[1].address_->ip() != nullptr); + + if (upstream_local_addresses[0].address_->ip()->version() == + upstream_local_addresses[1].address_->ip()->version()) { + throw EnvoyException(fmt::format( + "{}'s upstream binding config has two same IP version source addresses. Only two " + "different IP version source addresses can be supported in BindConfig's source_address " + "and extra_source_addresses/additional_source_addresses fields", + !(cluster_name.has_value()) ? "Bootstrap" + : fmt::format("Cluster {}", cluster_name.value()))); + } + } +} + +} // namespace + +std::string DefaultUpstreamLocalAddressSelectorFactory::name() const { + return std::string(kDefaultLocalAddressSelectorName); +} + +UpstreamLocalAddressSelectorConstSharedPtr +DefaultUpstreamLocalAddressSelectorFactory::createLocalAddressSelector( + std::vector<::Envoy::Upstream::UpstreamLocalAddress> upstream_local_addresses, + absl::optional cluster_name) const { + validate(upstream_local_addresses, cluster_name); + return std::make_shared(std::move(upstream_local_addresses)); +} + +/** + * Static registration for the default local address selector. @see RegisterFactory. + */ +REGISTER_FACTORY(DefaultUpstreamLocalAddressSelectorFactory, UpstreamLocalAddressSelectorFactory); + +} // namespace Upstream +} // namespace Envoy diff --git a/source/common/upstream/default_local_address_selector_factory.h b/source/common/upstream/default_local_address_selector_factory.h new file mode 100644 index 000000000000..7f8baa0fa3d6 --- /dev/null +++ b/source/common/upstream/default_local_address_selector_factory.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "envoy/config/upstream/local_address_selector/v3/default_local_address_selector.pb.h" +#include "envoy/network/address.h" +#include "envoy/network/socket.h" +#include "envoy/registry/registry.h" +#include "envoy/upstream/upstream.h" + +#include "absl/types/optional.h" + +namespace Envoy { +namespace Upstream { + +class DefaultUpstreamLocalAddressSelectorFactory : public UpstreamLocalAddressSelectorFactory { +public: + std::string name() const override; + + UpstreamLocalAddressSelectorConstSharedPtr createLocalAddressSelector( + std::vector<::Envoy::Upstream::UpstreamLocalAddress> upstream_local_addresses, + absl::optional cluster_name) const override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique< + envoy::config::upstream::local_address_selector::v3::DefaultLocalAddressSelector>(); + } +}; + +DECLARE_FACTORY(DefaultUpstreamLocalAddressSelectorFactory); + +} // namespace Upstream +} // namespace Envoy diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 298bb47ec4c9..bc6d09fc21c8 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -15,6 +15,7 @@ #include "envoy/config/core/v3/health_check.pb.h" #include "envoy/config/core/v3/protocol.pb.h" #include "envoy/config/endpoint/v3/endpoint_components.pb.h" +#include "envoy/config/upstream/local_address_selector/v3/default_local_address_selector.pb.h" #include "envoy/event/dispatcher.h" #include "envoy/event/timer.h" #include "envoy/extensions/filters/http/upstream_codec/v3/upstream_codec.pb.h" @@ -23,6 +24,7 @@ #include "envoy/init/manager.h" #include "envoy/network/dns.h" #include "envoy/network/transport_socket.h" +#include "envoy/registry/registry.h" #include "envoy/secret/secret_manager.h" #include "envoy/server/filter_config.h" #include "envoy/server/transport_socket_config.h" @@ -61,7 +63,6 @@ namespace Envoy { namespace Upstream { namespace { - std::string addressToString(Network::Address::InstanceConstSharedPtr address) { if (!address) { return ""; @@ -170,83 +171,9 @@ Stats::ScopeSharedPtr generateStatsScope(const envoy::config::cluster::v3::Clust "cluster.{}.", config.alt_stat_name().empty() ? config.name() : config.alt_stat_name())); } -} // namespace - -UpstreamLocalAddressSelectorImpl::UpstreamLocalAddressSelectorImpl( - const envoy::config::cluster::v3::Cluster& cluster_config, - const absl::optional& bootstrap_bind_config) { - base_socket_options_ = buildBaseSocketOptions( - cluster_config, bootstrap_bind_config.value_or(envoy::config::core::v3::BindConfig{})); - cluster_socket_options_ = buildClusterSocketOptions( - cluster_config, bootstrap_bind_config.value_or(envoy::config::core::v3::BindConfig{})); - - ASSERT(base_socket_options_ != nullptr); - ASSERT(cluster_socket_options_ != nullptr); - - if (cluster_config.has_upstream_bind_config()) { - parseBindConfig(cluster_config.name(), cluster_config.upstream_bind_config(), - base_socket_options_, cluster_socket_options_); - } else if (bootstrap_bind_config.has_value()) { - parseBindConfig("", *bootstrap_bind_config, base_socket_options_, cluster_socket_options_); - } -} - Network::ConnectionSocket::OptionsSharedPtr -UpstreamLocalAddressSelectorImpl::combineConnectionSocketOptions( - const Network::ConnectionSocket::OptionsSharedPtr& local_address_options, - const Network::ConnectionSocket::OptionsSharedPtr& options) const { - Network::ConnectionSocket::OptionsSharedPtr connection_options = - std::make_shared(); - - if (options) { - connection_options = std::make_shared(); - *connection_options = *options; - Network::Socket::appendOptions(connection_options, local_address_options); - } else { - *connection_options = *local_address_options; - } - - return connection_options; -} - -UpstreamLocalAddress UpstreamLocalAddressSelectorImpl::getUpstreamLocalAddress( - const Network::Address::InstanceConstSharedPtr& endpoint_address, - const Network::ConnectionSocket::OptionsSharedPtr& socket_options) const { - // If there is no upstream local address specified, then return a nullptr for the address. And - // return the socket options. - if (upstream_local_addresses_.empty()) { - UpstreamLocalAddress local_address; - local_address.address_ = nullptr; - local_address.socket_options_ = std::make_shared(); - Network::Socket::appendOptions(local_address.socket_options_, base_socket_options_); - Network::Socket::appendOptions(local_address.socket_options_, cluster_socket_options_); - local_address.socket_options_ = - combineConnectionSocketOptions(local_address.socket_options_, socket_options); - return local_address; - } - - for (auto& local_address : upstream_local_addresses_) { - if (local_address.address_ == nullptr) { - continue; - } - - ASSERT(local_address.address_->ip() != nullptr); - if (endpoint_address->ip() != nullptr && - local_address.address_->ip()->version() == endpoint_address->ip()->version()) { - return {local_address.address_, - combineConnectionSocketOptions(local_address.socket_options_, socket_options)}; - } - } - - return { - upstream_local_addresses_[0].address_, - combineConnectionSocketOptions(upstream_local_addresses_[0].socket_options_, socket_options)}; -} - -const Network::ConnectionSocket::OptionsSharedPtr -UpstreamLocalAddressSelectorImpl::buildBaseSocketOptions( - const envoy::config::cluster::v3::Cluster& cluster_config, - const envoy::config::core::v3::BindConfig& bootstrap_bind_config) { +buildBaseSocketOptions(const envoy::config::cluster::v3::Cluster& cluster_config, + const envoy::config::core::v3::BindConfig& bootstrap_bind_config) { Network::ConnectionSocket::OptionsSharedPtr base_options = std::make_shared(); @@ -272,132 +199,172 @@ UpstreamLocalAddressSelectorImpl::buildBaseSocketOptions( return base_options; } -const Network::ConnectionSocket::OptionsSharedPtr -UpstreamLocalAddressSelectorImpl::buildClusterSocketOptions( - const envoy::config::cluster::v3::Cluster& cluster_config, - const envoy::config::core::v3::BindConfig bind_config) { +Network::ConnectionSocket::OptionsSharedPtr +buildClusterSocketOptions(const envoy::config::cluster::v3::Cluster& cluster_config, + const envoy::config::core::v3::BindConfig& bootstrap_bind_config) { Network::ConnectionSocket::OptionsSharedPtr cluster_options = std::make_shared(); // Cluster socket_options trump cluster manager wide. - if (bind_config.socket_options().size() + + if (bootstrap_bind_config.socket_options().size() + cluster_config.upstream_bind_config().socket_options().size() > 0) { auto socket_options = !cluster_config.upstream_bind_config().socket_options().empty() ? cluster_config.upstream_bind_config().socket_options() - : bind_config.socket_options(); + : bootstrap_bind_config.socket_options(); Network::Socket::appendOptions( cluster_options, Network::SocketOptionFactory::buildLiteralOptions(socket_options)); } return cluster_options; } -void UpstreamLocalAddressSelectorImpl::parseBindConfig( - const std::string cluster_name, const envoy::config::core::v3::BindConfig& bind_config, - const Network::ConnectionSocket::OptionsSharedPtr& base_socket_options, - const Network::ConnectionSocket::OptionsSharedPtr& cluster_socket_options) { - if (bind_config.additional_source_addresses_size() > 0 && - bind_config.extra_source_addresses_size() > 0) { - throw EnvoyException( - fmt::format("Can't specify both `extra_source_addresses` and `additional_source_addresses` " - "in the {}'s upstream binding config", - cluster_name.empty() ? "Bootstrap" : fmt::format("Cluster {}", cluster_name))); - } - - if (bind_config.extra_source_addresses_size() > 1) { - throw EnvoyException(fmt::format( - "{}'s upstream binding config has more than one extra source addresses. Only one " - "extra source can be supported in BindConfig's extra_source_addresses field", - cluster_name.empty() ? "Bootstrap" : fmt::format("Cluster {}", cluster_name))); - } - - if (bind_config.additional_source_addresses_size() > 1) { - throw EnvoyException(fmt::format( - "{}'s upstream binding config has more than one additional source addresses. Only one " - "additional source can be supported in BindConfig's additional_source_addresses field", - cluster_name.empty() ? "Bootstrap" : fmt::format("Cluster {}", cluster_name))); - } +std::vector<::Envoy::Upstream::UpstreamLocalAddress> +parseBindConfig(::Envoy::OptRef bind_config, + const absl::optional& cluster_name, + Network::ConnectionSocket::OptionsSharedPtr base_socket_options, + Network::ConnectionSocket::OptionsSharedPtr cluster_socket_options) { + + std::vector<::Envoy::Upstream::UpstreamLocalAddress> upstream_local_addresses; + if (bind_config.has_value()) { + UpstreamLocalAddress upstream_local_address; + upstream_local_address.address_ = + bind_config->has_source_address() + ? ::Envoy::Network::Address::resolveProtoSocketAddress(bind_config->source_address()) + : nullptr; + upstream_local_address.socket_options_ = std::make_shared(); + + ::Envoy::Network::Socket::appendOptions(upstream_local_address.socket_options_, + base_socket_options); + ::Envoy::Network::Socket::appendOptions(upstream_local_address.socket_options_, + cluster_socket_options); + + upstream_local_addresses.push_back(upstream_local_address); + + for (const auto& extra_source_address : bind_config->extra_source_addresses()) { + UpstreamLocalAddress extra_upstream_local_address; + extra_upstream_local_address.address_ = + ::Envoy::Network::Address::resolveProtoSocketAddress(extra_source_address.address()); + + extra_upstream_local_address.socket_options_ = + std::make_shared<::Envoy::Network::ConnectionSocket::Options>(); + ::Envoy::Network::Socket::appendOptions(extra_upstream_local_address.socket_options_, + base_socket_options); + + if (extra_source_address.has_socket_options()) { + ::Envoy::Network::Socket::appendOptions( + extra_upstream_local_address.socket_options_, + ::Envoy::Network::SocketOptionFactory::buildLiteralOptions( + extra_source_address.socket_options().socket_options())); + } else { + ::Envoy::Network::Socket::appendOptions(extra_upstream_local_address.socket_options_, + cluster_socket_options); + } + upstream_local_addresses.push_back(extra_upstream_local_address); + } - if (!bind_config.has_source_address() && (bind_config.extra_source_addresses_size() > 0 || - bind_config.additional_source_addresses_size() > 0)) { - throw EnvoyException( - fmt::format("{}'s upstream binding config has extra/additional source addresses but no " - "source_address. Extra/additional addresses cannot be specified if " - "source_address is not set.", - cluster_name.empty() ? "Bootstrap" : fmt::format("Cluster {}", cluster_name))); + for (const auto& additional_source_address : bind_config->additional_source_addresses()) { + UpstreamLocalAddress additional_upstream_local_address; + additional_upstream_local_address.address_ = + ::Envoy::Network::Address::resolveProtoSocketAddress(additional_source_address); + additional_upstream_local_address.socket_options_ = + std::make_shared<::Envoy::Network::ConnectionSocket::Options>(); + ::Envoy::Network::Socket::appendOptions(additional_upstream_local_address.socket_options_, + base_socket_options); + ::Envoy::Network::Socket::appendOptions(additional_upstream_local_address.socket_options_, + cluster_socket_options); + upstream_local_addresses.push_back(additional_upstream_local_address); + } + } else { + // If there is no bind config specified, then return a nullptr for the address. + UpstreamLocalAddress local_address; + local_address.address_ = nullptr; + local_address.socket_options_ = std::make_shared<::Envoy::Network::ConnectionSocket::Options>(); + ::Envoy::Network::Socket::appendOptions(local_address.socket_options_, base_socket_options); + Network::Socket::appendOptions(local_address.socket_options_, cluster_socket_options); + upstream_local_addresses.push_back(local_address); + } + + // Verify that we have valid addresses if size is greater than 1. + if (upstream_local_addresses.size() > 1) { + for (auto const& upstream_local_address : upstream_local_addresses) { + if (upstream_local_address.address_ == nullptr) { + throw EnvoyException(fmt::format("{}'s upstream binding config has invalid IP addresses.", + !(cluster_name.has_value()) + ? "Bootstrap" + : fmt::format("Cluster {}", cluster_name.value()))); + } + } } - UpstreamLocalAddress upstream_local_address; - upstream_local_address.address_ = - bind_config.has_source_address() - ? Network::Address::resolveProtoSocketAddress(bind_config.source_address()) - : nullptr; - upstream_local_address.socket_options_ = std::make_shared(); + return upstream_local_addresses; +} - Network::Socket::appendOptions(upstream_local_address.socket_options_, base_socket_options); - Network::Socket::appendOptions(upstream_local_address.socket_options_, cluster_socket_options); +Envoy::Upstream::UpstreamLocalAddressSelectorConstSharedPtr createUpstreamLocalAddressSelector( + const envoy::config::cluster::v3::Cluster& cluster_config, + const absl::optional& bootstrap_bind_config) { - upstream_local_addresses_.push_back(upstream_local_address); + // Use the cluster bind config if specified. This completely overrides the + // bootstrap bind config when present. + OptRef bind_config; + absl::optional cluster_name; + if (cluster_config.has_upstream_bind_config()) { + bind_config.emplace(cluster_config.upstream_bind_config()); + cluster_name.emplace(cluster_config.name()); + } else if (bootstrap_bind_config.has_value()) { + bind_config.emplace(*bootstrap_bind_config); + } - if (bind_config.extra_source_addresses_size() == 1) { - UpstreamLocalAddress extra_upstream_local_address; - extra_upstream_local_address.address_ = Network::Address::resolveProtoSocketAddress( - bind_config.extra_source_addresses(0).address()); - ASSERT(extra_upstream_local_address.address_->ip() != nullptr && - upstream_local_address.address_->ip() != nullptr); - if (extra_upstream_local_address.address_->ip()->version() == - upstream_local_address.address_->ip()->version()) { + // Verify that bind config is valid. + if (bind_config.has_value()) { + if (bind_config->additional_source_addresses_size() > 0 && + bind_config->extra_source_addresses_size() > 0) { throw EnvoyException(fmt::format( - "{}'s upstream binding config has two same IP version source addresses. Only two " - "different IP version source addresses can be supported in BindConfig's source_address " - "and extra_source_addresses fields", - cluster_name.empty() ? "Bootstrap" : fmt::format("Cluster {}", cluster_name))); + "Can't specify both `extra_source_addresses` and `additional_source_addresses` " + "in the {}'s upstream binding config", + !(cluster_name.has_value()) ? "Bootstrap" + : fmt::format("Cluster {}", cluster_name.value()))); } - extra_upstream_local_address.socket_options_ = - std::make_shared(); - Network::Socket::appendOptions(extra_upstream_local_address.socket_options_, - base_socket_options); - - if (bind_config.extra_source_addresses(0).has_socket_options()) { - Network::Socket::appendOptions( - extra_upstream_local_address.socket_options_, - Network::SocketOptionFactory::buildLiteralOptions( - bind_config.extra_source_addresses(0).socket_options().socket_options())); - } else { - Network::Socket::appendOptions(extra_upstream_local_address.socket_options_, - cluster_socket_options); - } - - upstream_local_addresses_.push_back(extra_upstream_local_address); - } - - if (bind_config.additional_source_addresses_size() == 1) { - UpstreamLocalAddress additional_upstream_local_address; - additional_upstream_local_address.address_ = - Network::Address::resolveProtoSocketAddress(bind_config.additional_source_addresses(0)); - ASSERT(additional_upstream_local_address.address_->ip() != nullptr && - upstream_local_address.address_->ip() != nullptr); - if (additional_upstream_local_address.address_->ip()->version() == - upstream_local_address.address_->ip()->version()) { + if (!bind_config->has_source_address() && + (bind_config->extra_source_addresses_size() > 0 || + bind_config->additional_source_addresses_size() > 0)) { throw EnvoyException(fmt::format( - "{}'s upstream binding config has two same IP version source addresses. Only two " - "different IP version source addresses can be supported in BindConfig's source_address " - "and additional_source_addresses fields", - cluster_name.empty() ? "Bootstrap" : fmt::format("Cluster {}", cluster_name))); + "{}'s upstream binding config has extra/additional source addresses but no " + "source_address. Extra/additional addresses cannot be specified if " + "source_address is not set.", + !(cluster_name.has_value()) ? "Bootstrap" + : fmt::format("Cluster {}", cluster_name.value()))); } - - additional_upstream_local_address.socket_options_ = - std::make_shared(); - - Network::Socket::appendOptions(additional_upstream_local_address.socket_options_, - base_socket_options); - Network::Socket::appendOptions(additional_upstream_local_address.socket_options_, - cluster_socket_options); - - upstream_local_addresses_.push_back(additional_upstream_local_address); } + UpstreamLocalAddressSelectorFactory* local_address_selector_factory; + const envoy::config::core::v3::TypedExtensionConfig* const local_address_selector_config = + bind_config.has_value() && bind_config->has_local_address_selector() + ? &bind_config->local_address_selector() + : nullptr; + if (local_address_selector_config) { + local_address_selector_factory = + Config::Utility::getAndCheckFactory( + *local_address_selector_config, false); + } else { + // Create the default local address selector if one was not specified. + envoy::config::upstream::local_address_selector::v3::DefaultLocalAddressSelector default_config; + envoy::config::core::v3::TypedExtensionConfig typed_extension; + typed_extension.mutable_typed_config()->PackFrom(default_config); + local_address_selector_factory = + Config::Utility::getAndCheckFactory(typed_extension, + false); + } + return local_address_selector_factory->createLocalAddressSelector( + parseBindConfig( + bind_config, cluster_name, + buildBaseSocketOptions(cluster_config, bootstrap_bind_config.value_or( + envoy::config::core::v3::BindConfig{})), + buildClusterSocketOptions(cluster_config, bootstrap_bind_config.value_or( + envoy::config::core::v3::BindConfig{}))), + cluster_name); } +} // namespace + // TODO(pianiststickman): this implementation takes a lock on the hot path and puts a copy of the // stat name into every host that receives a copy of that metric. This can be improved by putting // a single copy of the stat name into a thread-local key->index map so that the lock can be avoided @@ -1068,8 +1035,7 @@ ClusterInfoImpl::ClusterInfoImpl( resource_managers_(config, runtime, name_, *stats_scope_, factory_context.clusterManager().clusterCircuitBreakersStatNames()), maintenance_mode_runtime_key_(absl::StrCat("upstream.maintenance_mode.", name_)), - upstream_local_address_selector_( - std::make_shared(config, bind_config)), + upstream_local_address_selector_(createUpstreamLocalAddressSelector(config, bind_config)), lb_policy_config_(std::make_unique(config)), upstream_config_(config.has_upstream_config() ? std::make_unique( diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index d2325d34f873..9bb5495d93e6 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -75,40 +75,6 @@ using UpstreamNetworkFilterConfigProviderManager = Filter::FilterConfigProviderManager; -/** - * An implementation of UpstreamLocalAddressSelector. - */ -class UpstreamLocalAddressSelectorImpl : public UpstreamLocalAddressSelector { -public: - UpstreamLocalAddressSelectorImpl( - const envoy::config::cluster::v3::Cluster& config, - const absl::optional& bootstrap_bind_config); - - // UpstreamLocalAddressSelector - UpstreamLocalAddress getUpstreamLocalAddress( - const Network::Address::InstanceConstSharedPtr& endpoint_address, - const Network::ConnectionSocket::OptionsSharedPtr& socket_options) const override; - -private: - const Network::ConnectionSocket::OptionsSharedPtr - buildBaseSocketOptions(const envoy::config::cluster::v3::Cluster& config, - const envoy::config::core::v3::BindConfig& bootstrap_bind_config); - const Network::ConnectionSocket::OptionsSharedPtr - buildClusterSocketOptions(const envoy::config::cluster::v3::Cluster& config, - const envoy::config::core::v3::BindConfig bind_config); - void parseBindConfig(const std::string cluster_name, - const envoy::config::core::v3::BindConfig& bind_config, - const Network::ConnectionSocket::OptionsSharedPtr& base_socket_options, - const Network::ConnectionSocket::OptionsSharedPtr& cluster_socket_options); - Network::ConnectionSocket::OptionsSharedPtr combineConnectionSocketOptions( - const Network::ConnectionSocket::OptionsSharedPtr& local_address_options, - const Network::ConnectionSocket::OptionsSharedPtr& options) const; - - Network::ConnectionSocket::OptionsSharedPtr base_socket_options_; - Network::ConnectionSocket::OptionsSharedPtr cluster_socket_options_; - std::vector upstream_local_addresses_; -}; - /** * Class for LBPolicies * Uses a absl::variant to store pointers for the LBPolicy @@ -976,7 +942,7 @@ class ClusterInfoImpl : public ClusterInfo, return std::ref(*(optional_cluster_stats_->timeout_budget_stats_)); } - std::shared_ptr getUpstreamLocalAddressSelector() const override { + UpstreamLocalAddressSelectorConstSharedPtr getUpstreamLocalAddressSelector() const override { return upstream_local_address_selector_; } const LoadBalancerSubsetInfo& lbSubsetInfo() const override { @@ -1109,7 +1075,7 @@ class ClusterInfoImpl : public ClusterInfo, const uint64_t features_; mutable ResourceManagers resource_managers_; const std::string maintenance_mode_runtime_key_; - std::shared_ptr upstream_local_address_selector_; + UpstreamLocalAddressSelectorConstSharedPtr upstream_local_address_selector_; const std::unique_ptr lb_policy_config_; std::unique_ptr upstream_config_; std::unique_ptr lb_subset_; diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 3f5b1f615782..9476537cd5df 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1254,6 +1254,13 @@ envoy.upstreams.tcp.tcp_protocol_options: - envoy.upstreams security_posture: unknown status: alpha +envoy.upstream.local_address_selector.default_local_address_selector: + categories: + - envoy.upstream.local_address_selector + security_posture: unknown + status: alpha + type_urls: + - envoy.config.upstream.local_address_selector.v3.DefaultLocalAddressSelector envoy.wasm.runtime.null: categories: - envoy.wasm.runtime diff --git a/test/common/http/http3/conn_pool_test.cc b/test/common/http/http3/conn_pool_test.cc index 17ed4ac9b2a4..d27e0b8fd92b 100644 --- a/test/common/http/http3/conn_pool_test.cc +++ b/test/common/http/http3/conn_pool_test.cc @@ -56,12 +56,11 @@ class Http3ConnPoolImplTest : public Event::TestUsingSimulatedTime, public testi Network::ConnectionSocket::OptionsSharedPtr options = std::make_shared(); options->push_back(socket_option_); - ON_CALL(*mockHost().cluster_.upstream_local_address_selector_, getUpstreamLocalAddress(_, _)) - .WillByDefault(Invoke([](const Network::Address::InstanceConstSharedPtr&, - const Network::ConnectionSocket::OptionsSharedPtr& socket_options) - -> Upstream::UpstreamLocalAddress { - return Upstream::UpstreamLocalAddress({nullptr, socket_options}); - })); + ON_CALL(*mockHost().cluster_.upstream_local_address_selector_, getUpstreamLocalAddressImpl(_)) + .WillByDefault(Invoke( + [](const Network::Address::InstanceConstSharedPtr&) -> Upstream::UpstreamLocalAddress { + return Upstream::UpstreamLocalAddress({nullptr, nullptr}); + })); Network::TransportSocketOptionsConstSharedPtr transport_options; pool_ = allocateConnPool( dispatcher_, random_, host_, Upstream::ResourcePriority::Default, options, @@ -163,16 +162,14 @@ TEST_F(Http3ConnPoolImplTest, CreationAndNewStream) { mockHost().cluster_.cluster_socket_options_ = std::make_shared(); std::shared_ptr cluster_socket_option{new Network::MockSocketOption()}; mockHost().cluster_.cluster_socket_options_->push_back(cluster_socket_option); - EXPECT_CALL(*mockHost().cluster_.upstream_local_address_selector_, getUpstreamLocalAddress(_, _)) - .WillOnce(Invoke([&](const Network::Address::InstanceConstSharedPtr&, - const Network::ConnectionSocket::OptionsSharedPtr& socket_options) - -> Upstream::UpstreamLocalAddress { - Network::ConnectionSocket::OptionsSharedPtr options = - std::make_shared(); - Network::Socket::appendOptions(options, mockHost().cluster_.cluster_socket_options_); - Network::Socket::appendOptions(options, socket_options); - return Upstream::UpstreamLocalAddress({nullptr, options}); - })); + EXPECT_CALL(*mockHost().cluster_.upstream_local_address_selector_, getUpstreamLocalAddressImpl(_)) + .WillOnce(Invoke( + [&](const Network::Address::InstanceConstSharedPtr&) -> Upstream::UpstreamLocalAddress { + Network::ConnectionSocket::OptionsSharedPtr options = + std::make_shared(); + Network::Socket::appendOptions(options, mockHost().cluster_.cluster_socket_options_); + return Upstream::UpstreamLocalAddress({nullptr, options}); + })); EXPECT_CALL(*cluster_socket_option, setOption(_, _)).Times(3u); EXPECT_CALL(*socket_option_, setOption(_, _)).Times(3u); auto* async_connect_callback = new NiceMock(&dispatcher_); diff --git a/test/common/tcp/conn_pool_test.cc b/test/common/tcp/conn_pool_test.cc index 643b26d7216a..2d90e65f0a19 100644 --- a/test/common/tcp/conn_pool_test.cc +++ b/test/common/tcp/conn_pool_test.cc @@ -146,7 +146,7 @@ class ConnPoolBase : public Tcp::ConnectionPool::Instance { EXPECT_CALL(*dispatcher, createTimer_(_)).Times(0); } - EXPECT_CALL(mock_dispatcher_, createClientConnection_(_, _, _, options_)) + EXPECT_CALL(mock_dispatcher_, createClientConnection_(_, _, _, _)) .WillOnce(Return(test_conn.connection_)); EXPECT_CALL(*test_conn.connection_, addReadFilter(_)) .WillOnce(Invoke( @@ -242,7 +242,7 @@ class TcpConnPoolImplTest : public Event::TestUsingSimulatedTime, public testing } void initialize() { - ON_CALL(*cluster_->upstream_local_address_selector_, getUpstreamLocalAddress(_, _)) + ON_CALL(*cluster_->upstream_local_address_selector_, getUpstreamLocalAddressImpl(_)) .WillByDefault( Return(Upstream::UpstreamLocalAddress({cluster_->source_address_, options_}))); conn_pool_ = std::make_unique(dispatcher_, host_, upstream_ready_cb_, options_, diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 9ff58a05dcd6..53c8d7143542 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -540,6 +540,7 @@ envoy_cc_test( data = ["//test/extensions/transport_sockets/tls/test_data:certs"], deps = [ ":utility_lib", + ":test_local_address_selector", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", @@ -549,6 +550,7 @@ envoy_cc_test( "//source/common/config:metadata_lib", "//source/common/event:dispatcher_lib", "//source/common/network:address_lib", + "//source/common/network:resolver_lib", "//source/common/network:utility_lib", # TODO(mattklein123): Split this into 2 tests for each cluster. "//source/extensions/clusters/static:static_cluster_lib", @@ -780,3 +782,45 @@ envoy_cc_benchmark_binary( "//source/common/upstream:scheduler_lib", ], ) + +envoy_cc_test( + name = "default_local_address_selector_test", + size = "small", + srcs = ["default_local_address_selector_test.cc"], + deps = [ + "//envoy/common:base_includes", + "//envoy/network:socket_interface", + "//envoy/upstream:upstream_interface", + "//source/common/network:address_lib", + "//source/common/network:utility_lib", + "//source/common/upstream:default_local_address_selector_factory", + "//test/test_common:utility_lib", + ], +) + +envoy_cc_test( + name = "local_address_selector_integration_test", + size = "large", + srcs = ["local_address_selector_integration_test.cc"], + deps = [ + "test_local_address_selector", + "//test/integration:http_integration_lib", + "//test/integration:http_protocol_integration_lib", + "//test/mocks/upstream:cluster_info_mocks", + "//test/test_common:registry_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/config/upstream/local_address_selector/v3:pkg_cc_proto", + ], +) + +envoy_cc_test_library( + name = "test_local_address_selector", + hdrs = [ + "test_local_address_selector.h", + ], + external_deps = ["abseil_optional"], + deps = [ + "//envoy/upstream:upstream_interface", + ], +) diff --git a/test/common/upstream/default_local_address_selector_test.cc b/test/common/upstream/default_local_address_selector_test.cc new file mode 100644 index 000000000000..ba2d54cd381b --- /dev/null +++ b/test/common/upstream/default_local_address_selector_test.cc @@ -0,0 +1,38 @@ +#include "envoy/common/exception.h" +#include "envoy/network/socket.h" +#include "envoy/upstream/upstream.h" + +#include "source/common/network/address_impl.h" +#include "source/common/network/utility.h" +#include "source/common/upstream/default_local_address_selector_factory.h" + +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Upstream { +namespace { + +TEST(ConfigTest, EmptyUpstreamAddresses) { + DefaultUpstreamLocalAddressSelectorFactory factory; + std::vector upstream_local_addresses; + EXPECT_THROW_WITH_MESSAGE( + factory.createLocalAddressSelector(upstream_local_addresses, absl::nullopt), EnvoyException, + "Bootstrap's upstream binding config has no valid source address."); +} + +TEST(ConfigTest, NullUpstreamAddress) { + DefaultUpstreamLocalAddressSelectorFactory factory; + std::vector upstream_local_addresses; + upstream_local_addresses.emplace_back( + UpstreamLocalAddress{nullptr, std::make_shared()}); + // This should be exception free. + UpstreamLocalAddressSelectorConstSharedPtr selector = + factory.createLocalAddressSelector(upstream_local_addresses, absl::nullopt); +} + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/local_address_selector_integration_test.cc b/test/common/upstream/local_address_selector_integration_test.cc new file mode 100644 index 000000000000..22b0ce57426a --- /dev/null +++ b/test/common/upstream/local_address_selector_integration_test.cc @@ -0,0 +1,249 @@ +#include + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/upstream/local_address_selector/v3/default_local_address_selector.pb.h" +#include "envoy/upstream/upstream.h" + +#include "test/common/upstream/test_local_address_selector.h" +#include "test/integration/http_protocol_integration.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/test_common/registry.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Upstream { + +// Basic test that instantiates the ``DefaultLocalAddressSelector``. +TEST_P(HttpProtocolIntegrationTest, Basic) { + + // Specify both IPv4 and IPv6 source addresses, so the right one is picked + // based on IP version of upstream. + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + uint32_t const port_value = 1234; + auto bind_config = bootstrap.mutable_cluster_manager()->mutable_upstream_bind_config(); + bind_config->mutable_source_address()->set_address("127.0.0.1"); + bind_config->mutable_source_address()->set_port_value(port_value); + auto extra_source_address = bind_config->add_extra_source_addresses(); + extra_source_address->mutable_address()->set_address("::1"); + extra_source_address->mutable_address()->set_port_value(port_value); + }); + + initialize(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", "host"}}; + + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + // Verify the proxied request was received upstream, as expected. + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(0U, upstream_request_->bodyLength()); + // Verify the proxied response was received downstream, as expected. + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + EXPECT_EQ(0U, response->body().size()); +} + +// Basic test that instantiates a custom upstream local address selector. +TEST_P(HttpProtocolIntegrationTest, CustomUpstreamLocalAddressSelector) { + + std::shared_ptr num_calls = std::make_shared(0); + TestUpstreamLocalAddressSelectorFactory factory(num_calls, true); + Registry::InjectFactory registration(factory); + + // Create a config that is invalid for the ``DefaultLocalAddressSelector``. + config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + uint32_t const port_value = 1234; + auto bind_config = bootstrap.mutable_cluster_manager()->mutable_upstream_bind_config(); + auto local_address_selector_config = bind_config->mutable_local_address_selector(); + ProtobufWkt::Empty empty; + local_address_selector_config->mutable_typed_config()->PackFrom(empty); + local_address_selector_config->set_name("mock.upstream.local.address.selector"); + bind_config->mutable_source_address()->set_address("::1"); + bind_config->mutable_source_address()->set_port_value(port_value); + auto extra_source_address = bind_config->add_extra_source_addresses(); + extra_source_address->mutable_address()->set_address("::1"); + extra_source_address->mutable_address()->set_port_value(port_value); + auto extra_source_address2 = bind_config->add_extra_source_addresses(); + extra_source_address2->mutable_address()->set_address("::2"); + extra_source_address2->mutable_address()->set_port_value(port_value); + }); + + initialize(); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", "host"}}; + + // Send the request headers from the client, wait until they are received upstream. When they + // are received, send the default response headers from upstream and wait until they are + // received at by client + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + // Verify that we called ``getUpstreamLocalAddressImpl`` on ``TestUpstreamLocalAddressSelector``. + EXPECT_EQ(*num_calls, 1); + + // Verify the proxied request was received upstream, as expected. + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(0U, upstream_request_->bodyLength()); + // Verify the proxied response was received downstream, as expected. + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + EXPECT_EQ(0U, response->body().size()); +} + +// Verify that the bind config specified on the cluster config overrides the one +// on the cluster manager. +TEST_P(HttpProtocolIntegrationTest, BindConfigOverride) { + + // Set up custom local address selector factory + std::shared_ptr num_calls = std::make_shared(0); + TestUpstreamLocalAddressSelectorFactory factory(num_calls, false); + Registry::InjectFactory registration(factory); + setUpstreamCount(2); + uint32_t const port_value_0 = 1234; + uint32_t const port_value_1 = 12345; + + config_helper_.addConfigModifier([this, port_value_0 = port_value_0, port_value_1 = port_value_1]( + envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + // Specify bind config on the bootstrap with default address selector. + auto bootstrap_bind_config = + bootstrap.mutable_cluster_manager()->mutable_upstream_bind_config(); + bootstrap_bind_config->mutable_source_address()->set_address("127.0.0.1"); + bootstrap_bind_config->mutable_source_address()->set_port_value(port_value_0); + auto bootstrap_extra_source_address = bootstrap_bind_config->add_extra_source_addresses(); + bootstrap_extra_source_address->mutable_address()->set_address("::1"); + bootstrap_extra_source_address->mutable_address()->set_port_value(port_value_0); + auto bootstrap_address_selector_config = + bootstrap_bind_config->mutable_local_address_selector(); + envoy::config::upstream::local_address_selector::v3::DefaultLocalAddressSelector config; + bootstrap_address_selector_config->mutable_typed_config()->PackFrom(config); + bootstrap_address_selector_config->set_name( + "envoy.upstream.local_address_selector.default_local_address_selector"); + + // Specify a cluster with bind config. + bootstrap.mutable_static_resources()->mutable_clusters()->Add()->MergeFrom( + *bootstrap.mutable_static_resources()->mutable_clusters(0)); + bootstrap.mutable_static_resources()->mutable_clusters(1)->set_name("cluster_1"); + auto bind_config = + bootstrap.mutable_static_resources()->mutable_clusters(1)->mutable_upstream_bind_config(); + bind_config->mutable_source_address()->set_address( + version_ == Network::Address::IpVersion::v4 ? "127.0.0.2" : "::1"); + bind_config->mutable_source_address()->set_port_value(port_value_1); + ProtobufWkt::Empty empty; + auto address_selector_config = bind_config->mutable_local_address_selector(); + address_selector_config->mutable_typed_config()->PackFrom(empty); + address_selector_config->set_name("test.upstream.local.address.selector"); + }); + + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { + auto default_route = + hcm.mutable_route_config()->mutable_virtual_hosts(0)->mutable_routes(0); + default_route->mutable_route()->set_cluster("cluster_0"); + default_route->mutable_match()->set_prefix("/path1"); + + // Add route that should direct to cluster with custom bind config. + auto second_route = + hcm.mutable_route_config()->mutable_virtual_hosts(0)->mutable_routes()->Add(); + second_route->mutable_route()->set_cluster("cluster_1"); + second_route->mutable_match()->set_prefix("/path2"); + }); + + initialize(); + + // Send request to cluster_0 + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/path1/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}; + auto response = codec_client_->makeRequestWithBody(request_headers, 1024); + waitForNextUpstreamRequest(); + + std::string address_string; + if (version_ == Network::Address::IpVersion::v4) { + address_string = "127.0.0.1"; + } else { + address_string = "::1"; + } + std::string address_cluster0 = fake_upstream_connection_->connection() + .connectionInfoProvider() + .remoteAddress() + ->ip() + ->addressAsString(); + size_t port_value_cluster0 = fake_upstream_connection_->connection() + .connectionInfoProvider() + .remoteAddress() + ->ip() + ->port(); + EXPECT_EQ(address_cluster0, address_string); + EXPECT_EQ(port_value_cluster0, port_value_0); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); + ASSERT_TRUE(response->waitForEndStream()); + + // Verify the proxied request was received upstream, as expected. + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(1024, upstream_request_->bodyLength()); + EXPECT_TRUE(response->complete()); + // Verify the proxied response was received downstream, as expected. + EXPECT_EQ("200", response->headers().getStatusValue()); + EXPECT_EQ(512, response->body().size()); + + auto first_connection = std::move(fake_upstream_connection_); + codec_client_->close(); + + // Verify that the custom local address selector was not invoked. + EXPECT_EQ(*num_calls, 0); + + // Send request to cluster_1. + codec_client_ = makeHttpConnection(lookupPort("http")); + Http::TestRequestHeaderMapImpl request_headers2{{":method", "GET"}, + {":path", "/path2/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}; + response = codec_client_->makeRequestWithBody(request_headers2, 1024); + waitForNextUpstreamRequest(1); + + if (version_ == Network::Address::IpVersion::v4) { + address_string = "127.0.0.2"; + } else { + address_string = "::1"; + } + std::string address_cluster1 = fake_upstream_connection_->connection() + .connectionInfoProvider() + .remoteAddress() + ->ip() + ->addressAsString(); + size_t port_value_cluster1 = fake_upstream_connection_->connection() + .connectionInfoProvider() + .remoteAddress() + ->ip() + ->port(); + EXPECT_EQ(address_cluster1, address_string); + EXPECT_EQ(port_value_cluster1, port_value_1); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); + ASSERT_TRUE(response->waitForEndStream()); + + // Verify that we called ``getUpstreamLocalAddressImpl`` on ``TestUpstreamLocalAddressSelector``. + EXPECT_EQ(*num_calls, 1); + + // Verify the proxied request was received upstream, as expected. + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(1024, upstream_request_->bodyLength()); + // Verify the proxied response was received downstream, as expected. + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + EXPECT_EQ(512, response->body().size()); +} + +INSTANTIATE_TEST_SUITE_P(Protocols, HttpProtocolIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( + {Http::CodecType::HTTP1}, {Http::CodecType::HTTP1})), + HttpProtocolIntegrationTest::protocolTestParamsToString); +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/test_local_address_selector.h b/test/common/upstream/test_local_address_selector.h new file mode 100644 index 000000000000..bc2683b70d31 --- /dev/null +++ b/test/common/upstream/test_local_address_selector.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +#include "envoy/upstream/upstream.h" + +namespace Envoy { +namespace Upstream { + +class TestUpstreamLocalAddressSelector : public UpstreamLocalAddressSelector { +public: + TestUpstreamLocalAddressSelector( + std::vector<::Envoy::Upstream::UpstreamLocalAddress> upstream_local_addresses, + std::shared_ptr num_calls, bool return_empty_source_address = false) + : upstream_local_addresses_{std::move(upstream_local_addresses)}, num_calls_{num_calls}, + return_empty_source_address_{return_empty_source_address} {} + + UpstreamLocalAddress + getUpstreamLocalAddressImpl(const Network::Address::InstanceConstSharedPtr&) const override { + ++(*num_calls_); + if (return_empty_source_address_) { + return UpstreamLocalAddress(); + } + current_idx_ = (current_idx_ + 1) % upstream_local_addresses_.size(); + return upstream_local_addresses_[current_idx_]; + } + +private: + std::vector upstream_local_addresses_; + mutable size_t current_idx_ = 0; + const std::shared_ptr num_calls_; + const bool return_empty_source_address_ = false; +}; + +class TestUpstreamLocalAddressSelectorFactory : public UpstreamLocalAddressSelectorFactory { +public: + TestUpstreamLocalAddressSelectorFactory( + std::shared_ptr num_calls = std::make_shared(0), + bool return_empty_source_address = false) + : num_calls_(num_calls), return_empty_source_address_{return_empty_source_address} {} + + UpstreamLocalAddressSelectorConstSharedPtr createLocalAddressSelector( + std::vector<::Envoy::Upstream::UpstreamLocalAddress> upstream_local_addresses, + absl::optional) const override { + return std::make_shared(upstream_local_addresses, num_calls_, + return_empty_source_address_); + } + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { return "test.upstream.local.address.selector"; } + +private: + std::shared_ptr num_calls_; + const bool return_empty_source_address_ = false; +}; + +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index a03a6c0586a2..b9c546684d5e 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -12,6 +12,7 @@ #include "envoy/config/core/v3/health_check.pb.h" #include "envoy/config/endpoint/v3/endpoint_components.pb.h" #include "envoy/http/codec.h" +#include "envoy/network/address.h" #include "envoy/stats/scope.h" #include "envoy/upstream/cluster_manager.h" #include "envoy/upstream/health_check_host_monitor.h" @@ -19,6 +20,7 @@ #include "source/common/config/metadata.h" #include "source/common/network/address_impl.h" +#include "source/common/network/resolver_impl.h" #include "source/common/network/transport_socket_options_impl.h" #include "source/common/network/utility.h" #include "source/common/singleton/manager_impl.h" @@ -27,6 +29,7 @@ #include "source/server/transport_socket_config_impl.h" #include "test/common/stats/stat_test_utility.h" +#include "test/common/upstream/test_local_address_selector.h" #include "test/common/upstream/utility.h" #include "test/mocks/common.h" #include "test/mocks/http/mocks.h" @@ -2875,7 +2878,7 @@ TEST_F(StaticClusterImplTest, SourceAddressPriorityWitExtraSourceAddress) { EnvoyException, "Bootstrap's upstream binding config has two same IP version source addresses. Only two " "different IP version source addresses can be supported in BindConfig's source_address and " - "extra_source_addresses fields"); + "extra_source_addresses/additional_source_addresses fields"); } { @@ -2899,8 +2902,9 @@ TEST_F(StaticClusterImplTest, SourceAddressPriorityWitExtraSourceAddress) { EXPECT_THROW_WITH_MESSAGE( std::shared_ptr cluster = createCluster(config, factory_context), EnvoyException, - "Bootstrap's upstream binding config has more than one extra source addresses. Only " - "one extra source can be supported in BindConfig's extra_source_addresses field"); + "Bootstrap's upstream binding config has more than one extra/additional source addresses. " + "Only one extra/additional source can be supported in BindConfig's " + "extra_source_addresses/additional_source_addresses field"); } { @@ -2980,12 +2984,12 @@ TEST_F(StaticClusterImplTest, SourceAddressPriorityWitExtraSourceAddress) { server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); - EXPECT_THROW_WITH_MESSAGE(std::shared_ptr cluster = - createCluster(config, factory_context), - EnvoyException, - "Cluster staticcluster's upstream binding config has more than one " - "extra source addresses. Only one extra source can be " - "supported in BindConfig's extra_source_addresses field"); + EXPECT_THROW_WITH_MESSAGE( + std::shared_ptr cluster = createCluster(config, factory_context), + EnvoyException, + "Cluster staticcluster's upstream binding config has more than one " + "extra/additional source addresses. Only one extra/additional source can be " + "supported in BindConfig's extra_source_addresses/additional_source_addresses field"); } { @@ -3141,7 +3145,7 @@ TEST_F(StaticClusterImplTest, SourceAddressPriorityWithDeprecatedAdditionalSourc EnvoyException, "Bootstrap's upstream binding config has two same IP version source addresses. Only two " "different IP version source addresses can be supported in BindConfig's source_address and " - "additional_source_addresses fields"); + "extra_source_addresses/additional_source_addresses fields"); } { @@ -3159,11 +3163,13 @@ TEST_F(StaticClusterImplTest, SourceAddressPriorityWithDeprecatedAdditionalSourc server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); ; - EXPECT_THROW_WITH_MESSAGE( - std::shared_ptr cluster = createCluster(config, factory_context), - EnvoyException, - "Bootstrap's upstream binding config has more than one additional source addresses. Only " - "one additional source can be supported in BindConfig's additional_source_addresses field"); + EXPECT_THROW_WITH_MESSAGE(std::shared_ptr cluster = + createCluster(config, factory_context), + EnvoyException, + "Bootstrap's upstream binding config has more than one " + "extra/additional source addresses. Only " + "one extra/additional source can be supported in BindConfig's " + "extra_source_addresses/additional_source_addresses field"); } { @@ -3200,12 +3206,12 @@ TEST_F(StaticClusterImplTest, SourceAddressPriorityWithDeprecatedAdditionalSourc server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, false); ; - EXPECT_THROW_WITH_MESSAGE(std::shared_ptr cluster = - createCluster(config, factory_context), - EnvoyException, - "Cluster staticcluster's upstream binding config has more than one " - "additional source addresses. Only one additional source can be " - "supported in BindConfig's additional_source_addresses field"); + EXPECT_THROW_WITH_MESSAGE( + std::shared_ptr cluster = createCluster(config, factory_context), + EnvoyException, + "Cluster staticcluster's upstream binding config has more than one " + "extra/additional source addresses. Only one extra/additional source can be " + "supported in BindConfig's extra_source_addresses/additional_source_addresses field"); } { @@ -3251,6 +3257,54 @@ TEST_F(StaticClusterImplTest, LedsUnsupported) { "LEDS is only supported when EDS is used. Static cluster staticcluster cannot use LEDS."); } +// Test ability to register custom local address selector. +TEST_F(StaticClusterImplTest, CustomUpstreamLocalAddressSelector) { + + TestUpstreamLocalAddressSelectorFactory factory; + Registry::InjectFactory registration(factory); + envoy::config::cluster::v3::Cluster config; + config.set_name("staticcluster"); + config.mutable_connect_timeout(); + ProtobufWkt::Empty empty; + auto address_selector_config = + server_context_.cluster_manager_.mutableBindConfig().mutable_local_address_selector(); + address_selector_config->mutable_typed_config()->PackFrom(empty); + address_selector_config->set_name("test.upstream.local.address.selector"); + server_context_.cluster_manager_.mutableBindConfig().mutable_source_address()->set_address( + "1.2.3.5"); + server_context_.cluster_manager_.mutableBindConfig() + .add_extra_source_addresses() + ->mutable_address() + ->set_address("2001::1"); + server_context_.cluster_manager_.mutableBindConfig() + .add_extra_source_addresses() + ->mutable_address() + ->set_address("1.2.3.6"); + + Envoy::Upstream::ClusterFactoryContextImpl factory_context( + server_context_, server_context_.cluster_manager_, nullptr, ssl_context_manager_, nullptr, + false); + + std::shared_ptr cluster = createCluster(config, factory_context); + + Network::Address::InstanceConstSharedPtr v6_remote_address = + std::make_shared("2001::3", 80, nullptr); + EXPECT_EQ("[2001::1]:0", cluster->info() + ->getUpstreamLocalAddressSelector() + ->getUpstreamLocalAddress(v6_remote_address, nullptr) + .address_->asString()); + Network::Address::InstanceConstSharedPtr remote_address = + std::make_shared("3.4.5.6", 80, nullptr); + EXPECT_EQ("1.2.3.6:0", cluster->info() + ->getUpstreamLocalAddressSelector() + ->getUpstreamLocalAddress(remote_address, nullptr) + .address_->asString()); + EXPECT_EQ("1.2.3.5:0", cluster->info() + ->getUpstreamLocalAddressSelector() + ->getUpstreamLocalAddress(remote_address, nullptr) + .address_->asString()); +} + class ClusterImplTest : public testing::Test, public UpstreamImplTestBase {}; // Test that the correct feature() is set when close_connections_on_host_health_failure is diff --git a/test/mocks/upstream/cluster_info.cc b/test/mocks/upstream/cluster_info.cc index 72f448576d25..9bce3ae3dec2 100644 --- a/test/mocks/upstream/cluster_info.cc +++ b/test/mocks/upstream/cluster_info.cc @@ -41,10 +41,9 @@ MockIdleTimeEnabledClusterInfo::~MockIdleTimeEnabledClusterInfo() = default; MockUpstreamLocalAddressSelector::MockUpstreamLocalAddressSelector( Network::Address::InstanceConstSharedPtr& address) : address_(address) { - ON_CALL(*this, getUpstreamLocalAddress(_, _)) + ON_CALL(*this, getUpstreamLocalAddressImpl(_)) .WillByDefault( - Invoke([&](const Network::Address::InstanceConstSharedPtr&, - const Network::ConnectionSocket::OptionsSharedPtr&) -> UpstreamLocalAddress { + Invoke([&](const Network::Address::InstanceConstSharedPtr&) -> UpstreamLocalAddress { UpstreamLocalAddress ret; ret.address_ = address_; ret.socket_options_ = nullptr; diff --git a/test/mocks/upstream/cluster_info.h b/test/mocks/upstream/cluster_info.h index 477b2a41f201..84a030b8cfbb 100644 --- a/test/mocks/upstream/cluster_info.h +++ b/test/mocks/upstream/cluster_info.h @@ -77,14 +77,26 @@ class MockUpstreamLocalAddressSelector : public UpstreamLocalAddressSelector { public: MockUpstreamLocalAddressSelector(Network::Address::InstanceConstSharedPtr& address); - MOCK_METHOD(UpstreamLocalAddress, getUpstreamLocalAddress, - (const Network::Address::InstanceConstSharedPtr& address, - const Network::ConnectionSocket::OptionsSharedPtr& connection_socket_options), - (const)); + MOCK_METHOD(UpstreamLocalAddress, getUpstreamLocalAddressImpl, + (const Network::Address::InstanceConstSharedPtr& address), (const)); Network::Address::InstanceConstSharedPtr& address_; }; +class MockUpstreamLocalAddressSelectorFactory : public UpstreamLocalAddressSelectorFactory { +public: + MOCK_METHOD(UpstreamLocalAddressSelectorConstSharedPtr, createLocalAddressSelector, + (std::vector<::Envoy::Upstream::UpstreamLocalAddress> upstream_local_addresses, + absl::optional cluster_name), + (const)); + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } + + std::string name() const override { return "mock.upstream.local.address.selector"; } +}; + class MockClusterInfo : public ClusterInfo { public: MockClusterInfo(); @@ -161,7 +173,7 @@ class MockClusterInfo : public ClusterInfo { MOCK_METHOD(ClusterLoadReportStats&, loadReportStats, (), (const)); MOCK_METHOD(ClusterRequestResponseSizeStatsOptRef, requestResponseSizeStats, (), (const)); MOCK_METHOD(ClusterTimeoutBudgetStatsOptRef, timeoutBudgetStats, (), (const)); - MOCK_METHOD(std::shared_ptr, getUpstreamLocalAddressSelector, (), + MOCK_METHOD(UpstreamLocalAddressSelectorConstSharedPtr, getUpstreamLocalAddressSelector, (), (const)); MOCK_METHOD(const LoadBalancerSubsetInfo&, lbSubsetInfo, (), (const)); MOCK_METHOD(const envoy::config::core::v3::Metadata&, metadata, (), (const)); diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 29be5380afed..dd28952a5dda 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -103,6 +103,7 @@ paths: - source/common/upstream/cds_api_impl.cc - source/common/upstream/outlier_detection_impl.cc - source/common/upstream/upstream_impl.cc + - source/common/upstream/default_local_address_selector_factory.cc - source/common/network/listen_socket_impl.cc - source/common/network/io_socket_handle_impl.cc - source/common/network/address_impl.cc diff --git a/tools/extensions/extensions_schema.yaml b/tools/extensions/extensions_schema.yaml index 6262a972cd70..c1bfd8812e05 100644 --- a/tools/extensions/extensions_schema.yaml +++ b/tools/extensions/extensions_schema.yaml @@ -27,6 +27,7 @@ builtin: - envoy.matching.inputs.subject - envoy.regex_engines.google_re2 - envoy.filters.http.upstream_codec +- envoy.upstream.local_address_selector.default_local_address_selector # All Envoy extensions must be tagged with their security hardening stance with # respect to downstream and upstream data plane threats. These are verbose @@ -107,6 +108,7 @@ categories: - envoy.transport_sockets.upstream - envoy.tls.cert_validator - envoy.upstreams +- envoy.upstream.local_address_selector - envoy.udp_packet_writer - envoy.wasm.runtime - envoy.xds_delegates From 60978158650a4797ddbb606d00fff7404fa6f872 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 Sep 2023 19:14:03 +0100 Subject: [PATCH 36/55] mac/ci: Retry flakey bazel invokation (#29525) Signed-off-by: Ryan Northey --- ci/mac_ci_setup.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ci/mac_ci_setup.sh b/ci/mac_ci_setup.sh index ee2e3f029522..2646a366e957 100755 --- a/ci/mac_ci_setup.sh +++ b/ci/mac_ci_setup.sh @@ -29,20 +29,23 @@ function install { } function retry () { - local returns=1 i=1 - while ((i<=HOMEBREW_RETRY_ATTEMPTS)); do + local returns=1 i=1 attempts + attempts="${1}" + interval="${2}" + shift 2 + while ((i<=attempts)); do if "$@"; then returns=0 break else - sleep "$HOMEBREW_RETRY_INTERVAL"; + sleep "$interval"; ((i++)) fi done return "$returns" } -if ! retry brew update; then +if ! retry "$HOMEBREW_RETRY_ATTEMPTS" "$HOMEBREW_RETRY_INTERVAL" brew update; then # Do not exit early if update fails. echo "Failed to update homebrew" fi @@ -53,4 +56,4 @@ do is_installed "${DEP}" || install "${DEP}" done -bazel version +retry 5 2 bazel version From e8471772c1f7c5f64c8e56100ebab33a351f9fc7 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 Sep 2023 20:40:30 +0100 Subject: [PATCH 37/55] ci/caching: Optimize bazel cache/fetch (#28977) Signed-off-by: Ryan Northey --- .azure-pipelines/bazel.yml | 69 +++++++++++++++- .azure-pipelines/cached.yml | 26 ++++-- .azure-pipelines/docker/create_cache.sh | 15 +++- .azure-pipelines/docker/load_caches.sh | 18 ++-- .azure-pipelines/docker/prepare_cache.sh | 9 +- .azure-pipelines/docker/prime_cache.sh | 4 +- .azure-pipelines/docker/save_cache.sh | 45 +++++----- .azure-pipelines/pipelines.yml | 10 +-- .azure-pipelines/stage/checks.yml | 25 +++--- .azure-pipelines/stage/linux.yml | 3 +- .azure-pipelines/stage/prechecks.yml | 1 + .azure-pipelines/stage/publish.yml | 48 +++++------ .azure-pipelines/stage/verify.yml | 10 ++- ci/do_ci.sh | 100 ++++++++++++++++------- 14 files changed, 259 insertions(+), 124 deletions(-) diff --git a/.azure-pipelines/bazel.yml b/.azure-pipelines/bazel.yml index 6bad103d3bc1..66b737caf2bf 100644 --- a/.azure-pipelines/bazel.yml +++ b/.azure-pipelines/bazel.yml @@ -2,7 +2,7 @@ parameters: - name: ciTarget displayName: "CI target" type: string - default: bazel.release + default: release - name: artifactSuffix displayName: "Suffix of artifact" type: string @@ -18,6 +18,9 @@ parameters: - name: pathCacheTemp type: string default: $(pathCacheTemp) +- name: cacheName + type: string + default: - name: tmpfsCacheDisabled type: string @@ -145,19 +148,25 @@ steps: - bash: | set -e CACHE_DIRS=( + "$(Build.StagingDirectory)/envoy" "$(Build.StagingDirectory)/.cache/" "$(Build.StagingDirectory)/bazel_root/install/" "$(Build.StagingDirectory)/repository_cache/" "$(Build.StagingDirectory)/bazel_root/base/external") sudo mkdir -p "${CACHE_DIRS[@]}" - sudo chown -R vsts:vsts "${CACHE_DIRS[@]}" $(Build.StagingDirectory)/bazel_root/ - echo "Created bazel cache directories: "${CACHE_DIRS[*]}"" + if id -u vsts &> /dev/null; then + sudo chown -R vsts:vsts "${CACHE_DIRS[@]}" $(Build.StagingDirectory)/bazel_root/ + else + sudo chown -R azure-pipelines:azure-pipelines "${CACHE_DIRS[@]}" $(Build.StagingDirectory)/bazel_root/ + fi + echo "Created bazel directories: "${CACHE_DIRS[*]}"" displayName: "Create bazel directories" - condition: and(succeeded(), eq('${{ parameters.managedAgent }}', true), eq('${{ parameters.tmpfsDockerDisabled }}', true)) + condition: and(succeeded(), eq('${{ parameters.tmpfsDockerDisabled }}', true)) # Caching - template: cached.yml parameters: + cacheName: "${{ parameters.cacheName }}" keyBazel: "${{ parameters.cacheKeyBazel }}" keyDocker: "${{ parameters.cacheKeyDocker }}" pathDockerBind: "${{ parameters.pathDockerBind }}" @@ -166,6 +175,36 @@ steps: tmpfsDisabled: "${{ parameters.tmpfsCacheDisabled }}" tmpfsDockerDisabled: "${{ parameters.tmpfsDockerDisabled }}" +- script: | + if [[ "${{ parameters.bazelUseBES }}" == 'false' ]]; then + unset GOOGLE_BES_PROJECT_ID + fi + ci/run_envoy_docker.sh 'ci/do_ci.sh fetch-${{ parameters.ciTarget }}' + condition: and(not(canceled()), not(failed()), ne('${{ parameters.cacheName }}', ''), ne(variables.CACHE_RESTORED, 'true')) + workingDirectory: $(Build.SourcesDirectory) + env: + ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) + GITHUB_TOKEN: "${{ parameters.authGithub }}" + BAZEL_STARTUP_EXTRA_OPTIONS: "${{ parameters.bazelStartupExtraOptions }}" + ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: + CI_TARGET_BRANCH: "origin/$(System.PullRequest.TargetBranch)" + ${{ if ne(variables['Build.Reason'], 'PullRequest') }}: + CI_TARGET_BRANCH: "origin/$(Build.SourceBranchName)" + # Any PR or CI run in envoy-presubmit uses the fake SCM hash + ${{ if or(eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.DefinitionName'], 'envoy-presubmit')) }}: + # sha1sum of `ENVOY_PULL_REQUEST` + BAZEL_FAKE_SCM_REVISION: e3b4a6e9570da15ac1caffdded17a8bebdc7dfc9 + ${{ if parameters.rbe }}: + GCP_SERVICE_ACCOUNT_KEY: $(GcpServiceAccountKey) + ENVOY_RBE: "1" + BAZEL_BUILD_EXTRA_OPTIONS: "${{ parameters.bazelConfigRBE }} ${{ parameters.bazelBuildExtraOptions }}" + ${{ if eq(parameters.rbe, false) }}: + BAZEL_BUILD_EXTRA_OPTIONS: "--config=ci ${{ parameters.bazelBuildExtraOptions }}" + BAZEL_REMOTE_CACHE: $(LocalBuildCache) + ${{ each var in parameters.env }}: + ${{ var.key }}: ${{ var.value }} + displayName: "Fetch assets (${{ parameters.ciTarget }})" + - ${{ each step in parameters.stepsPre }}: - ${{ each pair in step }}: ${{ pair.key }}: ${{ pair.value }} @@ -173,6 +212,13 @@ steps: - bash: | echo "disk space at beginning of build:" df -h + if [[ -e "$(Build.StagingDirectory)/bazel_root/base/external" ]]; then + du -sh "$(Build.StagingDirectory)/bazel_root/base/external" + fi + if [[ -e "$(Build.StagingDirectory)/repository_cache" ]]; then + du -sh "$(Build.StagingDirectory)/repository_cache" + fi + displayName: "Check disk space at beginning" - bash: | @@ -228,6 +274,9 @@ steps: cp -a $hprof $(Build.StagingDirectory)/envoy/hprof done + du -sh "$(Build.StagingDirectory)"/bazel_root/base/external + du -sh "$(Build.StagingDirectory)"/repository_cache + cp -a "$(Build.StagingDirectory)/bazel_root/base/server/jvm.out" $(Build.StagingDirectory)/envoy if [[ "${{ parameters.artifactSuffix }}" == ".arm64" ]]; then @@ -247,6 +296,18 @@ steps: - ${{ each pair in step }}: ${{ pair.key }}: ${{ pair.value }} +- script: | + set -e + sudo .azure-pipelines/docker/save_cache.sh "$(Build.StagingDirectory)" /mnt/cache/all true true + if id -u vsts &> /dev/null; then + sudo chown -R vsts:vsts /mnt/cache/all + else + sudo chown -R azure-pipelines:azure-pipelines /mnt/cache/all + fi + + displayName: "Cache/save (${{ parameters.cacheName}})" + condition: and(succeeded(), ne('${{ parameters.cacheName }}', ''), ne(variables.CACHE_RESTORED, 'true')) + - task: PublishTestResults@2 inputs: publishRunAttachments: false diff --git a/.azure-pipelines/cached.yml b/.azure-pipelines/cached.yml index acbf9e486491..f284a1fc9975 100644 --- a/.azure-pipelines/cached.yml +++ b/.azure-pipelines/cached.yml @@ -1,14 +1,14 @@ parameters: -- name: name - type: string - default: $(cacheKeyName) - name: arch type: string default: "" - name: version type: string default: $(cacheKeyVersion) +- name: cacheName + type: string + default: - name: keyDocker type: string @@ -45,20 +45,32 @@ steps: displayName: "Cache/prepare" - task: Cache@2 + condition: and(not(canceled()), ne('${{ parameters.cacheName }}', '')) + env: + VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" + displayName: "Cache (${{ parameters.cacheName }})" + inputs: + key: '${{ parameters.cacheName }} | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyDocker }} | ${{ parameters.keyBazel }}' + path: "${{ parameters.pathTemp }}/all" + cacheHitVar: CACHE_RESTORED + +- task: Cache@2 + condition: and(not(canceled()), not(failed()), or(ne(variables.CACHE_RESTORED, 'true'), eq('${{ parameters.cacheName }}', ''))) env: VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" displayName: "Cache (Docker)" inputs: - key: '${{ parameters.name }} | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyDocker }}' + key: '"${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyDocker }} | docker' path: "${{ parameters.pathTemp }}/docker" cacheHitVar: DOCKER_CACHE_RESTORED - task: Cache@2 + condition: and(not(canceled()), not(failed()), or(ne(variables.CACHE_RESTORED, 'true'), eq('${{ parameters.cacheName }}', ''))) env: VSO_DEDUP_REDIRECT_TIMEOUT_IN_SEC: "${{ parameters.cacheTimeoutWorkaround }}" displayName: "Cache (Bazel)" inputs: - key: '${{ parameters.name }} | bazel | "${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyBazel }}' + key: '"${{ parameters.version }}" | "${{ parameters.arch }}" | ${{ parameters.keyBazel }} | bazel' path: "${{ parameters.pathTemp }}/bazel" cacheHitVar: BAZEL_CACHE_RESTORED @@ -67,9 +79,9 @@ steps: env: DOCKER_RESTORED: $(DOCKER_CACHE_RESTORED) BAZEL_RESTORED: $(BAZEL_CACHE_RESTORED) - displayName: "Cache/prime (${{ parameters.name }})" + displayName: "Cache/prime (Docker/Bazel)" # TODO(phlax): figure if there is a way to test cache without downloading it - condition: and(not(canceled()), eq(${{ parameters.prime }}, true), or(ne(variables.DOCKER_CACHE_RESTORED, 'true'), ne(variables.BAZEL_CACHE_RESTORED, 'true'))) + condition: and(not(canceled()), eq(${{ parameters.prime }}, true), eq('${{ parameters.cacheName }}', ''), or(ne(variables.DOCKER_CACHE_RESTORED, 'true'), ne(variables.BAZEL_CACHE_RESTORED, 'true'))) # Load the caches for a job - script: sudo .azure-pipelines/docker/load_caches.sh "$(Build.StagingDirectory)" "${{ parameters.pathTemp }}" "${{ parameters.pathDockerBind }}" "${{ parameters.tmpfsDockerDisabled }}" diff --git a/.azure-pipelines/docker/create_cache.sh b/.azure-pipelines/docker/create_cache.sh index 4079df028f4c..e9d9f55b071c 100755 --- a/.azure-pipelines/docker/create_cache.sh +++ b/.azure-pipelines/docker/create_cache.sh @@ -3,7 +3,8 @@ set -o pipefail CACHE_TARBALL="${1}" -shift +ROOT_DIR="${2}" +shift 2 echo "Exporting ${*} -> ${CACHE_TARBALL}" @@ -12,9 +13,15 @@ mkdir -p "$CACHE_PATH" CACHE_ARGS=() for path in "$@"; do - total="$(du -sh "$path" | cut -f1)" - echo "Adding cache dir (${path}): ${total}" - CACHE_ARGS+=(-C "$path" .) + if [[ "$ROOT_DIR" == "." ]]; then + total="$(du -sh "$path" | cut -f1)" + echo "Adding cache dir (${path}): ${total}" + CACHE_ARGS+=(-C "$path" .) + else + total="$(du -sh "${ROOT_DIR}/$path" | cut -f1)" + echo "Adding cache dir (${ROOT_DIR}/${path}): ${total}" + CACHE_ARGS+=(-C "$ROOT_DIR" "$path") + fi done tar cf - "${CACHE_ARGS[@]}" | zstd - -q -T0 -o "$CACHE_TARBALL" diff --git a/.azure-pipelines/docker/load_caches.sh b/.azure-pipelines/docker/load_caches.sh index 56e4089fb944..73c03425cfd5 100755 --- a/.azure-pipelines/docker/load_caches.sh +++ b/.azure-pipelines/docker/load_caches.sh @@ -11,10 +11,15 @@ if [[ -z "$CACHE_PATH" ]]; then exit 1 fi -DOCKER_CACHE_PATH="${CACHE_PATH}/docker" -DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" +if [[ -e "${CACHE_PATH}/all" ]]; then + DOCKER_CACHE_PATH="${CACHE_PATH}/all" + BAZEL_CACHE_PATH="${CACHE_PATH}/all" +else + DOCKER_CACHE_PATH="${CACHE_PATH}/docker" + BAZEL_CACHE_PATH="${CACHE_PATH}/bazel" +fi -BAZEL_CACHE_PATH="${CACHE_PATH}/bazel" +DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" BAZEL_CACHE_TARBALL="${BAZEL_CACHE_PATH}/bazel.tar.zst" @@ -42,9 +47,7 @@ remount_docker () { extract_docker () { if [[ -e "${DOCKER_CACHE_TARBALL}" ]]; then echo "Extracting docker cache ${DOCKER_CACHE_TARBALL} -> /var/lib/docker ..." - ls -alh "$DOCKER_CACHE_TARBALL" zstd --stdout -d "$DOCKER_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C /var/lib/docker - touch /tmp/DOCKER_CACHE_RESTORED else echo "No Docker cache to restore, starting Docker with no data" fi @@ -54,6 +57,11 @@ extract_bazel () { if [[ -e "${BAZEL_CACHE_TARBALL}" ]]; then echo "Extracting bazel cache ${BAZEL_CACHE_TARBALL} -> ${ENVOY_DOCKER_BUILD_DIR} ..." zstd --stdout -d "$BAZEL_CACHE_TARBALL" | tar --warning=no-timestamp -xf - -C "${ENVOY_DOCKER_BUILD_DIR}" + if id -u vsts &> /dev/null; then + sudo chown -R vsts:vsts "${ENVOY_DOCKER_BUILD_DIR}" + else + sudo chown -R azure-pipelines:azure-pipelines "${ENVOY_DOCKER_BUILD_DIR}" + fi else echo "No bazel cache to restore, starting bazel with no data" fi diff --git a/.azure-pipelines/docker/prepare_cache.sh b/.azure-pipelines/docker/prepare_cache.sh index 792b9f8f5684..ff3a07ffbc93 100755 --- a/.azure-pipelines/docker/prepare_cache.sh +++ b/.azure-pipelines/docker/prepare_cache.sh @@ -3,7 +3,6 @@ DOCKER_CACHE_PATH="$1" NO_MOUNT_TMPFS="${2:-}" DOCKER_CACHE_OWNERSHIP="vsts:vsts" -TMPFS_SIZE=5G if [[ -z "$DOCKER_CACHE_PATH" ]]; then echo "prepare_docker_cache called without path arg" >&2 @@ -14,6 +13,14 @@ if ! id -u vsts &> /dev/null; then DOCKER_CACHE_OWNERSHIP=azure-pipelines fi +tmpfs_size () { + # Make this 2/3 of total memory + total_mem="$(grep MemTotal /proc/meminfo | cut -d' ' -f2- | xargs | cut -d' ' -f1)" + bc <<< "$total_mem"*2/3*1024 +} + +TMPFS_SIZE="$(tmpfs_size)" + echo "Creating cache directory (${DOCKER_CACHE_PATH}) ..." mkdir -p "${DOCKER_CACHE_PATH}" if [[ -z "$NO_MOUNT_TMPFS" ]]; then diff --git a/.azure-pipelines/docker/prime_cache.sh b/.azure-pipelines/docker/prime_cache.sh index 2ec003d16d95..368c9a8aa319 100755 --- a/.azure-pipelines/docker/prime_cache.sh +++ b/.azure-pipelines/docker/prime_cache.sh @@ -65,11 +65,11 @@ echo "================ Save caches ======================" if [[ "$DOCKER_RESTORED" != "true" ]]; then echo "Stopping docker" sudo systemctl stop docker docker.socket - sudo ./.azure-pipelines/docker/create_cache.sh "${DOCKER_CACHE_TARBALL}" /var/lib/docker + sudo ./.azure-pipelines/docker/create_cache.sh "${DOCKER_CACHE_TARBALL}" . /var/lib/docker fi if [[ "$BAZEL_RESTORED" != "true" ]]; then - sudo ./.azure-pipelines/docker/create_cache.sh "${BAZEL_CACHE_TARBALL}" "${BAZEL_PATH}" + sudo ./.azure-pipelines/docker/create_cache.sh "${BAZEL_CACHE_TARBALL}" . "${BAZEL_PATH}" fi sudo chmod o+r -R "${CACHE_PATH}" echo "===================================================" diff --git a/.azure-pipelines/docker/save_cache.sh b/.azure-pipelines/docker/save_cache.sh index 462177b3f03f..f51fa1006b7d 100755 --- a/.azure-pipelines/docker/save_cache.sh +++ b/.azure-pipelines/docker/save_cache.sh @@ -1,37 +1,42 @@ #!/bin/bash -e set -o pipefail +ENVOY_DOCKER_BUILD_DIR="$1" +CACHE_PATH="$2" +NO_MOUNT_TMPFS="${3:-}" +CACHE_BAZEL="${4:-}" -DOCKER_CACHE_PATH="$1" -NO_MOUNT_TMPFS="${2:-}" - -if [[ -z "$DOCKER_CACHE_PATH" ]]; then +if [[ -z "$CACHE_PATH" ]]; then echo "prime_docker_cache called without path arg" >&2 exit 1 fi -if [[ -e /tmp/DOCKER_CACHE_RESTORED ]]; then - echo "Not saving cache as it was restored" - exit 0 -fi - -DOCKER_CACHE_TARBALL="${DOCKER_CACHE_PATH}/docker.tar.zst" +DOCKER_CACHE_TARBALL="${CACHE_PATH}/docker.tar.zst" +BAZEL_CACHE_TARBALL="${CACHE_PATH}/bazel.tar.zst" docker images echo "Stopping Docker ..." -systemctl stop docker +systemctl stop docker docker.socket -echo "Creating directory to save tarball: ${DOCKER_CACHE_PATH}" -mkdir -p "$DOCKER_CACHE_PATH" +echo "Creating directory to save tarball: ${CACHE_PATH}" +mkdir -p "$CACHE_PATH" if [[ -z "$NO_MOUNT_TMPFS" ]]; then - echo "Mount tmpfs directory: ${DOCKER_CACHE_PATH}" - mount -t tmpfs none "$DOCKER_CACHE_PATH" + echo "Mount tmpfs directory: ${CACHE_PATH}" + mount -t tmpfs none "$CACHE_PATH" fi -echo "Creating tarball: /var/lib/docker -> ${DOCKER_CACHE_TARBALL}" -tar cf - -C /var/lib/docker . | zstd - -T0 -o "$DOCKER_CACHE_TARBALL" - -echo "Docker cache tarball created: ${DOCKER_CACHE_TARBALL}" -ls -lh "$DOCKER_CACHE_TARBALL" +./.azure-pipelines/docker/create_cache.sh \ + "${DOCKER_CACHE_TARBALL}" \ + . \ + /var/lib/docker + +if [[ "$CACHE_BAZEL" == "true" ]]; then + ./.azure-pipelines/docker/create_cache.sh \ + "${BAZEL_CACHE_TARBALL}" \ + "${ENVOY_DOCKER_BUILD_DIR}" \ + bazel_root/install \ + bazel_root/base/external \ + repository_cache +fi diff --git a/.azure-pipelines/pipelines.yml b/.azure-pipelines/pipelines.yml index 2325dbe4fc1b..69e784d00eef 100644 --- a/.azure-pipelines/pipelines.yml +++ b/.azure-pipelines/pipelines.yml @@ -44,20 +44,12 @@ variables: ## Variable settings # Caches (tip: append a version suffix while testing caches) -- name: cacheKeyName - value: envoy - name: cacheKeyVersion - value: v2 + value: v0 - name: cacheKeyBazel value: '.bazelversion | ./WORKSPACE | **/*.bzl, !mobile/**, !envoy-docs/**' - name: cacheKeyDocker value: ".bazelrc" -# Docker build uses separate docker cache -- name: cacheKeyDockerBuild - # VERSION.txt is included to refresh Docker images for release - value: '"publish_docker" | ci/Dockerfile-envoy | VERSION.txt' -- name: cacheKeyDockerBuildVersion - value: v0 - name: pathCacheTemp value: /mnt/cache diff --git a/.azure-pipelines/stage/checks.yml b/.azure-pipelines/stage/checks.yml index 09bec400b6ee..50fdb0956cda 100644 --- a/.azure-pipelines/stage/checks.yml +++ b/.azure-pipelines/stage/checks.yml @@ -43,43 +43,44 @@ jobs: matrix: # These are ordered by most time-consuming first. coverage: - CI_TARGET: "bazel.coverage" + CI_TARGET: "coverage" fuzz_coverage: - CI_TARGET: "bazel.fuzz_coverage" + CI_TARGET: "fuzz_coverage" compile_time_options: - CI_TARGET: "bazel.compile_time_options" + CI_TARGET: "compile_time_options" ENVOY_FILTER_EXAMPLE: true tsan: - CI_TARGET: "bazel.tsan" + CI_TARGET: "tsan" asan: - CI_TARGET: "bazel.asan" + CI_TARGET: "asan" ENVOY_FILTER_EXAMPLE: true # Disabled due to https://github.com/envoyproxy/envoy/pull/18218 # api_compat: - # CI_TARGET: "bazel.api_compat" + # CI_TARGET: "api_compat" gcc: - CI_TARGET: "bazel.gcc" + CI_TARGET: "gcc" msan: - CI_TARGET: "bazel.msan" + CI_TARGET: "msan" ENVOY_FILTER_EXAMPLE: true # # Temporarily disabled to facilitate release CI, should be resolved # as part of https://github.com/envoyproxy/envoy/issues/28566 # # clang_tidy: - # CI_TARGET: "bazel.clang_tidy" + # CI_TARGET: "clang_tidy" # REPO_FETCH_DEPTH: 0 # REPO_FETCH_TAGS: true # PUBLISH_TEST_RESULTS: false # PUBLISH_ENVOY: false api: - CI_TARGET: "bazel.api" + CI_TARGET: "api" timeoutInMinutes: 180 pool: envoy-x64-small steps: - template: ../bazel.yml parameters: ciTarget: $(CI_TARGET) + cacheName: $(CI_TARGET) envoyBuildFilterExample: $(ENVOY_FILTER_EXAMPLE) cacheTestResults: ${{ parameters.cacheTestResults }} managedAgent: false @@ -95,10 +96,10 @@ jobs: pathtoPublish: "$(Build.StagingDirectory)/tmp/lint-fixes" artifactName: "$(CI_TARGET).fixes" timeoutInMinutes: 10 - condition: and(failed(), eq(variables['CI_TARGET'], 'bazel.clang_tidy')) + condition: and(failed(), eq(variables['CI_TARGET'], 'clang_tidy')) - script: ci/run_envoy_docker.sh 'ci/do_ci.sh $(CI_TARGET)-upload' displayName: "Upload $(CI_TARGET) Report to GCS" - condition: and(not(canceled()), or(eq(variables['CI_TARGET'], 'bazel.coverage'), eq(variables['CI_TARGET'], 'bazel.fuzz_coverage'))) + condition: and(not(canceled()), or(eq(variables['CI_TARGET'], 'coverage'), eq(variables['CI_TARGET'], 'fuzz_coverage'))) env: ENVOY_DOCKER_BUILD_DIR: $(Build.StagingDirectory) ENVOY_RBE: "1" diff --git a/.azure-pipelines/stage/linux.yml b/.azure-pipelines/stage/linux.yml index d35f67094b5f..be4e4a6ada14 100644 --- a/.azure-pipelines/stage/linux.yml +++ b/.azure-pipelines/stage/linux.yml @@ -48,7 +48,8 @@ jobs: - template: ../bazel.yml parameters: managedAgent: ${{ parameters.managedAgent }} - ciTarget: bazel.release + ciTarget: release + cacheName: "release" bazelBuildExtraOptions: ${{ parameters.bazelBuildExtraOptions }} cacheTestResults: ${{ parameters.cacheTestResults }} cacheVersion: $(cacheKeyBazel) diff --git a/.azure-pipelines/stage/prechecks.yml b/.azure-pipelines/stage/prechecks.yml index 4c75d8d627dd..09ff21fe05d5 100644 --- a/.azure-pipelines/stage/prechecks.yml +++ b/.azure-pipelines/stage/prechecks.yml @@ -54,6 +54,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: $(CI_TARGET) + cacheName: $(CI_TARGET) cacheTestResults: ${{ parameters.cacheTestResults }} cacheVersion: $(cacheKeyBazel) publishEnvoy: false diff --git a/.azure-pipelines/stage/publish.yml b/.azure-pipelines/stage/publish.yml index 98143a23bb46..b04013b69721 100644 --- a/.azure-pipelines/stage/publish.yml +++ b/.azure-pipelines/stage/publish.yml @@ -101,18 +101,16 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.release" - itemPattern: "bazel.release/**/bin/*" + artifactName: "release" + itemPattern: "release/**/bin/*" targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: ciTarget: docker-upload + cacheName: docker-upload publishEnvoy: false publishTestResults: false pathDockerBind: "" - cacheKeyDocker: "$(cacheKeyDockerBuild)" - cacheKeyVersion: "$(cacheKeyDockerBuildVersion)" - pathCacheTemp: /var/azpcache tmpfsCacheDisabled: true diskspaceHack: true env: @@ -128,12 +126,12 @@ jobs: mkdir -p linux/amd64 linux/arm64 # x64 - cp -a $(Build.StagingDirectory)/bazel.release/x64/bin/release.tar.zst linux/amd64/release.tar.zst - cp -a $(Build.StagingDirectory)/bazel.release/x64/bin/schema_validator_tool linux/amd64/schema_validator_tool + cp -a $(Build.StagingDirectory)/release/x64/bin/release.tar.zst linux/amd64/release.tar.zst + cp -a $(Build.StagingDirectory)/release/x64/bin/schema_validator_tool linux/amd64/schema_validator_tool # arm64 - cp -a $(Build.StagingDirectory)/bazel.release/arm64/bin/release.tar.zst linux/arm64/release.tar.zst - cp -a $(Build.StagingDirectory)/bazel.release/arm64/bin/schema_validator_tool linux/arm64/schema_validator_tool + cp -a $(Build.StagingDirectory)/release/arm64/bin/release.tar.zst linux/arm64/release.tar.zst + cp -a $(Build.StagingDirectory)/release/arm64/bin/schema_validator_tool linux/arm64/schema_validator_tool # Debug what files appear to have been downloaded find linux -type f -name "*" | xargs ls -l @@ -148,13 +146,6 @@ jobs: DOCKERHUB_USERNAME: ${{ parameters.authDockerUser }} DOCKERHUB_PASSWORD: ${{ parameters.authDockerPassword }} DOCKER_BUILD_TIMEOUT: ${{ parameters.timeoutDockerBuild }} - stepsPost: - - script: | - set -e - sudo .azure-pipelines/docker/save_cache.sh /var/azpcache/docker true - sudo rm -rf /var/lib/docker - displayName: "Cache/save (publish_docker)" - condition: and(succeeded(), ne(variables.DOCKER_CACHE_RESTORED, 'true')) - job: package_x64 displayName: Linux debs (x64) @@ -169,12 +160,13 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.release" - itemPattern: "bazel.release/x64/bin/*" + artifactName: "release" + itemPattern: "release/x64/bin/*" targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: - ciTarget: bazel.distribution + ciTarget: distribution + cacheName: distribution publishTestResults: false stepsPre: - template: ../gpg.yml @@ -201,14 +193,15 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.release" - itemPattern: "bazel.release/arm64/bin/*" + artifactName: "release" + itemPattern: "release/arm64/bin/*" targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: managedAgent: false - ciTarget: bazel.distribution + ciTarget: distribution + cacheName: distribution rbe: false artifactSuffix: ".arm64" bazelBuildExtraOptions: "--sandbox_base=/tmp/sandbox_base" @@ -239,6 +232,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: docs + cacheName: docs cacheVersion: $(cacheKeyBazel) publishEnvoy: false publishTestResults: false @@ -315,18 +309,19 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.release" - itemPattern: "bazel.release/**/bin/*" + artifactName: "release" + itemPattern: "release/**/bin/*" targetPath: $(Build.StagingDirectory) - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.distribution" - itemPattern: "bazel.distribution/**/packages.*.tar.gz" + artifactName: "distribution" + itemPattern: "distribution/**/packages.*.tar.gz" targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: ciTarget: release.signed + cacheName: release-signed publishTestResults: false env: GCS_ARTIFACT_BUCKET: ${{ parameters.bucketGCP }} @@ -370,6 +365,7 @@ jobs: - template: ../bazel.yml parameters: ciTarget: verify.trigger + cacheName: verify-trigger authGithub: "$(key.value)" cacheVersion: $(cacheKeyBazel) publishEnvoy: false diff --git a/.azure-pipelines/stage/verify.yml b/.azure-pipelines/stage/verify.yml index f8f2e426fc08..2214bee7971e 100644 --- a/.azure-pipelines/stage/verify.yml +++ b/.azure-pipelines/stage/verify.yml @@ -18,13 +18,14 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.distribution" - itemPattern: "bazel.distribution/x64/packages.x64.tar.gz" + artifactName: "distribution" + itemPattern: "distribution/x64/packages.x64.tar.gz" downloadType: single targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: ciTarget: verify_distro + cacheName: verify_distro publishTestResults: false env: ENVOY_DOCKER_IN_DOCKER: 1 @@ -38,14 +39,15 @@ jobs: - task: DownloadBuildArtifacts@0 inputs: buildType: current - artifactName: "bazel.distribution" - itemPattern: "bazel.distribution/arm64/packages.arm64.tar.gz" + artifactName: "distribution" + itemPattern: "distribution/arm64/packages.arm64.tar.gz" downloadType: single targetPath: $(Build.StagingDirectory) - template: ../bazel.yml parameters: managedAgent: false ciTarget: verify_distro + cacheName: verify_distro rbe: false artifactSuffix: ".arm64" publishTestResults: false diff --git a/ci/do_ci.sh b/ci/do_ci.sh index 0e5cec26719d..e8bc752c4128 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -30,6 +30,44 @@ FETCH_TARGETS=( @envoy_api//... @envoy_build_tools//...) +FETCH_TARGETS=( + @bazel_tools//tools/jdk:remote_jdk11 + @com_github_bufbuild_buf//:bin/buf + @envoy_build_tools//... + //docs/... + //tools/proto_format/... + //tools/zstd + //tools/gsutil + //tools/code_format/...) + +FETCH_BUILD_TARGETS=( + @com_github_google_quiche//:ci_tests + //contrib/exe/... + //distribution/... + //source/exe/... + //test/tools/schema_validator/... + //test/...) + +retry () { + local n wait iterations + wait="${1}" + iterations="${2}" + shift 2 + n=0 + until [ "$n" -ge "$iterations" ]; do + "${@}" \ + && break + n=$((n+1)) + if [[ "$n" -lt "$iterations" ]]; then + sleep "$wait" + echo "Retrying ..." + else + echo "Fetch failed" + exit 1 + fi + done +} + if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then BUILD_ARCH_DIR="/linux/amd64" @@ -181,25 +219,27 @@ function run_ci_verify () { CI_TARGET=$1 shift +if [[ "$CI_TARGET" =~ bazel.* ]]; then + ORIG_CI_TARGET="$CI_TARGET" + CI_TARGET="$(echo "${CI_TARGET}" | cut -d. -f2-)" + echo "Using \`${ORIG_CI_TARGET}\` is deprecated, please use \`${CI_TARGET}\`" +fi + if [[ $# -ge 1 ]]; then COVERAGE_TEST_TARGETS=("$@") TEST_TARGETS=("$@") else # Coverage test will add QUICHE tests by itself. COVERAGE_TEST_TARGETS=("//test/...") - if [[ "$CI_TARGET" == "bazel.release" ]]; then + if [[ "${CI_TARGET}" == "release" ]]; then # We test contrib on release only. COVERAGE_TEST_TARGETS=("${COVERAGE_TEST_TARGETS[@]}" "//contrib/...") - elif [[ "${CI_TARGET}" == "bazel.msan" ]]; then + elif [[ "${CI_TARGET}" == "msan" ]]; then COVERAGE_TEST_TARGETS=("${COVERAGE_TEST_TARGETS[@]}" "-//test/extensions/...") fi TEST_TARGETS=("${COVERAGE_TEST_TARGETS[@]}" "@com_github_google_quiche//:ci_tests") fi -if [[ "$CI_TARGET" =~ bazel.* ]]; then - CI_TARGET="$(echo "${CI_TARGET}" | cut -d. -f2-)" -fi - case $CI_TARGET in api) # Use libstdc++ because the API booster links to prebuilt libclang*/libLLVM* installed in /opt/llvm/lib, @@ -509,9 +549,9 @@ case $CI_TARGET in # Extract the Envoy binary from the tarball mkdir -p distribution/custom if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then - ENVOY_RELEASE_TARBALL="/build/bazel.release/x64/bin/release.tar.zst" + ENVOY_RELEASE_TARBALL="/build/release/x64/bin/release.tar.zst" else - ENVOY_RELEASE_TARBALL="/build/bazel.release/arm64/bin/release.tar.zst" + ENVOY_RELEASE_TARBALL="/build/release/arm64/bin/release.tar.zst" fi bazel run "${BAZEL_BUILD_OPTIONS[@]}" \ //tools/zstd \ @@ -567,30 +607,32 @@ case $CI_TARGET in cat bazel-bin/distribution/dockerhub/readme.md ;; - fetch) + fetch|fetch-*) + case $CI_TARGET in + fetch) + targets=("${FETCH_TARGETS[@]}") + ;; + fetch-release) + targets=("${FETCH_BUILD_TARGETS[@]}") + ;; + *) + exit 0 + ;; + esac setup_clang_toolchain - echo "Fetching ${FETCH_TARGETS[*]} ..." FETCH_ARGS=( --noshow_progress --noshow_loading_progress) - # TODO(phlax): separate out retry logic - n=0 - until [ "$n" -ge 10 ]; do - bazel fetch "${BAZEL_GLOBAL_OPTIONS[@]}" \ - "${FETCH_ARGS[@]}" \ - "${FETCH_TARGETS[@]}" \ - && break - n=$((n+1)) - if [[ "$n" -lt 10 ]]; then - sleep 15 - echo "Retrying fetch ..." - else - echo "Fetch failed" - exit 1 - fi - done + echo "Fetching ${targets[*]} ..." + retry 15 10 bazel \ + fetch \ + "${BAZEL_GLOBAL_OPTIONS[@]}" \ + "${FETCH_ARGS[@]}" \ + "${targets[@]}" ;; + + fix_proto_format) # proto_format.sh needs to build protobuf. setup_clang_toolchain @@ -724,7 +766,7 @@ case $CI_TARGET in setup_clang_toolchain # The default config expects these files mkdir -p distribution/custom - cp -a /build/bazel.*/*64 distribution/custom/ + cp -a /build/*/*64 distribution/custom/ bazel build "${BAZEL_BUILD_OPTIONS[@]}" //distribution:signed cp -a bazel-bin/distribution/release.signed.tar.zst "${BUILD_DIR}/envoy/" "${ENVOY_SRCDIR}/ci/upload_gcs_artifact.sh" "${BUILD_DIR}/envoy" release @@ -774,9 +816,9 @@ case $CI_TARGET in # this can be required if any python deps require compilation setup_clang_toolchain if [[ "${ENVOY_BUILD_ARCH}" == "x86_64" ]]; then - PACKAGE_BUILD=/build/bazel.distribution/x64/packages.x64.tar.gz + PACKAGE_BUILD=/build/distribution/x64/packages.x64.tar.gz else - PACKAGE_BUILD=/build/bazel.distribution/arm64/packages.arm64.tar.gz + PACKAGE_BUILD=/build/distribution/arm64/packages.arm64.tar.gz fi bazel run "${BAZEL_BUILD_OPTIONS[@]}" \ //distribution:verify_packages \ From 12eb191df1fca12939296d4550e6f9ad84a1636e Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 8 Sep 2023 20:42:41 +0100 Subject: [PATCH 38/55] tools/ci: Optimize local formatter (#29521) Signed-off-by: Ryan Northey --- ci/do_ci.sh | 1 - tools/local_fix_format.sh | 46 ++++++++++++++++++++------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/ci/do_ci.sh b/ci/do_ci.sh index e8bc752c4128..67aeb96afae0 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -4,7 +4,6 @@ set -e - # TODO(phlax): Clarify and/or integrate SRCDIR and ENVOY_SRCDIR export SRCDIR="${SRCDIR:-$PWD}" export ENVOY_SRCDIR="${ENVOY_SRCDIR:-$PWD}" diff --git a/tools/local_fix_format.sh b/tools/local_fix_format.sh index 0715ca6466df..ebaf2d8929ce 100755 --- a/tools/local_fix_format.sh +++ b/tools/local_fix_format.sh @@ -44,13 +44,13 @@ fi # Runs the formatting functions on the specified args, echoing commands # if -vergbose was supplied to the script. -function format_one() { +function format_some() { ( if [[ "$verbose" == "1" ]]; then set -x fi - bazel run //tools/code_format:check_format -- fix "${1}" - ./tools/spelling/check_spelling_pedantic.py fix "$1" + bazel run //tools/code_format:check_format -- fix "$@" + ./tools/spelling/check_spelling_pedantic.py fix "$@" ) } @@ -65,25 +65,27 @@ function format_all() { } if [[ $# -gt 0 && "$1" == "-all" ]]; then - echo "Checking all files in the repo...this may take a while." - format_all + echo "Checking all files in the repo...this may take a while." + format_all else - if [[ $# -gt 0 && "$1" == "-main" ]]; then - shift - echo "Checking all files that have changed since the main branch." - args=$(git diff main | grep ^diff | awk '{print $3}' | cut -c 3-) - elif [[ $# == 0 ]]; then - args=$(git status|grep -E '(modified:|added:)'|awk '{print $2}') - args+=$(git status|grep -E 'new file:'|awk '{print $3}') - else - args="$*" - fi + if [[ $# -gt 0 && "$1" == "-main" ]]; then + shift + echo "Checking all files that have changed since the main branch." + args=$(git diff main | grep ^diff | awk '{print $3}' | cut -c 3-) + elif [[ $# == 0 ]]; then + args=$(git status|grep -E '(modified:|added:)'|awk '{print $2}') + args+=$(git status|grep -E 'new file:'|awk '{print $3}') + else + args="$*" + fi + + if [[ -z "$args" ]]; then + echo No files selected. Bailing out. + exit 0 + fi + + _changes="$(echo "$args" | tr '\n' ' ')" + IFS=' ' read -ra changes <<< "$_changes" - if [[ "$args" == "" ]]; then - echo No files selected. Bailing out. - exit 0 - fi - for arg in $args; do - format_one "$arg" - done + format_some "${changes[@]}" fi From 29b37bda8f87b4019c9b8f43c26459fe91df1478 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Fri, 8 Sep 2023 17:27:14 -0400 Subject: [PATCH 39/55] tools: Add option to skip bazel for format fix (#29528) tools: Add option to skip bazel for format fix Signed-off-by: Joshua Marantz --- tools/local_fix_format.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tools/local_fix_format.sh b/tools/local_fix_format.sh index ebaf2d8929ce..5c29c15c7593 100755 --- a/tools/local_fix_format.sh +++ b/tools/local_fix_format.sh @@ -35,6 +35,14 @@ if [[ $# -gt 0 && "$1" == "-run-build-setup" ]]; then . ci/build_setup.sh fi +use_bazel=1 +if [[ $# -gt 0 && "$1" == "-skip-bazel" ]]; then + echo -n "WARNING: not using bazel to invoke this script may result in " + echo "mismatched versions and incorrect formatting" + shift + use_bazel=0 +fi + if [[ $# -gt 0 && "$1" == "-verbose" ]]; then verbose=1 shift @@ -49,8 +57,13 @@ function format_some() { if [[ "$verbose" == "1" ]]; then set -x fi - bazel run //tools/code_format:check_format -- fix "$@" - ./tools/spelling/check_spelling_pedantic.py fix "$@" + if [[ "$use_bazel" == "1" ]]; then + bazel run //tools/code_format:check_format -- fix "$@" + else + for arg in "$@"; do + ./tools/spelling/check_spelling_pedantic.py fix "$arg" + done + fi ) } From bfec1286b63dfb4c54409f5a6f65876385c5d750 Mon Sep 17 00:00:00 2001 From: danzh Date: Fri, 8 Sep 2023 21:52:50 -0400 Subject: [PATCH 40/55] http1: `Connection: close` header lost in internal redirect (#29174) Envoy should close the connection after finishing serving a HTTP/1.1 request with Connection: close header and also send back Connection: close in its response header. This change fix a bug where if the request is redirected via recreateStream(), Envoy will drop Connection: close on the floor and treat the redirected request as if it doesn't have that header. The RFC doesn't enforce the server to close the connection immediately, but it is good to keep the behavior in sync with/without internal redirect. Signed-off-by: Dan Zhang --- changelogs/current.yaml | 6 + envoy/stream_info/stream_info.h | 14 ++ source/common/http/conn_manager_impl.cc | 34 +++-- source/common/http/conn_manager_impl.h | 7 +- source/common/runtime/runtime_features.cc | 1 + source/common/stream_info/stream_info_impl.h | 12 ++ .../stream_info/stream_info_impl_test.cc | 9 +- test/integration/integration_test.cc | 10 +- test/integration/redirect_integration_test.cc | 121 ++++++++++++++++++ test/mocks/stream_info/mocks.h | 3 + 10 files changed, 197 insertions(+), 20 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 598143a28934..6018d129e2ba 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -80,6 +80,12 @@ bug_fixes: :ref:`overprovisioning_factor` and :ref:`weighted_priority_health ` values were not respected when subset load balacing was enabled. The default values of 140 and false were always used. +- area: http1 + change: | + Fixed a bug where HTTP/1.1 requests with "Connection: close" header is handled differently if the requested is internally redirected. + Without internal redirect, the response will also have a "Connection: close" header and the connection will be closed after finishing + that request. Requests with internal redirect should be handled in the same way. This behavior can be reverted by setting runtime + ``envoy.reloadable_features.http1_connection_close_header_in_redirect`` to false. - area: redis change: | fixed a bug where redis key formatter is using the closed stream because of life time issues. diff --git a/envoy/stream_info/stream_info.h b/envoy/stream_info/stream_info.h index 4ba9b11beec7..9196813fd4ef 100644 --- a/envoy/stream_info/stream_info.h +++ b/envoy/stream_info/stream_info.h @@ -896,6 +896,20 @@ class StreamInfo { * @param failure_reason the downstream transport failure reason. */ virtual void setDownstreamTransportFailureReason(absl::string_view failure_reason) PURE; + + /** + * Checked by streams after finishing serving the request. + * @return bool true if the connection should be drained once this stream has + * finished sending and receiving. + */ + virtual bool shouldDrainConnectionUponCompletion() const PURE; + + /** + * Called if the connection decides to drain itself after serving this request. + * @param should_drain true to close the connection once this stream has + * finished sending and receiving. + */ + virtual void setShouldDrainConnectionUponCompletion(bool should_drain) PURE; }; // An enum representation of the Proxy-Status error space. diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 14b304a1bb46..5da6ce4da9e5 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -262,7 +262,8 @@ void ConnectionManagerImpl::doEndStream(ActiveStream& stream, bool check_for_def (!stream.response_headers_ || !stream.response_headers_->ContentLength()); // We also don't delay-close in the case of HTTP/1.1 where the request is // fully read, as there's no race condition to avoid. - bool connection_close = stream.state_.saw_connection_close_; + const bool connection_close = + stream.filter_manager_.streamInfo().shouldDrainConnectionUponCompletion(); bool request_complete = stream.filter_manager_.remoteDecodeComplete(); if (check_for_deferred_close) { @@ -383,7 +384,7 @@ RequestDecoder& ConnectionManagerImpl::newStream(ResponseEncoder& response_encod if (config_.maxRequestsPerConnection() > 0 && accumulated_requests_ >= config_.maxRequestsPerConnection()) { if (codec_->protocol() < Protocol::Http2) { - new_stream->state_.saw_connection_close_ = true; + new_stream->filter_manager_.streamInfo().setShouldDrainConnectionUponCompletion(true); // Prevent erroneous debug log of closing due to incoming connection close header. drain_state_ = DrainState::Closing; } else if (drain_state_ == DrainState::NotDraining) { @@ -1120,10 +1121,22 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapSharedPt request_header_timer_.reset(); } - // Both saw_connection_close_ and is_head_request_ affect local replies: set - // them as early as possible. + // Both shouldDrainConnectionUponCompletion() and is_head_request_ affect local replies: set them + // as early as possible. const Protocol protocol = connection_manager_.codec_->protocol(); - state_.saw_connection_close_ = HeaderUtility::shouldCloseConnection(protocol, *request_headers_); + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.http1_connection_close_header_in_redirect")) { + if (HeaderUtility::shouldCloseConnection(protocol, *request_headers_)) { + // Only mark the connection to be closed if the request indicates so. The connection might + // already be marked so before this step, in which case if shouldCloseConnection() returns + // false, the stream info value shouldn't be overridden. + filter_manager_.streamInfo().setShouldDrainConnectionUponCompletion(true); + } + } else { + filter_manager_.streamInfo().setShouldDrainConnectionUponCompletion( + HeaderUtility::shouldCloseConnection(protocol, *request_headers_)); + } + filter_manager_.streamInfo().protocol(protocol); // We end the decode here to mark that the downstream stream is complete. @@ -1314,7 +1327,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapSharedPt // accepted, err on the side of caution and refuse to process any further requests on this // connection, to avoid a class of HTTP/1.1 smuggling bugs where Upgrade or CONNECT payload // contains a smuggled HTTP request. - state_.saw_connection_close_ = true; + filter_manager_.streamInfo().setShouldDrainConnectionUponCompletion(true); connection_manager_.stats_.named_.downstream_rq_ws_on_non_ws_route_.inc(); sendLocalReply(Code::Forbidden, "", nullptr, absl::nullopt, StreamInfo::ResponseCodeDetails::get().UpgradeFailed); @@ -1605,7 +1618,7 @@ void ConnectionManagerImpl::ActiveStream::onLocalReply(Code code) { // The BadRequest error code indicates there has been a messaging error. if (code == Http::Code::BadRequest && connection_manager_.codec_->protocol() < Protocol::Http2 && !response_encoder_->streamErrorOnInvalidHttpMessage()) { - state_.saw_connection_close_ = true; + filter_manager_.streamInfo().setShouldDrainConnectionUponCompletion(true); } } @@ -1674,16 +1687,17 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ResponseHeaderMap& heade // As HTTP/1.0 and below can not do chunked encoding, if there is no content // length the response will be framed by connection close. if (!headers.ContentLength()) { - state_.saw_connection_close_ = true; + filter_manager_.streamInfo().setShouldDrainConnectionUponCompletion(true); } // If the request came with a keep-alive and no other factor resulted in a // connection close header, send an explicit keep-alive header. - if (!state_.saw_connection_close_) { + if (!filter_manager_.streamInfo().shouldDrainConnectionUponCompletion()) { headers.setConnection(Headers::get().ConnectionValues.KeepAlive); } } - if (connection_manager_.drain_state_ == DrainState::NotDraining && state_.saw_connection_close_) { + if (connection_manager_.drain_state_ == DrainState::NotDraining && + filter_manager_.streamInfo().shouldDrainConnectionUponCompletion()) { ENVOY_STREAM_LOG(debug, "closing connection due to connection close header", *this); connection_manager_.drain_state_ = DrainState::Closing; } diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index f1e20375a0b9..47749a9b77bf 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -338,9 +338,9 @@ class ConnectionManagerImpl : Logger::Loggable, struct State { State() : codec_saw_local_complete_(false), codec_encode_complete_(false), - on_reset_stream_called_(false), is_zombie_stream_(false), saw_connection_close_(false), - successful_upgrade_(false), is_internally_destroyed_(false), - is_internally_created_(false), is_tunneling_(false), decorated_propagate_(true) {} + on_reset_stream_called_(false), is_zombie_stream_(false), successful_upgrade_(false), + is_internally_destroyed_(false), is_internally_created_(false), is_tunneling_(false), + decorated_propagate_(true) {} // It's possibly for the codec to see the completed response but not fully // encode it. @@ -351,7 +351,6 @@ class ConnectionManagerImpl : Logger::Loggable, bool on_reset_stream_called_ : 1; // Whether the stream has been reset. bool is_zombie_stream_ : 1; // Whether stream is waiting for signal // the underlying codec to be destroyed. - bool saw_connection_close_ : 1; bool successful_upgrade_ : 1; // True if this stream was the original externally created stream, but was diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index f74e1ec37251..eb4fc0176ead 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -49,6 +49,7 @@ RUNTIME_GUARD(envoy_reloadable_features_format_ports_as_numbers); RUNTIME_GUARD(envoy_reloadable_features_handle_uppercase_scheme); RUNTIME_GUARD(envoy_reloadable_features_hmac_base64_encoding_only); RUNTIME_GUARD(envoy_reloadable_features_http1_allow_codec_error_response_after_1xx_headers); +RUNTIME_GUARD(envoy_reloadable_features_http1_connection_close_header_in_redirect); RUNTIME_GUARD(envoy_reloadable_features_http1_use_balsa_parser); RUNTIME_GUARD(envoy_reloadable_features_http2_decode_metadata_with_quiche); RUNTIME_GUARD(envoy_reloadable_features_http2_validate_authority_with_quiche); diff --git a/source/common/stream_info/stream_info_impl.h b/source/common/stream_info/stream_info_impl.h index 8d426ef9f5a2..ca72643ba82c 100644 --- a/source/common/stream_info/stream_info_impl.h +++ b/source/common/stream_info/stream_info_impl.h @@ -18,6 +18,7 @@ #include "source/common/common/macros.h" #include "source/common/common/utility.h" #include "source/common/network/socket_impl.h" +#include "source/common/runtime/runtime_features.h" #include "source/common/stream_info/filter_state_impl.h" #include "source/common/stream_info/stream_id_provider_impl.h" @@ -349,6 +350,10 @@ struct StreamInfoImpl : public StreamInfo { downstream_transport_failure_reason_ = std::string(info.downstreamTransportFailureReason()); bytes_retransmitted_ = info.bytesRetransmitted(); packets_retransmitted_ = info.packetsRetransmitted(); + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.http1_connection_close_header_in_redirect")) { + should_drain_connection_ = info.shouldDrainConnectionUponCompletion(); + } } // This function is used to copy over every field exposed in the StreamInfo interface, with a @@ -397,6 +402,12 @@ struct StreamInfoImpl : public StreamInfo { return downstream_transport_failure_reason_; } + bool shouldDrainConnectionUponCompletion() const override { return should_drain_connection_; } + + void setShouldDrainConnectionUponCompletion(bool should_drain) override { + should_drain_connection_ = should_drain; + } + TimeSource& time_source_; SystemTime start_time_; MonotonicTime start_time_monotonic_; @@ -455,6 +466,7 @@ struct StreamInfoImpl : public StreamInfo { BytesMeterSharedPtr downstream_bytes_meter_; bool is_shadow_{false}; std::string downstream_transport_failure_reason_; + bool should_drain_connection_{false}; }; } // namespace StreamInfo diff --git a/test/common/stream_info/stream_info_impl_test.cc b/test/common/stream_info/stream_info_impl_test.cc index 01da7f905a22..dcb0dcfe34d9 100644 --- a/test/common/stream_info/stream_info_impl_test.cc +++ b/test/common/stream_info/stream_info_impl_test.cc @@ -37,10 +37,11 @@ std::chrono::nanoseconds checkDuration(std::chrono::nanoseconds last, class StreamInfoImplTest : public testing::Test { protected: void assertStreamInfoSize(StreamInfoImpl stream_info) { - ASSERT_TRUE(sizeof(stream_info) == 840 || sizeof(stream_info) == 856 || - sizeof(stream_info) == 888 || sizeof(stream_info) == 776 || - sizeof(stream_info) == 728 || sizeof(stream_info) == 744 || - sizeof(stream_info) == 680 || sizeof(stream_info) == 696) + ASSERT_TRUE( + sizeof(stream_info) == 840 || sizeof(stream_info) == 856 || sizeof(stream_info) == 888 || + sizeof(stream_info) == 776 || sizeof(stream_info) == 728 || sizeof(stream_info) == 744 || + sizeof(stream_info) == 680 || sizeof(stream_info) == 696 || sizeof(stream_info) == 688 || + sizeof(stream_info) == 720 || sizeof(stream_info) == 704) << "If adding fields to StreamInfoImpl, please check to see if you " "need to add them to setFromForRecreateStream or setFrom! Current size " << sizeof(stream_info); diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index 0d12a2c92d8a..b716086e0cd5 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -397,18 +397,24 @@ TEST_P(IntegrationTest, RouterDirectResponseEmptyBody) { EXPECT_THAT(log, HasSubstr(route_name)); } -TEST_P(IntegrationTest, ConnectionClose) { +TEST_P(IntegrationTest, ConnectionCloseHeader) { autonomous_upstream_ = true; + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { hcm.mutable_delayed_close_timeout()->set_seconds(10); }); + initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); auto response = codec_client_->makeHeaderOnlyRequest(Http::TestRequestHeaderMapImpl{ {":method", "GET"}, {":path", "/"}, {":authority", "host"}, {"connection", "close"}}); ASSERT_TRUE(response->waitForEndStream()); - ASSERT_TRUE(codec_client_->waitForDisconnect()); + ASSERT_TRUE(codec_client_->waitForDisconnect(std::chrono::milliseconds(1000))); EXPECT_TRUE(response->complete()); EXPECT_THAT(response->headers(), HttpStatusIs("200")); + EXPECT_THAT(response->headers(), HeaderValueOf(Headers::get().Connection, "close")); + EXPECT_EQ(codec_client_->lastConnectionEvent(), Network::ConnectionEvent::RemoteClose); } TEST_P(IntegrationTest, RouterRequestAndResponseWithBodyNoBuffer) { diff --git a/test/integration/redirect_integration_test.cc b/test/integration/redirect_integration_test.cc index c3e7b0d3d7aa..b51d94cfd4dc 100644 --- a/test/integration/redirect_integration_test.cc +++ b/test/integration/redirect_integration_test.cc @@ -197,6 +197,127 @@ TEST_P(RedirectIntegrationTest, BasicInternalRedirect) { EXPECT_THAT(waitForAccessLog(access_log_name_, 1), HasSubstr("200 via_upstream -")); } +// Test the buggy behavior where Envoy doesn't respond to "Connection: close" header in requests +// which get redirected. +// TODO(danzh) remove the test once the runtime guard is deprecated. +TEST_P(RedirectIntegrationTest, ConnectionCloseHeaderDroppedInInternalRedirect) { + config_helper_.addRuntimeOverride( + "envoy.reloadable_features.http1_connection_close_header_in_redirect", "false"); + + if (downstreamProtocol() != Envoy::Http::CodecClient::Type::HTTP1) { + return; + } + useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); + // Validate that header sanitization is only called once. + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + // The delay should be ignored in light of "Connection: close". + hcm.mutable_delayed_close_timeout()->set_seconds(1); + hcm.set_via("via_value"); + hcm.mutable_common_http_protocol_options() + ->mutable_max_requests_per_connection() + ->set_value(10); + }); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + default_request_headers_.setHost("handle.internal.redirect"); + default_request_headers_.setConnection("close"); + IntegrationStreamDecoderPtr response = + codec_client_->makeHeaderOnlyRequest(default_request_headers_); + + waitForNextUpstreamRequest(); + + upstream_request_->encodeHeaders(redirect_response_, true); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value")); + + waitForNextUpstreamRequest(); + ASSERT(upstream_request_->headers().EnvoyOriginalUrl() != nullptr); + EXPECT_EQ("http://handle.internal.redirect/test/long/url", + upstream_request_->headers().getEnvoyOriginalUrlValue()); + EXPECT_EQ("/new/url", upstream_request_->headers().getPathValue()); + EXPECT_EQ("authority2", upstream_request_->headers().getHostValue()); + EXPECT_EQ("via_value", upstream_request_->headers().getViaValue()); + + upstream_request_->encodeHeaders(default_response_headers_, true); + + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + EXPECT_TRUE(response->headers().get(Envoy::Http::Headers::get().Connection).empty()); + + // Envoy won't close the connection with out the fix. + ASSERT_FALSE(codec_client_->waitForDisconnect(std::chrono::milliseconds(2000))); +} + +// Test that Envoy should correctly respond to "Connection: close" header in requests which get +// redirected by echoing "Connection: close" in response and closing the connection immediately. +TEST_P(RedirectIntegrationTest, ConnectionCloseHeaderHonoredInInternalRedirect) { + config_helper_.addRuntimeOverride( + "envoy.reloadable_features.http1_connection_close_header_in_redirect", "true"); + + if (downstreamProtocol() != Envoy::Http::CodecClient::Type::HTTP1) { + return; + } + useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %RESP(test-header)%"); + // Validate that header sanitization is only called once. + config_helper_.addConfigModifier( + [](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) { + // The delay should be ignored in light of "Connection: close". + hcm.mutable_delayed_close_timeout()->set_seconds(10); + hcm.set_via("via_value"); + // Make sure connection won't be closed because it only allows 1 request. + hcm.mutable_common_http_protocol_options() + ->mutable_max_requests_per_connection() + ->set_value(10); + }); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + default_request_headers_.setHost("handle.internal.redirect"); + default_request_headers_.setConnection("close"); + IntegrationStreamDecoderPtr response = + codec_client_->makeHeaderOnlyRequest(default_request_headers_); + + waitForNextUpstreamRequest(); + + upstream_request_->encodeHeaders(redirect_response_, true); + EXPECT_THAT(waitForAccessLog(access_log_name_, 0), + HasSubstr("302 internal_redirect test-header-value")); + + waitForNextUpstreamRequest(); + ASSERT(upstream_request_->headers().EnvoyOriginalUrl() != nullptr); + EXPECT_EQ("http://handle.internal.redirect/test/long/url", + upstream_request_->headers().getEnvoyOriginalUrlValue()); + EXPECT_EQ("/new/url", upstream_request_->headers().getPathValue()); + EXPECT_EQ("authority2", upstream_request_->headers().getHostValue()); + EXPECT_EQ("via_value", upstream_request_->headers().getViaValue()); + + upstream_request_->encodeHeaders(default_response_headers_, true); + + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + // "Connection: close" should be sent back in response. + EXPECT_THAT(response->headers(), + Envoy::Http::HeaderValueOf(Envoy::Http::Headers::get().Connection, "close")); + + // Envoy should close the connection immediately. + ASSERT_TRUE(codec_client_->waitForDisconnect(std::chrono::milliseconds(2000))); + EXPECT_EQ(codec_client_->lastConnectionEvent(), Envoy::Network::ConnectionEvent::RemoteClose); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.upstream_internal_redirect_succeeded_total") + ->value()); + + // 302 was never returned downstream + EXPECT_EQ(0, test_server_->counter("http.config_test.downstream_rq_3xx")->value()); + EXPECT_EQ(1, test_server_->counter("http.config_test.downstream_rq_2xx")->value()); +} + TEST_P(RedirectIntegrationTest, BasicInternalRedirectDownstreamBytesCount) { if (upstreamProtocol() != Http::CodecType::HTTP2) { return; diff --git a/test/mocks/stream_info/mocks.h b/test/mocks/stream_info/mocks.h index d88a54364a9f..c22c35debdba 100644 --- a/test/mocks/stream_info/mocks.h +++ b/test/mocks/stream_info/mocks.h @@ -139,6 +139,9 @@ class MockStreamInfo : public StreamInfo { MOCK_METHOD(bool, isShadow, (), (const, override)); MOCK_METHOD(void, setDownstreamTransportFailureReason, (absl::string_view failure_reason)); MOCK_METHOD(absl::string_view, downstreamTransportFailureReason, (), (const)); + MOCK_METHOD(bool, shouldDrainConnectionUponCompletion, (), (const)); + MOCK_METHOD(void, setShouldDrainConnectionUponCompletion, (bool)); + Envoy::Event::SimulatedTimeSystem ts_; SystemTime start_time_; MonotonicTime start_time_monotonic_; From d12d47b05a832ce04d9130a869fa72f7ddbcef28 Mon Sep 17 00:00:00 2001 From: Adam Anderson <6754028+AdamEAnderson@users.noreply.github.com> Date: Fri, 8 Sep 2023 22:04:55 -0700 Subject: [PATCH 41/55] Upstream zone-aware load balancing: support mismatched localities (#28970) Signed-off-by: Adam Anderson <6754028+AdamEAnderson@users.noreply.github.com> --- changelogs/current.yaml | 10 + .../cluster_manager/cluster_stats.rst | 2 +- .../upstream/load_balancing/zone_aware.rst | 1 - source/common/runtime/runtime_features.cc | 2 + source/common/upstream/BUILD | 1 + source/common/upstream/load_balancer_impl.cc | 223 +++++- source/common/upstream/load_balancer_impl.h | 41 + .../upstream/load_balancer_impl_test.cc | 726 +++++++++++++++++- test/common/upstream/subset_lb_test.cc | 171 +++++ 9 files changed, 1144 insertions(+), 33 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 6018d129e2ba..d803b2369f2d 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -25,6 +25,16 @@ behavior_changes: Switch from http_parser to BalsaParser for handling HTTP/1.1 traffic. See https://github.com/envoyproxy/envoy/issues/21245 for details. This behavioral change can be reverted by setting runtime flag ``envoy.reloadable_features.http1_use_balsa_parser`` to false. +- area: zone-aware routing + change: | + Zone-aware routing is now enabled even when the originating and upstream cluster have different numbers of zones. + Previously, zone-aware routing was disabled in that case and the ``lb_zone_number_differs`` stat on the cluster was incremented. + This behavioral change can be reverted by setting runtime guard + ``envoy.reloadable_features.enable_zone_routing_different_zone_counts`` to false. + Additionally, zone-aware routing now works correctly even when the originating and upstream cluster have different zone sets. + Previously, zone-aware routing would not route fairly in this case. + To revert the entire change, set the runtime flag ``envoy.reloadable_features.locality_routing_use_new_routing_logic`` + to false to get the old behavior and well-tested codepaths, undoing both changes. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst index 7deafe39415b..e712ebed5da4 100644 --- a/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst +++ b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst @@ -320,7 +320,7 @@ the following statistics: lb_zone_routing_sampled, Counter, Sending some requests to the same zone lb_zone_routing_cross_zone, Counter, Zone aware routing mode but have to send cross zone lb_local_cluster_not_ok, Counter, Local host set is not set or it is panic mode for local cluster - lb_zone_number_differs, Counter, Number of zones in local and upstream cluster different + lb_zone_number_differs, Counter, No zone aware routing because the feature flag is disabled and the number of zones in local and upstream cluster is different lb_zone_no_capacity_left, Counter, Total number of times ended with random zone selection due to rounding error original_dst_host_invalid, Counter, Total number of invalid hosts passed to original destination load balancer diff --git a/docs/root/intro/arch_overview/upstream/load_balancing/zone_aware.rst b/docs/root/intro/arch_overview/upstream/load_balancing/zone_aware.rst index 82ed3bf784ad..56fdba483cfa 100644 --- a/docs/root/intro/arch_overview/upstream/load_balancing/zone_aware.rst +++ b/docs/root/intro/arch_overview/upstream/load_balancing/zone_aware.rst @@ -21,7 +21,6 @@ performed: * Both originating and upstream cluster are not in :ref:`panic mode `. * Zone aware :ref:`routing is enabled `. -* The originating cluster has the same number of zones as the upstream cluster. * The upstream cluster has enough hosts. See :ref:`here ` for more information. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index eb4fc0176ead..1faeb59aad52 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -43,6 +43,7 @@ RUNTIME_GUARD(envoy_reloadable_features_enable_compression_bomb_protection); RUNTIME_GUARD(envoy_reloadable_features_enable_connect_udp_support); RUNTIME_GUARD(envoy_reloadable_features_enable_intermediate_ca); RUNTIME_GUARD(envoy_reloadable_features_enable_update_listener_socket_options); +RUNTIME_GUARD(envoy_reloadable_features_enable_zone_routing_different_zone_counts); RUNTIME_GUARD(envoy_reloadable_features_expand_agnostic_stream_lifetime); RUNTIME_GUARD(envoy_reloadable_features_ext_authz_http_send_original_xff); RUNTIME_GUARD(envoy_reloadable_features_format_ports_as_numbers); @@ -62,6 +63,7 @@ RUNTIME_GUARD(envoy_reloadable_features_ignore_optional_option_from_hcm_for_rout RUNTIME_GUARD(envoy_reloadable_features_immediate_response_use_filter_mutation_rule); RUNTIME_GUARD(envoy_reloadable_features_initialize_upstream_filters); RUNTIME_GUARD(envoy_reloadable_features_keep_endpoint_active_hc_status_on_locality_update); +RUNTIME_GUARD(envoy_reloadable_features_locality_routing_use_new_routing_logic); RUNTIME_GUARD(envoy_reloadable_features_lowercase_scheme); RUNTIME_GUARD(envoy_reloadable_features_no_extension_lookup_by_name); RUNTIME_GUARD(envoy_reloadable_features_no_full_scan_certs_on_sni_mismatch); diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index a8499ce61f4b..f74052b5f907 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -271,6 +271,7 @@ envoy_cc_library( "//source/common/protobuf:utility_lib", "//source/common/runtime:runtime_protos_lib", "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/load_balancing_policies/common/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/load_balancing_policies/least_request/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/load_balancing_policies/random/v3:pkg_cc_proto", diff --git a/source/common/upstream/load_balancer_impl.cc b/source/common/upstream/load_balancer_impl.cc index a45fa6918725..72a76e7df0d8 100644 --- a/source/common/upstream/load_balancer_impl.cc +++ b/source/common/upstream/load_balancer_impl.cc @@ -1,19 +1,23 @@ +#include "load_balancer_impl.h" #include "source/common/upstream/load_balancer_impl.h" #include #include #include +#include #include #include #include #include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/config/core/v3/base.pb.h" #include "envoy/runtime/runtime.h" #include "envoy/upstream/upstream.h" #include "source/common/common/assert.h" #include "source/common/common/logger.h" #include "source/common/protobuf/utility.h" +#include "source/common/runtime/runtime_features.h" #include "absl/container/fixed_array.h" @@ -427,6 +431,8 @@ ZoneAwareLoadBalancerBase::ZoneAwareLoadBalancerBase( fail_traffic_on_panic_(locality_config.has_value() ? locality_config->zone_aware_lb_config().fail_traffic_on_panic() : false), + use_new_locality_routing_(Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.locality_routing_use_new_routing_logic")), locality_weighted_balancing_(locality_config.has_value() && locality_config->has_locality_weighted_lb_config()) { ASSERT(!priority_set.hostSetsPerPriority().empty()); @@ -438,7 +444,11 @@ ZoneAwareLoadBalancerBase::ZoneAwareLoadBalancerBase( // If P=0 changes, regenerate locality routing structures. Locality based routing is // disabled at all other levels. if (local_priority_set_ && priority == 0) { - regenerateLocalityRoutingStructures(); + if (use_new_locality_routing_) { + regenerateLocalityRoutingStructuresNew(); + } else { + regenerateLocalityRoutingStructures(); + } } }); if (local_priority_set_) { @@ -452,11 +462,109 @@ ZoneAwareLoadBalancerBase::ZoneAwareLoadBalancerBase( ASSERT(priority == 0); // If the set of local Envoys changes, regenerate routing for P=0 as it does priority // based routing. - regenerateLocalityRoutingStructures(); + if (use_new_locality_routing_) { + regenerateLocalityRoutingStructuresNew(); + } else { + regenerateLocalityRoutingStructures(); + } }); } } +void ZoneAwareLoadBalancerBase::regenerateLocalityRoutingStructuresNew() { + ASSERT(local_priority_set_); + stats_.lb_recalculate_zone_structures_.inc(); + // resizePerPriorityState should ensure these stay in sync. + ASSERT(per_priority_state_.size() == priority_set_.hostSetsPerPriority().size()); + + // We only do locality routing for P=0 + uint32_t priority = 0; + PerPriorityState& state = *per_priority_state_[priority]; + // Do not perform any calculations if we cannot perform locality routing based on non runtime + // params. + if (earlyExitNonLocalityRoutingNew()) { + state.locality_routing_state_ = LocalityRoutingState::NoLocalityRouting; + return; + } + HostSet& host_set = *priority_set_.hostSetsPerPriority()[priority]; + const HostsPerLocality& upstreamHostsPerLocality = host_set.healthyHostsPerLocality(); + const size_t num_upstream_localities = upstreamHostsPerLocality.get().size(); + ASSERT(num_upstream_localities >= 2); + + // It is worth noting that all of the percentages calculated are orthogonal from + // how much load this priority level receives, percentageLoad(priority). + // + // If the host sets are such that 20% of load is handled locally and 80% is residual, and then + // half the hosts in all host sets go unhealthy, this priority set will + // still send half of the incoming load to the local locality and 80% to residual. + // + // Basically, fairness across localities within a priority is guaranteed. Fairness across + // localities across priorities is not. + const HostsPerLocality& localHostsPerLocality = localHostSet().healthyHostsPerLocality(); + auto locality_percentages = + calculateLocalityPercentagesNew(localHostsPerLocality, upstreamHostsPerLocality); + + // If we have lower percent of hosts in the local cluster in the same locality, + // we can push all of the requests directly to upstream cluster in the same locality. + if (upstreamHostsPerLocality.hasLocalLocality() && + locality_percentages[0].upstream_percentage > 0 && + locality_percentages[0].upstream_percentage >= locality_percentages[0].local_percentage) { + state.locality_routing_state_ = LocalityRoutingState::LocalityDirect; + return; + } + + state.locality_routing_state_ = LocalityRoutingState::LocalityResidual; + + // If we cannot route all requests to the same locality, calculate what percentage can be routed. + // For example, if local percentage is 20% and upstream is 10% + // we can route only 50% of requests directly. + // Local percent can be 0% if there are no upstream hosts in the local locality. + state.local_percent_to_route_ = + upstreamHostsPerLocality.hasLocalLocality() && locality_percentages[0].local_percentage > 0 + ? locality_percentages[0].upstream_percentage * 10000 / + locality_percentages[0].local_percentage + : 0; + + // Local locality does not have additional capacity (we have already routed what we could). + // Now we need to figure out how much traffic we can route cross locality and to which exact + // locality we should route. Percentage of requests routed cross locality to a specific locality + // needed be proportional to the residual capacity upstream locality has. + // + // residual_capacity contains capacity left in a given locality, we keep accumulating residual + // capacity to make search for sampled value easier. + // For example, if we have the following upstream and local percentage: + // local_percentage: 40000 40000 20000 + // upstream_percentage: 25000 50000 25000 + // Residual capacity would look like: 0 10000 5000. Now we need to sample proportionally to + // bucket sizes (residual capacity). For simplicity of finding where specific + // sampled value is, we accumulate values in residual capacity. This is what it will look like: + // residual_capacity: 0 10000 15000 + // Now to find a locality to route (bucket) we could simply iterate over residual_capacity + // searching where sampled value is placed. + state.residual_capacity_.resize(num_upstream_localities); + for (uint64_t i = 0; i < num_upstream_localities; ++i) { + uint64_t last_residual_capacity = i > 0 ? state.residual_capacity_[i - 1] : 0; + LocalityPercentages this_locality_percentages = locality_percentages[i]; + if (i == 0 && upstreamHostsPerLocality.hasLocalLocality()) { + // This is a local locality, we have already routed what we could. + state.residual_capacity_[i] = last_residual_capacity; + continue; + } + + // Only route to the localities that have additional capacity. + if (this_locality_percentages.upstream_percentage > + this_locality_percentages.local_percentage) { + state.residual_capacity_[i] = last_residual_capacity + + this_locality_percentages.upstream_percentage - + this_locality_percentages.local_percentage; + } else { + // Locality with index "i" does not have residual capacity, but we keep accumulating previous + // values to make search easier on the next step. + state.residual_capacity_[i] = last_residual_capacity; + } + } +} + void ZoneAwareLoadBalancerBase::regenerateLocalityRoutingStructures() { ASSERT(local_priority_set_); stats_.lb_recalculate_zone_structures_.inc(); @@ -546,6 +654,55 @@ void ZoneAwareLoadBalancerBase::resizePerPriorityState() { } } +bool ZoneAwareLoadBalancerBase::earlyExitNonLocalityRoutingNew() { + // We only do locality routing for P=0. + HostSet& host_set = *priority_set_.hostSetsPerPriority()[0]; + if (host_set.healthyHostsPerLocality().get().size() < 2) { + return true; + } + + // Do not perform locality routing if there are too few local localities for zone routing to have + // an effect. + if (localHostSet().hostsPerLocality().get().size() < 2) { + return true; + } + + // Do not perform locality routing if the local cluster doesn't have any hosts in the current + // envoy's local locality. This breaks our assumptions about the local cluster being correctly + // configured, so we don't have enough information to perform locality routing. Note: If other + // envoys do exist according to the local cluster, they will still be able to perform locality + // routing correctly. This will not cause a traffic imbalance because other envoys will not know + // about the current one, so they will not factor it into locality routing calculations. + if (!localHostSet().hostsPerLocality().hasLocalLocality() || + localHostSet().hostsPerLocality().get()[0].empty()) { + stats_.lb_local_cluster_not_ok_.inc(); + return true; + } + + // If the runtime guard is not enabled, keep the old behavior of not performing locality routing + // if the number of localities in the local cluster is different from the number of localities + // in the upstream cluster. + // The lb_zone_number_differs stat is only relevant if the runtime guard is disabled, + // so it is only incremented in that case. + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.enable_zone_routing_different_zone_counts") && + host_set.healthyHostsPerLocality().get().size() != + localHostSet().healthyHostsPerLocality().get().size()) { + stats_.lb_zone_number_differs_.inc(); + return true; + } + + // Do not perform locality routing for small clusters. + const uint64_t min_cluster_size = + runtime_.snapshot().getInteger(RuntimeMinClusterSize, min_cluster_size_); + if (host_set.healthyHosts().size() < min_cluster_size) { + stats_.lb_zone_cluster_too_small_.inc(); + return true; + } + + return false; +} + bool ZoneAwareLoadBalancerBase::earlyExitNonLocalityRouting() { // We only do locality routing for P=0. HostSet& host_set = *priority_set_.hostSetsPerPriority()[0]; @@ -615,6 +772,51 @@ bool LoadBalancerBase::isHostSetInPanic(const HostSet& host_set) const { return false; } +absl::FixedArray +ZoneAwareLoadBalancerBase::calculateLocalityPercentagesNew( + const HostsPerLocality& local_hosts_per_locality, + const HostsPerLocality& upstream_hosts_per_locality) { + uint64_t total_local_hosts = 0; + std::map local_counts; + for (const auto& locality_hosts : local_hosts_per_locality.get()) { + total_local_hosts += locality_hosts.size(); + // If there is no entry in the map for a given locality, it is assumed to have 0 hosts. + if (!locality_hosts.empty()) { + local_counts.insert(std::make_pair(locality_hosts[0]->locality(), locality_hosts.size())); + } + } + uint64_t total_upstream_hosts = 0; + for (const auto& locality_hosts : upstream_hosts_per_locality.get()) { + total_upstream_hosts += locality_hosts.size(); + } + + absl::FixedArray percentages(upstream_hosts_per_locality.get().size()); + for (uint32_t i = 0; i < upstream_hosts_per_locality.get().size(); ++i) { + const auto& upstream_hosts = upstream_hosts_per_locality.get()[i]; + if (upstream_hosts.empty()) { + // If there are no upstream hosts in a given locality, the upstream percentage is 0. + // We can't determine the locality of this group, so we can't find the corresponding local + // count. However, if there are no upstream hosts in a locality, the local percentage doesn't + // matter. + percentages[i] = LocalityPercentages{0, 0}; + continue; + } + const auto& locality = upstream_hosts[0]->locality(); + + const auto& local_count_it = local_counts.find(locality); + const uint64_t local_count = local_count_it == local_counts.end() ? 0 : local_count_it->second; + + const uint64_t local_percentage = + total_local_hosts > 0 ? 10000ULL * local_count / total_local_hosts : 0; + const uint64_t upstream_percentage = + total_upstream_hosts > 0 ? 10000ULL * upstream_hosts.size() / total_upstream_hosts : 0; + + percentages[i] = LocalityPercentages{local_percentage, upstream_percentage}; + } + + return percentages; +} + void ZoneAwareLoadBalancerBase::calculateLocalityPercentage( const HostsPerLocality& hosts_per_locality, uint64_t* ret) { uint64_t total_hosts = 0; @@ -634,18 +836,20 @@ uint32_t ZoneAwareLoadBalancerBase::tryChooseLocalLocalityHosts(const HostSet& h PerPriorityState& state = *per_priority_state_[host_set.priority()]; ASSERT(state.locality_routing_state_ != LocalityRoutingState::NoLocalityRouting); - // At this point it's guaranteed to be at least 2 localities & local exists. + // At this point it's guaranteed to be at least 2 localities in the upstream host set. const size_t number_of_localities = host_set.healthyHostsPerLocality().get().size(); ASSERT(number_of_localities >= 2U); - ASSERT(host_set.healthyHostsPerLocality().hasLocalLocality()); - // Try to push all of the requests to the same locality first. + // Try to push all of the requests to the same locality if possible. if (state.locality_routing_state_ == LocalityRoutingState::LocalityDirect) { + ASSERT(host_set.healthyHostsPerLocality().hasLocalLocality()); stats_.lb_zone_routing_all_directly_.inc(); return 0; } ASSERT(state.locality_routing_state_ == LocalityRoutingState::LocalityResidual); + ASSERT(host_set.healthyHostsPerLocality().hasLocalLocality() || + state.local_percent_to_route_ == 0); // If we cannot route all requests to the same locality, we already calculated how much we can // push to the local locality, check if we can push to local locality on current iteration. @@ -670,10 +874,13 @@ uint32_t ZoneAwareLoadBalancerBase::tryChooseLocalLocalityHosts(const HostSet& h // This potentially can be optimized to be O(log(N)) where N is the number of localities. // Linear scan should be faster for smaller N, in most of the scenarios N will be small. - // TODO(htuch): is there a bug here when threshold == 0? Seems like we pick - // local locality in that situation. Probably should start iterating at 1. + // + // Bucket 1: [0, state.residual_capacity_[0] - 1] + // Bucket 2: [state.residual_capacity_[0], state.residual_capacity_[1] - 1] + // ... + // Bucket N: [state.residual_capacity_[N-2], state.residual_capacity_[N-1] - 1] int i = 0; - while (threshold > state.residual_capacity_[i]) { + while (threshold >= state.residual_capacity_[i]) { i++; } diff --git a/source/common/upstream/load_balancer_impl.h b/source/common/upstream/load_balancer_impl.h index 586ddd10ab36..8338da065da4 100644 --- a/source/common/upstream/load_balancer_impl.h +++ b/source/common/upstream/load_balancer_impl.h @@ -362,6 +362,17 @@ class ZoneAwareLoadBalancerBase : public LoadBalancerBase { LocalityResidual }; + struct LocalityPercentages { + // The percentage of local hosts in a specific locality. + // Percentage is stored as integer number and scaled by 10000 multiplier for better precision. + // If upstream_percentage is 0, local_percentage may not be representative + // of the actual percentage and will be set to 0. + uint64_t local_percentage; + // The percentage of upstream hosts in a specific locality. + // Percentage is stored as integer number and scaled by 10000 multiplier for better precision. + uint64_t upstream_percentage; + }; + /** * Increase per_priority_state_ to at least priority_set.hostSetsPerPriority().size() */ @@ -371,6 +382,15 @@ class ZoneAwareLoadBalancerBase : public LoadBalancerBase { * @return decision on quick exit from locality aware routing based on cluster configuration. * This gets recalculated on update callback. */ + bool earlyExitNonLocalityRoutingNew(); + + /** + * @return decision on quick exit from locality aware routing based on cluster configuration. + * This gets recalculated on update callback. + * + * This is the legacy version of the function from previous versions of Envoy, kept temporarily + * as an alternate code-path to reduce the risk of changes. + */ bool earlyExitNonLocalityRouting(); /** @@ -379,16 +399,36 @@ class ZoneAwareLoadBalancerBase : public LoadBalancerBase { */ uint32_t tryChooseLocalLocalityHosts(const HostSet& host_set) const; + /** + * @return combined per-locality information about percentages of local/upstream hosts in each + * upstream locality. See LocalityPercentages for more details. The ordering of localities + * matches the ordering of upstream localities in the input upstream_hosts_per_locality. + */ + absl::FixedArray + calculateLocalityPercentagesNew(const HostsPerLocality& local_hosts_per_locality, + const HostsPerLocality& upstream_hosts_per_locality); + /** * @return (number of hosts in a given locality)/(total number of hosts) in `ret` param. * The result is stored as integer number and scaled by 10000 multiplier for better precision. * Caller is responsible for allocation/de-allocation of `ret`. + * + * This is the legacy version of the function from previous versions of Envoy, kept temporarily + * as an alternate code-path to reduce the risk of changes. */ void calculateLocalityPercentage(const HostsPerLocality& hosts_per_locality, uint64_t* ret); /** * Regenerate locality aware routing structures for fast decisions on upstream locality selection. */ + void regenerateLocalityRoutingStructuresNew(); + + /** + * Regenerate locality aware routing structures for fast decisions on upstream locality selection. + * + * This is the legacy version of the function from previous versions of Envoy, kept temporarily + * as an alternate code-path to reduce the risk of changes. + */ void regenerateLocalityRoutingStructures(); HostSet& localHostSet() const { return *local_priority_set_->hostSetsPerPriority()[0]; } @@ -443,6 +483,7 @@ class ZoneAwareLoadBalancerBase : public LoadBalancerBase { // Keep small members (bools and enums) at the end of class, to reduce alignment overhead. const uint32_t routing_enabled_; const bool fail_traffic_on_panic_ : 1; + const bool use_new_locality_routing_ : 1; // If locality weight aware routing is enabled. const bool locality_weighted_balancing_ : 1; diff --git a/test/common/upstream/load_balancer_impl_test.cc b/test/common/upstream/load_balancer_impl_test.cc index 8bf405ca46a9..518f2a4de1d0 100644 --- a/test/common/upstream/load_balancer_impl_test.cc +++ b/test/common/upstream/load_balancer_impl_test.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -73,12 +74,19 @@ class TestZoneAwareLoadBalancer : public ZoneAwareLoadBalancerBase { namespace { +struct LoadBalancerTestParam { + bool use_default_host_set; + bool use_new_locality_routing; +}; + class LoadBalancerTestBase : public Event::TestUsingSimulatedTime, - public testing::TestWithParam { + public testing::TestWithParam { protected: // Run all tests against both priority 0 and priority 1 host sets, to ensure // all the load balancers have equivalent functionality for failover host sets. - MockHostSet& hostSet() { return GetParam() ? host_set_ : failover_host_set_; } + MockHostSet& hostSet() { + return GetParam().use_default_host_set ? host_set_ : failover_host_set_; + } LoadBalancerTestBase() : stat_names_(stats_store_.symbolTable()), stats_(stat_names_, *stats_store_.rootScope()) { @@ -171,7 +179,9 @@ class LoadBalancerBaseTest : public LoadBalancerTestBase { TestLb lb_{priority_set_, stats_, runtime_, random_, common_config_}; }; -INSTANTIATE_TEST_SUITE_P(PrimaryOrFailover, LoadBalancerBaseTest, ::testing::Values(true)); +INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, LoadBalancerBaseTest, + ::testing::Values(LoadBalancerTestParam{true, false}, + LoadBalancerTestParam{true, true})); // Basic test of host set selection. TEST_P(LoadBalancerBaseTest, PrioritySelection) { @@ -702,6 +712,10 @@ class RoundRobinLoadBalancerTest : public LoadBalancerTestBase { common_config_.mutable_locality_weighted_lb_config(); } + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.locality_routing_use_new_routing_logic", + GetParam().use_new_locality_routing ? "true" : "false"}}); + lb_ = std::make_shared(priority_set_, local_priority_set_.get(), stats_, runtime_, random_, common_config_, round_robin_lb_config_, simTime()); @@ -921,7 +935,9 @@ TEST_P(FailoverTest, PrioritiesWithZeroWarmedHosts) { EXPECT_EQ(failover_host_set_.hosts_[0], lb_->chooseHost(nullptr)); } -INSTANTIATE_TEST_SUITE_P(PrimaryOrFailover, FailoverTest, ::testing::Values(true)); +INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, FailoverTest, + ::testing::Values(LoadBalancerTestParam{true, false}, + LoadBalancerTestParam{true, true})); TEST_P(RoundRobinLoadBalancerTest, NoHosts) { init(false); @@ -1241,7 +1257,7 @@ TEST_P(RoundRobinLoadBalancerTest, HostSelectionWithFilter) { HealthyAndDegradedLoad priority_load{Upstream::HealthyLoad({0, 0}), Upstream::DegradedLoad({0, 0})}; - if (GetParam()) { + if (&hostSet() == &host_set_) { priority_load.healthy_priority_load_ = HealthyLoad({100u, 0u}); } else { priority_load.healthy_priority_load_ = HealthyLoad({0u, 100u}); @@ -1314,10 +1330,19 @@ TEST_P(RoundRobinLoadBalancerTest, ZoneAwareSmallCluster) { EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); } -TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareDifferentZoneSize) { +TEST_P(RoundRobinLoadBalancerTest, ZoneAwareZonesMismatched) { if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. return; } + + // Setup is: + // L = local envoy + // U = upstream host + // + // Zone A: 1L, 1U + // Zone B: 1L, 0U + // Zone C: 0L, 1U + envoy::config::core::v3::Locality zone_a; zone_a.set_zone("A"); envoy::config::core::v3::Locality zone_b; @@ -1327,33 +1352,374 @@ TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareDifferentZoneSize) { HostVectorSharedPtr hosts( new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)})); - HostsPerLocalitySharedPtr upstream_hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_a)}, - {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_b)}, + // Upstream and local hosts when in zone A + HostsPerLocalitySharedPtr upstream_hosts_per_locality_a = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, {makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)}}); + HostsPerLocalitySharedPtr local_hosts_per_locality_a = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}}); + + hostSet().healthy_hosts_ = *hosts; + hostSet().hosts_ = *hosts; + hostSet().healthy_hosts_per_locality_ = upstream_hosts_per_locality_a; + common_config_.mutable_healthy_panic_threshold()->set_value(50); + common_config_.mutable_zone_aware_lb_config()->mutable_routing_enabled()->set_value(100); + common_config_.mutable_zone_aware_lb_config()->mutable_min_cluster_size()->set_value(2); + init(true); + updateHosts(hosts, local_hosts_per_locality_a); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 2)) + .WillRepeatedly(Return(2)); + + // Expect zone-aware routing direct mode when in zone A + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_zone_routing_all_directly_.value()); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(2U, stats_.lb_zone_routing_all_directly_.value()); + + // Upstream and local hosts when in zone B (no local upstream in B) + HostsPerLocalitySharedPtr upstream_hosts_per_locality_b = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)}}, + true); + HostsPerLocalitySharedPtr local_hosts_per_locality_b = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}}); + + hostSet().healthy_hosts_per_locality_ = upstream_hosts_per_locality_b; + updateHosts(hosts, local_hosts_per_locality_b); + + if (GetParam().use_new_locality_routing) { + // Expect zone-aware routing still enabled even though there is no upstream host in zone B + // Since zone A has no residual, we should always pick an upstream from zone C. + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(4999)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(2U, stats_.lb_zone_routing_cross_zone_.value()); + } else { + // When using the legacy locality routing algorithm, it falls back to non-zone aware routing in + // zone B. + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + } +} + +TEST_P(RoundRobinLoadBalancerTest, ZoneAwareResidualsMismatched) { + if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. + return; + } + + // Setup is: + // L = local envoy + // U = upstream host + // + // | expected | legacy | + // | residuals | residuals | + // ---------------------------------------- + // Zone A: 2L, 1U | 0% | 0% | + // Zone B: 2L, 0U | N/A | N/A | + // Zone C: 1L, 3U | 20.83% | 4.16% | + // Zone D: 1L, 3U | 20.83% | 20.83% | + // Zone E: 0L, 1U | 12.50% | 0% | + // ---------------------------------------- + // Totals: 6L, 8U | 54.18% | 25% | + // + // Idea is for the local cluster to be A, and for there to be residual from A. + // The number of local and upstream zones must be the same for zone routing to be enabled. + // Then we ensure that there are two different zones with different residuals. + // Finally we add a zone with local hosts but no upstream hosts just after the local zone to + // create a mismatch between the local percentages and upstream percentages vectors when + // performing residual calculations. + + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + envoy::config::core::v3::Locality zone_d; + zone_d.set_zone("D"); + envoy::config::core::v3::Locality zone_e; + zone_e.set_zone("E"); + + HostVectorSharedPtr hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:83", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:84", simTime(), zone_d), + makeTestHost(info_, "tcp://127.0.0.1:85", simTime(), zone_d), + makeTestHost(info_, "tcp://127.0.0.1:86", simTime(), zone_d), + makeTestHost(info_, "tcp://127.0.0.1:87", simTime(), zone_e)})); + HostVectorSharedPtr local_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:2", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:3", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:4", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:5", simTime(), zone_d)})); + + // Local zone is zone A + HostsPerLocalitySharedPtr upstream_hosts_per_locality = + makeHostsPerLocality({{// Zone A + makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {// Zone C + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:83", simTime(), zone_c)}, + {// Zone D + makeTestHost(info_, "tcp://127.0.0.1:84", simTime(), zone_d), + makeTestHost(info_, "tcp://127.0.0.1:85", simTime(), zone_d), + makeTestHost(info_, "tcp://127.0.0.1:86", simTime(), zone_d)}, + {// Zone E + makeTestHost(info_, "tcp://127.0.0.1:87", simTime(), zone_e)}}); + HostsPerLocalitySharedPtr local_hosts_per_locality = - makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}, - {makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)}}); + makeHostsPerLocality({{// Zone A + makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_a)}, + {// Zone B + makeTestHost(info_, "tcp://127.0.0.1:2", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:3", simTime(), zone_b)}, + {// Zone C + makeTestHost(info_, "tcp://127.0.0.1:4", simTime(), zone_c)}, + {// Zone D + makeTestHost(info_, "tcp://127.0.0.1:5", simTime(), zone_d)}}); hostSet().healthy_hosts_ = *hosts; hostSet().hosts_ = *hosts; hostSet().healthy_hosts_per_locality_ = upstream_hosts_per_locality; + common_config_.mutable_healthy_panic_threshold()->set_value(50); + common_config_.mutable_zone_aware_lb_config()->mutable_routing_enabled()->set_value(100); + common_config_.mutable_zone_aware_lb_config()->mutable_min_cluster_size()->set_value(6); + init(true); + updateHosts(local_hosts, local_hosts_per_locality); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(6)); + + // Residual mode traffic will go directly to the upstream in A 37.5% (3/8) of the time + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_zone_routing_sampled_.value()); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(3749)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(2U, stats_.lb_zone_routing_sampled_.value()); + + if (GetParam().use_new_locality_routing) { + // The other 5/8 of the time, traffic will go to cross-zone upstreams with residual capacity + // Zone B has no upstream hosts + // Zone C has a residual capacity of 20.83% (20.84% with rounding error): sampled value 0-2083 + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(3750)).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(3750)) + .WillOnce(Return(2083)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(nullptr)); + EXPECT_EQ(2U, stats_.lb_zone_routing_cross_zone_.value()); + // Zone D has a residual capacity of 20.83% (20.84% with rounding error): sampled value + // 2084-4167 + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(9999)) + .WillOnce(Return(2084)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(3U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(9999)) + .WillOnce(Return(4167)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][1], lb_->chooseHost(nullptr)); + EXPECT_EQ(4U, stats_.lb_zone_routing_cross_zone_.value()); + // Zone E has a residual capacity of 12.5%: sampled value 4168-5417 + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(9999)) + .WillOnce(Return(4168)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(5U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(9999)) + .WillOnce(Return(5417)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(6U, stats_.lb_zone_routing_cross_zone_.value()); + // At sampled value 5418, we loop back to the beginning of the vector and select zone C again + } else { + // When using the legacy locality routing algorithm, the routing is unfair. + // Zone C has a residual capacity of 4.16% (4.17% with rounding error): sampled value 0-416 + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(3750)).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(3750)).WillOnce(Return(416)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(nullptr)); + EXPECT_EQ(2U, stats_.lb_zone_routing_cross_zone_.value()); + // Zone D has a residual capacity of 20.83%: sampled value 417-2500 + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(417)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(3U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(9999)) + .WillOnce(Return(2500)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][1], lb_->chooseHost(nullptr)); + EXPECT_EQ(4U, stats_.lb_zone_routing_cross_zone_.value()); + // After this, we loop back to the beginning of the vector and select zone C again + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(3750)) + .WillOnce(Return(2501)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[1][2], lb_->chooseHost(nullptr)); + EXPECT_EQ(5U, stats_.lb_zone_routing_cross_zone_.value()); + } +} + +TEST_P(RoundRobinLoadBalancerTest, ZoneAwareDifferentZoneSize) { + if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. + return; + } + + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + + HostVectorSharedPtr upstream_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)})); + HostVectorSharedPtr local_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)})); + HostsPerLocalitySharedPtr upstream_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)}}); + HostsPerLocalitySharedPtr local_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}}); + + hostSet().healthy_hosts_ = *upstream_hosts; + hostSet().hosts_ = *upstream_hosts; + hostSet().healthy_hosts_per_locality_ = upstream_hosts_per_locality; common_config_.mutable_healthy_panic_threshold()->set_value(100); common_config_.mutable_zone_aware_lb_config()->mutable_routing_enabled()->set_value(98); - common_config_.mutable_zone_aware_lb_config()->mutable_min_cluster_size()->set_value(7); + common_config_.mutable_zone_aware_lb_config()->mutable_min_cluster_size()->set_value(3); init(true); - updateHosts(hosts, local_hosts_per_locality); + updateHosts(local_hosts, local_hosts_per_locality); EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 100)) .WillRepeatedly(Return(50)); EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 98)) .WillRepeatedly(Return(true)); - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 7)) - .WillRepeatedly(Return(7)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 3)) + .WillRepeatedly(Return(3)); + + if (GetParam().use_new_locality_routing) { + // 2/3 of the time we should get the host in zone B + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_zone_routing_sampled_.value()); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(6665)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(2U, stats_.lb_zone_routing_sampled_.value()); + + // 1/3 of the time we should sample the residuals across zones + // The only upstream zone with residual capacity is zone C + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(6666)).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(9999)) + .WillOnce(Return(3333)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(2U, stats_.lb_zone_routing_cross_zone_.value()); + } else { + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(0U, stats_.lb_zone_routing_all_directly_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_sampled_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_EQ(1U, stats_.lb_zone_number_differs_.value()); + } +} + +TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareDifferentZoneSizeDisabled) { + if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. + return; + } + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.enable_zone_routing_different_zone_counts", "false"}}); + + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + + HostVectorSharedPtr upstream_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)})); + HostVectorSharedPtr local_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)})); + HostsPerLocalitySharedPtr upstream_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_c)}}); + HostsPerLocalitySharedPtr local_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}}); + + hostSet().healthy_hosts_ = *upstream_hosts; + hostSet().hosts_ = *upstream_hosts; + hostSet().healthy_hosts_per_locality_ = upstream_hosts_per_locality; + common_config_.mutable_healthy_panic_threshold()->set_value(100); + common_config_.mutable_zone_aware_lb_config()->mutable_routing_enabled()->set_value(98); + common_config_.mutable_zone_aware_lb_config()->mutable_min_cluster_size()->set_value(3); + init(true); + updateHosts(local_hosts, local_hosts_per_locality); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 100)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 98)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 3)) + .WillRepeatedly(Return(3)); + + // Zone-aware routing should be disabled because the local zone and upstream zone count + // are different and the runtime feature flag is disabled. + EXPECT_CALL(random_, random()).WillOnce(Return(0)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(0U, stats_.lb_zone_routing_all_directly_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_sampled_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_cross_zone_.value()); EXPECT_EQ(1U, stats_.lb_zone_number_differs_.value()); } @@ -1458,6 +1824,306 @@ TEST_P(RoundRobinLoadBalancerTest, ZoneAwareRoutingSmallZone) { EXPECT_EQ(1U, stats_.lb_zone_routing_cross_zone_.value()); } +TEST_P(RoundRobinLoadBalancerTest, ZoneAwareNoMatchingZones) { + if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. + return; + } + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + envoy::config::core::v3::Locality zone_d; + zone_d.set_zone("D"); + envoy::config::core::v3::Locality zone_e; + zone_e.set_zone("E"); + envoy::config::core::v3::Locality zone_f; + zone_f.set_zone("F"); + HostVectorSharedPtr upstream_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_d), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_e), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_f)})); + HostVectorSharedPtr local_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b), + makeTestHost(info_, "tcp://127.0.0.1:2", simTime(), zone_c)})); + + HostsPerLocalitySharedPtr upstream_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_d)}, + {makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_e)}, + {makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_f)}}, + true); + + HostsPerLocalitySharedPtr local_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)}, + {makeTestHost(info_, "tcp://127.0.0.1:2", simTime(), zone_c)}}); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(3)); + + hostSet().healthy_hosts_ = *upstream_hosts; + hostSet().hosts_ = *upstream_hosts; + hostSet().healthy_hosts_per_locality_ = upstream_hosts_per_locality; + init(true); + updateHosts(local_hosts, local_hosts_per_locality); + + if (GetParam().use_new_locality_routing) { + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(3332)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[0][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(3333)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(2U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(6665)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[1][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(3U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(6666)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(4U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(0)) + .WillOnce(Return(9998)); // Rounding error: 3333 * 3 = 9999 != 10000 + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(5U, stats_.lb_zone_routing_cross_zone_.value()); + } else { + // When using the legacy locality routing algorithm, locality routing is disabled as the + // upstream has no hosts in the local zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(0U, stats_.lb_zone_routing_all_directly_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_sampled_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_cross_zone_.value()); + } +} + +TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareNotEnoughLocalZones) { + if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. + return; + } + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + + HostVectorSharedPtr upstream_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)})); + HostVectorSharedPtr local_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)})); + + HostsPerLocalitySharedPtr upstream_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}}); + + HostsPerLocalitySharedPtr local_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}}); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + hostSet().healthy_hosts_ = *upstream_hosts; + hostSet().hosts_ = *upstream_hosts; + hostSet().healthy_hosts_per_locality_ = upstream_hosts_per_locality; + init(true); + updateHosts(local_hosts, local_hosts_per_locality); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(0U, stats_.lb_zone_routing_all_directly_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_sampled_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_cross_zone_.value()); +} + +TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareNotEnoughUpstreamZones) { + if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. + return; + } + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_b; + zone_b.set_zone("B"); + + HostVectorSharedPtr upstream_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)})); + HostVectorSharedPtr local_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)})); + + HostsPerLocalitySharedPtr upstream_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}}); + + HostsPerLocalitySharedPtr local_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)}}); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(1)); + + hostSet().healthy_hosts_ = *upstream_hosts; + hostSet().hosts_ = *upstream_hosts; + hostSet().healthy_hosts_per_locality_ = upstream_hosts_per_locality; + init(true); + updateHosts(local_hosts, local_hosts_per_locality); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(0U, stats_.lb_zone_routing_all_directly_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_sampled_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_cross_zone_.value()); +} + +TEST_P(RoundRobinLoadBalancerTest, ZoneAwareEmptyLocalities) { + if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. + return; + } + + // L = local host + // U = upstream host + // + // Zone A: 1L, 0U [Residual: 0.00%] + // Zone B: 0L, 0U [Residual: N/A] + // Zone C: 1L, 2U [Residual: 8.33%] + // Zone D: 1L, 0U [Residual: N/A] + // Zone E: 0L, 1U [Residual: 16.67%] + // Zone F: 1L, 2U [Residual: 8.33%] + // Zone G: --, 0U [Residual: N/A] + // Zone H: --, 1U [Residual: 16.67%] + // Zone I: --, 0U [Residual: N/A] + // Total: 4L, 6U [Residual: 50.00%] + + envoy::config::core::v3::Locality zone_a; + zone_a.set_zone("A"); + envoy::config::core::v3::Locality zone_c; + zone_c.set_zone("C"); + envoy::config::core::v3::Locality zone_d; + zone_d.set_zone("D"); + envoy::config::core::v3::Locality zone_e; + zone_e.set_zone("E"); + envoy::config::core::v3::Locality zone_f; + zone_f.set_zone("F"); + envoy::config::core::v3::Locality zone_h; + zone_h.set_zone("H"); + + HostVectorSharedPtr upstream_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_e), + makeTestHost(info_, "tcp://127.0.0.1:83", simTime(), zone_f), + makeTestHost(info_, "tcp://127.0.0.1:84", simTime(), zone_f), + makeTestHost(info_, "tcp://127.0.0.1:85", simTime(), zone_h)})); + HostVectorSharedPtr local_hosts( + new HostVector({makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a), + makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:2", simTime(), zone_d), + makeTestHost(info_, "tcp://127.0.0.1:2", simTime(), zone_f)})); + + HostsPerLocalitySharedPtr upstream_hosts_per_locality = + makeHostsPerLocality({{}, + {}, + {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_c), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_c)}, + {makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), zone_e)}, + {makeTestHost(info_, "tcp://127.0.0.1:83", simTime(), zone_f), + makeTestHost(info_, "tcp://127.0.0.1:84", simTime(), zone_f)}, + {}, + {makeTestHost(info_, "tcp://127.0.0.1:85", simTime(), zone_h)}, + {}}); + + HostsPerLocalitySharedPtr local_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_c)}, + {makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_d)}, + {}, + {makeTestHost(info_, "tcp://127.0.0.1:2", simTime(), zone_f)}}); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(3)); + + hostSet().healthy_hosts_ = *upstream_hosts; + hostSet().hosts_ = *upstream_hosts; + hostSet().healthy_hosts_per_locality_ = upstream_hosts_per_locality; + init(true); + updateHosts(local_hosts, local_hosts_per_locality); + + if (GetParam().use_new_locality_routing) { + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(1U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(832)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][1], lb_->chooseHost(nullptr)); + EXPECT_EQ(2U, stats_.lb_zone_routing_cross_zone_.value()); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(833)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(3U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(0)) + .WillOnce(Return(2498)); // rounding error + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(4U, stats_.lb_zone_routing_cross_zone_.value()); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(2499)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[4][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(5U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(3331)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[4][1], lb_->chooseHost(nullptr)); + EXPECT_EQ(6U, stats_.lb_zone_routing_cross_zone_.value()); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(3332)); + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[6][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(7U, stats_.lb_zone_routing_cross_zone_.value()); + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(0)) + .WillOnce(Return(4997)); // rounding error + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[6][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(8U, stats_.lb_zone_routing_cross_zone_.value()); + + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(0)) + .WillOnce(Return(4998)); // wrap around + EXPECT_EQ(hostSet().healthy_hosts_per_locality_->get()[2][0], lb_->chooseHost(nullptr)); + EXPECT_EQ(9U, stats_.lb_zone_routing_cross_zone_.value()); + } else { + // When using the legacy locality routing algorithm, locality routing is disabled as the + // upstream has no hosts in the local zone. + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(0U, stats_.lb_zone_routing_all_directly_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_sampled_.value()); + EXPECT_EQ(0U, stats_.lb_zone_routing_cross_zone_.value()); + } +} + TEST_P(RoundRobinLoadBalancerTest, LowPrecisionForDistribution) { if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. return; @@ -1585,7 +2251,9 @@ TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingLocalEmpty) { HostsPerLocalitySharedPtr upstream_hosts_per_locality = makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, {makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}}); - HostsPerLocalitySharedPtr local_hosts_per_locality = makeHostsPerLocality({{{}}, {{}}}); + HostsPerLocalitySharedPtr local_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)}}); EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) .WillOnce(Return(50)) @@ -1627,7 +2295,9 @@ TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingLocalEmptyFailTrafficOnPani HostsPerLocalitySharedPtr upstream_hosts_per_locality = makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), zone_a)}, {makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), zone_b)}}); - HostsPerLocalitySharedPtr local_hosts_per_locality = makeHostsPerLocality({{{}}, {{}}}); + HostsPerLocalitySharedPtr local_hosts_per_locality = + makeHostsPerLocality({{makeTestHost(info_, "tcp://127.0.0.1:0", simTime(), zone_a)}, + {makeTestHost(info_, "tcp://127.0.0.1:1", simTime(), zone_b)}}); EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) .WillOnce(Return(50)) @@ -1683,8 +2353,11 @@ TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingNoLocalLocality) { EXPECT_EQ(1U, stats_.lb_local_cluster_not_ok_.value()); } -INSTANTIATE_TEST_SUITE_P(PrimaryOrFailover, RoundRobinLoadBalancerTest, - ::testing::Values(true, false)); +INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, RoundRobinLoadBalancerTest, + ::testing::Values(LoadBalancerTestParam{true, false}, + LoadBalancerTestParam{true, true}, + LoadBalancerTestParam{false, false}, + LoadBalancerTestParam{false, true})); TEST_P(RoundRobinLoadBalancerTest, SlowStartWithDefaultParams) { init(false); @@ -2543,8 +3216,11 @@ TEST_P(LeastRequestLoadBalancerTest, SlowStartWithActiveHC) { EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); } -INSTANTIATE_TEST_SUITE_P(PrimaryOrFailover, LeastRequestLoadBalancerTest, - ::testing::Values(true, false)); +INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, LeastRequestLoadBalancerTest, + ::testing::Values(LoadBalancerTestParam{true, false}, + LoadBalancerTestParam{true, true}, + LoadBalancerTestParam{false, false}, + LoadBalancerTestParam{false, true})); class RandomLoadBalancerTest : public LoadBalancerTestBase { public: @@ -2591,7 +3267,11 @@ TEST_P(RandomLoadBalancerTest, FailClusterOnPanic) { EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); } -INSTANTIATE_TEST_SUITE_P(PrimaryOrFailover, RandomLoadBalancerTest, ::testing::Values(true, false)); +INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, RandomLoadBalancerTest, + ::testing::Values(LoadBalancerTestParam{true, false}, + LoadBalancerTestParam{true, true}, + LoadBalancerTestParam{false, false}, + LoadBalancerTestParam{false, true})); TEST(LoadBalancerSubsetInfoImplTest, DefaultConfigIsDiabled) { auto subset_info = LoadBalancerSubsetInfoImpl( diff --git a/test/common/upstream/subset_lb_test.cc b/test/common/upstream/subset_lb_test.cc index 12dd0e8df37f..6d48420a7eb4 100644 --- a/test/common/upstream/subset_lb_test.cc +++ b/test/common/upstream/subset_lb_test.cc @@ -1977,6 +1977,177 @@ TEST_P(SubsetLoadBalancerTest, ZoneAwareBalancesSubsetsAfterUpdate) { EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); } +TEST_F(SubsetLoadBalancerTest, ZoneAwareComplicatedBalancesSubsets) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + // L=local cluster host + // U=upstream host + // + // residuals + // A: 2L 0U 0.00% + // B: 2L 2U 6.67% + // C: 2L 2U 6.67% + // D: 0L 1U 20.00% + // total: 6L 5U 33.33% + + zoneAwareInit({{ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:86", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:87", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:88", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:89", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:90", {{"version", "1.1"}}}, + }}, + {{ + {"tcp://127.0.0.1:91", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:92", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:93", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:94", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:95", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:96", {{"version", "1.1"}}}, + }}); + + TestLoadBalancerContext context({{"version", "1.1"}}); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(666)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(667)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[2][1], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(1334)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(&context)); +} + +TEST_P(SubsetLoadBalancerTest, ZoneAwareComplicatedBalancesSubsetsAfterUpdate) { + EXPECT_CALL(subset_info_, fallbackPolicy()) + .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); + + std::vector subset_selectors = {makeSelector( + {"version"}, + envoy::config::cluster::v3::Cluster::LbSubsetConfig::LbSubsetSelector::NOT_DEFINED)}; + EXPECT_CALL(subset_info_, subsetSelectors()).WillRepeatedly(ReturnRef(subset_selectors)); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillRepeatedly(Return(2)); + + // Before update: + // + // L=local cluster host + // U=upstream host + // + // residuals + // A: 2L 0U 0.00% + // B: 2L 2U 6.67% + // C: 2L 2U 6.67% + // D: 0L 1U 20.00% + // total: 6L 5U 33.33% + + zoneAwareInit({{ + {"tcp://127.0.0.1:80", {{"version", "1.0"}}}, + }, + { + {"tcp://127.0.0.1:82", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:83", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:84", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:85", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:86", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:87", {{"version", "1.1"}}}, + {"tcp://127.0.0.1:88", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:89", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:90", {{"version", "1.1"}}}, + }}, + {{ + {"tcp://127.0.0.1:91", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:92", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:93", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:94", {{"version", "1.1"}}}, + }, + { + {"tcp://127.0.0.1:95", {{"version", "1.0"}}}, + {"tcp://127.0.0.1:96", {{"version", "1.1"}}}, + }}); + + TestLoadBalancerContext context({{"version", "1.1"}}); + + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(666)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(667)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[2][1], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(0)).WillOnce(Return(1334)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(&context)); + + envoy::config::core::v3::Locality local_locality; + local_locality.set_zone("0"); + envoy::config::core::v3::Locality locality_2; + locality_2.set_zone("2"); + + modifyHosts({makeHost("tcp://127.0.0.1:8001", {{"version", "1.1"}}, local_locality)}, {}, + absl::optional(0)); + + modifyLocalHosts({makeHost("tcp://127.0.0.1:9001", {{"version", "1.1"}}, locality_2)}, {}, 2); + + // After update: + // + // L=local cluster host + // U=upstream host + // + // residuals + // A: 2L 1U 0.00% + // B: 2L 2U 4.76% + // C: 3L 2U 0.00% + // D: 0L 1U 16.67% + // total: 7L 6U 21.42% + // + // Chance of sampling local host in zone 0: 58.34% + + EXPECT_CALL(random_, random()) + .WillOnce(Return(0)) + .WillOnce(Return(5830)); // 58.31% local routing chance due to rounding error + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[0][1], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(5831)).WillOnce(Return(475)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][3], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(476)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[3][0], lb_->chooseHost(&context)); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(9999)).WillOnce(Return(2143)); + EXPECT_EQ(host_set_.healthy_hosts_per_locality_->get()[1][1], lb_->chooseHost(&context)); +} + TEST_F(SubsetLoadBalancerTest, DescribeMetadata) { EXPECT_CALL(subset_info_, fallbackPolicy()) .WillRepeatedly(Return(envoy::config::cluster::v3::Cluster::LbSubsetConfig::NO_FALLBACK)); From 766e33a8db01e70e576ef54fd1f0f78d230a40b1 Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Sun, 10 Sep 2023 21:22:04 +0300 Subject: [PATCH 42/55] udp_session_filters: add callback to inject read/write datagrams to filter chain (#29491) Signed-off-by: ohadvano --- changelogs/current.yaml | 5 + .../udp/udp_proxy/session_filters/filter.h | 8 + .../filters/udp/udp_proxy/udp_proxy_filter.cc | 42 +++++ .../filters/udp/udp_proxy/udp_proxy_filter.h | 9 ++ test/extensions/filters/udp/udp_proxy/BUILD | 2 + test/extensions/filters/udp/udp_proxy/mocks.h | 2 + .../udp/udp_proxy/session_filters/BUILD | 18 +++ .../udp_proxy/session_filters/buffer_filter.h | 138 +++++++++++++++++ .../session_filters/buffer_filter.proto | 9 ++ .../udp_proxy/udp_proxy_integration_test.cc | 144 ++++++++++++++++++ 10 files changed, 377 insertions(+) create mode 100644 test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h create mode 100644 test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.proto diff --git a/changelogs/current.yaml b/changelogs/current.yaml index d803b2369f2d..4812b3a5c9f8 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -217,6 +217,11 @@ new_features: change: | added :ref:`session_filters ` config to support optional filters that will run for each upstream UDP session. More information can be found in the UDP proxy documentation. +- area: udp_proxy + change: | + added ``injectDatagramToFilterChain()`` callback to UDP session filters that allows session filters to inject datagrams downstream + or upstream the filter chain during a filter chain iteration. This can be used, for example, by session filters that are required + to buffer datagrams due to an asynchronous call. - area: otlp_stats_sink change: | added :ref:` stats prefix option` diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/filter.h b/source/extensions/filters/udp/udp_proxy/session_filters/filter.h index a3f434a8c245..44ed8ab08790 100644 --- a/source/extensions/filters/udp/udp_proxy/session_filters/filter.h +++ b/source/extensions/filters/udp/udp_proxy/session_filters/filter.h @@ -26,6 +26,14 @@ class FilterCallbacks { * @return StreamInfo for logging purposes. */ virtual StreamInfo::StreamInfo& streamInfo() PURE; + + /** + * Allows a filter to inject a datagram to successive filters in the session filter chain. + * The injected datagram will be iterated as a regular received datagram, and may also be + * stopped by further filters. This can be used, for example, to continue processing previously + * buffered datagrams by a filter after an asynchronous operation ended. + */ + virtual void injectDatagramToFilterChain(Network::UdpRecvData& data) PURE; }; class ReadFilterCallbacks : public FilterCallbacks { diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc index b5c1697f1944..608764d0f262 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc @@ -475,6 +475,44 @@ void UdpProxyFilter::ActiveSession::onContinueFilterChain(ActiveReadFilter* filt } } +void UdpProxyFilter::ActiveSession::onInjectReadDatagramToFilterChain(ActiveReadFilter* filter, + Network::UdpRecvData& data) { + ASSERT(filter != nullptr); + + std::list::iterator entry = std::next(filter->entry()); + for (; entry != read_filters_.end(); entry++) { + if (!(*entry)->read_filter_) { + continue; + } + + auto status = (*entry)->read_filter_->onData(data); + if (status == ReadFilterStatus::StopIteration) { + return; + } + } + + writeUpstream(data); +} + +void UdpProxyFilter::ActiveSession::onInjectWriteDatagramToFilterChain(ActiveWriteFilter* filter, + Network::UdpRecvData& data) { + ASSERT(filter != nullptr); + + std::list::iterator entry = std::next(filter->entry()); + for (; entry != write_filters_.end(); entry++) { + if (!(*entry)->write_filter_) { + continue; + } + + auto status = (*entry)->write_filter_->onWrite(data); + if (status == WriteFilterStatus::StopIteration) { + return; + } + } + + writeDownstream(data); +} + void UdpProxyFilter::ActiveSession::processPacket( Network::Address::InstanceConstSharedPtr local_address, Network::Address::InstanceConstSharedPtr peer_address, Buffer::InstancePtr buffer, @@ -496,6 +534,10 @@ void UdpProxyFilter::ActiveSession::processPacket( } } + writeDownstream(recv_data); +} + +void UdpProxyFilter::ActiveSession::writeDownstream(Network::UdpRecvData& recv_data) { const uint64_t tx_buffer_length = recv_data.buffer_->length(); ENVOY_LOG(trace, "writing {} byte datagram downstream: downstream={} local={} upstream={}", tx_buffer_length, addresses_.peer_->asStringView(), addresses_.local_->asStringView(), diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h index 5b3c38bdc24d..494dde6e6dae 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h @@ -139,6 +139,9 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, uint64_t sessionId() const override { return parent_.sessionId(); }; StreamInfo::StreamInfo& streamInfo() override { return parent_.streamInfo(); }; void continueFilterChain() override { parent_.onContinueFilterChain(this); } + void injectDatagramToFilterChain(Network::UdpRecvData& data) override { + parent_.onInjectReadDatagramToFilterChain(this, data); + } ActiveSession& parent_; ReadFilterSharedPtr read_filter_; @@ -154,6 +157,9 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, // SessionFilters::WriteFilterCallbacks uint64_t sessionId() const override { return parent_.sessionId(); }; StreamInfo::StreamInfo& streamInfo() override { return parent_.streamInfo(); }; + void injectDatagramToFilterChain(Network::UdpRecvData& data) override { + parent_.onInjectWriteDatagramToFilterChain(this, data); + } ActiveSession& parent_; WriteFilterSharedPtr write_filter_; @@ -179,6 +185,7 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, void onNewSession(); void onData(Network::UdpRecvData& data); void writeUpstream(Network::UdpRecvData& data); + void writeDownstream(Network::UdpRecvData& data); void createFilterChain() { cluster_.filter_.config_->sessionFilterFactory().createFilterChain(*this); @@ -187,6 +194,8 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, uint64_t sessionId() const { return session_id_; }; StreamInfo::StreamInfo& streamInfo() { return udp_session_info_; }; void onContinueFilterChain(ActiveReadFilter* filter); + void onInjectReadDatagramToFilterChain(ActiveReadFilter* filter, Network::UdpRecvData& data); + void onInjectWriteDatagramToFilterChain(ActiveWriteFilter* filter, Network::UdpRecvData& data); // SessionFilters::FilterChainFactoryCallbacks void addReadFilter(ReadFilterSharedPtr filter) override { diff --git a/test/extensions/filters/udp/udp_proxy/BUILD b/test/extensions/filters/udp/udp_proxy/BUILD index 9ee76b603005..d0bdab133c6d 100644 --- a/test/extensions/filters/udp/udp_proxy/BUILD +++ b/test/extensions/filters/udp/udp_proxy/BUILD @@ -89,6 +89,8 @@ envoy_extension_cc_test( "//source/extensions/filters/udp/udp_proxy:config", "//source/extensions/filters/udp/udp_proxy/session_filters:filter_config_interface", "//source/extensions/filters/udp/udp_proxy/session_filters:filter_interface", + "//test/extensions/filters/udp/udp_proxy/session_filters:buffer_filter_config_lib", + "//test/extensions/filters/udp/udp_proxy/session_filters:buffer_filter_proto_cc_proto", "//test/extensions/filters/udp/udp_proxy/session_filters:drainer_filter_config_lib", "//test/extensions/filters/udp/udp_proxy/session_filters:drainer_filter_proto_cc_proto", "//test/integration:integration_lib", diff --git a/test/extensions/filters/udp/udp_proxy/mocks.h b/test/extensions/filters/udp/udp_proxy/mocks.h index 9f24496564a5..089806d5cdff 100644 --- a/test/extensions/filters/udp/udp_proxy/mocks.h +++ b/test/extensions/filters/udp/udp_proxy/mocks.h @@ -22,6 +22,7 @@ class MockReadFilterCallbacks : public ReadFilterCallbacks { MOCK_METHOD(uint64_t, sessionId, (), (const)); MOCK_METHOD(StreamInfo::StreamInfo&, streamInfo, ()); MOCK_METHOD(void, continueFilterChain, ()); + MOCK_METHOD(void, injectDatagramToFilterChain, (Network::UdpRecvData & data)); uint64_t session_id_{1}; NiceMock stream_info_; @@ -34,6 +35,7 @@ class MockWriteFilterCallbacks : public WriteFilterCallbacks { MOCK_METHOD(uint64_t, sessionId, (), (const)); MOCK_METHOD(StreamInfo::StreamInfo&, streamInfo, ()); + MOCK_METHOD(void, injectDatagramToFilterChain, (Network::UdpRecvData & data)); uint64_t session_id_{1}; NiceMock stream_info_; diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/BUILD b/test/extensions/filters/udp/udp_proxy/session_filters/BUILD index c6feadb8a612..066bd6185c2d 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/BUILD +++ b/test/extensions/filters/udp/udp_proxy/session_filters/BUILD @@ -40,3 +40,21 @@ envoy_cc_test_library( ], alwayslink = 1, ) + +envoy_proto_library( + name = "buffer_filter_proto", + srcs = ["buffer_filter.proto"], +) + +envoy_cc_test_library( + name = "buffer_filter_config_lib", + srcs = ["buffer_filter.h"], + deps = [ + ":buffer_filter_proto_cc_proto", + "//envoy/registry", + "//source/extensions/filters/udp/udp_proxy/session_filters:factory_base_lib", + "//source/extensions/filters/udp/udp_proxy/session_filters:filter_interface", + "//test/test_common:utility_lib", + ], + alwayslink = 1, +) diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h b/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h new file mode 100644 index 000000000000..e34f431b4e16 --- /dev/null +++ b/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h @@ -0,0 +1,138 @@ +#pragma once + +#include + +#include "envoy/registry/registry.h" + +#include "source/common/config/utility.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/factory_base.h" +#include "source/extensions/filters/udp/udp_proxy/session_filters/filter.h" + +#include "test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.pb.h" +#include "test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.pb.validate.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Extensions { +namespace UdpFilters { +namespace UdpProxy { +namespace SessionFilters { + +using BufferingFilterConfig = + test::extensions::filters::udp::udp_proxy::session_filters::BufferingFilterConfig; + +using BufferedDatagramPtr = std::unique_ptr; + +class BufferingSessionFilter : public Filter { +public: + BufferingSessionFilter(int downstream_datagrams_to_buffer, int upstream_datagrams_to_buffer, + bool continue_after_inject) + : downstream_datagrams_to_buffer_(downstream_datagrams_to_buffer), + upstream_datagrams_to_buffer_(upstream_datagrams_to_buffer), + continue_after_inject_(continue_after_inject) {} + + void initializeReadFilterCallbacks(ReadFilterCallbacks& callbacks) override { + read_callbacks_ = &callbacks; + } + + void initializeWriteFilterCallbacks(WriteFilterCallbacks& callbacks) override { + write_callbacks_ = &callbacks; + } + + ReadFilterStatus onNewSession() override { return ReadFilterStatus::Continue; } + + ReadFilterStatus onData(Network::UdpRecvData& data) override { + if (downstream_buffer_.size() < downstream_datagrams_to_buffer_) { + bufferRead(data); + return ReadFilterStatus::StopIteration; + } + + // There's no async callback, so we use the next datagram as a trigger to flush the buffer. + while (!downstream_buffer_.empty()) { + BufferedDatagramPtr buffered_datagram = std::move(downstream_buffer_.front()); + downstream_buffer_.pop(); + read_callbacks_->injectDatagramToFilterChain(*buffered_datagram); + } + + if (continue_after_inject_) { + return ReadFilterStatus::Continue; + } + + bufferRead(data); + return ReadFilterStatus::StopIteration; + } + + WriteFilterStatus onWrite(Network::UdpRecvData& data) override { + if (upstream_buffer_.size() < upstream_datagrams_to_buffer_) { + bufferWrite(data); + return WriteFilterStatus::StopIteration; + } + + // There's no async callback, so we use the next datagram as a trigger to flush the buffer. + while (!upstream_buffer_.empty()) { + BufferedDatagramPtr buffered_datagram = std::move(upstream_buffer_.front()); + upstream_buffer_.pop(); + write_callbacks_->injectDatagramToFilterChain(*buffered_datagram); + } + + if (continue_after_inject_) { + return WriteFilterStatus::Continue; + } + + bufferWrite(data); + return WriteFilterStatus::StopIteration; + } + +private: + void bufferRead(Network::UdpRecvData& data) { + auto buffered_datagram = std::make_unique(); + buffered_datagram->addresses_ = {std::move(data.addresses_.local_), + std::move(data.addresses_.peer_)}; + buffered_datagram->buffer_ = std::move(data.buffer_); + buffered_datagram->receive_time_ = data.receive_time_; + downstream_buffer_.push(std::move(buffered_datagram)); + } + + void bufferWrite(Network::UdpRecvData& data) { + auto buffered_datagram = std::make_unique(); + buffered_datagram->addresses_ = {std::move(data.addresses_.local_), + std::move(data.addresses_.peer_)}; + buffered_datagram->buffer_ = std::move(data.buffer_); + buffered_datagram->receive_time_ = data.receive_time_; + upstream_buffer_.push(std::move(buffered_datagram)); + } + + ReadFilterCallbacks* read_callbacks_; + WriteFilterCallbacks* write_callbacks_; + uint32_t downstream_datagrams_to_buffer_; + uint32_t upstream_datagrams_to_buffer_; + bool continue_after_inject_{false}; + std::queue downstream_buffer_; + std::queue upstream_buffer_; +}; + +class BufferingSessionFilterConfigFactory : public FactoryBase { +public: + BufferingSessionFilterConfigFactory() : FactoryBase("test.udp_session.buffer") {} + +private: + FilterFactoryCb + createFilterFactoryFromProtoTyped(const BufferingFilterConfig& config, + Server::Configuration::FactoryContext&) override { + return [config](FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addFilter(std::make_shared( + config.downstream_datagrams_to_buffer(), config.upstream_datagrams_to_buffer(), + config.continue_after_inject())); + }; + } +}; + +static Registry::RegisterFactory + register_buffer_udp_session_filter_; + +} // namespace SessionFilters +} // namespace UdpProxy +} // namespace UdpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.proto b/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.proto new file mode 100644 index 000000000000..e2a72eff7fad --- /dev/null +++ b/test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package test.extensions.filters.udp.udp_proxy.session_filters; + +message BufferingFilterConfig { + uint32 downstream_datagrams_to_buffer = 1; + uint32 upstream_datagrams_to_buffer = 2; + bool continue_after_inject = 3; +} diff --git a/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc b/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc index c917713475b7..366dd28db1a3 100644 --- a/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc +++ b/test/extensions/filters/udp/udp_proxy/udp_proxy_integration_test.cc @@ -4,6 +4,8 @@ #include "envoy/network/filter.h" #include "envoy/server/filter_config.h" +#include "test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.h" +#include "test/extensions/filters/udp/udp_proxy/session_filters/buffer_filter.pb.h" #include "test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.h" #include "test/extensions/filters/udp/udp_proxy/session_filters/drainer_filter.pb.h" #include "test/integration/integration.h" @@ -180,6 +182,34 @@ name: udp_proxy return session_filters; } + struct BufferFilterConfig { + int downstream_datagrams_to_buffer_; + int upstream_datagrams_to_buffer_; + bool continue_after_inject_; + }; + + std::string getBufferSessionFilterConfig(std::list session_filters_configs) { + std::string session_filters = R"EOF( + session_filters: +)EOF"; + + for (auto config : session_filters_configs) { + session_filters += fmt::format( + R"EOF( + - name: foo + typed_config: + '@type': type.googleapis.com/test.extensions.filters.udp.udp_proxy.session_filters.BufferingFilterConfig + downstream_datagrams_to_buffer: {} + upstream_datagrams_to_buffer: {} + continue_after_inject: {} +)EOF", + config.downstream_datagrams_to_buffer_, config.upstream_datagrams_to_buffer_, + config.continue_after_inject_); + } + + return session_filters; + } + void setupMultiple() { FakeUpstreamConfig::UdpConfig config; config.max_rx_datagram_size_ = absl::nullopt; @@ -628,5 +658,119 @@ TEST_P(UdpProxyIntegrationTest, WriteSessionFilterStopOnWrite) { EXPECT_EQ(expected_response, response_datagram.buffer_->toString()); } +TEST_P(UdpProxyIntegrationTest, BufferingFilterBasicFlow) { + setup(1, absl::nullopt, getBufferSessionFilterConfig({{2, 2, true}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + + Network::Test::UdpSyncPeer client(version_, Network::DEFAULT_UDP_MAX_DATAGRAM_SIZE); + client.write("hello1", *listener_address); + client.write("hello2", *listener_address); + + // Two downstream datagrams should be received, but none sent upstream due to filter buffering. + test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", 2); + EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.udp.sess_tx_datagrams")->value()); + + // Third downstream datagram should flush the previously buffered datagrams, due to + // injectDatagramToFilterChain() call. + client.write("hello3", *listener_address); + + // Wait for the upstream datagram. + Network::UdpRecvData request_datagram; + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ("hello1", request_datagram.buffer_->toString()); + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ("hello2", request_datagram.buffer_->toString()); + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ("hello3", request_datagram.buffer_->toString()); + EXPECT_EQ(3, test_server_->counter("udp.foo.downstream_sess_rx_datagrams")->value()); + EXPECT_EQ(3, test_server_->counter("cluster.cluster_0.udp.sess_tx_datagrams")->value()); + + // Two upstream datagrams should be received, but none sent downstream due to filter buffering. + fake_upstreams_[0]->sendUdpDatagram("response1", request_datagram.addresses_.peer_); + fake_upstreams_[0]->sendUdpDatagram("response2", request_datagram.addresses_.peer_); + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 2); + EXPECT_EQ(0, test_server_->counter("udp.foo.downstream_sess_tx_datagrams")->value()); + + // Third upstream datagram should flush the previously buffered datagrams, due to + // injectDatagramToFilterChain() call. + fake_upstreams_[0]->sendUdpDatagram("response3", request_datagram.addresses_.peer_); + + Network::UdpRecvData response_datagram; + client.recv(response_datagram); + EXPECT_EQ("response1", response_datagram.buffer_->toString()); + client.recv(response_datagram); + EXPECT_EQ("response2", response_datagram.buffer_->toString()); + client.recv(response_datagram); + EXPECT_EQ("response3", response_datagram.buffer_->toString()); + EXPECT_EQ(3, test_server_->counter("cluster.cluster_0.udp.sess_rx_datagrams")->value()); + EXPECT_EQ(3, test_server_->counter("udp.foo.downstream_sess_rx_datagrams")->value()); +} + +TEST_P(UdpProxyIntegrationTest, TwoBufferingFilters) { + setup(1, absl::nullopt, getBufferSessionFilterConfig({{1, 1, false}, {1, 1, false}})); + const uint32_t port = lookupPort("listener_0"); + const auto listener_address = Network::Utility::resolveUrl( + fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version_), port)); + + Network::Test::UdpSyncPeer client(version_, Network::DEFAULT_UDP_MAX_DATAGRAM_SIZE); + client.write("hello1", *listener_address); // Buffered in the first filter. + // 'hello1' will proceed to second filter. 'hello2' will buffer in first filter. + client.write("hello2", *listener_address); + + // Two downstream datagrams should be received, but none sent upstream due to filter buffering. + test_server_->waitForCounterEq("udp.foo.downstream_sess_rx_datagrams", 2); + EXPECT_EQ(0, test_server_->counter("cluster.cluster_0.udp.sess_tx_datagrams")->value()); + + // 'hello1' will flush upstream, 'hello2' will proceed to second filter. 'hello3' will + // buffer in the first filter. + client.write("hello3", *listener_address); + + // Wait for the upstream datagram. + Network::UdpRecvData request_datagram; + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ("hello1", request_datagram.buffer_->toString()); + EXPECT_EQ(3, test_server_->counter("udp.foo.downstream_sess_rx_datagrams")->value()); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.udp.sess_tx_datagrams")->value()); + + // 'hello2' will flush upstream, 'hello3' will proceed to second filter. 'hello4' will + // buffer in the first filter. + client.write("hello4", *listener_address); + + // Wait for the upstream datagram. + ASSERT_TRUE(fake_upstreams_[0]->waitForUdpDatagram(request_datagram)); + EXPECT_EQ("hello2", request_datagram.buffer_->toString()); + EXPECT_EQ(4, test_server_->counter("udp.foo.downstream_sess_rx_datagrams")->value()); + EXPECT_EQ(2, test_server_->counter("cluster.cluster_0.udp.sess_tx_datagrams")->value()); + + // Testing the upstream to downstream direction. + // Two upstream datagrams should be received, but none sent downstream due to filter buffering. + fake_upstreams_[0]->sendUdpDatagram("response1", request_datagram.addresses_.peer_); + // 'response1' will proceed to second filter. 'response2' will buffer in first filter. + fake_upstreams_[0]->sendUdpDatagram("response2", request_datagram.addresses_.peer_); + test_server_->waitForCounterEq("cluster.cluster_0.udp.sess_rx_datagrams", 2); + EXPECT_EQ(0, test_server_->counter("udp.foo.downstream_sess_tx_datagrams")->value()); + + // 'response1' will flush downstream, 'response2' will proceed to second filter. 'response3' will + // buffer in the first filter. + fake_upstreams_[0]->sendUdpDatagram("response3", request_datagram.addresses_.peer_); + + // Wait for the downstream datagram. + Network::UdpRecvData response_datagram; + client.recv(response_datagram); + EXPECT_EQ("response1", response_datagram.buffer_->toString()); + EXPECT_EQ(3, test_server_->counter("cluster.cluster_0.udp.sess_rx_datagrams")->value()); + EXPECT_EQ(1, test_server_->counter("udp.foo.downstream_sess_tx_datagrams")->value()); + + // 'response2' will flush downstream, 'response3' will proceed to second filter. 'response4' will + // buffer in the first filter. + fake_upstreams_[0]->sendUdpDatagram("response4", request_datagram.addresses_.peer_); + client.recv(response_datagram); + EXPECT_EQ("response2", response_datagram.buffer_->toString()); + EXPECT_EQ(4, test_server_->counter("cluster.cluster_0.udp.sess_rx_datagrams")->value()); + EXPECT_EQ(2, test_server_->counter("udp.foo.downstream_sess_tx_datagrams")->value()); +} + } // namespace } // namespace Envoy From ada39e8e5d59037b3d19f41dee3bc36af8546462 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Sun, 10 Sep 2023 22:14:41 -0400 Subject: [PATCH 43/55] fuzz: Resolve h1/h2 codec fuzz flakes by using cleanup hooks rather than static pods (#29522) #10281 introduced persistent fuzz state to avoid rebuilding complex test infrastructure between test methods, and improve fuzzing performance. However it introduced a static-init fiasco by statically initiailizing a non-pod, which has non-deterministic destruction order compared to other static-inits. This causes flaky tests, particularly on ARM. This PR adds a new mechanism to the fuzz-runner infrastructure to allow cleanup hooks to be established, that will be run after all test methods but before main() returns, in a deterministic, platform-independent order. Additional Description: n/a Risk Level: low -- test only Testing: //test/integration/... Signed-off-by: Joshua Marantz --- test/fuzz/fuzz_runner.cc | 21 +++++++++++++++++++ test/fuzz/fuzz_runner.h | 20 ++++++++++++++++-- test/fuzz/main.cc | 4 +++- .../h1_capture_direct_response_fuzz_test.cc | 2 +- test/integration/h1_capture_fuzz_test.cc | 2 +- test/integration/h1_fuzz.cc | 8 +++---- .../h2_capture_direct_response_fuzz_test.cc | 2 +- test/integration/h2_capture_fuzz_test.cc | 2 +- test/integration/h2_fuzz.cc | 8 +++---- 9 files changed, 54 insertions(+), 15 deletions(-) diff --git a/test/fuzz/fuzz_runner.cc b/test/fuzz/fuzz_runner.cc index 926e411448ae..d1b54095bd87 100644 --- a/test/fuzz/fuzz_runner.cc +++ b/test/fuzz/fuzz_runner.cc @@ -56,6 +56,27 @@ void Runner::setupEnvironment(int argc, char** argv, spdlog::level::level_enum d } } +using Hooks = std::vector>; +static Hooks* cleanup_hooks = nullptr; + +void addCleanupHook(std::function cleanup) { + if (cleanup_hooks == nullptr) { + cleanup_hooks = new Hooks; + } + cleanup_hooks->push_back(cleanup); +} + +void runCleanupHooks() { + if (cleanup_hooks != nullptr) { + // Run hooks in reverse order from how they were added. + for (auto iter = cleanup_hooks->rbegin(), end = cleanup_hooks->rend(); iter != end; ++iter) { + (*iter)(); + } + delete cleanup_hooks; + cleanup_hooks = nullptr; + } +} + } // namespace Fuzz } // namespace Envoy diff --git a/test/fuzz/fuzz_runner.h b/test/fuzz/fuzz_runner.h index 5349d1241cd5..a99fa20dd2d4 100644 --- a/test/fuzz/fuzz_runner.h +++ b/test/fuzz/fuzz_runner.h @@ -52,6 +52,18 @@ class Runner { static spdlog::level::level_enum log_level_; }; +/** + * Establishes a function to run before the test process exits. This enables + * threads, mocks, and other objects that are expensive to create to be shared + * between test methods. + */ +void addCleanupHook(std::function); + +/** + * Runs all cleanup hooks. + */ +void runCleanupHooks(); + } // namespace Fuzz } // namespace Envoy @@ -63,9 +75,13 @@ extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv); extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); #ifdef PERSISTENT_FUZZER -#define PERSISTENT_FUZZ_VAR static +template T& initFuzzVar(T* ptr) { + Envoy::Fuzz::addCleanupHook([ptr]() { delete ptr; }); + return *ptr; +} +#define PERSISTENT_FUZZ_VAR(type, var, args) static type& var = initFuzzVar(new type(args)) #else -#define PERSISTENT_FUZZ_VAR +#define PERSISTENT_FUZZ_VAR(type, var, args) type var args #endif #define DEFINE_TEST_ONE_INPUT_IMPL \ diff --git a/test/fuzz/main.cc b/test/fuzz/main.cc index 7d2f64e3b158..07e4952369a6 100644 --- a/test/fuzz/main.cc +++ b/test/fuzz/main.cc @@ -94,5 +94,7 @@ int main(int argc, char** argv) { testing::InitGoogleMock(&argc, argv); Envoy::Fuzz::Runner::setupEnvironment(argc, argv, spdlog::level::info); - return RUN_ALL_TESTS(); + int status = RUN_ALL_TESTS(); + Envoy::Fuzz::runCleanupHooks(); + return status; } diff --git a/test/integration/h1_capture_direct_response_fuzz_test.cc b/test/integration/h1_capture_direct_response_fuzz_test.cc index af93de0b152c..f8df5c5fa75a 100644 --- a/test/integration/h1_capture_direct_response_fuzz_test.cc +++ b/test/integration/h1_capture_direct_response_fuzz_test.cc @@ -32,7 +32,7 @@ void H1FuzzIntegrationTest::initialize() { DEFINE_PROTO_FUZZER(const test::integration::CaptureFuzzTestCase& input) { RELEASE_ASSERT(!TestEnvironment::getIpVersionsForTest().empty(), ""); const auto ip_version = TestEnvironment::getIpVersionsForTest()[0]; - PERSISTENT_FUZZ_VAR H1FuzzIntegrationTest h1_fuzz_integration_test(ip_version); + PERSISTENT_FUZZ_VAR(H1FuzzIntegrationTest, h1_fuzz_integration_test, (ip_version)); h1_fuzz_integration_test.replay(input, true); } diff --git a/test/integration/h1_capture_fuzz_test.cc b/test/integration/h1_capture_fuzz_test.cc index 426e74abb391..f5c43377895a 100644 --- a/test/integration/h1_capture_fuzz_test.cc +++ b/test/integration/h1_capture_fuzz_test.cc @@ -7,7 +7,7 @@ DEFINE_PROTO_FUZZER(const test::integration::CaptureFuzzTestCase& input) { // Pick an IP version to use for loopback, it doesn't matter which. RELEASE_ASSERT(!TestEnvironment::getIpVersionsForTest().empty(), ""); const auto ip_version = TestEnvironment::getIpVersionsForTest()[0]; - PERSISTENT_FUZZ_VAR H1FuzzIntegrationTest h1_fuzz_integration_test(ip_version); + PERSISTENT_FUZZ_VAR(H1FuzzIntegrationTest, h1_fuzz_integration_test, (ip_version)); h1_fuzz_integration_test.replay(input, false); } diff --git a/test/integration/h1_fuzz.cc b/test/integration/h1_fuzz.cc index 6b3c1530868e..4d661617be57 100644 --- a/test/integration/h1_fuzz.cc +++ b/test/integration/h1_fuzz.cc @@ -12,10 +12,10 @@ namespace Envoy { void H1FuzzIntegrationTest::replay(const test::integration::CaptureFuzzTestCase& input, bool ignore_response) { - PERSISTENT_FUZZ_VAR bool initialized = [this]() -> bool { - initialize(); - return true; - }(); + struct Init { + Init(H1FuzzIntegrationTest* test) { test->initialize(); } + }; + PERSISTENT_FUZZ_VAR(Init, initialized, (this)); UNREFERENCED_PARAMETER(initialized); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("http")); FakeRawConnectionPtr fake_upstream_connection; diff --git a/test/integration/h2_capture_direct_response_fuzz_test.cc b/test/integration/h2_capture_direct_response_fuzz_test.cc index 1c9ec4c27804..20b2473a8be6 100644 --- a/test/integration/h2_capture_direct_response_fuzz_test.cc +++ b/test/integration/h2_capture_direct_response_fuzz_test.cc @@ -36,7 +36,7 @@ void H2FuzzIntegrationTest::initialize() { DEFINE_PROTO_FUZZER(const test::integration::H2CaptureFuzzTestCase& input) { RELEASE_ASSERT(!TestEnvironment::getIpVersionsForTest().empty(), ""); const auto ip_version = TestEnvironment::getIpVersionsForTest()[0]; - PERSISTENT_FUZZ_VAR H2FuzzIntegrationTest h2_fuzz_integration_test(ip_version); + PERSISTENT_FUZZ_VAR(H2FuzzIntegrationTest, h2_fuzz_integration_test, (ip_version)); h2_fuzz_integration_test.replay(input, true); } diff --git a/test/integration/h2_capture_fuzz_test.cc b/test/integration/h2_capture_fuzz_test.cc index 51db5f87a6e7..bed3aa093703 100644 --- a/test/integration/h2_capture_fuzz_test.cc +++ b/test/integration/h2_capture_fuzz_test.cc @@ -24,7 +24,7 @@ DEFINE_PROTO_FUZZER(const test::integration::H2CaptureFuzzTestCase& input) { // Pick an IP version to use for loopback, it doesn't matter which. FUZZ_ASSERT(!TestEnvironment::getIpVersionsForTest().empty()); const auto ip_version = TestEnvironment::getIpVersionsForTest()[0]; - PERSISTENT_FUZZ_VAR H2FuzzIntegrationTest h2_fuzz_integration_test(ip_version); + PERSISTENT_FUZZ_VAR(H2FuzzIntegrationTest, h2_fuzz_integration_test, (ip_version)); h2_fuzz_integration_test.replay(input, false); } diff --git a/test/integration/h2_fuzz.cc b/test/integration/h2_fuzz.cc index c08417c2b4fb..337ca1e5109d 100644 --- a/test/integration/h2_fuzz.cc +++ b/test/integration/h2_fuzz.cc @@ -169,10 +169,10 @@ void H2FuzzIntegrationTest::sendFrame(const test::integration::H2TestFrame& prot void H2FuzzIntegrationTest::replay(const test::integration::H2CaptureFuzzTestCase& input, bool ignore_response) { - PERSISTENT_FUZZ_VAR bool initialized = [this]() -> bool { - initialize(); - return true; - }(); + struct Init { + Init(H2FuzzIntegrationTest* test) { test->initialize(); } + }; + PERSISTENT_FUZZ_VAR(Init, initialized, (this)); UNREFERENCED_PARAMETER(initialized); IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("http")); FakeRawConnectionPtr fake_upstream_connection; From 2bf76b863ddc10188836f968460db599c7bc653a Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 11 Sep 2023 02:04:37 -0400 Subject: [PATCH 44/55] Use short package local dependencies (#29429) Signed-off-by: Yan Avlasov --- test/mocks/server/BUILD | 94 ++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/test/mocks/server/BUILD b/test/mocks/server/BUILD index f0a0deb36052..554313ac5267 100644 --- a/test/mocks/server/BUILD +++ b/test/mocks/server/BUILD @@ -22,9 +22,9 @@ envoy_cc_mock( srcs = ["admin.cc"], hdrs = ["admin.h"], deps = [ + ":config_tracker_mocks", "//envoy/server:admin_interface", "//test/mocks/network:socket_mocks", - "//test/mocks/server:config_tracker_mocks", ], ) @@ -100,8 +100,8 @@ envoy_cc_mock( srcs = ["guard_dog.cc"], hdrs = ["guard_dog.h"], deps = [ + ":watch_dog_mocks", "//envoy/server:guarddog_interface", - "//test/mocks/server:watch_dog_mocks", ], ) @@ -152,8 +152,8 @@ envoy_cc_mock( srcs = ["worker_factory.cc"], hdrs = ["worker_factory.h"], deps = [ + ":worker_mocks", "//envoy/server:worker_interface", - "//test/mocks/server:worker_mocks", ], ) @@ -181,9 +181,9 @@ envoy_cc_mock( srcs = ["instance.cc"], hdrs = ["instance.h"], deps = [ + ":server_factory_context_mocks", + ":transport_socket_factory_context_mocks", "//envoy/server:instance_interface", - "//test/mocks/server:server_factory_context_mocks", - "//test/mocks/server:transport_socket_factory_context_mocks", ], ) @@ -201,6 +201,13 @@ envoy_cc_mock( srcs = ["server_factory_context.cc"], hdrs = ["server_factory_context.h"], deps = [ + ":admin_mocks", + ":drain_manager_mocks", + ":hot_restart_mocks", + ":listener_manager_mocks", + ":options_mocks", + ":overload_manager_mocks", + ":server_lifecycle_notifier_mocks", "//envoy/server:factory_context_interface", "//source/common/grpc:context_lib", "//source/common/http:context_lib", @@ -220,13 +227,6 @@ envoy_cc_mock( "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/secret:secret_mocks", - "//test/mocks/server:admin_mocks", - "//test/mocks/server:drain_manager_mocks", - "//test/mocks/server:hot_restart_mocks", - "//test/mocks/server:listener_manager_mocks", - "//test/mocks/server:options_mocks", - "//test/mocks/server:overload_manager_mocks", - "//test/mocks/server:server_lifecycle_notifier_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:cluster_manager_mocks", @@ -238,11 +238,11 @@ envoy_cc_mock( srcs = ["factory_context.cc"], hdrs = ["factory_context.h"], deps = [ + ":drain_manager_mocks", + ":instance_mocks", + ":overload_manager_mocks", + ":server_lifecycle_notifier_mocks", "//source/common/router:context_lib", - "//test/mocks/server:drain_manager_mocks", - "//test/mocks/server:instance_mocks", - "//test/mocks/server:overload_manager_mocks", - "//test/mocks/server:server_lifecycle_notifier_mocks", ], ) @@ -251,10 +251,10 @@ envoy_cc_mock( srcs = ["transport_socket_factory_context.cc"], hdrs = ["transport_socket_factory_context.h"], deps = [ + ":server_factory_context_mocks", "//envoy/server:tracer_config_interface", "//source/common/secret:secret_manager_impl_lib", "//test/mocks/access_log:access_log_mocks", - "//test/mocks/server:server_factory_context_mocks", ], ) @@ -263,8 +263,8 @@ envoy_cc_mock( srcs = ["listener_factory_context.cc"], hdrs = ["listener_factory_context.h"], deps = [ + ":factory_context_mocks", "//envoy/server:listener_manager_interface", - "//test/mocks/server:factory_context_mocks", ], ) @@ -273,6 +273,7 @@ envoy_cc_mock( srcs = ["health_checker_factory_context.cc"], hdrs = ["health_checker_factory_context.h"], deps = [ + ":factory_context_mocks", "//envoy/server:health_checker_config_interface", "//test/mocks:common_lib", "//test/mocks/access_log:access_log_mocks", @@ -281,7 +282,6 @@ envoy_cc_mock( "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:cluster_priority_set_mocks", "//test/mocks/upstream:health_check_event_logger_mocks", "//test/mocks/upstream:health_checker_mocks", @@ -293,9 +293,9 @@ envoy_cc_mock( srcs = ["filter_chain_factory_context.cc"], hdrs = ["filter_chain_factory_context.h"], deps = [ + ":factory_context_mocks", "//envoy/server:filter_config_interface", "//source/common/router:context_lib", - "//test/mocks/server:factory_context_mocks", ], ) @@ -314,9 +314,9 @@ envoy_cc_mock( srcs = ["tracer_factory_context.cc"], hdrs = ["tracer_factory_context.h"], deps = [ + ":instance_mocks", + ":tracer_factory_mocks", "//envoy/server:configuration_interface", - "//test/mocks/server:instance_mocks", - "//test/mocks/server:tracer_factory_mocks", ], ) @@ -325,30 +325,30 @@ envoy_cc_mock( srcs = [], hdrs = ["mocks.h"], deps = [ - "//test/mocks/server:admin_mocks", - "//test/mocks/server:admin_stream_mocks", - "//test/mocks/server:bootstrap_extension_factory_mocks", - "//test/mocks/server:config_tracker_mocks", - "//test/mocks/server:drain_manager_mocks", - "//test/mocks/server:factory_context_mocks", - "//test/mocks/server:fatal_action_factory_mocks", - "//test/mocks/server:filter_chain_factory_context_mocks", - "//test/mocks/server:guard_dog_mocks", - "//test/mocks/server:health_checker_factory_context_mocks", - "//test/mocks/server:hot_restart_mocks", - "//test/mocks/server:instance_mocks", - "//test/mocks/server:listener_component_factory_mocks", - "//test/mocks/server:listener_factory_context_mocks", - "//test/mocks/server:listener_manager_mocks", - "//test/mocks/server:main_mocks", - "//test/mocks/server:options_mocks", - "//test/mocks/server:overload_manager_mocks", - "//test/mocks/server:server_lifecycle_notifier_mocks", - "//test/mocks/server:tracer_factory_context_mocks", - "//test/mocks/server:tracer_factory_mocks", - "//test/mocks/server:transport_socket_factory_context_mocks", - "//test/mocks/server:watch_dog_mocks", - "//test/mocks/server:worker_factory_mocks", - "//test/mocks/server:worker_mocks", + ":admin_mocks", + ":admin_stream_mocks", + ":bootstrap_extension_factory_mocks", + ":config_tracker_mocks", + ":drain_manager_mocks", + ":factory_context_mocks", + ":fatal_action_factory_mocks", + ":filter_chain_factory_context_mocks", + ":guard_dog_mocks", + ":health_checker_factory_context_mocks", + ":hot_restart_mocks", + ":instance_mocks", + ":listener_component_factory_mocks", + ":listener_factory_context_mocks", + ":listener_manager_mocks", + ":main_mocks", + ":options_mocks", + ":overload_manager_mocks", + ":server_lifecycle_notifier_mocks", + ":tracer_factory_context_mocks", + ":tracer_factory_mocks", + ":transport_socket_factory_context_mocks", + ":watch_dog_mocks", + ":worker_factory_mocks", + ":worker_mocks", ], ) From 87d4b68338719880cb0d973536ade2e24117148d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 10:36:27 +0100 Subject: [PATCH 45/55] build(deps): bump orjson from 3.9.6 to 3.9.7 in /tools/base (#29543) Bumps [orjson](https://github.com/ijl/orjson) from 3.9.6 to 3.9.7. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.9.6...3.9.7) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 122 ++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index fd2edcee938d..631577fbb63f 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -922,67 +922,67 @@ oauth2client==4.1.3 \ # via # gcs-oauth2-boto-plugin # google-apitools -orjson==3.9.6 \ - --hash=sha256:018f85b53e5c7a8bd1b5ce900358760dddb8a7b9b2da1545c9a17cf42ae99cc6 \ - --hash=sha256:03977a50b682b546c03b7d6e4f39d41cd0568cae533aabd339c853ff33c44a35 \ - --hash=sha256:0520473680d24290558d26aeb8b7d8ba6835955e01ff09a9d0ea866049a0d9c3 \ - --hash=sha256:0664ad3c14dfb61ec794e469525556367a0d9bdc4246a64a6f0b3f8140f89d87 \ - --hash=sha256:08cc162e221105e195301030b3d98e668335da6020424cc61e4ea85fd0d49456 \ - --hash=sha256:0bd7b491ae93221b38c4a6a99a044e0f84a99fba36321e22cf94a38ac7f517d8 \ - --hash=sha256:0ee1664ccc7bdd6de64b6f3f04633837391e2c8e8e04bfd8b3a3270597de2e22 \ - --hash=sha256:118171ed986d71f8201571911d6ec8c8e8e498afd8a8dd038ac55d642d8246b8 \ - --hash=sha256:15e4442fea9aae10074a06e9e486373b960ef61d5735836cb026dd4d104f511d \ - --hash=sha256:181e56cbd94149a721fdbc5417b6283c668e9995a320e6279a87ac8c736d0c6f \ - --hash=sha256:1c7d9a4db055d8febdf949273bc9bc7a15179ea92cdc7c77d0f992fdbf52cfa4 \ - --hash=sha256:1daedb551d3a71873caad350b2b824c56d38e6f03381d7d2d516b9eb01196cdf \ - --hash=sha256:20c7bad91dabf327fb7d034bb579e7d613d1a003f5ed773a3324acc038ae5f9a \ - --hash=sha256:212e6ec66d0bcc9882f9bd0e1870b486a6ead115975108fe17e5e87d0666044e \ - --hash=sha256:212f0524ecd04f217f023bb9f2226f8ff41805cfc69f02d1cbd57300b13cd644 \ - --hash=sha256:2ec2b39c4a38a763e18b93a70ce2114fa322b88ce1896769332271af4f5b33b6 \ - --hash=sha256:2fd1771f0f41569734a85d572735aa47c89b2d0e98b0aa89edc5db849cffd1ef \ - --hash=sha256:32e3e1cc335b1d4539e131fb3a361953b9d7b499e27f81c3648359c0e70ed7aa \ - --hash=sha256:418202b229b00d628e52bc5883a06d43aeecd0449962ad5b4f68113a7fd741a6 \ - --hash=sha256:4601ff8efd8cc45b21a23e0d70bc6f6f67270e95bf8bf4746c4960f696114f47 \ - --hash=sha256:48761464611a333a83686f21f70b483951eb11c6136d7ab46848da03ac90beb1 \ - --hash=sha256:496c1515b6b4a1435667035a955e4531cbea341b0a50e86db42b4b6d0b9c78b0 \ - --hash=sha256:49ecdeb3ae767e6abefd5711c75052692d53a65cce00d6d8caabb5a9b756fcb1 \ - --hash=sha256:49f2f632c8e2db6e9e024d3ea5b9b1343fb5bc4e52d3139c2c724d84f952fae8 \ - --hash=sha256:517a48ddb9684d69002e1ee16d9eb5213be338837936b5dad4bccde61ac4c2ef \ - --hash=sha256:593a939aa8cf0c39a9f8f706681439a172ce98d679bc2b387130bcc219be1ee4 \ - --hash=sha256:62f8d96904024620edd73d0b2d72321ba5fd499ee3a459dd8691d44252db3310 \ - --hash=sha256:66c9e0b728a1e0b1c4acb1f9c728800176a86a4c5b3e3bdb0c00d9dba8823ef0 \ - --hash=sha256:676f037a3ef590f6664d70be956659c7c164daa91b652504cf54d59c252cf29c \ - --hash=sha256:72f6ef36a66a7a2e98d1e247c7a5b7e92d26731c9e9e9a3de627e82a56d1aee6 \ - --hash=sha256:8ba3f11b0197508c4c5e99d11a088182360fd1d4177fe281824105b0cf452137 \ - --hash=sha256:8cfa39f734dac4f5e64d79e5735355d09e6c6a8ade1312daab63efeac325da8a \ - --hash=sha256:93de6166da3ee5523d25acbae6d77f5a76525a1a81b69966a3091a3497f8f9ee \ - --hash=sha256:9b2dd0042fc1527960ddb3e7e81376df9cb799e9c0d31931befd14dc77a4f422 \ - --hash=sha256:9c8ae23b3cde20c2d5472cd9efe35623ebf3c7648e62c8e534082528394078fb \ - --hash=sha256:a1c9987b3e9920c90456c879d9f2ec030f1f7417a1c8ea53badbaceb7a8dcab6 \ - --hash=sha256:a3cb03b1aaf94633d78b389d4110ed5cfd4fc6c09c99a1c61ba418f512b92de7 \ - --hash=sha256:a6843d59c882608da5a026d54e04016924c279f29ead28db9e99d55613326687 \ - --hash=sha256:a6c8702fbb658cb3eb2ac88d50e0c921782a4041012f9138e737341288abe817 \ - --hash=sha256:aeec3598ad7e6b5f0267fa0e57ebc27f140eed7d8e4c68a193d814af3973e1a3 \ - --hash=sha256:b0032c152f29688f84d0660de992df3d76163c45b2ba7ba1aa9bc1f770e84316 \ - --hash=sha256:b077ec427eb805264ab9606406819cb745bef6be0a3d903613c9fa8421547a46 \ - --hash=sha256:b9466e66982ddf18bc0e96e383be5fecc867f659aee3cd621a70de0af0154ac1 \ - --hash=sha256:c0cdbf3b293a11f33aa1b164783b2df8a368bf5a5ec0d46a5f241f00927f3df8 \ - --hash=sha256:cefad5742f0ee2cfae795756eefcedabf4f6e4910fc530cc06f72df2d1ada781 \ - --hash=sha256:dd7b8cf05ed44c79c4a74de128ee481adb1b2446939d565fc7611d34b07d0b3b \ - --hash=sha256:de609af7958f0f9010de04356edb4f58f0cfadbb17103c198561a721981fba74 \ - --hash=sha256:df0545fc5a5f699d7693498847064df56a94990f5a779276549622083e1e850b \ - --hash=sha256:df35c8c0b1a0dad33dc679375d9e6464b0100f1899f72d6b8f9938d877d6f67e \ - --hash=sha256:e2f394a2c112080b8ccec2cc24cc196375980914afa943446df46b6cc133f0ab \ - --hash=sha256:e46ea20dcc8b9d6e5377e125a8101dc59da06086f08e924b6b3c45322709c484 \ - --hash=sha256:e898a5150c3375512f76820bd9a009aab717ffde551f60f381aa8bad9f503bda \ - --hash=sha256:ea3be461eb2aa2299399445001a1d9a53d170efc7dbe39087f163f40733cd9c1 \ - --hash=sha256:f00458dd180d332820545009afca77f3dc4658526995e1aab4127357f473693d \ - --hash=sha256:f19579ba3dbf069b77ebeb70f9628571c9969e51a558cdda7eace9d1885f379f \ - --hash=sha256:f5ff143d42c4a7e6ef0ecdaeca41348eb0ab730a60e3e9927fd0153ba5d4bb60 \ - --hash=sha256:f742af5b28fa153a89e6d87a13bae0ac94bf5c8ac56335102a0e1d9267ed1bc7 \ - --hash=sha256:f994f52901814cf70cc68b835da8394ea50a5464426d122275ac96a0bc39ba20 \ - --hash=sha256:fad6866871411ee9737d4b26fbc7dbe1f66f371ce8a9fffc329bb76805752c4f \ - --hash=sha256:fcb3921062e495a3df770517b5ad9db18a7e0db70d42453bdbb545d8fceb0f85 +orjson==3.9.7 \ + --hash=sha256:01d647b2a9c45a23a84c3e70e19d120011cba5f56131d185c1b78685457320bb \ + --hash=sha256:0eb850a87e900a9c484150c414e21af53a6125a13f6e378cf4cc11ae86c8f9c5 \ + --hash=sha256:11c10f31f2c2056585f89d8229a56013bc2fe5de51e095ebc71868d070a8dd81 \ + --hash=sha256:14d3fb6cd1040a4a4a530b28e8085131ed94ebc90d72793c59a713de34b60838 \ + --hash=sha256:154fd67216c2ca38a2edb4089584504fbb6c0694b518b9020ad35ecc97252bb9 \ + --hash=sha256:1c3cee5c23979deb8d1b82dc4cc49be59cccc0547999dbe9adb434bb7af11cf7 \ + --hash=sha256:1eb0b0b2476f357eb2975ff040ef23978137aa674cd86204cfd15d2d17318588 \ + --hash=sha256:1f8b47650f90e298b78ecf4df003f66f54acdba6a0f763cc4df1eab048fe3738 \ + --hash=sha256:21a3344163be3b2c7e22cef14fa5abe957a892b2ea0525ee86ad8186921b6cf0 \ + --hash=sha256:23be6b22aab83f440b62a6f5975bcabeecb672bc627face6a83bc7aeb495dc7e \ + --hash=sha256:26ffb398de58247ff7bde895fe30817a036f967b0ad0e1cf2b54bda5f8dcfdd9 \ + --hash=sha256:2f8fcf696bbbc584c0c7ed4adb92fd2ad7d153a50258842787bc1524e50d7081 \ + --hash=sha256:355efdbbf0cecc3bd9b12589b8f8e9f03c813a115efa53f8dc2a523bfdb01334 \ + --hash=sha256:36b1df2e4095368ee388190687cb1b8557c67bc38400a942a1a77713580b50ae \ + --hash=sha256:38e34c3a21ed41a7dbd5349e24c3725be5416641fdeedf8f56fcbab6d981c900 \ + --hash=sha256:3aab72d2cef7f1dd6104c89b0b4d6b416b0db5ca87cc2fac5f79c5601f549cc2 \ + --hash=sha256:410aa9d34ad1089898f3db461b7b744d0efcf9252a9415bbdf23540d4f67589f \ + --hash=sha256:45a47f41b6c3beeb31ac5cf0ff7524987cfcce0a10c43156eb3ee8d92d92bf22 \ + --hash=sha256:4891d4c934f88b6c29b56395dfc7014ebf7e10b9e22ffd9877784e16c6b2064f \ + --hash=sha256:4c616b796358a70b1f675a24628e4823b67d9e376df2703e893da58247458956 \ + --hash=sha256:5198633137780d78b86bb54dafaaa9baea698b4f059456cd4554ab7009619221 \ + --hash=sha256:5a2937f528c84e64be20cb80e70cea76a6dfb74b628a04dab130679d4454395c \ + --hash=sha256:5da9032dac184b2ae2da4bce423edff7db34bfd936ebd7d4207ea45840f03905 \ + --hash=sha256:5e736815b30f7e3c9044ec06a98ee59e217a833227e10eb157f44071faddd7c5 \ + --hash=sha256:63ef3d371ea0b7239ace284cab9cd00d9c92b73119a7c274b437adb09bda35e6 \ + --hash=sha256:70b9a20a03576c6b7022926f614ac5a6b0914486825eac89196adf3267c6489d \ + --hash=sha256:76a0fc023910d8a8ab64daed8d31d608446d2d77c6474b616b34537aa7b79c7f \ + --hash=sha256:7951af8f2998045c656ba8062e8edf5e83fd82b912534ab1de1345de08a41d2b \ + --hash=sha256:7a34a199d89d82d1897fd4a47820eb50947eec9cda5fd73f4578ff692a912f89 \ + --hash=sha256:7bab596678d29ad969a524823c4e828929a90c09e91cc438e0ad79b37ce41166 \ + --hash=sha256:7ea3e63e61b4b0beeb08508458bdff2daca7a321468d3c4b320a758a2f554d31 \ + --hash=sha256:80acafe396ab689a326ab0d80f8cc61dec0dd2c5dca5b4b3825e7b1e0132c101 \ + --hash=sha256:82720ab0cf5bb436bbd97a319ac529aee06077ff7e61cab57cee04a596c4f9b4 \ + --hash=sha256:83cc275cf6dcb1a248e1876cdefd3f9b5f01063854acdfd687ec360cd3c9712a \ + --hash=sha256:85e39198f78e2f7e054d296395f6c96f5e02892337746ef5b6a1bf3ed5910142 \ + --hash=sha256:8769806ea0b45d7bf75cad253fba9ac6700b7050ebb19337ff6b4e9060f963fa \ + --hash=sha256:8bdb6c911dae5fbf110fe4f5cba578437526334df381b3554b6ab7f626e5eeca \ + --hash=sha256:8f4b0042d8388ac85b8330b65406c84c3229420a05068445c13ca28cc222f1f7 \ + --hash=sha256:90fe73a1f0321265126cbba13677dcceb367d926c7a65807bd80916af4c17047 \ + --hash=sha256:915e22c93e7b7b636240c5a79da5f6e4e84988d699656c8e27f2ac4c95b8dcc0 \ + --hash=sha256:9274ba499e7dfb8a651ee876d80386b481336d3868cba29af839370514e4dce0 \ + --hash=sha256:9d62c583b5110e6a5cf5169ab616aa4ec71f2c0c30f833306f9e378cf51b6c86 \ + --hash=sha256:9ef82157bbcecd75d6296d5d8b2d792242afcd064eb1ac573f8847b52e58f677 \ + --hash=sha256:a19e4074bc98793458b4b3ba35a9a1d132179345e60e152a1bb48c538ab863c4 \ + --hash=sha256:a347d7b43cb609e780ff8d7b3107d4bcb5b6fd09c2702aa7bdf52f15ed09fa09 \ + --hash=sha256:b4fb306c96e04c5863d52ba8d65137917a3d999059c11e659eba7b75a69167bd \ + --hash=sha256:b6df858e37c321cefbf27fe7ece30a950bcc3a75618a804a0dcef7ed9dd9c92d \ + --hash=sha256:b8e59650292aa3a8ea78073fc84184538783966528e442a1b9ed653aa282edcf \ + --hash=sha256:bcb9a60ed2101af2af450318cd89c6b8313e9f8df4e8fb12b657b2e97227cf08 \ + --hash=sha256:c3ba725cf5cf87d2d2d988d39c6a2a8b6fc983d78ff71bc728b0be54c869c884 \ + --hash=sha256:ca1706e8b8b565e934c142db6a9592e6401dc430e4b067a97781a997070c5378 \ + --hash=sha256:cd3e7aae977c723cc1dbb82f97babdb5e5fbce109630fbabb2ea5053523c89d3 \ + --hash=sha256:cf334ce1d2fadd1bf3e5e9bf15e58e0c42b26eb6590875ce65bd877d917a58aa \ + --hash=sha256:d8692948cada6ee21f33db5e23460f71c8010d6dfcfe293c9b96737600a7df78 \ + --hash=sha256:e5205ec0dfab1887dd383597012199f5175035e782cdb013c542187d280ca443 \ + --hash=sha256:e7e7f44e091b93eb39db88bb0cb765db09b7a7f64aea2f35e7d86cbf47046c65 \ + --hash=sha256:e94b7b31aa0d65f5b7c72dd8f8227dbd3e30354b99e7a9af096d967a77f2a580 \ + --hash=sha256:f26fb3e8e3e2ee405c947ff44a3e384e8fa1843bc35830fe6f3d9a95a1147b6e \ + --hash=sha256:f738fee63eb263530efd4d2e9c76316c1f47b3bbf38c1bf45ae9625feed0395e \ + --hash=sha256:f9e01239abea2f52a429fe9d95c96df95f078f0172489d691b4a848ace54a476 # via # -r requirements.in # envoy-base-utils From ae9707035d40b37e8ddc4e7804ee4fc777810656 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 10:36:51 +0100 Subject: [PATCH 46/55] build(deps): bump golang from 1.21.0-bullseye to 1.21.1-bullseye in /examples/shared/golang (#29467) build(deps): bump golang in /examples/shared/golang Bumps golang from 1.21.0-bullseye to 1.21.1-bullseye. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/golang/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/golang/Dockerfile b/examples/shared/golang/Dockerfile index abf71524ec02..0929b3a1e79e 100644 --- a/examples/shared/golang/Dockerfile +++ b/examples/shared/golang/Dockerfile @@ -3,7 +3,7 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean \ && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | tee /etc/apt/apt.conf.d/keep-cache -FROM golang:1.21.0-bullseye@sha256:02f350d8452d3f9693a450586659ecdc6e40e9be8f8dfc6d402300d87223fdfa as golang-base +FROM golang:1.21.1-bullseye@sha256:873fce589a521deb54ec10f3deea6675135fed9ad91d62943a5c90d2fc059ba7 as golang-base FROM golang-base as golang-control-plane-builder From cc3bf34d780b238e09ac8a2c817e7febeb7e0eab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 10:38:12 +0100 Subject: [PATCH 47/55] build(deps): bump python from `0bc6588` to `e5f001b` in /examples/shared/python (#29473) build(deps): bump python in /examples/shared/python Bumps python from `0bc6588` to `e5f001b`. --- updated-dependencies: - dependency-name: python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/python/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/python/Dockerfile b/examples/shared/python/Dockerfile index 6a60a8d3f4db..0ae475fff96d 100644 --- a/examples/shared/python/Dockerfile +++ b/examples/shared/python/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11.5-slim-bullseye@sha256:0bc6588e043ceff0278c3936467fce6dad52c5889bf4eb257ad5147a17522064 as python-base +FROM python:3.11.5-slim-bullseye@sha256:e5f001b561a810874d3ebf785cfdb46155a47392093c301aebc5299ec1128b9e as python-base RUN rm -f /etc/apt/apt.conf.d/docker-clean \ && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | tee /etc/apt/apt.conf.d/keep-cache ARG PYTHON_REQUIREMENTS_FILE=aiohttp/requirements.txt From a1c6b9f14bfbb4e6f9e7bfc4dce84c88ce6f096b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 10:38:23 +0100 Subject: [PATCH 48/55] build(deps): bump node from `36d587c` to `3e1a690` in /examples/shared/node (#29476) build(deps): bump node in /examples/shared/node Bumps node from `36d587c` to `3e1a690`. --- updated-dependencies: - dependency-name: node dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/node/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/node/Dockerfile b/examples/shared/node/Dockerfile index 7f1a6340b1b3..755382e56bc2 100644 --- a/examples/shared/node/Dockerfile +++ b/examples/shared/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.6-bullseye-slim@sha256:36d587c8542eafc209a8419b1b8ba3225b002b6d15e75bd094c78ca6cd605ec3 as node-base +FROM node:20.6-bullseye-slim@sha256:3e1a690b3dda477e2a0191acc7c545a2758fea90181bd9c87475096b76956351 as node-base FROM node-base as node-http-auth From 35babc42015c0cfbac20d28c549544ff232f6637 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 10:39:26 +0100 Subject: [PATCH 49/55] build(deps): bump debian from `61386e1` to `3bc5e94` in /examples/shared/websocket (#29472) build(deps): bump debian in /examples/shared/websocket Bumps debian from `61386e1` to `3bc5e94`. --- updated-dependencies: - dependency-name: debian dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/websocket/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/websocket/Dockerfile b/examples/shared/websocket/Dockerfile index 24c4d72ce907..9fd37ad2c579 100644 --- a/examples/shared/websocket/Dockerfile +++ b/examples/shared/websocket/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bullseye-slim@sha256:61386e11b5256efa33823cbfafd668dd651dbce810b24a8fb7b2e32fa7f65a85 as websocket-base +FROM debian:bullseye-slim@sha256:3bc5e94a0e8329c102203c3f5f26fd67835f0c81633dd6949de0557867a87fac as websocket-base ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ From 615208fbcf326793f49cc1d96acd43afea0b0c7f Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Mon, 11 Sep 2023 16:33:52 +0200 Subject: [PATCH 50/55] doc: fix the expected date of the next release (#29552) Signed-off-by: Michael Kaufmann --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 4a7ae5ada781..bdd6ca20bdd9 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -97,7 +97,7 @@ deadline of 3 weeks. | 1.25.0 | 2023/01/15 | 2023/01/18 | +3 days | 2024/01/18 | | 1.26.0 | 2023/04/15 | 2023/04/18 | +3 days | 2024/04/18 | | 1.27.0 | 2023/07/14 | 2023/07/27 | +13 days | 2024/07/27 | -| 1.28.0 | 2024/10/16 | | | | +| 1.28.0 | 2023/10/16 | | | | ### Cutting a major release From 7f51530e2d2b73e256c6b407aff9d0a075cca8a1 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 11 Sep 2023 10:37:59 -0400 Subject: [PATCH 51/55] UHV: add global runtime key for toggling UHV on/off (#29391) Add parameter to HttpProtocolIntegrationTest based suites to run with UHV The option to run tests with UHV is still behind the ENVOY_ENABLE_UHV flag No impact on the production build. --------- Signed-off-by: Yan Avlasov --- changelogs/current.yaml | 7 + source/common/runtime/runtime_features.cc | 4 + .../network/http_connection_manager/config.cc | 11 ++ source/extensions/upstreams/http/config.cc | 11 ++ .../http_connection_manager/config_test.cc | 114 ++++++++++++++-- test/extensions/upstreams/http/BUILD | 1 + test/extensions/upstreams/http/config_test.cc | 99 +++++++++++--- test/integration/BUILD | 2 +- ...fault_header_validator_integration_test.cc | 98 +++++++------- test/integration/header_integration_test.cc | 23 +++- .../http2_flood_integration_test.cc | 11 ++ test/integration/http_protocol_integration.cc | 17 ++- test/integration/http_protocol_integration.h | 12 +- test/integration/integration_test.cc | 20 +++ test/integration/integration_test.h | 2 + test/integration/protocol_integration_test.cc | 123 +++++++++--------- 16 files changed, 410 insertions(+), 145 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 4812b3a5c9f8..9ccb67204a85 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -36,6 +36,13 @@ behavior_changes: To revert the entire change, set the runtime flag ``envoy.reloadable_features.locality_routing_use_new_routing_logic`` to false to get the old behavior and well-tested codepaths, undoing both changes. +- area: UHV + change: | + Introduced runtime flag ``envoy.reloadable_features.enable_universal_header_validator`` for toggling Universal Header Validator + (UHV) on and off. + The default value is off. This option is currently functional only when the ``ENVOY_ENABLE_UHV`` build flag is enabled. + See https://github.com/envoyproxy/envoy/issues/10646 for more information about UHV. + minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* - area: ext_authz diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 1faeb59aad52..95d1bfb84beb 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -120,6 +120,10 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_refresh_rtt_after_request); FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_reject_all); // TODO(adisuissa): enable by default once this is tested in prod. FALSE_RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); +// TODO(#10646) change to true when UHV is sufficiently tested +// For more information about Universal Header Validation, please see +// https://github.com/envoyproxy/envoy/issues/10646 +FALSE_RUNTIME_GUARD(envoy_reloadable_features_enable_universal_header_validator); // TODO(wbpcode): enable by default after a complete deprecation period. FALSE_RUNTIME_GUARD(envoy_reloadable_features_no_downgrade_to_canonical_name); diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index f1af02142f13..c5fa082f456a 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -133,6 +133,11 @@ createHeaderValidatorFactory([[maybe_unused]] const envoy::extensions::filters:: Http::HeaderValidatorFactoryPtr header_validator_factory; #ifdef ENVOY_ENABLE_UHV + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.enable_universal_header_validator")) { + // This will cause codecs to use legacy header validation and path normalization + return nullptr; + } ::envoy::config::core::v3::TypedExtensionConfig legacy_header_validator_config; if (!config.has_typed_header_validation_config()) { // If header validator is not configured ensure that the defaults match Envoy's original @@ -184,6 +189,12 @@ createHeaderValidatorFactory([[maybe_unused]] const envoy::extensions::filters:: fmt::format("This Envoy binary does not support header validator extensions.: '{}'", config.typed_header_validation_config().name())); } + + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.enable_universal_header_validator")) { + throw EnvoyException( + "Header validator can not be enabled since this Envoy binary does not support it."); + } #endif return header_validator_factory; } diff --git a/source/extensions/upstreams/http/config.cc b/source/extensions/upstreams/http/config.cc index 41feefaaf5d9..2d8a704e0a3e 100644 --- a/source/extensions/upstreams/http/config.cc +++ b/source/extensions/upstreams/http/config.cc @@ -109,6 +109,11 @@ Envoy::Http::HeaderValidatorFactoryPtr createHeaderValidatorFactory( Envoy::Http::HeaderValidatorFactoryPtr header_validator_factory; #ifdef ENVOY_ENABLE_UHV + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.enable_universal_header_validator")) { + // This will cause codecs to use legacy header validation and path normalization + return nullptr; + } ::envoy::config::core::v3::TypedExtensionConfig legacy_header_validator_config; if (!options.has_header_validation_config()) { // If header validator is not configured ensure that the defaults match Envoy's original @@ -144,6 +149,12 @@ Envoy::Http::HeaderValidatorFactoryPtr createHeaderValidatorFactory( fmt::format("This Envoy binary does not support header validator extensions: '{}'", options.header_validation_config().name())); } + + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.enable_universal_header_validator")) { + throw EnvoyException( + "Header validator can not be enabled since this Envoy binary does not support it."); + } #endif return header_validator_factory; } 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 023ad7368d5c..3e87f787765d 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -3028,6 +3028,11 @@ TEST_F(HttpConnectionManagerConfigTest, HeaderValidatorConfig) { "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router )EOF"; + // Enable UHV runtime flag to test config translation + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.enable_universal_header_validator", "true"}}); + TestHeaderValidatorFactoryConfig factory; Registry::InjectFactory registration(factory); EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) @@ -3053,9 +3058,13 @@ TEST_F(HttpConnectionManagerConfigTest, HeaderValidatorConfig) { #endif } -TEST_F(HttpConnectionManagerConfigTest, DefaultHeaderValidatorConfig) { +TEST_F(HttpConnectionManagerConfigTest, HeaderValidatorConfigWithRuntimeDisabled) { const std::string yaml_string = R"EOF( stat_prefix: ingress_http + typed_header_validation_config: + name: custom_header_validator + typed_config: + "@type": type.googleapis.com/test.http_connection_manager.CustomHeaderValidator route_config: name: local_route http_filters: @@ -3064,6 +3073,48 @@ TEST_F(HttpConnectionManagerConfigTest, DefaultHeaderValidatorConfig) { "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router )EOF"; + TestHeaderValidatorFactoryConfig factory; + Registry::InjectFactory registration(factory); + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); +#ifdef ENVOY_ENABLE_UHV + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, tracer_manager_, + filter_config_provider_manager_); + // Without envoy.reloadable_features.enable_universal_header_validator runtime set, UHV is always + // disabled + EXPECT_EQ(nullptr, config.makeHeaderValidator(Http::Protocol::Http2)); +#else + // If UHV is disabled, providing config should result in rejection + EXPECT_THROW( + { + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), + context_, date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, tracer_manager_, + filter_config_provider_manager_); + }, + EnvoyException); +#endif +} + +TEST_F(HttpConnectionManagerConfigTest, DefaultHeaderValidatorConfigWithRuntimeEnabled) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + )EOF"; + + // Enable UHV runtime flag to test config translation + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.enable_universal_header_validator", "true"}}); ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig proto_config; DefaultHeaderValidatorFactoryConfigOverride factory(proto_config); @@ -3072,11 +3123,11 @@ TEST_F(HttpConnectionManagerConfigTest, DefaultHeaderValidatorConfig) { .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, &Runtime::MockSnapshot::featureEnabledDefault)); EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); +#ifdef ENVOY_ENABLE_UHV HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, tracer_manager_, filter_config_provider_manager_); -#ifdef ENVOY_ENABLE_UHV EXPECT_NE(nullptr, config.makeHeaderValidator(Http::Protocol::Http2)); EXPECT_FALSE(proto_config.restrict_http_methods()); EXPECT_FALSE(proto_config.strip_fragment_from_path()); @@ -3087,11 +3138,48 @@ TEST_F(HttpConnectionManagerConfigTest, DefaultHeaderValidatorConfig) { UriPathNormalizationOptions::KEEP_UNCHANGED); EXPECT_FALSE(proto_config.http1_protocol_options().allow_chunked_length()); #else - // If UHV is disabled, config should be accepted and factory should be nullptr - EXPECT_EQ(nullptr, config.makeHeaderValidator(Http::Protocol::Http2)); + // If UHV is disabled, enabling envoy.reloadable_features.enable_universal_header_validator should + // result in rejection + EXPECT_THROW( + { + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), + context_, date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, tracer_manager_, + filter_config_provider_manager_); + }, + EnvoyException); #endif } +TEST_F(HttpConnectionManagerConfigTest, DefaultHeaderValidatorConfig) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + route_config: + name: local_route + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + )EOF"; + + ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig + proto_config; + DefaultHeaderValidatorFactoryConfigOverride factory(proto_config); + Registry::InjectFactory registration(factory); + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillRepeatedly(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); + EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, tracer_manager_, + filter_config_provider_manager_); + + // Without envoy.reloadable_features.enable_universal_header_validator runtime set, UHV is always + // disabled + EXPECT_EQ(nullptr, config.makeHeaderValidator(Http::Protocol::Http2)); +} + TEST_F(HttpConnectionManagerConfigTest, TranslateLegacyConfigToDefaultHeaderValidatorConfig) { const std::string yaml_string = R"EOF( stat_prefix: ingress_http @@ -3108,8 +3196,10 @@ TEST_F(HttpConnectionManagerConfigTest, TranslateLegacyConfigToDefaultHeaderVali "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router )EOF"; - // Make sure the http_reject_path_with_fragment runtime value is reflected in config TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.enable_universal_header_validator", "true"}}); + // Make sure the http_reject_path_with_fragment runtime value is reflected in config scoped_runtime.mergeValues( {{"envoy.reloadable_features.http_reject_path_with_fragment", "false"}}); @@ -3121,11 +3211,11 @@ TEST_F(HttpConnectionManagerConfigTest, TranslateLegacyConfigToDefaultHeaderVali featureEnabled(_, An())) .WillRepeatedly(Return(true)); EXPECT_CALL(context_.runtime_loader_.snapshot_, getInteger(_, _)).Times(AnyNumber()); +#ifdef ENVOY_ENABLE_UHV HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), context_, date_provider_, route_config_provider_manager_, scoped_routes_config_provider_manager_, tracer_manager_, filter_config_provider_manager_); -#ifdef ENVOY_ENABLE_UHV EXPECT_NE(nullptr, config.makeHeaderValidator(Http::Protocol::Http2)); EXPECT_TRUE(proto_config.strip_fragment_from_path()); EXPECT_FALSE(proto_config.restrict_http_methods()); @@ -3136,8 +3226,16 @@ TEST_F(HttpConnectionManagerConfigTest, TranslateLegacyConfigToDefaultHeaderVali UriPathNormalizationOptions::UNESCAPE_AND_FORWARD); EXPECT_TRUE(proto_config.http1_protocol_options().allow_chunked_length()); #else - // If UHV is disabled, config should be accepted and factory should be nullptr - EXPECT_EQ(nullptr, config.makeHeaderValidator(Http::Protocol::Http2)); + // If UHV is disabled, enabling envoy.reloadable_features.enable_universal_header_validator should + // result in rejection + EXPECT_THROW( + { + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromYaml(yaml_string), + context_, date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_, tracer_manager_, + filter_config_provider_manager_); + }, + EnvoyException); #endif } diff --git a/test/extensions/upstreams/http/BUILD b/test/extensions/upstreams/http/BUILD index de84aa12fd30..3099bea990f8 100644 --- a/test/extensions/upstreams/http/BUILD +++ b/test/extensions/upstreams/http/BUILD @@ -27,6 +27,7 @@ envoy_cc_test( "//test/mocks/http:header_validator_mocks", "//test/mocks/server:instance_mocks", "//test/test_common:registry_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/extensions/http/header_validators/envoy_default/v3:pkg_cc_proto", ], diff --git a/test/extensions/upstreams/http/config_test.cc b/test/extensions/upstreams/http/config_test.cc index 7b54807970be..8a85979c8d01 100644 --- a/test/extensions/upstreams/http/config_test.cc +++ b/test/extensions/upstreams/http/config_test.cc @@ -10,6 +10,7 @@ #include "test/mocks/http/header_validator.h" #include "test/mocks/server/instance.h" #include "test/test_common/registry.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -170,6 +171,11 @@ TEST_F(ConfigTest, HeaderValidatorConfig) { typed_config: "@type": type.googleapis.com/test.upstreams.http.CustomHeaderValidator )EOF"; + // Enable UHV runtime flag to test config instantiation + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.enable_universal_header_validator", "true"}}); + TestHeaderValidatorFactoryConfig factory; Registry::InjectFactory<::Envoy::Http::HeaderValidatorFactoryConfig> registration(factory); TestUtility::loadFromYamlAndValidate(yaml_string, options_); @@ -184,29 +190,74 @@ TEST_F(ConfigTest, HeaderValidatorConfig) { #endif } -TEST_F(ConfigTest, DefaultHeaderValidatorConfig) { +TEST_F(ConfigTest, HeaderValidatorConfigWithRuntimeDisabled) { + const std::string yaml_string = R"EOF( + use_downstream_protocol_config: + http3_protocol_options: {} + header_validation_config: + name: custom_header_validator + typed_config: + "@type": type.googleapis.com/test.upstreams.http.CustomHeaderValidator + )EOF"; + + TestHeaderValidatorFactoryConfig factory; + Registry::InjectFactory<::Envoy::Http::HeaderValidatorFactoryConfig> registration(factory); + TestUtility::loadFromYamlAndValidate(yaml_string, options_); +#ifdef ENVOY_ENABLE_UHV + ProtocolOptionsConfigImpl config(options_, server_context_); + NiceMock<::Envoy::Http::MockHeaderValidatorStats> stats; + // Without envoy.reloadable_features.enable_universal_header_validator set UHV is always disabled + EXPECT_EQ(nullptr, config.header_validator_factory_); +#else + // If UHV is disabled, providing config should result in rejection + EXPECT_THROW({ ProtocolOptionsConfigImpl config(options_, server_context_); }, EnvoyException); +#endif +} + +TEST_F(ConfigTest, DefaultHeaderValidatorConfigWithRuntimeEnabled) { + // Enable UHV runtime flag to test config translation + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.enable_universal_header_validator", "true"}}); ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig proto_config; DefaultHeaderValidatorFactoryConfigOverride factory(proto_config); Registry::InjectFactory<::Envoy::Http::HeaderValidatorFactoryConfig> registration(factory); NiceMock<::Envoy::Http::MockHeaderValidatorStats> stats; - ProtocolOptionsConfigImpl config(options_, server_context_); #ifdef ENVOY_ENABLE_UHV + ProtocolOptionsConfigImpl config(options_, server_context_); EXPECT_NE(nullptr, config.header_validator_factory_->createClientHeaderValidator( ::Envoy::Http::Protocol::Http2, stats)); EXPECT_FALSE(proto_config.http1_protocol_options().allow_chunked_length()); #else - // If UHV is disabled, config should be accepted and factory should be nullptr - EXPECT_EQ(nullptr, config.header_validator_factory_); + // If UHV is disabled but envoy.reloadable_features.enable_universal_header_validator is set, the + // config is rejected + EXPECT_THROW({ ProtocolOptionsConfigImpl config(options_, server_context_); }, EnvoyException); #endif } +TEST_F(ConfigTest, DefaultHeaderValidatorConfigWithoutOverride) { + ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig + proto_config; + DefaultHeaderValidatorFactoryConfigOverride factory(proto_config); + Registry::InjectFactory<::Envoy::Http::HeaderValidatorFactoryConfig> registration(factory); + NiceMock<::Envoy::Http::MockHeaderValidatorStats> stats; + ProtocolOptionsConfigImpl config(options_, server_context_); + // By default envoy.reloadable_features.enable_universal_header_validator is false preventing UHV + // use + EXPECT_EQ(nullptr, config.header_validator_factory_); +} + TEST_F(ConfigTest, TranslateDownstreamLegacyConfigToDefaultHeaderValidatorConfig) { const std::string yaml_string = R"EOF( use_downstream_protocol_config: http_protocol_options: allow_chunked_length: true )EOF"; + // Enable UHV runtime flag to test config translation + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.enable_universal_header_validator", "true"}}); ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig proto_config; @@ -214,14 +265,15 @@ TEST_F(ConfigTest, TranslateDownstreamLegacyConfigToDefaultHeaderValidatorConfig DefaultHeaderValidatorFactoryConfigOverride factory(proto_config); Registry::InjectFactory<::Envoy::Http::HeaderValidatorFactoryConfig> registration(factory); NiceMock<::Envoy::Http::MockHeaderValidatorStats> stats; - ProtocolOptionsConfigImpl config(options_, server_context_); #ifdef ENVOY_ENABLE_UHV + ProtocolOptionsConfigImpl config(options_, server_context_); EXPECT_NE(nullptr, config.header_validator_factory_->createClientHeaderValidator( ::Envoy::Http::Protocol::Http2, stats)); EXPECT_TRUE(proto_config.http1_protocol_options().allow_chunked_length()); #else - // If UHV is disabled, config should be accepted and factory should be nullptr - EXPECT_EQ(nullptr, config.header_validator_factory_); + // If UHV is disabled but envoy.reloadable_features.enable_universal_header_validator is set, the + // config is rejected + EXPECT_THROW({ ProtocolOptionsConfigImpl config(options_, server_context_); }, EnvoyException); #endif } @@ -231,6 +283,10 @@ TEST_F(ConfigTest, TranslateAutoLegacyConfigToDefaultHeaderValidatorConfig) { http_protocol_options: allow_chunked_length: true )EOF"; + // Enable UHV runtime flag to test config translation + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.enable_universal_header_validator", "true"}}); ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig proto_config; @@ -238,14 +294,15 @@ TEST_F(ConfigTest, TranslateAutoLegacyConfigToDefaultHeaderValidatorConfig) { DefaultHeaderValidatorFactoryConfigOverride factory(proto_config); Registry::InjectFactory<::Envoy::Http::HeaderValidatorFactoryConfig> registration(factory); NiceMock<::Envoy::Http::MockHeaderValidatorStats> stats; - ProtocolOptionsConfigImpl config(options_, server_context_); #ifdef ENVOY_ENABLE_UHV + ProtocolOptionsConfigImpl config(options_, server_context_); EXPECT_NE(nullptr, config.header_validator_factory_->createClientHeaderValidator( ::Envoy::Http::Protocol::Http2, stats)); EXPECT_TRUE(proto_config.http1_protocol_options().allow_chunked_length()); #else - // If UHV is disabled, config should be accepted and factory should be nullptr - EXPECT_EQ(nullptr, config.header_validator_factory_); + // If UHV is disabled but envoy.reloadable_features.enable_universal_header_validator is set, the + // config is rejected + EXPECT_THROW({ ProtocolOptionsConfigImpl config(options_, server_context_); }, EnvoyException); #endif } @@ -255,6 +312,10 @@ TEST_F(ConfigTest, TranslateExplicitLegacyConfigToDefaultHeaderValidatorConfig) http_protocol_options: allow_chunked_length: true )EOF"; + // Enable UHV runtime flag to test config translation + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.enable_universal_header_validator", "true"}}); ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig proto_config; @@ -262,14 +323,15 @@ TEST_F(ConfigTest, TranslateExplicitLegacyConfigToDefaultHeaderValidatorConfig) DefaultHeaderValidatorFactoryConfigOverride factory(proto_config); Registry::InjectFactory<::Envoy::Http::HeaderValidatorFactoryConfig> registration(factory); NiceMock<::Envoy::Http::MockHeaderValidatorStats> stats; - ProtocolOptionsConfigImpl config(options_, server_context_); #ifdef ENVOY_ENABLE_UHV + ProtocolOptionsConfigImpl config(options_, server_context_); EXPECT_NE(nullptr, config.header_validator_factory_->createClientHeaderValidator( ::Envoy::Http::Protocol::Http2, stats)); EXPECT_TRUE(proto_config.http1_protocol_options().allow_chunked_length()); #else - // If UHV is disabled, config should be accepted and factory should be nullptr - EXPECT_EQ(nullptr, config.header_validator_factory_); + // If UHV is disabled but envoy.reloadable_features.enable_universal_header_validator is set, the + // config is rejected + EXPECT_THROW({ ProtocolOptionsConfigImpl config(options_, server_context_); }, EnvoyException); #endif } @@ -279,6 +341,10 @@ TEST_F(ConfigTest, TranslateExplicitH2LegacyConfigToDefaultHeaderValidatorConfig http2_protocol_options: allow_connect: true )EOF"; + // Enable UHV runtime flag to test config translation + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.enable_universal_header_validator", "true"}}); ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig proto_config; @@ -286,14 +352,15 @@ TEST_F(ConfigTest, TranslateExplicitH2LegacyConfigToDefaultHeaderValidatorConfig DefaultHeaderValidatorFactoryConfigOverride factory(proto_config); Registry::InjectFactory<::Envoy::Http::HeaderValidatorFactoryConfig> registration(factory); NiceMock<::Envoy::Http::MockHeaderValidatorStats> stats; - ProtocolOptionsConfigImpl config(options_, server_context_); #ifdef ENVOY_ENABLE_UHV + ProtocolOptionsConfigImpl config(options_, server_context_); EXPECT_NE(nullptr, config.header_validator_factory_->createClientHeaderValidator( ::Envoy::Http::Protocol::Http2, stats)); EXPECT_FALSE(proto_config.http1_protocol_options().allow_chunked_length()); #else - // If UHV is disabled, config should be accepted and factory should be nullptr - EXPECT_EQ(nullptr, config.header_validator_factory_); + // If UHV is disabled but envoy.reloadable_features.enable_universal_header_validator is set, the + // config is rejected + EXPECT_THROW({ ProtocolOptionsConfigImpl config(options_, server_context_); }, EnvoyException); #endif } diff --git a/test/integration/BUILD b/test/integration/BUILD index 642b899f7bf0..4968a81ef8ba 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -792,7 +792,7 @@ envoy_cc_test( ], # As this test has many H1/H2/v4/v6 tests it takes a while to run. # Shard it enough to bring the run time in line with other integration tests. - shard_count = 8, + shard_count = 16, tags = [ "cpu:3", ], diff --git a/test/integration/default_header_validator_integration_test.cc b/test/integration/default_header_validator_integration_test.cc index 8a37c43b5115..63547e83bad2 100644 --- a/test/integration/default_header_validator_integration_test.cc +++ b/test/integration/default_header_validator_integration_test.cc @@ -135,25 +135,25 @@ TEST_P(DownstreamUhvIntegrationTest, BackslashInUriPathConversionWithUhvOverride {":path", "/path\\with%5Cback%5Cslashes"}, {":scheme", "http"}, {":authority", "host"}}); -#ifdef ENVOY_ENABLE_UHV - // By default Envoy disconnects connection on protocol errors - ASSERT_TRUE(codec_client_->waitForDisconnect()); - if (downstream_protocol_ != Http::CodecType::HTTP2) { - ASSERT_TRUE(response->complete()); - EXPECT_EQ("400", response->headers().getStatusValue()); + if (use_universal_header_validator_) { + // By default Envoy disconnects connection on protocol errors + ASSERT_TRUE(codec_client_->waitForDisconnect()); + if (downstream_protocol_ != Http::CodecType::HTTP2) { + ASSERT_TRUE(response->complete()); + EXPECT_EQ("400", response->headers().getStatusValue()); + } else { + ASSERT_TRUE(response->reset()); + EXPECT_EQ(Http::StreamResetReason::ConnectionTermination, response->resetReason()); + } } else { - ASSERT_TRUE(response->reset()); - EXPECT_EQ(Http::StreamResetReason::ConnectionTermination, response->resetReason()); - } -#else - waitForNextUpstreamRequest(); + waitForNextUpstreamRequest(); - EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path/with%5Cback%5Cslashes"); + EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path/with%5Cback%5Cslashes"); - // Send a headers only response. - upstream_request_->encodeHeaders(default_response_headers_, true); - ASSERT_TRUE(response->waitForEndStream()); -#endif + // Send a headers only response. + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response->waitForEndStream()); + } } // By default the `allow_non_compliant_characters_in_path` == true and UHV behaves just like legacy @@ -222,11 +222,11 @@ TEST_P(DownstreamUhvIntegrationTest, UrlEncodedTripletsCasePreservedWithUhvOverr {":authority", "host"}}); waitForNextUpstreamRequest(); -#ifdef ENVOY_ENABLE_UHV - EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path/with%3Bmixed%5Ccase%FEsequences"); -#else - EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path/with%3bmixed%5Ccase%Fesequences"); -#endif + if (use_universal_header_validator_) { + EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path/with%3Bmixed%5Ccase%FEsequences"); + } else { + EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path/with%3bmixed%5Ccase%Fesequences"); + } // Send a headers only response. upstream_request_->encodeHeaders(default_response_headers_, true); ASSERT_TRUE(response->waitForEndStream()); @@ -375,25 +375,25 @@ TEST_P(DownstreamUhvIntegrationTest, MalformedUrlEncodedTripletsRejectedWithUhvO {":path", "/path%Z%30with%XYbad%7Jencoding%A"}, {":scheme", "http"}, {":authority", "host"}}); -#ifdef ENVOY_ENABLE_UHV - // By default Envoy disconnects connection on protocol errors - ASSERT_TRUE(codec_client_->waitForDisconnect()); - if (downstream_protocol_ != Http::CodecType::HTTP2) { - ASSERT_TRUE(response->complete()); - EXPECT_EQ("400", response->headers().getStatusValue()); + if (use_universal_header_validator_) { + // By default Envoy disconnects connection on protocol errors + ASSERT_TRUE(codec_client_->waitForDisconnect()); + if (downstream_protocol_ != Http::CodecType::HTTP2) { + ASSERT_TRUE(response->complete()); + EXPECT_EQ("400", response->headers().getStatusValue()); + } else { + ASSERT_TRUE(response->reset()); + EXPECT_EQ(Http::StreamResetReason::ConnectionTermination, response->resetReason()); + } } else { - ASSERT_TRUE(response->reset()); - EXPECT_EQ(Http::StreamResetReason::ConnectionTermination, response->resetReason()); - } -#else - waitForNextUpstreamRequest(); + waitForNextUpstreamRequest(); - EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path%Z0with%XYbad%7Jencoding%A"); + EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path%Z0with%XYbad%7Jencoding%A"); - // Send a headers only response. - upstream_request_->encodeHeaders(default_response_headers_, true); - ASSERT_TRUE(response->waitForEndStream()); -#endif + // Send a headers only response. + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response->waitForEndStream()); + } } // By default the `uhv_allow_malformed_url_encoding` == true and UHV behaves just like legacy path @@ -455,20 +455,20 @@ TEST_P(DownstreamUhvIntegrationTest, UhvAllowsPercent00WithOverride) { {":path", "/path%00/to/something"}, {":scheme", "http"}, {":authority", "host"}}); -#ifdef ENVOY_ENABLE_UHV - waitForNextUpstreamRequest(); + if (use_universal_header_validator_) { + waitForNextUpstreamRequest(); - EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path%00/to/something"); + EXPECT_EQ(upstream_request_->headers().getPathValue(), "/path%00/to/something"); - // Send a headers only response. - upstream_request_->encodeHeaders(default_response_headers_, true); - ASSERT_TRUE(response->waitForEndStream()); -#else - // In legacy mode %00 in URL path always causes request to be rejected - ASSERT_TRUE(response->waitForEndStream()); - ASSERT_TRUE(response->complete()); - EXPECT_EQ("400", response->headers().getStatusValue()); -#endif + // Send a headers only response. + upstream_request_->encodeHeaders(default_response_headers_, true); + ASSERT_TRUE(response->waitForEndStream()); + } else { + // In legacy mode %00 in URL path always causes request to be rejected + ASSERT_TRUE(response->waitForEndStream()); + ASSERT_TRUE(response->complete()); + EXPECT_EQ("400", response->headers().getStatusValue()); + } } } // namespace Envoy diff --git a/test/integration/header_integration_test.cc b/test/integration/header_integration_test.cc index 35b1af2e39b7..b9905ff94054 100644 --- a/test/integration/header_integration_test.cc +++ b/test/integration/header_integration_test.cc @@ -24,12 +24,13 @@ namespace Envoy { namespace { std::string ipSuppressEnvoyHeadersTestParamsToString( - const ::testing::TestParamInfo>& params) { + const ::testing::TestParamInfo>& params) { return fmt::format( - "{}_{}", + "{}_{}_{}", TestUtility::ipTestParamsToString( ::testing::TestParamInfo(std::get<0>(params.param), 0)), - std::get<1>(params.param) ? "with_x_envoy_from_router" : "without_x_envoy_from_router"); + std::get<1>(params.param) ? "with_x_envoy_from_router" : "without_x_envoy_from_router", + std::get<2>(params.param) ? "with_UHV" : "without_UHV"); } void disableHeaderValueOptionAppend( @@ -178,7 +179,7 @@ stat_prefix: header_test } // namespace class HeaderIntegrationTest - : public testing::TestWithParam>, + : public testing::TestWithParam>, public HttpIntegrationTest { public: HeaderIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, std::get<0>(GetParam())) {} @@ -422,6 +423,9 @@ class HeaderIntegrationTest }; } + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + std::get<2>(GetParam()) ? "true" : "false"); + HttpIntegrationTest::initialize(); } @@ -464,10 +468,19 @@ class HeaderIntegrationTest FakeStreamPtr eds_stream_; }; +#ifdef ENVOY_ENABLE_UHV +INSTANTIATE_TEST_SUITE_P( + IpVersionsSuppressEnvoyHeaders, HeaderIntegrationTest, + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), testing::Bool(), + testing::Values(true)), + ipSuppressEnvoyHeadersTestParamsToString); +#else INSTANTIATE_TEST_SUITE_P( IpVersionsSuppressEnvoyHeaders, HeaderIntegrationTest, - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), testing::Bool()), + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), testing::Bool(), + testing::Values(false)), ipSuppressEnvoyHeadersTestParamsToString); +#endif TEST_P(HeaderIntegrationTest, WeightedClusterWithClusterHeader) { config_helper_.addConfigModifier( diff --git a/test/integration/http2_flood_integration_test.cc b/test/integration/http2_flood_integration_test.cc index 2065ace59d3b..09839ea0ddb6 100644 --- a/test/integration/http2_flood_integration_test.cc +++ b/test/integration/http2_flood_integration_test.cc @@ -51,6 +51,7 @@ std::string testParamsToString( // destructor stops Envoy the SocketInterfaceSwap destructor needs to run after it. This order of // multiple inheritance ensures that SocketInterfaceSwap destructor runs after // Http2FrameIntegrationTest destructor completes. +// TODO(#28841) parameterize to run with and without UHV class Http2FloodMitigationTest : public SocketInterfaceSwap, public testing::TestWithParam>, @@ -72,6 +73,13 @@ class Http2FloodMitigationTest deferredProcessing(GetParam()) ? "true" : "false"); } + void enableUhvRuntimeFeature() { +#ifdef ENVOY_ENABLE_UHV + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif + } + protected: void initializeUpstreamFloodTest(); std::vector serializeFrames(const Http2Frame& frame, uint32_t num_frames); @@ -1113,6 +1121,7 @@ TEST_P(Http2FloodMitigationTest, WindowUpdate) { // Verify that the HTTP/2 connection is terminated upon receiving invalid HEADERS frame. TEST_P(Http2FloodMitigationTest, ZerolenHeader) { + enableUhvRuntimeFeature(); useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%"); beginSession(); @@ -1232,6 +1241,7 @@ TEST_P(Http2FloodMitigationTest, UpstreamEmptyHeaders) { // Verify that the HTTP/2 connection is terminated upon receiving invalid HEADERS frame. TEST_P(Http2FloodMitigationTest, UpstreamZerolenHeader) { + enableUhvRuntimeFeature(); initializeUpstreamFloodTest(); // Send client request which will send an upstream request. codec_client_ = makeHttpConnection(lookupPort("http")); @@ -1254,6 +1264,7 @@ TEST_P(Http2FloodMitigationTest, UpstreamZerolenHeader) { // Verify that the HTTP/2 connection is terminated upon receiving invalid HEADERS frame. TEST_P(Http2FloodMitigationTest, UpstreamZerolenHeaderAllowed) { + enableUhvRuntimeFeature(); useAccessLog("%RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%"); config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, ""); diff --git a/test/integration/http_protocol_integration.cc b/test/integration/http_protocol_integration.cc index d691ee4c53b6..9ea5174638f2 100644 --- a/test/integration/http_protocol_integration.cc +++ b/test/integration/http_protocol_integration.cc @@ -33,12 +33,20 @@ std::vector HttpProtocolIntegrationTest::getProtocolTest defer_processing_values.push_back(true); } + std::vector use_header_validator_values; +#ifdef ENVOY_ENABLE_UHV + use_header_validator_values.push_back(true); +#else + use_header_validator_values.push_back(false); +#endif for (Http1ParserImpl http1_implementation : http1_implementations) { for (Http2Impl http2_implementation : http2_implementations) { for (bool defer_processing : defer_processing_values) { - ret.push_back(HttpProtocolTestParams{ip_version, downstream_protocol, - upstream_protocol, http1_implementation, - http2_implementation, defer_processing}); + for (bool use_header_validator : use_header_validator_values) { + ret.push_back(HttpProtocolTestParams{ + ip_version, downstream_protocol, upstream_protocol, http1_implementation, + http2_implementation, defer_processing, use_header_validator}); + } } } } @@ -90,7 +98,8 @@ std::string HttpProtocolIntegrationTest::protocolTestParamsToString( TestUtility::http1ParserImplToString(params.param.http1_implementation), http2ImplementationToString(params.param.http2_implementation), params.param.defer_processing_backedup_streams ? "WithDeferredProcessing" - : "NoDeferredProcessing"); + : "NoDeferredProcessing", + params.param.use_universal_header_validator ? "Uhv" : "Legacy"); } void HttpProtocolIntegrationTest::setUpstreamOverrideStreamErrorOnInvalidHttpMessage() { diff --git a/test/integration/http_protocol_integration.h b/test/integration/http_protocol_integration.h index 79ff2103d097..25c96660a997 100644 --- a/test/integration/http_protocol_integration.h +++ b/test/integration/http_protocol_integration.h @@ -14,6 +14,7 @@ struct HttpProtocolTestParams { Http1ParserImpl http1_implementation; Http2Impl http2_implementation; bool defer_processing_backedup_streams; + bool use_universal_header_validator; }; // Allows easy testing of Envoy code for HTTP/HTTP2 upstream/downstream. @@ -70,12 +71,15 @@ class HttpProtocolIntegrationTest : public testing::TestWithParam(GetParam()).defer_processing_backedup_streams ? "true" : "false"); + config_helper_.addRuntimeOverride( + "envoy.reloadable_features.enable_universal_header_validator", + std::get<0>(GetParam()).use_universal_header_validator ? "true" : "false"); } static std::string testParamsToString( const ::testing::TestParamInfo>& params) { diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index b716086e0cd5..b37cdd34b415 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -860,6 +860,10 @@ TEST_P(IntegrationTest, UpstreamDisconnectWithTwoRequests) { } TEST_P(IntegrationTest, TestSmuggling) { +#ifdef ENVOY_ENABLE_UHV + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif config_helper_.disableDelayClose(); initialize(); @@ -921,6 +925,10 @@ TEST_P(IntegrationTest, TestSmuggling) { } TEST_P(IntegrationTest, TestInvalidTransferEncoding) { +#ifdef ENVOY_ENABLE_UHV + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif config_helper_.disableDelayClose(); initialize(); @@ -966,6 +974,10 @@ TEST_P(IntegrationTest, TestPipelinedResponses) { } TEST_P(IntegrationTest, TestServerAllowChunkedLength) { +#ifdef ENVOY_ENABLE_UHV + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif config_helper_.addConfigModifier( [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& hcm) -> void { @@ -1000,6 +1012,10 @@ TEST_P(IntegrationTest, TestServerAllowChunkedLength) { } TEST_P(IntegrationTest, TestClientAllowChunkedLength) { +#ifdef ENVOY_ENABLE_UHV + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() == 1, ""); if (fake_upstreams_[0]->httpType() == Http::CodecType::HTTP1) { @@ -2176,6 +2192,10 @@ TEST_P(IntegrationTest, ConnectWithChunkedBody) { } TEST_P(IntegrationTest, ConnectWithTEChunked) { +#ifdef ENVOY_ENABLE_UHV + config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator", + "true"); +#endif config_helper_.addConfigModifier( [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& hcm) -> void { ConfigHelper::setConnectConfig(hcm, false, false); }); diff --git a/test/integration/integration_test.h b/test/integration/integration_test.h index 915e10cab9bd..59152ab5eadb 100644 --- a/test/integration/integration_test.h +++ b/test/integration/integration_test.h @@ -7,6 +7,7 @@ // A test class for testing HTTP/1.1 upstream and downstreams namespace Envoy { +// TODO(#28841) parameterize to run with and without UHV class IntegrationTest : public testing::TestWithParam>, public HttpIntegrationTest { @@ -21,6 +22,7 @@ class IntegrationTest const Http1ParserImpl http1_implementation_; }; +// TODO(#28841) parameterize to run with and without UHV class UpstreamEndpointIntegrationTest : public testing::TestWithParam>, public HttpIntegrationTest { diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index e7dbaffbe58b..a2402bafe07f 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -71,10 +71,10 @@ TEST_P(ProtocolIntegrationTest, ShutdownWithActiveConnPoolConnections) { } TEST_P(ProtocolIntegrationTest, LogicalDns) { -#ifdef ENVOY_ENABLE_UHV - // TODO(#27132): auto_host_rewrite is broken for IPv6 and is failing UHV validation - return; -#endif + if (use_universal_header_validator_) { + // TODO(#27132): auto_host_rewrite is broken for IPv6 and is failing UHV validation + return; + } config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() == 1, ""); auto& cluster = *bootstrap.mutable_static_resources()->mutable_clusters(0); @@ -3947,10 +3947,10 @@ TEST_P(ProtocolIntegrationTest, UpstreamDisconnectBeforeResponseCompleteWireByte TEST_P(DownstreamProtocolIntegrationTest, BadRequest) { config_helper_.disableDelayClose(); // we only care about upstream protocol. -#ifdef ENVOY_ENABLE_UHV - // permissive parsing is enabled - return; -#endif + if (use_universal_header_validator_) { + // permissive parsing is enabled + return; + } if (downstreamProtocol() != Http::CodecType::HTTP1) { return; @@ -4032,10 +4032,10 @@ TEST_P(DownstreamProtocolIntegrationTest, ValidateUpstreamHeaders) { } TEST_P(ProtocolIntegrationTest, ValidateUpstreamMixedCaseHeaders) { -#ifdef ENVOY_ENABLE_UHV - // UHV does not support this case so far. - return; -#endif + if (use_universal_header_validator_) { + // UHV does not support this case so far. + return; + } if (upstreamProtocol() != Http::CodecType::HTTP1) { autonomous_allow_incomplete_streams_ = true; autonomous_upstream_ = true; @@ -4081,11 +4081,11 @@ TEST_P(ProtocolIntegrationTest, ValidateUpstreamMixedCaseHeaders) { } TEST_P(ProtocolIntegrationTest, ValidateUpstreamHeadersWithOverride) { -#ifdef ENVOY_ENABLE_UHV - // UHV always validated headers before sending them upstream. This test is not applicable - // when UHV is enabled. - return; -#endif + if (use_universal_header_validator_) { + // UHV always validated headers before sending them upstream. This test is not applicable + // when UHV is enabled. + return; + } if (upstreamProtocol() == Http::CodecType::HTTP3) { testing_upstream_intentionally_ = true; } @@ -4201,10 +4201,10 @@ TEST_P(ProtocolIntegrationTest, BufferContinue) { } TEST_P(DownstreamProtocolIntegrationTest, ContentLengthSmallerThanPayload) { -#ifdef ENVOY_ENABLE_UHV - // UHV does not track consistency of content-length and amount of DATA in HTTP/2 - return; -#endif + if (use_universal_header_validator_) { + // UHV does not track consistency of content-length and amount of DATA in HTTP/2 + return; + } initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -4233,10 +4233,10 @@ TEST_P(DownstreamProtocolIntegrationTest, ContentLengthSmallerThanPayload) { } TEST_P(DownstreamProtocolIntegrationTest, ContentLengthLargerThanPayload) { -#ifdef ENVOY_ENABLE_UHV - // UHV does not track consistency of content-length and amount of DATA in HTTP/2 - return; -#endif + if (use_universal_header_validator_) { + // UHV does not track consistency of content-length and amount of DATA in HTTP/2 + return; + } if (downstreamProtocol() == Http::CodecType::HTTP1) { // HTTP/1.x request rely on Content-Length header to determine payload length. So there is no @@ -4613,20 +4613,20 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidSchemeHeaderWithWhitespace) { {":scheme", "/admin http"}, {":authority", "sni.lyft.com"}}); -#ifdef ENVOY_ENABLE_UHV - if (downstreamProtocol() != Http::CodecType::HTTP1) { - ASSERT_TRUE(response->waitForReset()); - EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("invalid")); - return; - } -#else - if (downstreamProtocol() == Http::CodecType::HTTP2 && - GetParam().http2_implementation == Http2Impl::Nghttp2) { - ASSERT_TRUE(response->waitForReset()); - EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("invalid")); - return; + if (use_universal_header_validator_) { + if (downstreamProtocol() != Http::CodecType::HTTP1) { + ASSERT_TRUE(response->waitForReset()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("invalid")); + return; + } + } else { + if (downstreamProtocol() == Http::CodecType::HTTP2 && + GetParam().http2_implementation == Http2Impl::Nghttp2) { + ASSERT_TRUE(response->waitForReset()); + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("invalid")); + return; + } } -#endif // Other HTTP codecs accept the bad scheme but the Envoy should replace it with a valid one. waitForNextUpstreamRequest(); if (upstreamProtocol() == Http::CodecType::HTTP1) { @@ -4674,14 +4674,15 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidTrailer) { ASSERT_TRUE(response->reset()); EXPECT_EQ(Http::StreamResetReason::ConnectionTermination, response->resetReason()); } -#ifndef ENVOY_ENABLE_UHV - // TODO(#24620) UHV does not include the DPE prefix in the downstream protocol error reasons - if (downstreamProtocol() != Http::CodecType::HTTP3) { - // TODO(#24630) QUIC also does not include the DPE prefix in the downstream protocol error - // reasons - EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("DPE")); + + if (!use_universal_header_validator_) { + // TODO(#24620) UHV does not include the DPE prefix in the downstream protocol error reasons + if (downstreamProtocol() != Http::CodecType::HTTP3) { + // TODO(#24630) QUIC also does not include the DPE prefix in the downstream protocol error + // reasons + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("DPE")); + } } -#endif } // Verify that stream is reset with invalid trailers, when configured. @@ -4728,14 +4729,14 @@ TEST_P(DownstreamProtocolIntegrationTest, InvalidTrailerStreamError) { codec_client_->close(); ASSERT_TRUE(response->reset()); EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason()); -#ifndef ENVOY_ENABLE_UHV - // TODO(#24620) UHV does not include the DPE prefix in the downstream protocol error reasons - if (downstreamProtocol() != Http::CodecType::HTTP3) { - // TODO(#24630) QUIC also does not include the DPE prefix in the downstream protocol error - // reasons - EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("DPE")); + if (!use_universal_header_validator_) { + // TODO(#24620) UHV does not include the DPE prefix in the downstream protocol error reasons + if (downstreamProtocol() != Http::CodecType::HTTP3) { + // TODO(#24630) QUIC also does not include the DPE prefix in the downstream protocol error + // reasons + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("DPE")); + } } -#endif } TEST_P(DownstreamProtocolIntegrationTest, UnknownPseudoHeader) { @@ -4756,15 +4757,15 @@ TEST_P(DownstreamProtocolIntegrationTest, UnknownPseudoHeader) { {":scheme", "http"}, {":authority", "host"}}); ASSERT_TRUE(response->waitForReset()); -#ifdef ENVOY_ENABLE_UHV - EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("invalid")); -#else - EXPECT_THAT(waitForAccessLog(access_log_name_), - HasSubstr((downstreamProtocol() == Http::CodecType::HTTP2 && - GetParam().http2_implementation == Http2Impl::Oghttp2) - ? "violation" - : "invalid")); -#endif + if (use_universal_header_validator_) { + EXPECT_THAT(waitForAccessLog(access_log_name_), HasSubstr("invalid")); + } else { + EXPECT_THAT(waitForAccessLog(access_log_name_), + HasSubstr((downstreamProtocol() == Http::CodecType::HTTP2 && + GetParam().http2_implementation == Http2Impl::Oghttp2) + ? "violation" + : "invalid")); + } } } // namespace Envoy From 6b90ae8049b92b15b8f78dec534f0a7c99fceff9 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Mon, 11 Sep 2023 07:47:11 -0700 Subject: [PATCH 52/55] network: fix bug in unix-domain socket peerAddress() on macOS (#29524) network: fix bug in unix-domain socket peerAddress() On some versions of macOS, `getpeeraddr` returns a length of 3, which is enough to read the `ss_family` and include a 1-byte `sun_path`, but Envoy was incorrectly treating this as invalid. Signed-off-by: Greg Greenway --- changelogs/current.yaml | 3 +++ source/common/network/io_socket_handle_impl.cc | 18 ++---------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ccb67204a85..c45710a1c4e0 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -135,6 +135,9 @@ bug_fixes: - area: router check tool change: | Fixed a bug where the route coverage is not correctly calculated when a route has weighted clusters. +- area: unix domain sockets + change: | + Fixed a crash on some versions of macOS when using a listener on a unix-domain socket. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/network/io_socket_handle_impl.cc b/source/common/network/io_socket_handle_impl.cc index 6c8ee4ecb12e..0838985e654b 100644 --- a/source/common/network/io_socket_handle_impl.cc +++ b/source/common/network/io_socket_handle_impl.cc @@ -17,21 +17,6 @@ using Envoy::Api::SysCallSizeResult; namespace Envoy { namespace { -/** - * On different platforms the sockaddr struct for unix domain - * sockets is different. We use this function to get the - * length of the platform specific struct. - */ -constexpr socklen_t udsAddressLength() { -#if defined(__APPLE__) - return sizeof(sockaddr); -#elif defined(WIN32) - return sizeof(sockaddr_un); -#else - return sizeof(sa_family_t); -#endif -} - constexpr int messageTypeContainsIP() { #ifdef IP_RECVDSTADDR return IP_RECVDSTADDR; @@ -575,7 +560,8 @@ Address::InstanceConstSharedPtr IoSocketHandleImpl::peerAddress() { fmt::format("getpeername failed for '{}': {}", fd_, errorDetails(result.errno_))); } - if (ss_len == udsAddressLength() && ss.ss_family == AF_UNIX) { + if (ss_len >= (offsetof(sockaddr_storage, ss_family) + sizeof(ss.ss_family)) && + ss.ss_family == AF_UNIX) { // For Unix domain sockets, can't find out the peer name, but it should match our own // name for the socket (i.e. the path should match, barring any namespace or other // mechanisms to hide things, of which there are many). From 95e84e6e94547a683bd00623777aaa41f13efc93 Mon Sep 17 00:00:00 2001 From: yanjunxiang-google <78807980+yanjunxiang-google@users.noreply.github.com> Date: Mon, 11 Sep 2023 10:48:28 -0400 Subject: [PATCH 53/55] Changing the internal_listener related extensions to stable. (#29493) --------- Signed-off-by: Yanjun Xiang --- .../extensions/bootstrap/internal_listener/v3/BUILD | 5 +---- .../internal_listener/v3/internal_listener.proto | 3 --- source/extensions/extensions_metadata.yaml | 12 ++++++------ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/api/envoy/extensions/bootstrap/internal_listener/v3/BUILD b/api/envoy/extensions/bootstrap/internal_listener/v3/BUILD index ec1e778e06e5..ee92fb652582 100644 --- a/api/envoy/extensions/bootstrap/internal_listener/v3/BUILD +++ b/api/envoy/extensions/bootstrap/internal_listener/v3/BUILD @@ -5,8 +5,5 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") licenses(["notice"]) # Apache 2 api_proto_package( - deps = [ - "@com_github_cncf_udpa//udpa/annotations:pkg", - "@com_github_cncf_udpa//xds/annotations/v3:pkg", - ], + deps = ["@com_github_cncf_udpa//udpa/annotations:pkg"], ) diff --git a/api/envoy/extensions/bootstrap/internal_listener/v3/internal_listener.proto b/api/envoy/extensions/bootstrap/internal_listener/v3/internal_listener.proto index b5a130b3b758..bdb52f41e84a 100644 --- a/api/envoy/extensions/bootstrap/internal_listener/v3/internal_listener.proto +++ b/api/envoy/extensions/bootstrap/internal_listener/v3/internal_listener.proto @@ -4,8 +4,6 @@ package envoy.extensions.bootstrap.internal_listener.v3; import "google/protobuf/wrappers.proto"; -import "xds/annotations/v3/status.proto"; - import "udpa/annotations/status.proto"; import "validate/validate.proto"; @@ -14,7 +12,6 @@ option java_outer_classname = "InternalListenerProto"; option java_multiple_files = true; option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/bootstrap/internal_listener/v3;internal_listenerv3"; option (udpa.annotations.file_status).package_version_status = ACTIVE; -option (xds.annotations.v3.file_status).work_in_progress = true; // [#protodoc-title: Internal Listener] // Internal Listener :ref:`overview `. diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 9476537cd5df..f63117e8aff8 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -57,8 +57,8 @@ envoy.access_loggers.wasm: envoy.bootstrap.internal_listener: categories: - envoy.bootstrap - security_posture: unknown - status: alpha + security_posture: robust_to_untrusted_downstream_and_upstream + status: stable type_urls: - envoy.extensions.bootstrap.internal_listener.v3.InternalListener envoy.bootstrap.wasm: @@ -872,8 +872,8 @@ envoy.internal_redirect_predicates.safe_cross_scheme: envoy.io_socket.user_space: categories: - envoy.io_socket - security_posture: unknown - status: alpha + security_posture: robust_to_untrusted_downstream_and_upstream + status: stable undocumented: true envoy.matching.common_inputs.environment_variable: categories: @@ -1159,8 +1159,8 @@ envoy.transport_sockets.alts: envoy.transport_sockets.internal_upstream: categories: - envoy.transport_sockets.upstream - security_posture: unknown - status: alpha + security_posture: robust_to_untrusted_downstream_and_upstream + status: stable type_urls: - envoy.extensions.transport_sockets.internal_upstream.v3.InternalUpstreamTransport envoy.transport_sockets.raw_buffer: From accc5716727bb34c050100f33a25bc4ae0cba31d Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 11 Sep 2023 16:03:41 +0100 Subject: [PATCH 54/55] code/format: Fix/update and shift owner checks -> `envoy.code.check` (#29538) Signed-off-by: Ryan Northey --- BUILD | 2 + CODEOWNERS | 3 +- tools/base/requirements.in | 2 +- tools/base/requirements.txt | 6 +- tools/code/BUILD | 8 + tools/code_format/check_format.py | 558 +++++++++++++----------------- tools/local_fix_format.sh | 31 +- 7 files changed, 269 insertions(+), 341 deletions(-) diff --git a/BUILD b/BUILD index 8e5e07c3073c..34c8e7a4b633 100644 --- a/BUILD +++ b/BUILD @@ -6,6 +6,8 @@ exports_files([ ".clang-format", "pytest.ini", ".coveragerc", + "CODEOWNERS", + "OWNERS.md", ]) alias( diff --git a/CODEOWNERS b/CODEOWNERS index af350f9c058e..9827474281cd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -363,6 +363,7 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123 /contrib/vcl/ @florincoras @KfreeZ /contrib/hyperscan/ @zhxie @soulxu /contrib/language/ @realtimetodie @realtimetodie -/contrib/dlb/ @mattklein123 @daixiang0 +# TODO(phlax): move this extension (https://github.com/envoyproxy/envoy/issues/29550) +/contrib/network/connection_balance/dlb @mattklein123 @daixiang0 /contrib/qat/ @giantcroc @soulxu /contrib/generic_proxy/ @wbpcode @soulxu @zhaohuabing @rojkov @htuch diff --git a/tools/base/requirements.in b/tools/base/requirements.in index 81e3d3926be2..dac380272b35 100644 --- a/tools/base/requirements.in +++ b/tools/base/requirements.in @@ -7,7 +7,7 @@ coloredlogs cryptography>=41.0.1 dependatool>=0.2.2 envoy.base.utils>=0.4.11 -envoy.code.check>=0.5.7 +envoy.code.check>=0.5.8 envoy.dependency.check>=0.1.7 envoy.distribution.release>=0.0.9 envoy.distribution.repo>=0.0.8 diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 631577fbb63f..4bb9504c318a 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -439,9 +439,9 @@ envoy-base-utils==0.4.11 \ # envoy-docs-sphinx-runner # envoy-github-release # envoy-gpg-sign -envoy-code-check==0.5.7 \ - --hash=sha256:5cd2c5a6a9e4b85bc3342cea58f1b6200ebf5ef926e7b8320a1f73bd49145811 \ - --hash=sha256:628b8ff787278e130cc576302a9f383c3a0a645841e5f7323c72359dc59c2935 +envoy-code-check==0.5.8 \ + --hash=sha256:03f32588cc9ed98ab6703cbca6f81df1527db71c3a0f962be6a6084ded40d528 \ + --hash=sha256:2b12c51098c78d393823cf055a54e9308c37321d769041f01a2f35b04074d6f3 # via -r requirements.in envoy-dependency-check==0.1.8 \ --hash=sha256:ac9820e446bb44e05121e5c93c210f40ca37076580b0d082da2c63e7784c338a \ diff --git a/tools/code/BUILD b/tools/code/BUILD index 97f87721da64..77466a7b4905 100644 --- a/tools/code/BUILD +++ b/tools/code/BUILD @@ -31,6 +31,8 @@ jq( envoy_entry_point( name = "check", args = [ + "--codeowners=$(location //:CODEOWNERS)", + "--owners=$(location //:OWNERS.md)", "--extensions_build_config=$(location :extensions_build_config)", "--extensions_fuzzed_count=%s" % FUZZ_FILTER_COUNT, "--path=%s" % PATH, @@ -39,6 +41,8 @@ envoy_entry_point( ], data = [ ":extensions_build_config", + "//:CODEOWNERS", + "//:OWNERS.md", "@com_github_aignas_rules_shellcheck//:shellcheck", "@go_sdk//:bin/gofmt", ], @@ -54,6 +58,8 @@ genrule( -l warn \ -v warn \ -x mobile/dist/envoy-pom.xml \ + --codeowners=$(location //:CODEOWNERS) \ + --owners=$(location //:OWNERS.md) \ --extensions_build_config=$(location :extensions_build_config) \ --extensions_fuzzed_count=%s \ --path=%s \ @@ -68,6 +74,8 @@ genrule( tools = [ ":check", ":extensions_build_config", + "//:CODEOWNERS", + "//:OWNERS.md", "//bazel:volatile-scm-hash", "@com_github_aignas_rules_shellcheck//:shellcheck", "@go_sdk//:bin/gofmt", diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index 9debd78cef6d..e6a89940fc81 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -13,7 +13,7 @@ import traceback import shutil from functools import cached_property -from typing import Callable, Dict, List, Pattern, Tuple, Union +from typing import Callable, Dict, Iterator, List, Pattern, Tuple, Union # The way this script is currently used (ie no bazel) it relies on system deps. # As `pyyaml` is present in `envoy-build-ubuntu` it should be safe to use here. @@ -122,15 +122,109 @@ def _normalize( class FormatChecker: - def __init__(self, args, source_path): - self.args = args - self.config_path = args.config_path - self.operation_type = args.operation_type - self.source_path = source_path - self.target_path = args.target_path - self.api_prefix = args.api_prefix - self.envoy_build_rule_check = not args.skip_envoy_build_rule_check - self._include_dir_order = args.include_dir_order + def __init__(self, args): + self._args = args + # TODO(phlax): completely rewrite file discovery in this file - its a mess + self.source_path = os.getcwd() + if self.args.path: + os.chdir(self.args.path) + os.environ["BAZEL_EXECROOT"] = self.source_path + self._include_dir_order = self.args.include_dir_order + + @property + def api_prefix(self): + return self.args.api_prefix + + @property + def config_path(self): + return self.args.config_path + + @property + def envoy_build_rule_check(self): + return not self.args.skip_envoy_build_rule_check + + @property + def excluded_prefixes(self): + return ( + self.config.paths["excluded"] + tuple(self.args.add_excluded_prefixes) + if self.args.add_excluded_prefixes else self.config.paths["excluded"]) + + @property + def error_messages(self): + return [] + + @property + def operation_type(self): + return self.args.operation_type + + @cached_property + def args(self): + parser = argparse.ArgumentParser(description="Check or fix file format.") + parser.add_argument( + "operation_type", + type=str, + choices=["check", "fix"], + help="specify if the run should 'check' or 'fix' format.") + parser.add_argument( + "target_path", + nargs="*", + default=["."], + help="specify the root directory for the script to recurse over. Default '.'.") + parser.add_argument("--path", default=".", help="specify the root path.") + parser.add_argument( + "--config_path", + default="./tools/code_format/config.yaml", + help="specify the config path. Default './tools/code_format/config.yaml'.") + parser.add_argument( + "--fail_on_diff", + action="store_true", + help="exit with failure if running fix produces changes.") + parser.add_argument( + "--add-excluded-prefixes", type=str, nargs="+", help="exclude additional prefixes.") + parser.add_argument( + "-j", + "--num-workers", + type=int, + default=multiprocessing.cpu_count(), + help="number of worker processes to use; defaults to one per core.") + parser.add_argument( + "--api-prefix", type=str, default="./api/", help="path of the API tree.") + parser.add_argument( + "--skip_envoy_build_rule_check", + action="store_true", + help="skip checking for '@envoy//' prefix in build rules.") + parser.add_argument( + "--namespace_check", + type=str, + nargs="?", + default="Envoy", + help="specify namespace check string. Default 'Envoy'.") + parser.add_argument( + "--namespace_check_excluded_paths", + type=str, + nargs="+", + default=[], + help="exclude paths from the namespace_check.") + parser.add_argument( + "--build_fixer_check_excluded_paths", + type=str, + nargs="+", + default=[], + help="exclude paths from envoy_build_fixer check.") + parser.add_argument( + "--bazel_tools_check_excluded_paths", + type=str, + nargs="+", + default=[], + help="exclude paths from bazel_tools check.") + parser.add_argument("--buildifier_path", type=str, help="Path to buildifier executable.") + parser.add_argument("--buildozer_path", type=str, help="Path to buildozer executable.") + parser.add_argument( + "--include_dir_order", + type=str, + default="", + help="specify the header block include directory order.") + return parser.parse_args(self._args) @cached_property def build_fixer_check_excluded_paths(self): @@ -765,44 +859,38 @@ def fix_build_line(self, file_path, line, line_number): def fix_build_path(self, file_path): self.evaluate_lines(file_path, functools.partial(self.fix_build_line, file_path)) - error_messages = [] # TODO(htuch): Add API specific BUILD fixer script. - if not self.is_build_fixer_excluded_file(file_path) and not self.is_api_file( - file_path) and not self.is_starlark_file(file_path) and not self.is_workspace_file( - file_path): - if os.system("%s %s %s" % - (self.config.paths["build_fixer_py"], file_path, file_path)) != 0: - error_messages += ["envoy_build_fixer rewrite failed for file: %s" % file_path] - - if os.system("%s -lint=fix -mode=fix %s" % (self.config.buildifier_path, file_path)) != 0: - error_messages += ["buildifier rewrite failed for file: %s" % file_path] + if self._run_build_fixer(file_path): + fixer_command = f"{self.config.paths['build_fixer_py']} {file_path} {file_path}" + if os.system(fixer_command) != 0: + error_messages.append(f"envoy_build_fixer rewrite failed for file: {file_path}") + + buildifier_command = f"{self.config.buildifier_path} -lint=fix -mode=fix {file_path}" + if os.system(buildifier_command) != 0: + error_messages.append(f"buildifier rewrite failed for file: {file_path}") return error_messages def check_build_path(self, file_path): error_messages = [] - - if not self.is_build_fixer_excluded_file(file_path) and not self.is_api_file( - file_path) and not self.is_starlark_file(file_path) and not self.is_workspace_file( - file_path): - command = "%s %s | diff %s -" % ( - self.config.paths["build_fixer_py"], file_path, file_path) - error_messages += self.execute_command( - command, "envoy_build_fixer check failed", file_path) - - if self.is_build_file(file_path) and file_path.startswith(self.api_prefix + "envoy"): + if self._run_build_fixer(file_path): + command = f"{self.config.paths['build_fixer_py']} {file_path} | diff {file_path} -" + error_messages.extend( + self.execute_command(command, "envoy_build_fixer check failed", file_path)) + envoy_api_build_file = ( + self.is_build_file(file_path) and file_path.startswith(f"{self.api_prefix}envoy")) + if envoy_api_build_file: found = False for line in self.read_lines(file_path): if "api_proto_package(" in line: found = True break if not found: - error_messages += ["API build file does not provide api_proto_package()"] - - command = "%s -mode=diff %s" % (self.config.buildifier_path, file_path) - error_messages += self.execute_command(command, "buildifier check failed", file_path) - error_messages += self.check_file_contents(file_path, self.check_build_line) + error_messages.append("API build file does not provide api_proto_package()") + command = f"{self.config.buildifier_path} -mode=diff {file_path}" + error_messages.extend(self.execute_command(command, "buildifier check failed", file_path)) + error_messages.extend(self.check_file_contents(file_path, self.check_build_line)) return error_messages def fix_source_path(self, file_path): @@ -843,9 +931,9 @@ def execute_command(self, command, error_message, file_path, regex=None): return [] except subprocess.CalledProcessError as e: if (e.returncode != 0 and e.returncode != 1): - return ["ERROR: something went wrong while executing: %s" % e.cmd] + return [f"ERROR: something went wrong while executing: {e.cmd}"] # In case we can't find any line numbers, record an error message first. - error_messages = ["%s for file: %s" % (error_message, file_path)] + error_messages = [f"{error_message} for file: {file_path}\n{e}"] for line in e.output.decode('utf-8').splitlines(): for num in regex.findall(line): error_messages.append(" %s:%s" % (file_path, num)) @@ -869,28 +957,29 @@ def check_format(self, file_path, fail_on_diff=False): orig_error_messages = [] # Apply fixes first, if asked, and then run checks. If we wind up attempting to fix # an issue, but there's still an error, that's a problem. - try_to_fix = self.operation_type == "fix" - if self.is_build_file(file_path) or self.is_starlark_file( - file_path) or self.is_workspace_file(file_path): - if try_to_fix: + check_build_path = ( + self.is_build_file(file_path) or self.is_starlark_file(file_path) + or self.is_workspace_file(file_path)) + if check_build_path: + if self.operation_type == "fix": orig_error_messages = self.check_build_path(file_path) if orig_error_messages: - error_messages += self.fix_build_path(file_path) - error_messages += self.check_build_path(file_path) + error_messages.extend( + [*self.fix_build_path(file_path), *self.check_build_path(file_path)]) else: - error_messages += self.check_build_path(file_path) + error_messages.extend(self.check_build_path(file_path)) else: - if try_to_fix: + if self.operation_type == "fix": orig_error_messages = self.check_source_path(file_path) if orig_error_messages: - error_messages += self.fix_source_path(file_path) - error_messages += self.check_source_path(file_path) + error_messages.extend( + [*self.fix_source_path(file_path), *self.check_source_path(file_path)]) else: - error_messages += self.check_source_path(file_path) + error_messages.extend(self.check_source_path(file_path)) if error_messages: - return ["From %s" % file_path] + error_messages - if not error_messages and fail_on_diff: + return [f"From {file_path}", *error_messages] + if fail_on_diff: return orig_error_messages return error_messages @@ -901,58 +990,20 @@ def check_format_return_trace_on_error(self, file_path, fail_on_diff=False): except: return traceback.format_exc().split("\n") - def check_owners(self, dir_name, owned_directories, error_messages): - """Checks to make sure a given directory is present either in CODEOWNERS or OWNED_EXTENSIONS - Args: - dir_name: the directory being checked. - owned_directories: directories currently listed in CODEOWNERS. - error_messages: where to put an error message for new unowned directories. - """ - found = False - for owned in owned_directories: - if owned.startswith(dir_name) or dir_name.startswith(owned): - found = True - break - if not found: - error_messages.append( - "New directory %s appears to not have owners in CODEOWNERS" % dir_name) + def normalize_path(self, path): + """Convert path to form ./path/to/dir/ for directories and ./path/to/file otherwise""" + if not path.startswith(("./", "/")): + path = "./" + path - def check_format_visitor(self, arg, dir_name, names, fail_on_diff=False): - """Run check_format in parallel for the given files. - Args: - arg: a tuple (pool, result_list, owned_directories, error_messages) - pool and result_list are for starting tasks asynchronously. - owned_directories tracks directories listed in the CODEOWNERS file. - error_messages is a list of string format errors. - dir_name: the parent directory of the given files. - names: a list of file names. - """ + isdir = os.path.isdir(path) + if isdir and not path.endswith("/"): + path += "/" - # Unpack the multiprocessing.Pool process pool and list of results. Since - # python lists are passed as references, this is used to collect the list of - # async results (futures) from running check_format and passing them back to - # the caller. - pool, result_list, owned_directories, error_messages = arg - - # Sanity check CODEOWNERS. This doesn't need to be done in a multi-threaded - # manner as it is a small and limited list. - source_prefix = './source/' - core_extensions_full_prefix = './source/extensions/' - # Check to see if this directory is a subdir under /source/extensions - # Also ignore top level directories under /source/extensions since we don't - # need owners for source/extensions/access_loggers etc, just the subdirectories. - if dir_name.startswith( - core_extensions_full_prefix) and '/' in dir_name[len(core_extensions_full_prefix):]: - self.check_owners(dir_name[len(source_prefix):], owned_directories, error_messages) - - # For contrib extensions we track ownership at the top level only. - contrib_prefix = './contrib/' - if dir_name.startswith(contrib_prefix): - top_level = pathlib.PurePath('/', *pathlib.PurePath(dir_name).parts[:2], '/') - self.check_owners(str(top_level), owned_directories, error_messages) - - dir_name = normalize_path(dir_name) + return path + def check_format_visitor(self, pool, results, files): + """Run check_format in parallel for the given files. + """ # TODO(phlax): improve class/process handling - this is required because if these # are not cached before the class is sent into the pool, it only caches them on the # forked proc @@ -962,127 +1013,91 @@ def check_format_visitor(self, arg, dir_name, names, fail_on_diff=False): self.config.replacements self.config.dir_order - for file_name in names: - result = pool.apply_async( - self.check_format_return_trace_on_error, - args=(os.path.join(dir_name, file_name), fail_on_diff)) - result_list.append(result) + for filepath in files: + results.append( + pool.apply_async( + self.check_format_return_trace_on_error, + args=(filepath, self.args.fail_on_diff))) # check_error_messages iterates over the list with error messages and prints # errors and returns a bool based on whether there were any errors. - def check_error_messages(self, error_messages): - if error_messages: - for e in error_messages: - print("ERROR: %s" % e) + def check_error_messages(self): + if self.error_messages: + for e in self.error_messages: + print(f"ERROR: {e}") return True return False - def included_for_memcpy(self, file_path): - return file_path in self.config.paths["memcpy"]["include"] - - -def normalize_path(path): - """Convert path to form ./path/to/dir/ for directories and ./path/to/file otherwise""" - if not path.startswith("./") and not path.startswith("/"): - path = "./" + path + def pooled_check_format(self, files) -> list[str]: + pool = multiprocessing.Pool(processes=self.args.num_workers) + # For each file in target_path, start a new task in the pool and collect the + # results (results is passed by reference, and is used as an output). + results = [] + self.check_format_visitor(pool, results, files) + # Close the pool to new tasks, wait for all of the running tasks to finish, + # then collect the error messages. + pool.close() + pool.join() + return results - isdir = os.path.isdir(path) - if isdir and not path.endswith("/"): - path += "/" + @property + def target_paths(self) -> Iterator[str]: + _files = [] + for target in self.args.target_path: + if os.path.isfile(target): + # All of our `excluded_prefixes` start with "./", but the provided + # target path argument might not. Add it here if it is missing, + # and use that normalized path for both lookup and `check_format`. + normalized_target_path = self.normalize_path(target) + skip = ( + normalized_target_path.startswith(self.excluded_prefixes) + or not normalized_target_path.endswith(self.config.suffixes["included"])) + if not skip: + yield normalized_target_path + else: + for root, _, files in os.walk(target): + for filename in files: + file_path = os.path.join(root, filename) + check_file = ( + not file_path.startswith(self.excluded_prefixes) + and file_path.endswith(self.config.suffixes["included"]) and not ( + file_path.endswith(self.config.suffixes["proto"]) + and root.startswith(self.args.api_prefix))) + if check_file: + yield file_path + + def run_checks(self): + # these are needed curently to put the build tool paths into the env + self.config.buildifier_path + self.config.buildozer_path + self.check_visibility() + # We first run formatting on non-BUILD files, since the BUILD file format + # requires analysis of srcs/hdrs in the BUILD file, and we don't want these + # to be rewritten by other multiprocessing pooled processes. + results = [ + *self.pooled_check_format(f for f in self.target_paths if not self.is_build_file(f)), + *self.pooled_check_format(f for f in self.target_paths if self.is_build_file(f)) + ] + self.error_messages.extend(sum((r.get() for r in results), [])) - return path + if self.check_error_messages(): + if self.args.operation_type == "check": + print("ERROR: check format failed. run '//tools/code_format:check_format -- fix'") + else: + print("ERROR: check format failed. diff has been applied'") + sys.exit(1) + if self.args.operation_type == "check": + print("PASS") -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Check or fix file format.") - parser.add_argument( - "operation_type", - type=str, - choices=["check", "fix"], - help="specify if the run should 'check' or 'fix' format.") - parser.add_argument( - "target_path", - type=str, - nargs="?", - default=".", - help="specify the root directory for the script to recurse over. Default '.'.") - parser.add_argument("--path", default=".", help="specify the root path.") - parser.add_argument( - "--config_path", - default="./tools/code_format/config.yaml", - help="specify the config path. Default './tools/code_format/config.yaml'.") - parser.add_argument( - "--fail_on_diff", - action="store_true", - help="exit with failure if running fix produces changes.") - parser.add_argument( - "--add-excluded-prefixes", type=str, nargs="+", help="exclude additional prefixes.") - parser.add_argument( - "-j", - "--num-workers", - type=int, - default=multiprocessing.cpu_count(), - help="number of worker processes to use; defaults to one per core.") - parser.add_argument("--api-prefix", type=str, default="./api/", help="path of the API tree.") - parser.add_argument( - "--skip_envoy_build_rule_check", - action="store_true", - help="skip checking for '@envoy//' prefix in build rules.") - parser.add_argument( - "--namespace_check", - type=str, - nargs="?", - default="Envoy", - help="specify namespace check string. Default 'Envoy'.") - parser.add_argument( - "--namespace_check_excluded_paths", - type=str, - nargs="+", - default=[], - help="exclude paths from the namespace_check.") - parser.add_argument( - "--build_fixer_check_excluded_paths", - type=str, - nargs="+", - default=[], - help="exclude paths from envoy_build_fixer check.") - parser.add_argument( - "--bazel_tools_check_excluded_paths", - type=str, - nargs="+", - default=[], - help="exclude paths from bazel_tools check.") - parser.add_argument("--buildifier_path", type=str, help="Path to buildifier executable.") - parser.add_argument("--buildozer_path", type=str, help="Path to buildozer executable.") - parser.add_argument( - "--include_dir_order", - type=str, - default="", - help="specify the header block include directory order.") - args = parser.parse_args() - - # TODO(phlax): completely rewrite file discovery in this file - its a mess - source_path = os.getcwd() - os.chdir(args.path) - format_checker = FormatChecker(args, source_path) - - excluded_prefixes = format_checker.config.paths["excluded"] - if args.add_excluded_prefixes: - excluded_prefixes += tuple(args.add_excluded_prefixes) - - # Check whether all needed external tools are available. - ct_error_messages = format_checker.check_tools() - if format_checker.check_error_messages(ct_error_messages): - sys.exit(1) - - def check_visibility(error_messages): + def check_visibility(self): command = ( "git diff $(tools/git/last_github_commit.sh) -- source/extensions/* %s |grep '+.*visibility ='" - % "".join([f"':(exclude){c}' " for c in format_checker.config["visibility_excludes"]])) + % "".join([f"':(exclude){c}' " for c in self.config["visibility_excludes"]])) try: output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT).strip() if output: - error_messages.append( + self.error_messages.append( "This change appears to add visibility rules. Please get senior maintainer " "approval to add an exemption to visibility_excludes in tools/code_format/config.yaml" ) @@ -1091,133 +1106,24 @@ def check_visibility(error_messages): shell=True, stderr=subprocess.STDOUT).strip() if output: - error_messages.append( + self.error_messages.append( "envoy_package is not allowed to be used in source/extensions BUILD files.") except subprocess.CalledProcessError as e: if (e.returncode != 0 and e.returncode != 1): - error_messages.append("Failed to check visibility with command %s" % command) - - def get_owners(): - with open('./OWNERS.md') as f: - maintainers = ["@UNOWNED"] - for line in f: - if "Senior extension maintainers" in line: - return maintainers - m = format_checker.config.re["maintainers"].search(line) - if m is not None: - maintainers.append("@" + m.group(1).lower()) - - # Returns the list of directories with owners listed in CODEOWNERS. May append errors to - # error_messages. - def owned_directories(error_messages): - owned = [] - try: - maintainers = get_owners() - - with open('./CODEOWNERS') as f: - for line in f: - # If this line is of the form "extensions/... @owner1 @owner2" capture the directory - # name and store it in the list of directories with documented owners. - m = format_checker.config.re["codeowners_extensions"].search(line) - if m is not None and not line.startswith('#'): - owned.append(m.group(1).strip()) - owners = format_checker.config.re["owner"].findall(m.group(2).strip()) - if len(owners) < 2: - error_messages.append( - "Extensions require at least 2 owners in CODEOWNERS:\n" - " {}".format(line)) - maintainer = len(set(owners).intersection(set(maintainers))) > 0 - if not maintainer: - error_messages.append( - "Extensions require at least one maintainer OWNER:\n" - " {}".format(line)) - - m = format_checker.config.re["codeowners_contrib"].search(line) - if m is not None and not line.startswith('#'): - stripped_path = m.group(1).strip() - if not stripped_path.endswith('/'): - error_messages.append( - "Contrib CODEOWNERS entry '{}' must end in '/'".format( - stripped_path)) - continue - - if not (stripped_path.count('/') == 3 or - (stripped_path.count('/') == 4 - and stripped_path.startswith('/contrib/common/'))): - error_messages.append( - "Contrib CODEOWNERS entry '{}' must be 2 directories deep unless in /contrib/common/ and then it can be 3 directories deep" - .format(stripped_path)) - continue - - owned.append(stripped_path) - owners = format_checker.config.re["owner"].findall(m.group(2).strip()) - if len(owners) < 2: - error_messages.append( - "Contrib extensions require at least 2 owners in CODEOWNERS:\n" - " {}".format(line)) - - return owned - except IOError: - return [] # for the check format tests. - - # Calculate the list of owned directories once per run. - error_messages = [] - owned_directories = owned_directories(error_messages) - - check_visibility(error_messages) - - if os.path.isfile(args.target_path): - # All of our `excluded_prefixes` start with "./", but the provided - # target path argument might not. Add it here if it is missing, - # and use that normalized path for both lookup and `check_format`. - normalized_target_path = normalize_path(args.target_path) - if not normalized_target_path.startswith( - excluded_prefixes) and normalized_target_path.endswith( - format_checker.config.suffixes["included"]): - error_messages += format_checker.check_format(normalized_target_path) - else: - results = [] + self.error_messages.append("Failed to check visibility with command %s" % command) - def pooled_check_format(path_predicate): - pool = multiprocessing.Pool(processes=args.num_workers) - # For each file in target_path, start a new task in the pool and collect the - # results (results is passed by reference, and is used as an output). - for root, _, files in os.walk(args.target_path): - _files = [] - for filename in files: - file_path = os.path.join(root, filename) - check_file = ( - path_predicate(filename) and not file_path.startswith(excluded_prefixes) - and file_path.endswith(format_checker.config.suffixes["included"]) and not ( - file_path.endswith(format_checker.config.suffixes["proto"]) - and root.startswith(args.api_prefix))) - if check_file: - _files.append(filename) - if not _files: - continue - format_checker.check_format_visitor( - (pool, results, owned_directories, error_messages), root, _files, - args.fail_on_diff) + def included_for_memcpy(self, file_path): + return file_path in self.config.paths["memcpy"]["include"] - # Close the pool to new tasks, wait for all of the running tasks to finish, - # then collect the error messages. - pool.close() - pool.join() + def _run_build_fixer(self, filepath: str) -> bool: + return ( + not self.is_build_fixer_excluded_file(filepath) and not self.is_api_file(filepath) + and not self.is_starlark_file(filepath) and not self.is_workspace_file(filepath)) - # We first run formatting on non-BUILD files, since the BUILD file format - # requires analysis of srcs/hdrs in the BUILD file, and we don't want these - # to be rewritten by other multiprocessing pooled processes. - pooled_check_format(lambda f: not format_checker.is_build_file(f)) - pooled_check_format(lambda f: format_checker.is_build_file(f)) - error_messages += sum((r.get() for r in results), []) +def main(*args): + FormatChecker(args).run_checks() - if format_checker.check_error_messages(error_messages): - if args.operation_type == "check": - print("ERROR: check format failed. run 'tools/code_format/check_format.py fix'") - else: - print("ERROR: check format failed. diff has been applied'") - sys.exit(1) - if args.operation_type == "check": - print("PASS") +if __name__ == "__main__": + main(*sys.argv[1:]) diff --git a/tools/local_fix_format.sh b/tools/local_fix_format.sh index 5c29c15c7593..b392610cc48a 100755 --- a/tools/local_fix_format.sh +++ b/tools/local_fix_format.sh @@ -35,12 +35,22 @@ if [[ $# -gt 0 && "$1" == "-run-build-setup" ]]; then . ci/build_setup.sh fi + use_bazel=1 if [[ $# -gt 0 && "$1" == "-skip-bazel" ]]; then - echo -n "WARNING: not using bazel to invoke this script may result in " - echo "mismatched versions and incorrect formatting" - shift - use_bazel=0 + echo "WARNING: not using bazel to invoke this script may result in mismatched" \ + "versions and incorrect formatting" >&2 + shift + use_bazel=0 + + BUILDIFIER_BIN="$(command -v buildifier)" || { + echo "Local buildifier not found, exiting" >&2 + exit 1 + } + BUILDOZER_BIN="$(command -v buildozer)" || { + echo "Local buildozer not found, exiting" >&2 + exit 1 + } fi if [[ $# -gt 0 && "$1" == "-verbose" ]]; then @@ -52,19 +62,20 @@ fi # Runs the formatting functions on the specified args, echoing commands # if -vergbose was supplied to the script. -function format_some() { - ( +format_some () { if [[ "$verbose" == "1" ]]; then set -x fi + if [[ "$use_bazel" == "1" ]]; then - bazel run //tools/code_format:check_format -- fix "$@" + bazel run //tools/code_format:check_format fix "$@" + ./tools/spelling/check_spelling_pedantic.py fix "$@" else for arg in "$@"; do - ./tools/spelling/check_spelling_pedantic.py fix "$arg" + ./tools/code_format/check_format.py --buildozer_path "$BUILDOZER_BIN" --buildifier_path "$BUILDIFIER_BIN" fix "$arg" + ./tools/spelling/check_spelling_pedantic.py fix "$arg" done fi - ) } function format_all() { @@ -84,7 +95,7 @@ else if [[ $# -gt 0 && "$1" == "-main" ]]; then shift echo "Checking all files that have changed since the main branch." - args=$(git diff main | grep ^diff | awk '{print $3}' | cut -c 3-) + args=$(git diff --name-only main) elif [[ $# == 0 ]]; then args=$(git status|grep -E '(modified:|added:)'|awk '{print $2}') args+=$(git status|grep -E 'new file:'|awk '{print $3}') From 154219f1eaed58aeb17388f45cefbb77c1acc616 Mon Sep 17 00:00:00 2001 From: Joshua Marantz Date: Mon, 11 Sep 2023 12:04:39 -0400 Subject: [PATCH 55/55] buffer: use compiler version rather than other platform ifdefs to determine library call to use to serialize doubles (#29532) Signed-off-by: Joshua Marantz --- source/common/buffer/buffer_util.cc | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/source/common/buffer/buffer_util.cc b/source/common/buffer/buffer_util.cc index 4f4856165039..849549714527 100644 --- a/source/common/buffer/buffer_util.cc +++ b/source/common/buffer/buffer_util.cc @@ -1,6 +1,7 @@ #include "source/common/buffer/buffer_util.h" #include +#include #include "source/common/common/macros.h" @@ -21,13 +22,9 @@ void Util::serializeDouble(double number, Buffer::Instance& buffer) { // generate the string_view, and does not work on all platforms yet. // // The accuracy is checked in buffer_util_test. -#if defined(__APPLE__) || defined(GCC_COMPILER) - // On Apple and gcc, std::to_chars does not work with 'double', so we revert - // to the next fastest correct implementation. - buffer.addFragments({fmt::to_string(number)}); -#else - // This version is awkward, and doesn't work on Apple as of August 2023, but - // it is the fastest correct option on other platforms. +#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 14000 + // This version is awkward, and doesn't work on all platforms used in Envoy CI + // as of August 2023, but it is the fastest correct option on modern compilers. char buf[100]; std::to_chars_result result = std::to_chars(buf, buf + sizeof(buf), number); ENVOY_BUG(result.ec == std::errc{}, std::make_error_code(result.ec).message()); @@ -36,6 +33,11 @@ void Util::serializeDouble(double number, Buffer::Instance& buffer) { // Note: there is room to speed this up further by serializing the number directly // into the buffer. However, buffer does not currently make it easy and fast // to get (say) 100 characters of raw buffer to serialize into. +#else + // On older compilers, such as those found on Apple, and gcc, std::to_chars + // does not work with 'double', so we revert to the next fastest correct + // implementation. + buffer.addFragments({fmt::to_string(number)}); #endif }