From eaa9d0aacb44f4c6e9ff3d4af20fb82cab8816c2 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 18 Mar 2024 19:43:24 -0400 Subject: [PATCH 01/11] Make the instance_interface dependency explicit (#32935) Signed-off-by: Yan Avlasov --- source/server/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/source/server/BUILD b/source/server/BUILD index c86be8637fc7..bca0cd626d7e 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -397,6 +397,7 @@ envoy_cc_library( hdrs = ["listener_manager_factory.h"], deps = [ "//envoy/server:factory_context_interface", + "//envoy/server:instance_interface", "//envoy/server:listener_manager_interface", "//envoy/server:worker_interface", "//source/common/quic:quic_stat_names_lib", From 307218e7ac4db4320ea0003952adcf2f93d4d8de Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Mon, 18 Mar 2024 19:49:26 -0400 Subject: [PATCH 02/11] Make macro work outside of the Envoy namespace (#32893) --------- Signed-off-by: Yan Avlasov --- envoy/common/exception.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/envoy/common/exception.h b/envoy/common/exception.h index 4d5d9fd45bc0..946e92788739 100644 --- a/envoy/common/exception.h +++ b/envoy/common/exception.h @@ -17,7 +17,7 @@ namespace Envoy { #define throwEnvoyExceptionOrPanic(x) PANIC(x) #define throwExceptionOrPanic(x, y) PANIC(y) #else -#define throwEnvoyExceptionOrPanic(x) throw EnvoyException(x) +#define throwEnvoyExceptionOrPanic(x) throw ::Envoy::EnvoyException(x) #define throwExceptionOrPanic(y, x) throw y(x) #endif From 7fec609a507371d7176c61aa4623f445543f294f Mon Sep 17 00:00:00 2001 From: Thomas <1607559+thomasvnoort@users.noreply.github.com> Date: Tue, 19 Mar 2024 02:22:28 +0100 Subject: [PATCH 03/11] rbac: rules_stat_prefix to emit metrics with a prefix (#31835) This is akin to shadow_rules_stat_prefix but for non-shadowing rules. Since only shadow rules emit dynamic metadata, this prefix only applies to metrics. --------- Signed-off-by: Thomas van Noort --- .../filters/http/rbac/v3/rbac.proto | 7 +- changelogs/current.yaml | 4 + .../http/http_filters/rbac_filter.rst | 7 +- source/common/config/well_known_names.cc | 3 + source/common/config/well_known_names.h | 2 + .../extensions/filters/common/rbac/utility.cc | 11 ++- .../extensions/filters/common/rbac/utility.h | 6 +- .../filters/http/rbac/rbac_filter.cc | 2 +- .../filters/network/rbac/rbac_filter.cc | 2 +- test/common/stats/tag_extractor_impl_test.cc | 11 +++ .../filters/http/rbac/rbac_filter_test.cc | 81 +++++++++++------ .../filters/network/rbac/filter_test.cc | 88 ++++++++++++------- 12 files changed, 152 insertions(+), 72 deletions(-) diff --git a/api/envoy/extensions/filters/http/rbac/v3/rbac.proto b/api/envoy/extensions/filters/http/rbac/v3/rbac.proto index eeb505a17fb7..b14478b70e8f 100644 --- a/api/envoy/extensions/filters/http/rbac/v3/rbac.proto +++ b/api/envoy/extensions/filters/http/rbac/v3/rbac.proto @@ -22,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // [#extension: envoy.filters.http.rbac] // RBAC filter config. -// [#next-free-field: 6] +// [#next-free-field: 7] message RBAC { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.rbac.v2.RBAC"; @@ -34,6 +34,11 @@ message RBAC { config.rbac.v3.RBAC rules = 1 [(udpa.annotations.field_migrate).oneof_promotion = "rules_specifier"]; + // If specified, rules will emit stats with the given prefix. + // This is useful to distinguish the stat when there are more than 1 RBAC filter configured with + // rules. + string rules_stat_prefix = 6; + // The match tree to use when resolving RBAC action for incoming requests. Requests do not // match any matcher will be denied. // If absent, no enforcing RBAC matcher will be applied. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 092048320fae..aa29fe080592 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -339,6 +339,10 @@ new_features: Update ``aws_request_signing`` filter to support optionally sending the aws signature in query parameters rather than headers, by specifying the :ref:`query_string ` configuration section. +- area: rbac + change: | + Added :ref:`rules_stat_prefix ` + to allow adding custom prefix to the stats emitted by rules. deprecated: - area: listener diff --git a/docs/root/configuration/http/http_filters/rbac_filter.rst b/docs/root/configuration/http/http_filters/rbac_filter.rst index db30b5123a01..4e2e5990392e 100644 --- a/docs/root/configuration/http/http_filters/rbac_filter.rst +++ b/docs/root/configuration/http/http_filters/rbac_filter.rst @@ -33,7 +33,12 @@ The RBAC filter outputs statistics in the ``http..rbac.`` namespace ` comes from the owning HTTP connection manager. -For the shadow rule statistics ``shadow_allowed`` and ``shadow_denied``, the :ref:`shadow_rules_stat_prefix ` +For the rule statistics ``allowed`` and ``denied``, +the :ref:`rules_stat_prefix ` +can be used to add an extra prefix to output the statistics in the ``http..rbac..`` namespace. + +For the shadow rule statistics ``shadow_allowed`` and ``shadow_denied``, +the :ref:`shadow_rules_stat_prefix ` can be used to add an extra prefix to output the statistics in the ``http..rbac..`` namespace. .. csv-table:: diff --git a/source/common/config/well_known_names.cc b/source/common/config/well_known_names.cc index 4e390568c31d..104b182bfb1a 100644 --- a/source/common/config/well_known_names.cc +++ b/source/common/config/well_known_names.cc @@ -207,6 +207,9 @@ TagNameValues::TagNameValues() { // (.).rbac.** addTokenized(RBAC_PREFIX, "$.rbac.**"); + + // http..rbac.(.)* + addTokenized(RBAC_HTTP_PREFIX, "http.*.rbac.$.**"); } void TagNameValues::addRe2(const std::string& name, const std::string& regex, diff --git a/source/common/config/well_known_names.h b/source/common/config/well_known_names.h index a0a40cc79496..8cf5fbd14715 100644 --- a/source/common/config/well_known_names.h +++ b/source/common/config/well_known_names.h @@ -123,6 +123,8 @@ class TagNameValues { const std::string CONNECTION_LIMIT_PREFIX = "envoy.connection_limit_prefix"; // Stats prefix for the RBAC network filter const std::string RBAC_PREFIX = "envoy.rbac_prefix"; + // Stats prefix for the RBAC http filter + const std::string RBAC_HTTP_PREFIX = "envoy.rbac_http_prefix"; // Stats prefix for the TCP Proxy network filter const std::string TCP_PREFIX = "envoy.tcp_prefix"; // Stats prefix for the UDP Proxy network filter diff --git a/source/extensions/filters/common/rbac/utility.cc b/source/extensions/filters/common/rbac/utility.cc index 710342d7b8fd..cc14523ee1fb 100644 --- a/source/extensions/filters/common/rbac/utility.cc +++ b/source/extensions/filters/common/rbac/utility.cc @@ -10,11 +10,14 @@ namespace Filters { namespace Common { namespace RBAC { -RoleBasedAccessControlFilterStats -generateStats(const std::string& prefix, const std::string& shadow_prefix, Stats::Scope& scope) { +RoleBasedAccessControlFilterStats generateStats(const std::string& prefix, + const std::string& rules_prefix, + const std::string& shadow_rules_prefix, + Stats::Scope& scope) { const std::string final_prefix = Envoy::statPrefixJoin(prefix, "rbac."); - return {ENFORCE_RBAC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix)) - SHADOW_RBAC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix + shadow_prefix))}; + return { + ENFORCE_RBAC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix + rules_prefix)) + SHADOW_RBAC_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix + shadow_rules_prefix))}; } std::string responseDetail(const std::string& policy_id) { diff --git a/source/extensions/filters/common/rbac/utility.h b/source/extensions/filters/common/rbac/utility.h index 79cd8be7d2bf..fe17def3c6e1 100644 --- a/source/extensions/filters/common/rbac/utility.h +++ b/source/extensions/filters/common/rbac/utility.h @@ -34,8 +34,10 @@ struct RoleBasedAccessControlFilterStats { SHADOW_RBAC_FILTER_STATS(GENERATE_COUNTER_STRUCT) }; -RoleBasedAccessControlFilterStats -generateStats(const std::string& prefix, const std::string& shadow_prefix, Stats::Scope& scope); +RoleBasedAccessControlFilterStats generateStats(const std::string& prefix, + const std::string& rules_prefix, + const std::string& shadow_rules_prefix, + Stats::Scope& scope); template std::unique_ptr diff --git a/source/extensions/filters/http/rbac/rbac_filter.cc b/source/extensions/filters/http/rbac/rbac_filter.cc index bb6950942611..f1ee12283824 100644 --- a/source/extensions/filters/http/rbac/rbac_filter.cc +++ b/source/extensions/filters/http/rbac/rbac_filter.cc @@ -61,7 +61,7 @@ RoleBasedAccessControlFilterConfig::RoleBasedAccessControlFilterConfig( const std::string& stats_prefix, Stats::Scope& scope, Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor& validation_visitor) - : stats_(Filters::Common::RBAC::generateStats(stats_prefix, + : stats_(Filters::Common::RBAC::generateStats(stats_prefix, proto_config.rules_stat_prefix(), proto_config.shadow_rules_stat_prefix(), scope)), shadow_rules_stat_prefix_(proto_config.shadow_rules_stat_prefix()), engine_(Filters::Common::RBAC::createEngine(proto_config, context, validation_visitor, diff --git a/source/extensions/filters/network/rbac/rbac_filter.cc b/source/extensions/filters/network/rbac/rbac_filter.cc index 500a0c2f617e..7d07de0f6d18 100644 --- a/source/extensions/filters/network/rbac/rbac_filter.cc +++ b/source/extensions/filters/network/rbac/rbac_filter.cc @@ -56,7 +56,7 @@ RoleBasedAccessControlFilterConfig::RoleBasedAccessControlFilterConfig( const envoy::extensions::filters::network::rbac::v3::RBAC& proto_config, Stats::Scope& scope, Server::Configuration::ServerFactoryContext& context, ProtobufMessage::ValidationVisitor& validation_visitor) - : stats_(Filters::Common::RBAC::generateStats(proto_config.stat_prefix(), + : stats_(Filters::Common::RBAC::generateStats(proto_config.stat_prefix(), "", proto_config.shadow_rules_stat_prefix(), scope)), shadow_rules_stat_prefix_(proto_config.shadow_rules_stat_prefix()), engine_(Filters::Common::RBAC::createEngine(proto_config, context, validation_visitor, diff --git a/test/common/stats/tag_extractor_impl_test.cc b/test/common/stats/tag_extractor_impl_test.cc index 43e74080a685..747f9daca0c1 100644 --- a/test/common/stats/tag_extractor_impl_test.cc +++ b/test/common/stats/tag_extractor_impl_test.cc @@ -450,6 +450,17 @@ TEST(TagExtractorTest, DefaultTagExtractors) { rbac_prefix.name_ = tag_names.RBAC_PREFIX; rbac_prefix.value_ = "my_rbac_prefix"; regex_tester.testRegex("my_rbac_prefix.rbac.allowed", "rbac.allowed", {rbac_prefix}); + + // RBAC HTTP Filter Prefix + Tag rbac_http_hcm_prefix; + rbac_http_hcm_prefix.name_ = tag_names.HTTP_CONN_MANAGER_PREFIX; + rbac_http_hcm_prefix.value_ = "hcm_prefix"; + + Tag rbac_http_prefix; + rbac_http_prefix.name_ = tag_names.RBAC_HTTP_PREFIX; + rbac_http_prefix.value_ = "prefix"; + regex_tester.testRegex("http.hcm_prefix.rbac.prefix.allowed", "http.rbac.allowed", + {rbac_http_hcm_prefix, rbac_http_prefix}); } TEST(TagExtractorTest, ExtAuthzTagExtractors) { diff --git a/test/extensions/filters/http/rbac/rbac_filter_test.cc b/test/extensions/filters/http/rbac/rbac_filter_test.cc index c8578f2e2622..93cacd425885 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_test.cc @@ -35,7 +35,8 @@ enum class LogResult { Yes, No, Undecided }; class RoleBasedAccessControlFilterTest : public testing::Test { public: - void setupPolicy(envoy::config::rbac::v3::RBAC::Action action) { + void setupPolicy(envoy::config::rbac::v3::RBAC::Action action, + std::string rules_stat_prefix = "") { envoy::extensions::filters::http::rbac::v3::RBAC config; envoy::config::rbac::v3::Policy policy; @@ -47,6 +48,7 @@ class RoleBasedAccessControlFilterTest : public testing::Test { policy.add_principals()->set_any(true); config.mutable_rules()->set_action(action); (*config.mutable_rules()->mutable_policies())["foo"] = policy; + config.set_rules_stat_prefix(rules_stat_prefix); envoy::config::rbac::v3::Policy shadow_policy; auto shadow_policy_rules = shadow_policy.add_permissions()->mutable_or_rules(); @@ -55,7 +57,7 @@ class RoleBasedAccessControlFilterTest : public testing::Test { shadow_policy.add_principals()->set_any(true); config.mutable_shadow_rules()->set_action(action); (*config.mutable_shadow_rules()->mutable_policies())["bar"] = shadow_policy; - config.set_shadow_rules_stat_prefix("prefix_"); + config.set_shadow_rules_stat_prefix("shadow_rules_prefix_"); setupConfig(std::make_shared( config, "test", *store_.rootScope(), context_, @@ -156,7 +158,7 @@ class RoleBasedAccessControlFilterTest : public testing::Test { TestUtility::loadFromYaml(fmt::format(shadow_matcher_yaml, action, on_no_match_action), shadow_matcher); *config.mutable_shadow_matcher() = shadow_matcher; - config.set_shadow_rules_stat_prefix("prefix_"); + config.set_shadow_rules_stat_prefix("shadow_rules_prefix_"); setupConfig(std::make_shared( config, "test", *store_.rootScope(), context_, @@ -238,7 +240,7 @@ class RoleBasedAccessControlFilterTest : public testing::Test { *config.mutable_matcher() = matcher; *config.mutable_shadow_matcher() = matcher; - config.set_shadow_rules_stat_prefix("prefix_"); + config.set_shadow_rules_stat_prefix("shadow_rules_prefix_"); setupConfig(std::make_shared( config, "test", *store_.rootScope(), context_, @@ -335,8 +337,9 @@ TEST_F(RoleBasedAccessControlFilterTest, Allowed) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -359,8 +362,9 @@ TEST_F(RoleBasedAccessControlFilterTest, RequestedServerName) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -404,12 +408,16 @@ TEST_F(RoleBasedAccessControlFilterTest, Denied) { EXPECT_EQ(1U, config_->stats().shadow_allowed_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); auto filter_meta = req_info_.dynamicMetadata().filter_metadata().at("envoy.filters.http.rbac"); - EXPECT_EQ("allowed", filter_meta.fields().at("prefix_shadow_engine_result").string_value()); - EXPECT_EQ("bar", filter_meta.fields().at("prefix_shadow_effective_policy_id").string_value()); + EXPECT_EQ("allowed", + filter_meta.fields().at("shadow_rules_prefix_shadow_engine_result").string_value()); + EXPECT_EQ( + "bar", + filter_meta.fields().at("shadow_rules_prefix_shadow_effective_policy_id").string_value()); EXPECT_EQ("rbac_access_denied_matched_policy[none]", callbacks_.details()); checkAccessLogMetadata(LogResult::Undecided); } @@ -496,8 +504,9 @@ TEST_F(RoleBasedAccessControlFilterTest, MatcherAllowed) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -520,8 +529,9 @@ TEST_F(RoleBasedAccessControlFilterTest, RequestedServerNameMatcher) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -565,12 +575,16 @@ TEST_F(RoleBasedAccessControlFilterTest, MatcherDenied) { EXPECT_EQ(1U, config_->stats().shadow_allowed_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); auto filter_meta = req_info_.dynamicMetadata().filter_metadata().at("envoy.filters.http.rbac"); - EXPECT_EQ("allowed", filter_meta.fields().at("prefix_shadow_engine_result").string_value()); - EXPECT_EQ("bar", filter_meta.fields().at("prefix_shadow_effective_policy_id").string_value()); + EXPECT_EQ("allowed", + filter_meta.fields().at("shadow_rules_prefix_shadow_engine_result").string_value()); + EXPECT_EQ( + "bar", + filter_meta.fields().at("shadow_rules_prefix_shadow_effective_policy_id").string_value()); EXPECT_EQ("rbac_access_denied_matched_policy[none]", callbacks_.details()); checkAccessLogMetadata(LogResult::Undecided); } @@ -618,8 +632,9 @@ TEST_F(RoleBasedAccessControlFilterTest, ShouldLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -639,8 +654,9 @@ TEST_F(RoleBasedAccessControlFilterTest, ShouldNotLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -660,8 +676,9 @@ TEST_F(RoleBasedAccessControlFilterTest, MatcherShouldLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -681,8 +698,9 @@ TEST_F(RoleBasedAccessControlFilterTest, MatcherShouldNotLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("test.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("test.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("test.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("test.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); Buffer::OwnedImpl data(""); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_->decodeData(data, false)); @@ -691,6 +709,13 @@ TEST_F(RoleBasedAccessControlFilterTest, MatcherShouldNotLog) { checkAccessLogMetadata(LogResult::No); } +TEST_F(RoleBasedAccessControlFilterTest, RulesStatPrefix) { + setupPolicy(envoy::config::rbac::v3::RBAC::ALLOW, "rules_prefix_"); + + EXPECT_EQ("test.rbac.rules_prefix_.allowed", config_->stats().allowed_.name()); + EXPECT_EQ("test.rbac.rules_prefix_.denied", config_->stats().denied_.name()); +} + // Upstream Ip and Port matcher tests. class UpstreamIpPortMatcherTests : public RoleBasedAccessControlFilterTest { public: diff --git a/test/extensions/filters/network/rbac/filter_test.cc b/test/extensions/filters/network/rbac/filter_test.cc index df60598f379d..78b4d05d32ea 100644 --- a/test/extensions/filters/network/rbac/filter_test.cc +++ b/test/extensions/filters/network/rbac/filter_test.cc @@ -33,7 +33,7 @@ class RoleBasedAccessControlNetworkFilterTest : public testing::Test { envoy::extensions::filters::network::rbac::v3::RBAC config; config.set_stat_prefix("tcp."); - config.set_shadow_rules_stat_prefix("prefix_"); + config.set_shadow_rules_stat_prefix("shadow_rules_prefix_"); if (with_policy) { envoy::config::rbac::v3::Policy policy; @@ -69,7 +69,7 @@ class RoleBasedAccessControlNetworkFilterTest : public testing::Test { std::string on_no_match_action = "DENY") { envoy::extensions::filters::network::rbac::v3::RBAC config; config.set_stat_prefix("tcp."); - config.set_shadow_rules_stat_prefix("prefix_"); + config.set_shadow_rules_stat_prefix("shadow_rules_prefix_"); if (with_matcher) { constexpr absl::string_view matcher_yaml = R"EOF( @@ -250,8 +250,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithOneTimeEnforcement) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithContinuousEnforcement) { @@ -270,8 +271,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithContinuousEnforcement EXPECT_EQ(2U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, RequestedServerName) { @@ -291,8 +293,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, RequestedServerName) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithNoPolicy) { @@ -309,8 +312,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithNoPolicy) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, Denied) { @@ -330,13 +334,17 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, Denied) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); auto filter_meta = stream_info_.dynamicMetadata().filter_metadata().at(NetworkFilterNames::get().Rbac); - EXPECT_EQ("bar", filter_meta.fields().at("prefix_shadow_effective_policy_id").string_value()); - EXPECT_EQ("allowed", filter_meta.fields().at("prefix_shadow_engine_result").string_value()); + EXPECT_EQ( + "bar", + filter_meta.fields().at("shadow_rules_prefix_shadow_effective_policy_id").string_value()); + EXPECT_EQ("allowed", + filter_meta.fields().at("shadow_rules_prefix_shadow_engine_result").string_value()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherAllowedWithOneTimeEnforcement) { @@ -355,8 +363,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherAllowedWithOneTimeEnforce EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherAllowedWithContinuousEnforcement) { @@ -375,8 +384,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherAllowedWithContinuousEnfo EXPECT_EQ(2U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, RequestedServerNameMatcher) { @@ -396,8 +406,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, RequestedServerNameMatcher) { EXPECT_EQ(1U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithNoMatcher) { @@ -414,8 +425,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, AllowedWithNoMatcher) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); } TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherDenied) { @@ -435,13 +447,17 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherDenied) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); auto filter_meta = stream_info_.dynamicMetadata().filter_metadata().at(NetworkFilterNames::get().Rbac); - EXPECT_EQ("bar", filter_meta.fields().at("prefix_shadow_effective_policy_id").string_value()); - EXPECT_EQ("allowed", filter_meta.fields().at("prefix_shadow_engine_result").string_value()); + EXPECT_EQ( + "bar", + filter_meta.fields().at("shadow_rules_prefix_shadow_effective_policy_id").string_value()); + EXPECT_EQ("allowed", + filter_meta.fields().at("shadow_rules_prefix_shadow_engine_result").string_value()); } // Log Tests @@ -456,8 +472,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, ShouldLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); checkAccessLogMetadata(true); } @@ -473,8 +490,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, ShouldNotLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); checkAccessLogMetadata(false); } @@ -504,8 +522,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherShouldLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); checkAccessLogMetadata(true); } @@ -521,8 +540,9 @@ TEST_F(RoleBasedAccessControlNetworkFilterTest, MatcherShouldNotLog) { EXPECT_EQ(0U, config_->stats().shadow_denied_.value()); EXPECT_EQ("tcp.rbac.allowed", config_->stats().allowed_.name()); EXPECT_EQ("tcp.rbac.denied", config_->stats().denied_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_allowed", config_->stats().shadow_allowed_.name()); - EXPECT_EQ("tcp.rbac.prefix_.shadow_denied", config_->stats().shadow_denied_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_allowed", + config_->stats().shadow_allowed_.name()); + EXPECT_EQ("tcp.rbac.shadow_rules_prefix_.shadow_denied", config_->stats().shadow_denied_.name()); checkAccessLogMetadata(false); } From d3c90ed4a92119fa20167e61750efafa687ae2e5 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 18 Mar 2024 22:07:30 -0400 Subject: [PATCH 04/11] filesystem: move addWatch to statusor (#32820) Risk Level: low Testing: updated tests Docs Changes: n/a Release Notes: n/a envoyproxy/envoy-mobile#176 Signed-off-by: Alyssa Wilk --- envoy/filesystem/watcher.h | 4 +- source/common/config/watched_directory.cc | 5 +- .../common/filesystem/inotify/watcher_impl.cc | 7 +- .../common/filesystem/inotify/watcher_impl.h | 2 +- .../common/filesystem/kqueue/watcher_impl.cc | 6 +- .../common/filesystem/kqueue/watcher_impl.h | 2 +- .../common/filesystem/win32/watcher_impl.cc | 9 ++- source/common/filesystem/win32/watcher_impl.h | 2 +- source/common/runtime/runtime_impl.cc | 8 +- source/common/secret/sds_api.cc | 6 +- .../zstd/common/dictionary_manager.h | 4 +- .../filesystem_subscription_impl.cc | 11 +-- .../injected_resource_monitor.cc | 4 +- test/common/config/watched_directory_test.cc | 3 +- test/common/filesystem/watcher_impl_test.cc | 77 +++++++++++-------- test/common/runtime/runtime_impl_test.cc | 15 ++++ test/common/secret/sds_api_test.cc | 4 + .../compression/zstd/zstd_compression_test.cc | 1 + .../filesystem_subscription_impl_test.cc | 7 +- .../filesystem_subscription_test_harness.h | 7 +- test/mocks/filesystem/mocks.h | 2 +- tools/code_format/config.yaml | 3 +- 22 files changed, 121 insertions(+), 68 deletions(-) diff --git a/envoy/filesystem/watcher.h b/envoy/filesystem/watcher.h index dd5c97b286e2..3cfd754a6cdb 100644 --- a/envoy/filesystem/watcher.h +++ b/envoy/filesystem/watcher.h @@ -8,6 +8,7 @@ #include "envoy/common/platform.h" #include "envoy/common/pure.h" +#include "absl/status/statusor.h" #include "absl/strings/string_view.h" namespace Envoy { @@ -35,8 +36,9 @@ class Watcher { * for the given directory. * @param events supplies the events to watch. * @param cb supplies the callback to invoke when a change occurs. + * @return a failure status if the file does not exist */ - virtual void addWatch(absl::string_view path, uint32_t events, OnChangedCb cb) PURE; + virtual absl::Status addWatch(absl::string_view path, uint32_t events, OnChangedCb cb) PURE; }; using WatcherPtr = std::unique_ptr; diff --git a/source/common/config/watched_directory.cc b/source/common/config/watched_directory.cc index fc2a3a832a50..483cc08460d5 100644 --- a/source/common/config/watched_directory.cc +++ b/source/common/config/watched_directory.cc @@ -6,8 +6,9 @@ namespace Config { WatchedDirectory::WatchedDirectory(const envoy::config::core::v3::WatchedDirectory& config, Event::Dispatcher& dispatcher) { watcher_ = dispatcher.createFilesystemWatcher(); - watcher_->addWatch(absl::StrCat(config.path(), "/"), Filesystem::Watcher::Events::MovedTo, - [this](uint32_t) { cb_(); }); + THROW_IF_NOT_OK(watcher_->addWatch(absl::StrCat(config.path(), "/"), + Filesystem::Watcher::Events::MovedTo, + [this](uint32_t) { cb_(); })); } } // namespace Config diff --git a/source/common/filesystem/inotify/watcher_impl.cc b/source/common/filesystem/inotify/watcher_impl.cc index 864da79f5b59..1f53022ddcb7 100644 --- a/source/common/filesystem/inotify/watcher_impl.cc +++ b/source/common/filesystem/inotify/watcher_impl.cc @@ -32,23 +32,24 @@ WatcherImpl::WatcherImpl(Event::Dispatcher& dispatcher, Filesystem::Instance& fi WatcherImpl::~WatcherImpl() { close(inotify_fd_); } -void WatcherImpl::addWatch(absl::string_view path, uint32_t events, OnChangedCb callback) { +absl::Status WatcherImpl::addWatch(absl::string_view path, uint32_t events, OnChangedCb callback) { // Because of general inotify pain, we always watch the directory that the file lives in, // and then synthetically raise per file events. auto result_or_error = file_system_.splitPathFromFilename(path); - THROW_IF_STATUS_NOT_OK(result_or_error, throw); + RETURN_IF_STATUS_NOT_OK(result_or_error); const PathSplitResult result = result_or_error.value(); const uint32_t watch_mask = IN_MODIFY | IN_MOVED_TO; int watch_fd = inotify_add_watch(inotify_fd_, std::string(result.directory_).c_str(), watch_mask); if (watch_fd == -1) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( fmt::format("unable to add filesystem watch for file {}: {}", path, errorDetails(errno))); } ENVOY_LOG(debug, "added watch for directory: '{}' file: '{}' fd: {}", result.directory_, result.file_, watch_fd); callback_map_[watch_fd].watches_.push_back({std::string(result.file_), events, callback}); + return absl::OkStatus(); } void WatcherImpl::onInotifyEvent() { diff --git a/source/common/filesystem/inotify/watcher_impl.h b/source/common/filesystem/inotify/watcher_impl.h index f2474b3c1d73..bf76f5f17de1 100644 --- a/source/common/filesystem/inotify/watcher_impl.h +++ b/source/common/filesystem/inotify/watcher_impl.h @@ -26,7 +26,7 @@ class WatcherImpl : public Watcher, Logger::Loggable { ~WatcherImpl() override; // Filesystem::Watcher - void addWatch(absl::string_view path, uint32_t events, OnChangedCb cb) override; + absl::Status addWatch(absl::string_view path, uint32_t events, OnChangedCb cb) override; private: struct FileWatch { diff --git a/source/common/filesystem/kqueue/watcher_impl.cc b/source/common/filesystem/kqueue/watcher_impl.cc index 200c08dc8104..ca20aab97a90 100644 --- a/source/common/filesystem/kqueue/watcher_impl.cc +++ b/source/common/filesystem/kqueue/watcher_impl.cc @@ -32,11 +32,13 @@ WatcherImpl::~WatcherImpl() { watches_.clear(); } -void WatcherImpl::addWatch(absl::string_view path, uint32_t events, Watcher::OnChangedCb cb) { +absl::Status WatcherImpl::addWatch(absl::string_view path, uint32_t events, + Watcher::OnChangedCb cb) { FileWatchPtr watch = addWatch(path, events, cb, false); if (watch == nullptr) { - throwEnvoyExceptionOrPanic(absl::StrCat("invalid watch path ", path)); + return absl::InvalidArgumentError(absl::StrCat("invalid watch path ", path)); } + return absl::OkStatus(); } WatcherImpl::FileWatchPtr WatcherImpl::addWatch(absl::string_view path, uint32_t events, diff --git a/source/common/filesystem/kqueue/watcher_impl.h b/source/common/filesystem/kqueue/watcher_impl.h index ba5d908a05a0..22e7f9f06dbc 100644 --- a/source/common/filesystem/kqueue/watcher_impl.h +++ b/source/common/filesystem/kqueue/watcher_impl.h @@ -27,7 +27,7 @@ class WatcherImpl : public Watcher, Logger::Loggable { ~WatcherImpl(); // Filesystem::Watcher - void addWatch(absl::string_view path, uint32_t events, OnChangedCb cb) override; + absl::Status addWatch(absl::string_view path, uint32_t events, OnChangedCb cb) override; private: struct FileWatch : LinkedObject { diff --git a/source/common/filesystem/win32/watcher_impl.cc b/source/common/filesystem/win32/watcher_impl.cc index 601c787461bf..6cb9d00a1bc7 100644 --- a/source/common/filesystem/win32/watcher_impl.cc +++ b/source/common/filesystem/win32/watcher_impl.cc @@ -50,13 +50,13 @@ WatcherImpl::~WatcherImpl() { ::CloseHandle(thread_exit_event_); } -void WatcherImpl::addWatch(absl::string_view path, uint32_t events, OnChangedCb cb) { +absl::Status WatcherImpl::addWatch(absl::string_view path, uint32_t events, OnChangedCb cb) { if (path == Platform::null_device_path) { - return; + return absl::OkStatus(); } const absl::StatusOr result_or_error = file_system_.splitPathFromFilename(path); - THROW_IF_STATUS_NOT_OK(result_or_error, throw); + RETURN_IF_STATUS_NOT_OK(result_or_error); const PathSplitResult& result = result_or_error.value(); // ReadDirectoryChangesW only has a Unicode version, so we need // to use wide strings here @@ -67,7 +67,7 @@ void WatcherImpl::addWatch(absl::string_view path, uint32_t events, OnChangedCb directory.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if (dir_handle == INVALID_HANDLE_VALUE) { - throw EnvoyException( + return absl::InvalidArgumentError( fmt::format("unable to open directory {}: {}", result.directory_, GetLastError())); } std::string fii_key(sizeof(FILE_ID_INFO), '\0'); @@ -108,6 +108,7 @@ void WatcherImpl::addWatch(absl::string_view path, uint32_t events, OnChangedCb callback_map_[fii_key]->watches_.push_back({file, events, cb}); ENVOY_LOG(debug, "added watch for file '{}' in directory '{}'", result.file_, result.directory_); + return absl::OkStatus(); } void WatcherImpl::onDirectoryEvent() { diff --git a/source/common/filesystem/win32/watcher_impl.h b/source/common/filesystem/win32/watcher_impl.h index ce6acd518b49..5431d5f3f0a2 100644 --- a/source/common/filesystem/win32/watcher_impl.h +++ b/source/common/filesystem/win32/watcher_impl.h @@ -31,7 +31,7 @@ class WatcherImpl : public Watcher, Logger::Loggable { ~WatcherImpl(); // Filesystem::Watcher - void addWatch(absl::string_view path, uint32_t events, OnChangedCb cb) override; + absl::Status addWatch(absl::string_view path, uint32_t events, OnChangedCb cb) override; private: static void issueFirstRead(ULONG_PTR param); diff --git a/source/common/runtime/runtime_impl.cc b/source/common/runtime/runtime_impl.cc index ab2843df4888..2dada0d1be29 100644 --- a/source/common/runtime/runtime_impl.cc +++ b/source/common/runtime/runtime_impl.cc @@ -576,8 +576,12 @@ LoaderImpl::LoaderImpl(Event::Dispatcher& dispatcher, ThreadLocal::SlotAllocator if (watcher_ == nullptr) { watcher_ = dispatcher.createFilesystemWatcher(); } - watcher_->addWatch(layer.disk_layer().symlink_root(), Filesystem::Watcher::Events::MovedTo, - [this](uint32_t) -> void { THROW_IF_NOT_OK(loadNewSnapshot()); }); + creation_status = watcher_->addWatch( + layer.disk_layer().symlink_root(), Filesystem::Watcher::Events::MovedTo, + [this](uint32_t) -> void { THROW_IF_NOT_OK(loadNewSnapshot()); }); + if (!creation_status.ok()) { + return; + } break; case envoy::config::bootstrap::v3::RuntimeLayer::LayerSpecifierCase::kRtdsLayer: subscriptions_.emplace_back( diff --git a/source/common/secret/sds_api.cc b/source/common/secret/sds_api.cc index 344da3ff5833..e378652c313c 100644 --- a/source/common/secret/sds_api.cc +++ b/source/common/secret/sds_api.cc @@ -122,9 +122,9 @@ absl::Status SdsApi::onConfigUpdate(const std::vectoraddWatch(absl::StrCat(result_or_error.value().directory_, "/"), - Filesystem::Watcher::Events::MovedTo, - [this](uint32_t) { onWatchUpdate(); }); + RETURN_IF_NOT_OK(watcher_->addWatch(absl::StrCat(result_or_error.value().directory_, "/"), + Filesystem::Watcher::Events::MovedTo, + [this](uint32_t) { onWatchUpdate(); })); } } else { watcher_.reset(); // Destroy the old watch if any diff --git a/source/extensions/compression/zstd/common/dictionary_manager.h b/source/extensions/compression/zstd/common/dictionary_manager.h index 45501345e5ec..43d8c4974281 100644 --- a/source/extensions/compression/zstd/common/dictionary_manager.h +++ b/source/extensions/compression/zstd/common/dictionary_manager.h @@ -44,9 +44,9 @@ template class envoy::config::core::v3::DataSource::SpecifierCase::kFilename) { is_watch_added = true; const auto& filename = source.filename(); - watcher_->addWatch( + THROW_IF_NOT_OK(watcher_->addWatch( filename, Filesystem::Watcher::Events::Modified | Filesystem::Watcher::Events::MovedTo, - [this, id, filename](uint32_t) { onDictionaryUpdate(id, filename); }); + [this, id, filename](uint32_t) { onDictionaryUpdate(id, filename); })); } } diff --git a/source/extensions/config_subscription/filesystem/filesystem_subscription_impl.cc b/source/extensions/config_subscription/filesystem/filesystem_subscription_impl.cc index bc33cecee33e..b149e552b398 100644 --- a/source/extensions/config_subscription/filesystem/filesystem_subscription_impl.cc +++ b/source/extensions/config_subscription/filesystem/filesystem_subscription_impl.cc @@ -27,11 +27,12 @@ FilesystemSubscriptionImpl::FilesystemSubscriptionImpl( stats_(stats), api_(api), validation_visitor_(validation_visitor) { if (!path_config_source.has_watched_directory()) { file_watcher_ = dispatcher.createFilesystemWatcher(); - file_watcher_->addWatch(path_, Filesystem::Watcher::Events::MovedTo, [this](uint32_t) { - if (started_) { - refresh(); - } - }); + THROW_IF_NOT_OK( + file_watcher_->addWatch(path_, Filesystem::Watcher::Events::MovedTo, [this](uint32_t) { + if (started_) { + refresh(); + } + })); } else { directory_watcher_ = std::make_unique(path_config_source.watched_directory(), dispatcher); diff --git a/source/extensions/resource_monitors/injected_resource/injected_resource_monitor.cc b/source/extensions/resource_monitors/injected_resource/injected_resource_monitor.cc index 23e7ab516d33..bb64744cda07 100644 --- a/source/extensions/resource_monitors/injected_resource/injected_resource_monitor.cc +++ b/source/extensions/resource_monitors/injected_resource/injected_resource_monitor.cc @@ -17,8 +17,8 @@ InjectedResourceMonitor::InjectedResourceMonitor( Server::Configuration::ResourceMonitorFactoryContext& context) : filename_(config.filename()), watcher_(context.mainThreadDispatcher().createFilesystemWatcher()), api_(context.api()) { - watcher_->addWatch(filename_, Filesystem::Watcher::Events::MovedTo, - [this](uint32_t) { onFileChanged(); }); + THROW_IF_NOT_OK(watcher_->addWatch(filename_, Filesystem::Watcher::Events::MovedTo, + [this](uint32_t) { onFileChanged(); })); } void InjectedResourceMonitor::onFileChanged() { file_changed_ = true; } diff --git a/test/common/config/watched_directory_test.cc b/test/common/config/watched_directory_test.cc index b22dd7dbb983..6ae512b7b481 100644 --- a/test/common/config/watched_directory_test.cc +++ b/test/common/config/watched_directory_test.cc @@ -7,6 +7,7 @@ #include "gtest/gtest.h" +using testing::DoAll; using testing::Return; using testing::SaveArg; @@ -21,7 +22,7 @@ TEST(WatchedDirectory, All) { EXPECT_CALL(dispatcher, createFilesystemWatcher_()).WillOnce(Return(watcher)); Filesystem::Watcher::OnChangedCb cb; EXPECT_CALL(*watcher, addWatch("foo/bar/", Filesystem::Watcher::Events::MovedTo, _)) - .WillOnce(SaveArg<2>(&cb)); + .WillOnce(DoAll(SaveArg<2>(&cb), Return(absl::OkStatus()))); WatchedDirectory wd(config, dispatcher); bool called = false; wd.setCallback([&called] { called = true; }); diff --git a/test/common/filesystem/watcher_impl_test.cc b/test/common/filesystem/watcher_impl_test.cc index d110ad4b8f25..29282ff84949 100644 --- a/test/common/filesystem/watcher_impl_test.cc +++ b/test/common/filesystem/watcher_impl_test.cc @@ -47,11 +47,14 @@ TEST_F(WatcherImplTest, All) { WatchCallback callback; EXPECT_CALL(callback, called(Watcher::Events::MovedTo)).Times(2); - watcher->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_link"), - Watcher::Events::MovedTo, [&](uint32_t events) -> void { - callback.called(events); - dispatcher_->exit(); - }); + ASSERT_TRUE(watcher + ->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_link"), + Watcher::Events::MovedTo, + [&](uint32_t events) -> void { + callback.called(events); + dispatcher_->exit(); + }) + .ok()); TestEnvironment::renameFile(TestEnvironment::temporaryPath("envoy_test/watcher_new_link"), TestEnvironment::temporaryPath("envoy_test/watcher_link")); dispatcher_->run(Event::Dispatcher::RunType::Block); @@ -75,11 +78,14 @@ TEST_F(WatcherImplTest, Create) { { std::ofstream file(TestEnvironment::temporaryPath("envoy_test/watcher_target")); } WatchCallback callback; - watcher->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_link"), - Watcher::Events::MovedTo, [&](uint32_t events) -> void { - callback.called(events); - dispatcher_->exit(); - }); + ASSERT_TRUE(watcher + ->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_link"), + Watcher::Events::MovedTo, + [&](uint32_t events) -> void { + callback.called(events); + dispatcher_->exit(); + }) + .ok()); { std::ofstream file(TestEnvironment::temporaryPath("envoy_test/other_file")); } dispatcher_->run(Event::Dispatcher::RunType::NonBlock); @@ -99,11 +105,14 @@ TEST_F(WatcherImplTest, Modify) { std::ofstream file(TestEnvironment::temporaryPath("envoy_test/watcher_target")); WatchCallback callback; - watcher->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_target"), - Watcher::Events::Modified, [&](uint32_t events) -> void { - callback.called(events); - dispatcher_->exit(); - }); + ASSERT_TRUE(watcher + ->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_target"), + Watcher::Events::Modified, + [&](uint32_t events) -> void { + callback.called(events); + dispatcher_->exit(); + }) + .ok()); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); file << "text" << std::flush; @@ -115,13 +124,14 @@ TEST_F(WatcherImplTest, Modify) { TEST_F(WatcherImplTest, BadPath) { Filesystem::WatcherPtr watcher = dispatcher_->createFilesystemWatcher(); - EXPECT_THROW( - watcher->addWatch("this_is_not_a_file", Watcher::Events::MovedTo, [&](uint32_t) -> void {}), - EnvoyException); + EXPECT_FALSE( + watcher->addWatch("this_is_not_a_file", Watcher::Events::MovedTo, [&](uint32_t) -> void {}) + .ok()); - EXPECT_THROW(watcher->addWatch("this_is_not_a_dir/file", Watcher::Events::MovedTo, - [&](uint32_t) -> void {}), - EnvoyException); + EXPECT_FALSE( + watcher + ->addWatch("this_is_not_a_dir/file", Watcher::Events::MovedTo, [&](uint32_t) -> void {}) + .ok()); } TEST_F(WatcherImplTest, ParentDirectoryRemoved) { @@ -132,9 +142,11 @@ TEST_F(WatcherImplTest, ParentDirectoryRemoved) { WatchCallback callback; EXPECT_CALL(callback, called(testing::_)).Times(0); - watcher->addWatch(TestEnvironment::temporaryPath("envoy_test_empty/watcher_link"), - Watcher::Events::MovedTo, - [&](uint32_t events) -> void { callback.called(events); }); + ASSERT_TRUE(watcher + ->addWatch(TestEnvironment::temporaryPath("envoy_test_empty/watcher_link"), + Watcher::Events::MovedTo, + [&](uint32_t events) -> void { callback.called(events); }) + .ok()); int rc = rmdir(TestEnvironment::temporaryPath("envoy_test_empty").c_str()); EXPECT_EQ(0, rc); @@ -146,9 +158,9 @@ TEST_F(WatcherImplTest, RootDirectoryPath) { Filesystem::WatcherPtr watcher = dispatcher_->createFilesystemWatcher(); #ifndef WIN32 - EXPECT_NO_THROW(watcher->addWatch("/", Watcher::Events::MovedTo, [&](uint32_t) -> void {})); + EXPECT_TRUE(watcher->addWatch("/", Watcher::Events::MovedTo, [&](uint32_t) -> void {}).ok()); #else - EXPECT_NO_THROW(watcher->addWatch("c:\\", Watcher::Events::MovedTo, [&](uint32_t) -> void {})); + EXPECT_TRUE(watcher->addWatch("c:\\", Watcher::Events::MovedTo, [&](uint32_t) -> void {}).ok()); #endif } @@ -169,11 +181,14 @@ TEST_F(WatcherImplTest, SymlinkAtomicRename) { WatchCallback callback; EXPECT_CALL(callback, called(Watcher::Events::MovedTo)); - watcher->addWatch(TestEnvironment::temporaryPath("envoy_test/"), Watcher::Events::MovedTo, - [&](uint32_t events) -> void { - callback.called(events); - dispatcher_->exit(); - }); + ASSERT_TRUE(watcher + ->addWatch(TestEnvironment::temporaryPath("envoy_test/"), + Watcher::Events::MovedTo, + [&](uint32_t events) -> void { + callback.called(events); + dispatcher_->exit(); + }) + .ok()); TestEnvironment::createPath(TestEnvironment::temporaryPath("envoy_test/..timestamp2")); { std::ofstream file(TestEnvironment::temporaryPath("envoy_test/..timestamp2/watched_file")); } diff --git a/test/common/runtime/runtime_impl_test.cc b/test/common/runtime/runtime_impl_test.cc index b3bfb11fa75d..54cebe55ecfb 100644 --- a/test/common/runtime/runtime_impl_test.cc +++ b/test/common/runtime/runtime_impl_test.cc @@ -55,6 +55,7 @@ class LoaderImplTest : public testing::Test { Invoke([this](absl::string_view path, uint32_t, Filesystem::Watcher::OnChangedCb cb) { EXPECT_EQ(path, expected_watch_root_); on_changed_cbs_.emplace_back(cb); + return absl::OkStatus(); })); return mock_watcher; })); @@ -97,6 +98,7 @@ class DiskLoaderImplTest : public LoaderImplTest { absl::Status creation_status; loader_ = std::make_unique(dispatcher_, tls_, layered_runtime, local_info_, store_, generator_, validation_visitor_, *api_, creation_status); + THROW_IF_NOT_OK(creation_status); } void write(const std::string& path, const std::string& value) { @@ -329,6 +331,19 @@ TEST_F(DiskLoaderImplTest, OverrideFolderDoesNotExist) { EXPECT_EQ(1, store_.counter("runtime.override_dir_not_exists").value()); } +TEST_F(DiskLoaderImplTest, FileDoesNotExist) { + EXPECT_CALL(dispatcher_, createFilesystemWatcher_()).WillRepeatedly(InvokeWithoutArgs([] { + Filesystem::MockWatcher* mock_watcher = new NiceMock(); + EXPECT_CALL(*mock_watcher, addWatch(_, Filesystem::Watcher::Events::MovedTo, _)) + .WillRepeatedly(Return(absl::InvalidArgumentError("file does not exist"))); + return mock_watcher; + })); + + EXPECT_THROW_WITH_MESSAGE( + run("test/common/runtime/test_data/current", "envoy_override_does_not_exist"), EnvoyException, + "file does not exist"); +} + TEST_F(DiskLoaderImplTest, PercentHandling) { setup(); run("test/common/runtime/test_data/current", "envoy_override"); diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc index edc3327cbfb9..8d5e933f6e46 100644 --- a/test/common/secret/sds_api_test.cc +++ b/test/common/secret/sds_api_test.cc @@ -248,6 +248,7 @@ class TlsCertificateSdsRotationApiTest : public testing::TestWithParam, .WillOnce( Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { watch_cbs_.push_back(cb); + return absl::OkStatus(); })); EXPECT_CALL(filesystem_, fileReadToEnd(cert_path_)).WillOnce(Return(cert_value)); EXPECT_CALL(filesystem_, fileReadToEnd(key_path_)).WillOnce(Return(key_value)); @@ -262,6 +263,7 @@ class TlsCertificateSdsRotationApiTest : public testing::TestWithParam, .WillRepeatedly( Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { watch_cbs_.push_back(cb); + return absl::OkStatus(); })); } EXPECT_TRUE( @@ -318,10 +320,12 @@ class CertificateValidationContextSdsRotationApiTest : public testing::TestWithP EXPECT_CALL(*watcher, addWatch(watch_path, Filesystem::Watcher::Events::MovedTo, _)) .WillOnce(Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { watch_cbs_.push_back(cb); + return absl::OkStatus(); })); EXPECT_CALL(*watcher, addWatch(watch_path, Filesystem::Watcher::Events::MovedTo, _)) .WillOnce(Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { watch_cbs_.push_back(cb); + return absl::OkStatus(); })); EXPECT_TRUE( subscription_factory_.callbacks_->onConfigUpdate(decoded_resources.refvec_, "").ok()); diff --git a/test/extensions/compression/zstd/zstd_compression_test.cc b/test/extensions/compression/zstd/zstd_compression_test.cc index 25a5b3efcaac..f6ece8fbf7f7 100644 --- a/test/extensions/compression/zstd/zstd_compression_test.cc +++ b/test/extensions/compression/zstd/zstd_compression_test.cc @@ -49,6 +49,7 @@ class ZstdCompressionTest { .WillRepeatedly( Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { watch_cbs_.push_back(cb); + return absl::OkStatus(); })); return mock_watcher; })); diff --git a/test/extensions/config_subscription/filesystem/filesystem_subscription_impl_test.cc b/test/extensions/config_subscription/filesystem/filesystem_subscription_impl_test.cc index e358f635a989..ae959a94a79d 100644 --- a/test/extensions/config_subscription/filesystem/filesystem_subscription_impl_test.cc +++ b/test/extensions/config_subscription/filesystem/filesystem_subscription_impl_test.cc @@ -107,8 +107,11 @@ class FilesystemCollectionSubscriptionImplTest : public testing::Test, EXPECT_CALL(*dispatcher, createFilesystemWatcher_()).WillOnce(InvokeWithoutArgs([this] { Filesystem::MockWatcher* mock_watcher = new Filesystem::MockWatcher(); EXPECT_CALL(*mock_watcher, addWatch(path_.path(), Filesystem::Watcher::Events::MovedTo, _)) - .WillOnce(Invoke([this](absl::string_view, uint32_t, - Filesystem::Watcher::OnChangedCb cb) { on_changed_cb_ = cb; })); + .WillOnce( + Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { + on_changed_cb_ = cb; + return absl::OkStatus(); + })); return mock_watcher; })); return dispatcher; diff --git a/test/extensions/config_subscription/filesystem/filesystem_subscription_test_harness.h b/test/extensions/config_subscription/filesystem/filesystem_subscription_test_harness.h index a91fbc5e4a1b..14a4d6d08743 100644 --- a/test/extensions/config_subscription/filesystem/filesystem_subscription_test_harness.h +++ b/test/extensions/config_subscription/filesystem/filesystem_subscription_test_harness.h @@ -46,8 +46,11 @@ class FilesystemSubscriptionTestHarness : public SubscriptionTestHarness { EXPECT_CALL(*dispatcher, createFilesystemWatcher_()).WillOnce(InvokeWithoutArgs([this] { Filesystem::MockWatcher* mock_watcher = new Filesystem::MockWatcher(); EXPECT_CALL(*mock_watcher, addWatch(path_.path(), Filesystem::Watcher::Events::MovedTo, _)) - .WillOnce(Invoke([this](absl::string_view, uint32_t, - Filesystem::Watcher::OnChangedCb cb) { on_changed_cb_ = cb; })); + .WillOnce( + Invoke([this](absl::string_view, uint32_t, Filesystem::Watcher::OnChangedCb cb) { + on_changed_cb_ = cb; + return absl::OkStatus(); + })); return mock_watcher; })); return dispatcher; diff --git a/test/mocks/filesystem/mocks.h b/test/mocks/filesystem/mocks.h index 524cefd269f3..7032ce506d4f 100644 --- a/test/mocks/filesystem/mocks.h +++ b/test/mocks/filesystem/mocks.h @@ -75,7 +75,7 @@ class MockWatcher : public Watcher { MockWatcher(); ~MockWatcher() override; - MOCK_METHOD(void, addWatch, (absl::string_view, uint32_t, OnChangedCb)); + MOCK_METHOD(absl::Status, addWatch, (absl::string_view, uint32_t, OnChangedCb)); }; } // namespace Filesystem diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 01d341738e95..9e8952000b44 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -139,11 +139,9 @@ paths: - source/common/router/config_impl.cc - source/common/router/scoped_config_impl.cc - source/common/router/header_parser.cc - - source/common/filesystem/inotify/watcher_impl.cc - source/common/filesystem/posix/directory_iterator_impl.cc - source/common/filesystem/kqueue/watcher_impl.cc - source/common/filesystem/win32/directory_iterator_impl.cc - - source/common/filesystem/win32/watcher_impl.cc - source/common/common/utility.cc - source/common/common/regex.cc - source/common/common/matchers.cc @@ -172,6 +170,7 @@ paths: - source/common/local_reply/local_reply.cc - source/common/tls/context_impl.cc - source/common/tls/context_config_impl.cc + - source/common/config/watched_directory.cc # Only one C++ file should instantiate grpc_init grpc_init: From 1b768b49f9a85a865c35735a4944a001975055e9 Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Tue, 19 Mar 2024 04:42:27 +0200 Subject: [PATCH 05/11] access_logs: extract commands parsing to a separate method (#32944) * extract commands parsing to a separate method Signed-off-by: ohadvano * fix format Signed-off-by: ohadvano --------- Signed-off-by: ohadvano --- .../formatter/substitution_format_string.h | 31 +++-- .../substitution_format_string_test.cc | 106 ++++++++++++++++++ 2 files changed, 129 insertions(+), 8 deletions(-) diff --git a/source/common/formatter/substitution_format_string.h b/source/common/formatter/substitution_format_string.h index 3cdd032a9eee..fc1d6f5e6e83 100644 --- a/source/common/formatter/substitution_format_string.h +++ b/source/common/formatter/substitution_format_string.h @@ -22,16 +22,18 @@ namespace Formatter { */ class SubstitutionFormatStringUtils { public: + using FormattersConfig = + ProtobufWkt::RepeatedPtrField; + /** - * Generate a formatter object from config SubstitutionFormatString. + * Parse list of formatter configurations to commands. */ template - static FormatterBasePtr - fromProtoConfig(const envoy::config::core::v3::SubstitutionFormatString& config, + static std::vector> + parseFormatters(const FormattersConfig& formatters, Server::Configuration::GenericFactoryContext& context) { - // Instantiate formatter extensions. std::vector> commands; - for (const auto& formatter : config.formatters()) { + for (const auto& formatter : formatters) { auto* factory = Envoy::Config::Utility::getFactory>(formatter); if (!factory) { @@ -47,12 +49,24 @@ class SubstitutionFormatStringUtils { commands.push_back(std::move(parser)); } + return commands; + } + + /** + * Generate a formatter object from config SubstitutionFormatString. + */ + template + static FormatterBasePtr + fromProtoConfig(const envoy::config::core::v3::SubstitutionFormatString& config, + Server::Configuration::GenericFactoryContext& context) { + // Instantiate formatter extensions. + auto commands = parseFormatters(config.formatters(), context); switch (config.format_case()) { case envoy::config::core::v3::SubstitutionFormatString::FormatCase::kTextFormat: return std::make_unique>( config.text_format(), config.omit_empty_values(), commands); case envoy::config::core::v3::SubstitutionFormatString::FormatCase::kJsonFormat: - return std::make_unique>( + return createJsonFormatter( config.json_format(), true, config.omit_empty_values(), config.has_json_format_options() ? config.json_format_options().sort_properties() : false, commands); @@ -75,9 +89,10 @@ class SubstitutionFormatStringUtils { template static FormatterBasePtr createJsonFormatter(const ProtobufWkt::Struct& struct_format, bool preserve_types, - bool omit_empty_values, bool sort_properties) { + bool omit_empty_values, bool sort_properties, + const std::vector>& commands = {}) { return std::make_unique>( - struct_format, preserve_types, omit_empty_values, sort_properties); + struct_format, preserve_types, omit_empty_values, sort_properties, commands); } }; diff --git a/test/common/formatter/substitution_format_string_test.cc b/test/common/formatter/substitution_format_string_test.cc index 4f012f7aa4e8..6d74620d2a0e 100644 --- a/test/common/formatter/substitution_format_string_test.cc +++ b/test/common/formatter/substitution_format_string_test.cc @@ -212,5 +212,111 @@ TEST_F(SubstitutionFormatStringUtilsTest, TestFromProtoConfigJsonWithMultipleExt EXPECT_TRUE(TestUtility::jsonStringEqual(out_json, expected)); } +TEST_F(SubstitutionFormatStringUtilsTest, TestParseFormattersWithUnknownExtension) { + const std::string yaml = R"EOF( + name: envoy.formatter.TestFormatterUnknown + typed_config: + "@type": type.googleapis.com/google.protobuf.Any + )EOF"; + + SubstitutionFormatStringUtils::FormattersConfig config; + auto* entry1 = config.Add(); + envoy::config::core::v3::TypedExtensionConfig proto; + TestUtility::loadFromYaml(yaml, proto); + *entry1 = proto; + + EXPECT_THROW_WITH_MESSAGE(SubstitutionFormatStringUtils::parseFormatters(config, context_), + EnvoyException, + "Formatter not found: envoy.formatter.TestFormatterUnknown"); +} + +TEST_F(SubstitutionFormatStringUtilsTest, TestParseFormattersWithInvalidFormatter) { + FailCommandFactory fail_factory; + Registry::InjectFactory command_register(fail_factory); + + const std::string yaml = R"EOF( + name: envoy.formatter.FailFormatter + typed_config: + "@type": type.googleapis.com/google.protobuf.UInt64Value + )EOF"; + + SubstitutionFormatStringUtils::FormattersConfig config; + auto* entry1 = config.Add(); + envoy::config::core::v3::TypedExtensionConfig proto; + TestUtility::loadFromYaml(yaml, proto); + *entry1 = proto; + + EXPECT_THROW_WITH_MESSAGE(SubstitutionFormatStringUtils::parseFormatters(config, context_), + EnvoyException, + "Failed to create command parser: envoy.formatter.FailFormatter"); +} + +TEST_F(SubstitutionFormatStringUtilsTest, TestParseFormattersWithSingleExtension) { + TestCommandFactory factory; + Registry::InjectFactory command_register(factory); + + const std::string yaml = R"EOF( + name: envoy.formatter.TestFormatter + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + )EOF"; + + SubstitutionFormatStringUtils::FormattersConfig config; + auto* entry1 = config.Add(); + envoy::config::core::v3::TypedExtensionConfig proto; + TestUtility::loadFromYaml(yaml, proto); + *entry1 = proto; + + auto commands = SubstitutionFormatStringUtils::parseFormatters(config, context_); + ASSERT_EQ(1, commands.size()); + + absl::optional max_length = {}; + ASSERT_TRUE(commands[0] != nullptr); + auto provider = commands[0]->parse("COMMAND_EXTENSION", "", max_length); + ASSERT_TRUE(provider != nullptr); +} + +TEST_F(SubstitutionFormatStringUtilsTest, TestParseFormattersWithMultipleExtensions) { + TestCommandFactory factory; + Registry::InjectFactory command_register(factory); + AdditionalCommandFactory additional_factory; + Registry::InjectFactory additional_command_register(additional_factory); + + const std::string test_command_yaml = R"EOF( + name: envoy.formatter.TestFormatter + typed_config: + "@type": type.googleapis.com/google.protobuf.StringValue + )EOF"; + + const std::string additional_command_yaml = R"EOF( + name: envoy.formatter.AdditionalFormatter + typed_config: + "@type": type.googleapis.com/google.protobuf.UInt32Value + )EOF"; + + SubstitutionFormatStringUtils::FormattersConfig config; + + auto* entry1 = config.Add(); + envoy::config::core::v3::TypedExtensionConfig test_command_proto; + TestUtility::loadFromYaml(test_command_yaml, test_command_proto); + *entry1 = test_command_proto; + + auto* entry2 = config.Add(); + envoy::config::core::v3::TypedExtensionConfig additional_command_proto; + TestUtility::loadFromYaml(additional_command_yaml, additional_command_proto); + *entry2 = additional_command_proto; + + auto commands = SubstitutionFormatStringUtils::parseFormatters(config, context_); + ASSERT_EQ(2, commands.size()); + + absl::optional max_length = {}; + ASSERT_TRUE(commands[0] != nullptr); + auto test_command_provider = commands[0]->parse("COMMAND_EXTENSION", "", max_length); + ASSERT_TRUE(test_command_provider != nullptr); + ASSERT_TRUE(commands[1] != nullptr); + auto additional_command_provider = commands[1]->parse("ADDITIONAL_EXTENSION", "", max_length); + ASSERT_TRUE(additional_command_provider != nullptr); +} + } // namespace Formatter } // namespace Envoy From ffcc257e16c9046b2fec7497a6bf9293d8ada286 Mon Sep 17 00:00:00 2001 From: code Date: Tue, 19 Mar 2024 21:22:20 +0800 Subject: [PATCH 06/11] generic proxy: complete the development of HTTP1 codec (#32488) * generic proxy: complete the development of HTTP1 codec Signed-off-by: wbpcode * minor update Signed-off-by: wbpcode * add TODOs Signed-off-by: wbpcode * more validation and single frame mode for HTTP Signed-off-by: wbpcode * more test and validation Signed-off-by: wbpcode * handle the 100 continue and the 1xx response Signed-off-by: wbpcode * minor update Signed-off-by: wbpcode * address comments Signed-off-by: wbpcode * address comments Signed-off-by: wbpcode --------- Signed-off-by: wbpcode --- api/BUILD | 1 + .../generic_proxy/codecs/http1/v3/BUILD | 12 + .../generic_proxy/codecs/http1/v3/http1.proto | 49 + api/versioning/BUILD | 1 + contrib/contrib_build_config.bzl | 1 + contrib/extensions_metadata.yaml | 7 + .../network/source/codecs/dubbo/config.h | 3 +- .../filters/network/source/codecs/http1/BUILD | 28 + .../network/source/codecs/http1/config.cc | 653 ++++++++ .../network/source/codecs/http1/config.h | 394 +++++ .../filters/network/source/router/router.cc | 37 +- .../filters/network/source/router/router.h | 3 +- .../filters/network/source/upstream.cc | 40 +- .../filters/network/source/upstream.h | 1 - .../filters/network/test/codecs/http1/BUILD | 21 + .../network/test/codecs/http1/config_test.cc | 1482 +++++++++++++++++ tools/proto_format/format_api.py | 1 + 17 files changed, 2694 insertions(+), 40 deletions(-) create mode 100644 api/contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3/BUILD create mode 100644 api/contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3/http1.proto create mode 100644 contrib/generic_proxy/filters/network/source/codecs/http1/BUILD create mode 100644 contrib/generic_proxy/filters/network/source/codecs/http1/config.cc create mode 100644 contrib/generic_proxy/filters/network/source/codecs/http1/config.h create mode 100644 contrib/generic_proxy/filters/network/test/codecs/http1/BUILD create mode 100644 contrib/generic_proxy/filters/network/test/codecs/http1/config_test.cc diff --git a/api/BUILD b/api/BUILD index 829715418693..613bcf064591 100644 --- a/api/BUILD +++ b/api/BUILD @@ -83,6 +83,7 @@ proto_library( "//contrib/envoy/extensions/filters/network/client_ssl_auth/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/action/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/codecs/dubbo/v3:pkg", + "//contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/codecs/kafka/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/matcher/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/router/v3:pkg", diff --git a/api/contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3/BUILD b/api/contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3/BUILD new file mode 100644 index 000000000000..d49202b74ab4 --- /dev/null +++ b/api/contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3/BUILD @@ -0,0 +1,12 @@ +# 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_xds//udpa/annotations:pkg", + "@com_github_cncf_xds//xds/annotations/v3:pkg", + ], +) diff --git a/api/contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3/http1.proto b/api/contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3/http1.proto new file mode 100644 index 000000000000..973a190d8138 --- /dev/null +++ b/api/contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3/http1.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package envoy.extensions.filters.network.generic_proxy.codecs.http1.v3; + +import "google/protobuf/wrappers.proto"; + +import "xds/annotations/v3/status.proto"; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.network.generic_proxy.codecs.http1.v3"; +option java_outer_classname = "Http1Proto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3;http1v3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; +option (xds.annotations.v3.file_status).work_in_progress = true; + +// [#protodoc-title: HTTP1 codec configuration for Generic Proxy] +// [#extension: envoy.generic_proxy.codecs.http1] + +// Configuration for HTTP codec. This HTTP1 codec is used to parse and serialize HTTP1 messages +// for the generic proxy filter. +// Any decoding error will result in the generic proxy closing the connection. +// +// .. note:: +// This codec only supports HTTP1.1 messages and does not support HTTP1.0 messages. And it limits +// part of the HTTP1.1 features, such as upgrade, connect, etc. +// This codec is mainly designed for the features evaluation of the generic proxy filter. Please +// be cautious when using it in production. +message Http1CodecConfig { + // If true, the codec will parse and serialize HTTP1 messages in a single frame per message. + // + // A frame is a minimal unit of data that can be processed by the generic proxy. If false, the + // codec will parse and serialize HTTP1 messages in a streaming way. In this case, the codec + // will output multiple frames for a single HTTP1 message to the generic proxy. + // If true, the codec will buffer the entire HTTP1 message body before sending it to the generic + // proxy. This may have better performance in small message scenarios and is more friendly to + // handle the HTTP1 message body. This also may result in higher memory usage and latency if + // the message body is large. + // + // Default is true. + google.protobuf.BoolValue single_frame_mode = 1; + + // The maximum size of the HTTP1 message body in bytes. If not set, 8*1024*1024 (8MB) is used. + // This only makes sense when single_frame_mode is true. + // If the HTTP1 message body size exceeds this value, this will result in a decoding error + // and the generic proxy will close the connection. + google.protobuf.UInt32Value max_buffer_size = 2; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index b74e3df2a867..56952b6ff2ad 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -21,6 +21,7 @@ proto_library( "//contrib/envoy/extensions/filters/network/client_ssl_auth/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/action/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/codecs/dubbo/v3:pkg", + "//contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/codecs/kafka/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/matcher/v3:pkg", "//contrib/envoy/extensions/filters/network/generic_proxy/router/v3:pkg", diff --git a/contrib/contrib_build_config.bzl b/contrib/contrib_build_config.bzl index 49b1f7afde79..7be1cd02ffad 100644 --- a/contrib/contrib_build_config.bzl +++ b/contrib/contrib_build_config.bzl @@ -73,6 +73,7 @@ CONTRIB_EXTENSIONS = { # "envoy.filters.generic.router": "//contrib/generic_proxy/filters/network/source/router:config", "envoy.generic_proxy.codecs.dubbo": "//contrib/generic_proxy/filters/network/source/codecs/dubbo:config", + "envoy.generic_proxy.codecs.http1": "//contrib/generic_proxy/filters/network/source/codecs/http1:config", "envoy.generic_proxy.codecs.kafka": "//contrib/generic_proxy/filters/network/source/codecs/kafka:config", # diff --git a/contrib/extensions_metadata.yaml b/contrib/extensions_metadata.yaml index d8aee9d710fb..76218e5a2b85 100644 --- a/contrib/extensions_metadata.yaml +++ b/contrib/extensions_metadata.yaml @@ -144,6 +144,13 @@ envoy.generic_proxy.codecs.kafka: status: wip type_urls: - envoy.extensions.filters.network.generic_proxy.codecs.kafka.v3.KafkaCodecConfig +envoy.generic_proxy.codecs.http1: + categories: + - envoy.generic_proxy.codecs + security_posture: requires_trusted_downstream_and_upstream + status: wip + type_urls: + - envoy.extensions.filters.network.generic_proxy.codecs.http1.v3.Http1CodecConfig envoy.router.cluster_specifier_plugin.golang: categories: - envoy.router.cluster_specifier_plugin diff --git a/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h b/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h index 6ef3932be244..86acc6f2aa9c 100644 --- a/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h +++ b/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h @@ -151,8 +151,7 @@ class DubboServerCodec public: using DubboDecoderBase::DubboDecoderBase; - ResponsePtr respond(absl::Status status, absl::string_view short_response_flags, - const Request& request) override; + ResponsePtr respond(absl::Status status, absl::string_view data, const Request& request) override; }; class DubboClientCodec diff --git a/contrib/generic_proxy/filters/network/source/codecs/http1/BUILD b/contrib/generic_proxy/filters/network/source/codecs/http1/BUILD new file mode 100644 index 000000000000..adbc830baa9d --- /dev/null +++ b/contrib/generic_proxy/filters/network/source/codecs/http1/BUILD @@ -0,0 +1,28 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_contrib_extension", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_contrib_extension( + name = "config", + srcs = [ + "config.cc", + ], + hdrs = [ + "config.h", + ], + deps = [ + "//contrib/generic_proxy/filters/network/source/interface:codec_interface", + "//source/common/http:codes_lib", + "//source/common/http:header_utility_lib", + "//source/common/http:headers_lib", + "//source/common/http:utility_lib", + "//source/common/http/http1:balsa_parser_lib", + "@envoy_api//contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3:pkg_cc_proto", + ], +) diff --git a/contrib/generic_proxy/filters/network/source/codecs/http1/config.cc b/contrib/generic_proxy/filters/network/source/codecs/http1/config.cc new file mode 100644 index 000000000000..c16287b4db60 --- /dev/null +++ b/contrib/generic_proxy/filters/network/source/codecs/http1/config.cc @@ -0,0 +1,653 @@ +#include "contrib/generic_proxy/filters/network/source/codecs/http1/config.h" + +#include "source/common/http/codes.h" +#include "source/common/http/header_utility.h" +#include "source/common/http/utility.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace GenericProxy { +namespace Codec { +namespace Http1 { + +static constexpr absl::string_view CRLF = "\r\n"; +// Last chunk as defined here https://tools.ietf.org/html/rfc7230#section-4.1 +static constexpr absl::string_view LAST_CHUNK = "0\r\n"; + +static constexpr absl::string_view SPACE = " "; +static constexpr absl::string_view COLON_SPACE = ": "; + +static constexpr absl::string_view REQUEST_POSTFIX = " HTTP/1.1\r\n"; +static constexpr absl::string_view RESPONSE_PREFIX = "HTTP/1.1 "; + +static constexpr absl::string_view HOST_HEADER_PREFIX = ":a"; + +static constexpr uint32_t DEFAULT_MAX_BUFFER_SIZE = 8 * 1024 * 1024; + +static constexpr absl::string_view _100_CONTINUE_RESPONSE = "HTTP/1.1 100 Continue\r\n" + "content-length: 0\r\n" + "\r\n"; + +void encodeNormalHeaders(Buffer::Instance& buffer, const Http::RequestOrResponseHeaderMap& headers, + bool chunk_encoding) { + headers.iterate([&buffer](const Http::HeaderEntry& header) { + absl::string_view key_to_use = header.key().getStringView(); + // Translate :authority -> host so that upper layers do not need to deal with this. + if (absl::StartsWith(key_to_use, HOST_HEADER_PREFIX)) { + key_to_use = Http::Headers::get().HostLegacy; + } + + // Skip all headers starting with ':' that make it here. + if (key_to_use[0] == ':') { + return Http::HeaderMap::Iterate::Continue; + } + + buffer.addFragments({key_to_use, COLON_SPACE, header.value().getStringView(), CRLF}); + return Http::HeaderMap::Iterate::Continue; + }); + + if (chunk_encoding) { + if (headers.TransferEncoding() == nullptr) { + buffer.add("Transfer-Encoding: chunked\r\n"); + } + } +} + +absl::Status validateCommonHeaders(const Http::RequestOrResponseHeaderMap& headers) { + // Both Transfer-Encoding and Content-Length are set. + if (headers.TransferEncoding() != nullptr && headers.ContentLength() != nullptr) { + return absl::InvalidArgumentError("Both transfer-encoding and content-length are set"); + } + + // Transfer-Encoding is not chunked. + if (headers.TransferEncoding() != nullptr) { + absl::string_view encoding = headers.TransferEncoding()->value().getStringView(); + if (!absl::EqualsIgnoreCase(encoding, Http::Headers::get().TransferEncodingValues.Chunked)) { + return absl::InvalidArgumentError("transfer-encoding is not chunked"); + } + } + + return absl::OkStatus(); +} + +bool Utility::isChunked(const Http::RequestOrResponseHeaderMap& headers, bool bodiless) { + // If the message has body and the content length is not set, then treat it as chunked. + // Note all upgrade and connect requests are rejected so this is safe. + return !bodiless && headers.ContentLength() == nullptr; +} + +bool Utility::hasBody(const Envoy::Http::Http1::Parser& parser, bool response, + bool response_for_head_request) { + // Response for HEAD request should not have body. + if (response_for_head_request) { + ASSERT(response); + return false; + } + + // 1xx, 204, 304 responses should not have body. + if (response) { + const Envoy::Http::Code code = parser.statusCode(); + if (code < Http::Code::OK || code == Http::Code::NoContent || code == Http::Code::NotModified) { + return false; + } + } + + // Check the transfer-encoding and content-length headers in other cases. + return parser.isChunked() || parser.contentLength().value_or(0) > 0; +} + +absl::Status Utility::validateRequestHeaders(Http::RequestHeaderMap& headers) { + // No upgrade and connect support for now. + // TODO(wbpcode): add support for upgrade and connect in the future. + if (Http::Utility::isUpgrade(headers) || + headers.getMethodValue() == Http::Headers::get().MethodValues.Connect) { + return absl::InvalidArgumentError("upgrade or connect are not supported"); + } + + if (auto status = validateCommonHeaders(headers); !status.ok()) { + return status; + } + + // One of method, path, host is missing. + if (headers.Method() == nullptr || headers.Path() == nullptr || headers.Host() == nullptr) { + return absl::InvalidArgumentError("missing required headers"); + } + + return absl::OkStatus(); +} + +absl::Status Utility::validateResponseHeaders(Http::ResponseHeaderMap& headers, + Envoy::Http::Code code) { + if (auto status = validateCommonHeaders(headers); !status.ok()) { + return status; + } + + ASSERT(headers.Status() != nullptr); + + if (code < Http::Code::OK || code == Http::Code::NoContent || code == Http::Code::NotModified) { + // There is no clear description in the RFC about the transfer-encoding behavior + // of NotModified response. But 1xx, 204 responses should not have transfer-encoding. + // See https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-6 + if (code != Http::Code::NotModified) { + if (headers.TransferEncoding() != nullptr) { + return absl::InvalidArgumentError("transfer-encoding is set for 1xx, 204 response"); + } + } + + // 1xx, 204, 304 responses should not have body. + if (headers.ContentLength() != nullptr) { + if (headers.ContentLength()->value().getStringView() != "0") { + return absl::InvalidArgumentError( + "content-length (non-zero) is set for 1xx, 204, 304 response"); + } + } + } + + return absl::OkStatus(); +} + +absl::Status Utility::encodeRequestHeaders(Buffer::Instance& buffer, + const Http::RequestHeaderMap& headers, + bool chunk_encoding) { + const Http::HeaderEntry* method = headers.Method(); + const Http::HeaderEntry* path = headers.Path(); + const Http::HeaderEntry* host = headers.Host(); + + if (method == nullptr || path == nullptr || host == nullptr) { + return absl::InvalidArgumentError("missing required headers"); + } + + absl::string_view host_or_path_view = path->value().getStringView(); + + buffer.addFragments({method->value().getStringView(), SPACE, host_or_path_view, REQUEST_POSTFIX}); + encodeNormalHeaders(buffer, headers, chunk_encoding); + buffer.add(CRLF); + + return absl::OkStatus(); +} + +uint64_t Utility::statusToHttpStatus(absl::StatusCode status_code) { + switch (status_code) { + case absl::StatusCode::kOk: + return 200; + case absl::StatusCode::kCancelled: + return 499; + case absl::StatusCode::kUnknown: + // Internal server error. + return 500; + case absl::StatusCode::kInvalidArgument: + // Bad request. + return 400; + case absl::StatusCode::kDeadlineExceeded: + // Gateway Time-out. + return 504; + case absl::StatusCode::kNotFound: + // Not found. + return 404; + case absl::StatusCode::kAlreadyExists: + // Conflict. + return 409; + case absl::StatusCode::kPermissionDenied: + // Forbidden. + return 403; + case absl::StatusCode::kResourceExhausted: + // Too many requests. + return 429; + case absl::StatusCode::kFailedPrecondition: + // Bad request. + return 400; + case absl::StatusCode::kAborted: + // Conflict. + return 409; + case absl::StatusCode::kOutOfRange: + // Bad request. + return 400; + case absl::StatusCode::kUnimplemented: + // Not implemented. + return 501; + case absl::StatusCode::kInternal: + // Internal server error. + return 500; + case absl::StatusCode::kUnavailable: + // Service unavailable. + return 503; + case absl::StatusCode::kDataLoss: + // Internal server error. + return 500; + case absl::StatusCode::kUnauthenticated: + // Unauthorized. + return 401; + default: + // Internal server error. + return 500; + } +} + +absl::Status Utility::encodeResponseHeaders(Buffer::Instance& buffer, + const Http::ResponseHeaderMap& headers, + bool chunk_encoding) { + const Http::HeaderEntry* status = headers.Status(); + if (status == nullptr) { + return absl::InvalidArgumentError("missing required headers"); + } + uint64_t numeric_status = Http::Utility::getResponseStatus(headers); + + absl::string_view reason_phrase; + const char* status_string = Http::CodeUtility::toString(static_cast(numeric_status)); + uint32_t status_string_len = strlen(status_string); + reason_phrase = {status_string, status_string_len}; + + buffer.addFragments({RESPONSE_PREFIX, absl::StrCat(numeric_status), SPACE, reason_phrase, CRLF}); + encodeNormalHeaders(buffer, headers, chunk_encoding); + buffer.add(CRLF); + + return absl::OkStatus(); +} + +void Utility::encodeBody(Buffer::Instance& dst_buffer, Buffer::Instance& src_buffer, + bool chunk_encoding, bool end_stream) { + if (src_buffer.length() > 0) { + // Chunk header. + if (chunk_encoding) { + dst_buffer.add(absl::StrCat(absl::Hex(src_buffer.length()), CRLF)); + } + + // Body. + dst_buffer.move(src_buffer); + + // Chunk footer. + if (chunk_encoding) { + dst_buffer.add(CRLF); + } + } + + // Add additional LAST_CHUNK if this is the last frame and chunk encoding is enabled. + if (end_stream) { + if (chunk_encoding) { + dst_buffer.addFragments({LAST_CHUNK, CRLF}); + } + } +} + +bool Http1CodecBase::decodeBuffer(Buffer::Instance& buffer) { + decoding_buffer_.move(buffer); + + // Always resume before decoding. + parser_->resume(); + + while (decoding_buffer_.length() > 0) { + const auto slice = decoding_buffer_.frontSlice(); + const auto nread = parser_->execute(static_cast(slice.mem_), slice.len_); + decoding_buffer_.drain(nread); + const auto status = parser_->getStatus(); + + // Parser is paused by the callback. Do nothing and return. Don't handle the buffered body + // because parser is paused and no callback should be called. + if (status == Http::Http1::ParserStatus::Paused) { + return true; + } + // Parser has encountered an error. Return false to indicate decoding failure. Ignore the + // buffered body. + if (status == Http::Http1::ParserStatus::Error) { + // Decoding error. + return false; + } + // No more data to read and parser is not paused, break to avoid infinite loop. This is + // preventive check. The parser should not be in this state in normal cases. + if (nread == 0) { + return true; + } + } + // Try to dispatch any buffered body. If the message is complete then this will be a no-op. + dispatchBufferedBody(false); + return true; +} + +void Http1CodecBase::dispatchBufferedBody(bool end_stream) { + if (single_frame_mode_) { + // Do nothing until the onMessageComplete callback if we are in single frame mode. + + // Check if the buffered body is too large. + if (bufferedBodyOverflow()) { + // Pause the parser to avoid further parsing. + parser_->pause(); + // Tell the caller that the decoding failed. + onDecodingFailure(); + } + return; + } + + if (buffered_body_.length() > 0 || end_stream) { + ENVOY_LOG(debug, + "Generic proxy HTTP1 codec: decoding request/response body (end_stream={} size={})", + end_stream, buffered_body_.length()); + auto frame = std::make_unique(buffered_body_, end_stream); + onDecodingSuccess(std::move(frame)); + } +} + +bool Http1CodecBase::bufferedBodyOverflow() { + if (buffered_body_.length() < max_buffer_size_) { + return false; + } + + ENVOY_LOG(warn, "Generic proxy HTTP1 codec: body size exceeds max size({} vs {})", + buffered_body_.length(), max_buffer_size_); + return true; +} + +Http::Http1::CallbackResult Http1ServerCodec::onMessageBeginImpl() { + if (active_request_.has_value()) { + ENVOY_LOG(error, + "Generic proxy HTTP1 codec: multiple requests on the same connection at same time."); + return Http::Http1::CallbackResult::Error; + } + active_request_ = ActiveRequest{}; + + active_request_->request_headers_ = Http::RequestHeaderMapImpl::create(); + return Http::Http1::CallbackResult::Success; +} + +Http::Http1::CallbackResult Http1ServerCodec::onHeadersCompleteImpl() { + if (!parser_->isHttp11()) { + ENVOY_LOG(error, + "Generic proxy HTTP1 codec: unsupported HTTP version, only HTTP/1.1 is supported."); + return Http::Http1::CallbackResult::Error; + } + + active_request_->request_headers_->setMethod(parser_->methodName()); + + // Validate request headers. + const auto validate_headers_status = + Utility::validateRequestHeaders(*active_request_->request_headers_); + if (!validate_headers_status.ok()) { + ENVOY_LOG(error, "Generic proxy HTTP1 codec: failed to validate request headers: {}", + validate_headers_status.message()); + return Http::Http1::CallbackResult::Error; + } + + const bool non_end_stream = Utility::hasBody(*parser_, false, false); + ENVOY_LOG(debug, "decoding request headers complete (end_stream={}):\n{}", !non_end_stream, + *active_request_->request_headers_); + + // Handle the Expect header first. + if (active_request_->request_headers_->Expect() != nullptr) { + if (absl::EqualsIgnoreCase(active_request_->request_headers_->getExpectValue(), + Envoy::Http::Headers::get().ExpectValues._100Continue)) { + // Remove the expect header then the upstream server won't handle it again. + active_request_->request_headers_->removeExpect(); + + // Send 100 Continue response directly. We won't proxy the 100 Continue because + // the complexity in the generic proxy framework is too high. + Buffer::OwnedImpl buffer(_100_CONTINUE_RESPONSE); + callbacks_->writeToConnection(buffer); + } + } + + if (single_frame_mode_) { + // Do nothing until the onMessageComplete callback if we are in single frame mode. + return Http::Http1::CallbackResult::Success; + } else if (non_end_stream) { + auto request = + std::make_unique(std::move(active_request_->request_headers_), false); + onDecodingSuccess(std::move(request)); + } else { + deferred_end_stream_headers_ = true; + } + + return Http::Http1::CallbackResult::Success; +} + +Http::Http1::CallbackResult Http1ServerCodec::onMessageCompleteImpl() { + active_request_->request_complete_ = true; + + if (single_frame_mode_) { + // Check if the buffered body is too large. + if (bufferedBodyOverflow()) { + // Pause the parser to avoid further parsing. + parser_->pause(); + // Tell the caller that the decoding failed. + return Http::Http1::CallbackResult::Error; + } + + ASSERT(!deferred_end_stream_headers_); + auto request = + std::make_unique(std::move(active_request_->request_headers_), true); + request->optionalBuffer().move(buffered_body_); + + if (request->optionalBuffer().length() > 0) { + request->headerMap().removeTransferEncoding(); + request->headerMap().setContentLength(request->optionalBuffer().length()); + } + onDecodingSuccess(std::move(request)); + } else if (deferred_end_stream_headers_) { + deferred_end_stream_headers_ = false; + auto request = + std::make_unique(std::move(active_request_->request_headers_), true); + onDecodingSuccess(std::move(request)); + } else { + dispatchBufferedBody(true); + } + + parser_->pause(); + return Http::Http1::CallbackResult::Success; +} + +void Http1ServerCodec::encode(const StreamFrame& frame, EncodingCallbacks& callbacks) { + const bool response_end_stream = frame.frameFlags().endStream(); + + if (auto* headers = dynamic_cast(&frame); headers != nullptr) { + ENVOY_LOG(debug, "encoding response headers (end_stream={}):\n{}", response_end_stream, + *headers->response_); + + active_request_->response_chunk_encoding_ = + Utility::isChunked(*headers->response_, response_end_stream); + + auto status = Utility::encodeResponseHeaders(encoding_buffer_, *headers->response_, + active_request_->response_chunk_encoding_); + if (!status.ok()) { + ENVOY_LOG(error, "Generic proxy HTTP1 codec: failed to encode response headers: {}", + status.message()); + callbacks_->connection()->close(Network::ConnectionCloseType::FlushWrite); + return; + } + + // Encode the optional buffer if it exists. This is used for local response or for the + // request/responses in single frame mode. + if (headers->optionalBuffer().length() > 0) { + ASSERT(response_end_stream); + Utility::encodeBody(encoding_buffer_, headers->optionalBuffer(), + active_request_->response_chunk_encoding_, response_end_stream); + } + + } else if (auto* body = dynamic_cast(&frame); body != nullptr) { + ENVOY_LOG(debug, "encoding response body (end_stream={} size={})", response_end_stream, + body->buffer().length()); + Utility::encodeBody(encoding_buffer_, body->buffer(), active_request_->response_chunk_encoding_, + response_end_stream); + } + + callbacks.onEncodingSuccess(encoding_buffer_, response_end_stream); + + if (response_end_stream) { + if (active_request_->request_complete_) { + active_request_.reset(); + return; + } + ENVOY_LOG(debug, "Generic proxy HTTP1 server codec: response complete before request complete"); + if (callbacks_->connection().has_value()) { + callbacks_->connection()->close(Network::ConnectionCloseType::FlushWrite); + } + } +} + +void Http1ClientCodec::encode(const StreamFrame& frame, EncodingCallbacks& callbacks) { + const bool request_end_stream = frame.frameFlags().endStream(); + + if (auto* headers = dynamic_cast(&frame); headers != nullptr) { + ENVOY_LOG(debug, "encoding request headers (end_stream={}):\n{}", request_end_stream, + *headers->request_); + + ASSERT(!expect_response_.has_value()); + expect_response_ = ExpectResponse{}; + expect_response_->request_chunk_encoding_ = + Utility::isChunked(*headers->request_, request_end_stream); + expect_response_->head_request_ = + headers->request_->getMethodValue() == Http::Headers::get().MethodValues.Head; + + auto status = Utility::encodeRequestHeaders(encoding_buffer_, *headers->request_, + expect_response_->request_chunk_encoding_); + if (!status.ok()) { + ENVOY_LOG(error, "Generic proxy HTTP1 codec: failed to encode request headers: {}", + status.message()); + callbacks_->connection()->close(Network::ConnectionCloseType::FlushWrite); + return; + } + + // Encode the optional buffer if it exists. This is used for local response or for the + // request/responses in single frame mode. + if (headers->optionalBuffer().length() > 0) { + ASSERT(request_end_stream); + Utility::encodeBody(encoding_buffer_, headers->optionalBuffer(), + expect_response_->request_chunk_encoding_, request_end_stream); + } + + } else if (auto* body = dynamic_cast(&frame); body != nullptr) { + ENVOY_LOG(debug, "encoding request body (end_stream={} size={})", request_end_stream, + body->buffer().length()); + Utility::encodeBody(encoding_buffer_, body->buffer(), expect_response_->request_chunk_encoding_, + request_end_stream); + } + + if (request_end_stream) { + expect_response_->request_complete_ = true; + } + + callbacks.onEncodingSuccess(encoding_buffer_, request_end_stream); +} + +Http::Http1::CallbackResult Http1ClientCodec::onMessageBeginImpl() { + if (!expect_response_.has_value()) { + ENVOY_LOG(error, "Generic proxy HTTP1 codec: unexpected HTTP response from upstream"); + return Http::Http1::CallbackResult::Error; + } + expect_response_->response_headers_ = Http::ResponseHeaderMapImpl::create(); + return Http::Http1::CallbackResult::Success; +} + +Http::Http1::CallbackResult Http1ClientCodec::onHeadersCompleteImpl() { + if (!parser_->isHttp11()) { + ENVOY_LOG(error, + "Generic proxy HTTP1 codec: unsupported HTTP version, only HTTP/1.1 is supported."); + return Http::Http1::CallbackResult::Error; + } + + expect_response_->response_headers_->setStatus( + std::to_string(static_cast(parser_->statusCode()))); + + // Validate response headers. + const auto validate_headers_status = + Utility::validateResponseHeaders(*expect_response_->response_headers_, parser_->statusCode()); + if (!validate_headers_status.ok()) { + ENVOY_LOG(error, "Generic proxy HTTP1 codec: failed to validate response headers: {}", + validate_headers_status.message()); + return Http::Http1::CallbackResult::Error; + } + + const bool non_end_stream = Utility::hasBody(*parser_, true, expect_response_->head_request_); + + ENVOY_LOG(debug, "decoding response headers complete (end_stream={}):\n{}", !non_end_stream, + *expect_response_->response_headers_); + + if (single_frame_mode_) { + // Do nothing until the onMessageComplete callback if we are in single frame mode. + return Http::Http1::CallbackResult::Success; + } else if (non_end_stream) { + auto request = + std::make_unique(std::move(expect_response_->response_headers_), false); + onDecodingSuccess(std::move(request)); + } else { + deferred_end_stream_headers_ = true; + } + + return Http::Http1::CallbackResult::Success; +} + +Http::Http1::CallbackResult Http1ClientCodec::onMessageCompleteImpl() { + const auto status_code = parser_->statusCode(); + if (status_code < Envoy::Http::Code::OK) { + // There is no difference bewteen single frame mode and normal mode for 1xx responses + // because they are headers only responses. + + ASSERT(buffered_body_.length() == 0); + + expect_response_->response_headers_.reset(); + buffered_body_.drain(buffered_body_.length()); + + // 100 Continue response. Ignore it. + // 101 Switching Protocols response. Ignore it because we don't support upgrade for now. + // 102 Processing response. Ignore it. + // 103 Early Hints response. Ignore it. + + // Return success to continue parsing the actual response. + return Http::Http1::CallbackResult::Success; + } + + if (single_frame_mode_) { + // Check if the buffered body is too large. + if (bufferedBodyOverflow()) { + // Pause the parser to avoid further parsing. + parser_->pause(); + // Tell the caller that the decoding failed. + return Http::Http1::CallbackResult::Error; + } + + ASSERT(!deferred_end_stream_headers_); + auto response = + std::make_unique(std::move(expect_response_->response_headers_), true); + response->optionalBuffer().move(buffered_body_); + + if (response->optionalBuffer().length() > 0) { + response->headerMap().removeTransferEncoding(); + response->headerMap().setContentLength(response->optionalBuffer().length()); + } + + onDecodingSuccess(std::move(response)); + } else if (deferred_end_stream_headers_) { + deferred_end_stream_headers_ = false; + auto response = + std::make_unique(std::move(expect_response_->response_headers_), true); + onDecodingSuccess(std::move(response)); + } else { + dispatchBufferedBody(true); + } + // If both request and response is complete, reset the state and pause the parser. + if (expect_response_->request_complete_) { + parser_->pause(); + expect_response_.reset(); + return Http::Http1::CallbackResult::Success; + } else { + ENVOY_LOG(debug, "Generic proxy HTTP1 client codec: response complete before request complete"); + return Http::Http1::CallbackResult::Error; + } +} + +CodecFactoryPtr +Http1CodecFactoryConfig::createCodecFactory(const Protobuf::Message& config, + Envoy::Server::Configuration::FactoryContext&) { + const auto& typed_config = dynamic_cast(config); + + return std::make_unique( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(typed_config, single_frame_mode, true), + PROTOBUF_GET_WRAPPED_OR_DEFAULT(typed_config, max_buffer_size, DEFAULT_MAX_BUFFER_SIZE)); +} + +REGISTER_FACTORY(Http1CodecFactoryConfig, CodecFactoryConfig); + +} // namespace Http1 +} // namespace Codec +} // namespace GenericProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/generic_proxy/filters/network/source/codecs/http1/config.h b/contrib/generic_proxy/filters/network/source/codecs/http1/config.h new file mode 100644 index 000000000000..6a77b3329d64 --- /dev/null +++ b/contrib/generic_proxy/filters/network/source/codecs/http1/config.h @@ -0,0 +1,394 @@ +#pragma once + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/http/header_map_impl.h" +#include "source/common/http/http1/balsa_parser.h" +#include "source/common/http/http1/parser.h" + +#include "contrib/envoy/extensions/filters/network/generic_proxy/codecs/http1/v3/http1.pb.h" +#include "contrib/generic_proxy/filters/network/source/interface/codec.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace GenericProxy { +namespace Codec { +namespace Http1 { + +using ProtoConfig = + envoy::extensions::filters::network::generic_proxy::codecs::http1::v3::Http1CodecConfig; + +template class HttpHeaderFrame : public Interface { +public: + absl::string_view protocol() const override { return "http1"; } + void forEach(StreamBase::IterateCallback callback) const override { + headerMap().iterate([cb = std::move(callback)](const Http::HeaderEntry& entry) { + if (cb(entry.key().getStringView(), entry.value().getStringView())) { + return Http::HeaderMap::Iterate::Continue; + } + return Http::HeaderMap::Iterate::Break; + }); + }; + absl::optional get(absl::string_view key) const override { + const Http::LowerCaseString lower_key{key}; + const auto entry = headerMap().get(lower_key); + if (!entry.empty()) { + return entry[0]->value().getStringView(); + } + return absl::nullopt; + } + void set(absl::string_view key, absl::string_view val) override { + headerMap().setCopy(Http::LowerCaseString(key), std::string(val)); + } + void erase(absl::string_view key) override { headerMap().remove(Http::LowerCaseString(key)); } + + FrameFlags frameFlags() const override { return frame_flags_; } + + virtual Http::RequestOrResponseHeaderMap& headerMap() const PURE; + + // Optional buffer for the raw body. This is only make sense for local response and + // request/responses in single frame mode. + Buffer::Instance& optionalBuffer() const { return buffer_; } + +protected: + FrameFlags frame_flags_; + mutable Envoy::Buffer::OwnedImpl buffer_; +}; + +class HttpRequestFrame : public HttpHeaderFrame { +public: + HttpRequestFrame(Http::RequestHeaderMapPtr request, bool end_stream) + : request_(std::move(request)) { + ASSERT(request_ != nullptr); + frame_flags_ = {StreamFlags{}, end_stream}; + } + + absl::string_view host() const override { return request_->getHostValue(); } + absl::string_view path() const override { return request_->getPathValue(); } + absl::string_view method() const override { return request_->getMethodValue(); } + + Http::RequestOrResponseHeaderMap& headerMap() const override { return *request_; } + Http::RequestHeaderMapPtr request_; +}; + +class HttpResponseFrame : public HttpHeaderFrame { +public: + HttpResponseFrame(Http::ResponseHeaderMapPtr response, bool end_stream) + : response_(std::move(response)) { + ASSERT(response_ != nullptr); + + const bool drain_close = Envoy::StringUtil::caseFindToken( + response_->getConnectionValue(), ",", Http::Headers::get().ConnectionValues.Close); + + frame_flags_ = {StreamFlags{0, false, drain_close, false}, end_stream}; + } + + StreamStatus status() const override { + auto status_view = response_->getStatusValue(); + int32_t status = 0; + if (absl::SimpleAtoi(status_view, &status)) { + return {status, status < 500 && status > 99}; + } + // Unknown HTTP status. Return -1 and false. + return {-1, false}; + } + + Http::RequestOrResponseHeaderMap& headerMap() const override { return *response_; } + + Http::ResponseHeaderMapPtr response_; +}; + +class HttpRawBodyFrame : public StreamFrame { +public: + HttpRawBodyFrame(Envoy::Buffer::Instance& buffer, bool end_stream) + : frame_flags_({StreamFlags{}, end_stream}) { + buffer_.move(buffer); + } + FrameFlags frameFlags() const override { return frame_flags_; } + + Buffer::Instance& buffer() const { return buffer_; } + +private: + mutable Buffer::OwnedImpl buffer_; + const FrameFlags frame_flags_; +}; + +class Utility { +public: + static absl::Status encodeRequestHeaders(Buffer::Instance& buffer, + const Http::RequestHeaderMap& headers, + bool chunk_encoding); + static absl::Status encodeResponseHeaders(Buffer::Instance& buffer, + const Http::ResponseHeaderMap& headers, + bool chunk_encoding); + static void encodeBody(Buffer::Instance& dst_buffer, Buffer::Instance& src_buffer, + bool chunk_encoding, bool end_stream); + + static absl::Status validateRequestHeaders(Http::RequestHeaderMap& headers); + static absl::Status validateResponseHeaders(Http::ResponseHeaderMap& headers, + Envoy::Http::Code code); + + static bool isChunked(const Http::RequestOrResponseHeaderMap& headers, bool bodiless); + + static bool hasBody(const Envoy::Http::Http1::Parser& parser, bool response, + bool response_for_head_request); + + static uint64_t statusToHttpStatus(absl::StatusCode status_code); +}; + +struct ActiveRequest { + Http::RequestHeaderMapPtr request_headers_; + + bool request_complete_{}; + bool response_chunk_encoding_{}; +}; + +struct ExpectResponse { + Http::ResponseHeaderMapPtr response_headers_; + + bool request_complete_{}; + bool head_request_{}; + bool request_chunk_encoding_{}; +}; + +class Http1CodecBase : public Http::Http1::ParserCallbacks, + public Envoy::Logger::Loggable { +public: + Http1CodecBase(bool single_frame_mode, uint32_t max_buffer_size, bool server_codec) + : single_frame_mode_(single_frame_mode), max_buffer_size_(max_buffer_size) { + if (server_codec) { + parser_ = Http::Http1::ParserPtr{new Http::Http1::BalsaParser( + Http::Http1::MessageType::Request, this, 64 * 1024, false, false)}; + } else { + parser_ = Http::Http1::ParserPtr{new Http::Http1::BalsaParser( + Http::Http1::MessageType::Response, this, 64 * 1024, false, false)}; + } + } + + // ParserCallbacks. + Http::Http1::CallbackResult onMessageBegin() override { + header_parsing_state_ = HeaderParsingState::Field; + return onMessageBeginImpl(); + } + Http::Http1::CallbackResult onUrl(const char* data, size_t length) override { + onUrlImpl(data, length); + return Http::Http1::CallbackResult::Success; + } + Http::Http1::CallbackResult onStatus(const char* data, size_t length) override { + onStatusImpl(data, length); + return Http::Http1::CallbackResult::Success; + } + Http::Http1::CallbackResult onHeaderField(const char* data, size_t length) override { + if (header_parsing_state_ == HeaderParsingState::Done) { + // Ignore trailers for now. + return Http::Http1::CallbackResult::Success; + } + if (header_parsing_state_ == HeaderParsingState::Value) { + completeCurrentHeader(); + } + current_header_field_.append(data, length); + + header_parsing_state_ = HeaderParsingState::Field; + return Http::Http1::CallbackResult::Success; + } + Http::Http1::CallbackResult onHeaderValue(const char* data, size_t length) override { + if (header_parsing_state_ == HeaderParsingState::Done) { + // Ignore trailers for now. + return Http::Http1::CallbackResult::Success; + } + + absl::string_view value(data, length); + if (current_header_value_.empty()) { + value = StringUtil::ltrim(value); + } + + current_header_value_.append(value.data(), value.size()); + + header_parsing_state_ = HeaderParsingState::Value; + return Http::Http1::CallbackResult::Success; + } + Http::Http1::CallbackResult onHeadersComplete() override { + completeCurrentHeader(); + header_parsing_state_ = HeaderParsingState::Done; + return onHeadersCompleteImpl(); + } + void bufferBody(const char* data, size_t length) override { buffered_body_.add(data, length); } + Http::Http1::CallbackResult onMessageComplete() override { return onMessageCompleteImpl(); } + void onChunkHeader(bool is_final_chunk) override { + if (is_final_chunk) { + dispatchBufferedBody(false); + } + } + + virtual Http::Http1::CallbackResult onMessageBeginImpl() PURE; + virtual void onUrlImpl(const char* data, size_t length) PURE; + virtual void onStatusImpl(const char* data, size_t length) PURE; + virtual Http::Http1::CallbackResult onHeadersCompleteImpl() PURE; + virtual Http::Http1::CallbackResult onMessageCompleteImpl() PURE; + + void completeCurrentHeader() { + current_header_value_.rtrim(); + current_header_field_.inlineTransform([](char c) { return absl::ascii_tolower(c); }); + headerMap().addViaMove(std::move(current_header_field_), std::move(current_header_value_)); + + ASSERT(current_header_field_.empty()); + ASSERT(current_header_value_.empty()); + } + + bool decodeBuffer(Buffer::Instance& buffer); + + void dispatchBufferedBody(bool end_stream); + bool bufferedBodyOverflow(); + + virtual Http::HeaderMap& headerMap() PURE; + + virtual void onDecodingSuccess(StreamFramePtr&& frame) PURE; + virtual void onDecodingFailure() PURE; + +protected: + enum class HeaderParsingState { Field, Value, Done }; + + Envoy::Buffer::OwnedImpl decoding_buffer_; + Envoy::Buffer::OwnedImpl encoding_buffer_; + + Buffer::OwnedImpl buffered_body_; + + Http::Http1::ParserPtr parser_; + Http::HeaderString current_header_field_; + Http::HeaderString current_header_value_; + HeaderParsingState header_parsing_state_{HeaderParsingState::Field}; + + const bool single_frame_mode_{}; + const uint32_t max_buffer_size_{}; + + bool deferred_end_stream_headers_{}; +}; + +class Http1ServerCodec : public Http1CodecBase, public ServerCodec { +public: + Http1ServerCodec(bool single_frame_mode, uint32_t max_buffer_size) + : Http1CodecBase(single_frame_mode, max_buffer_size, true) {} + + Http::Http1::CallbackResult onMessageBeginImpl() override; + void onUrlImpl(const char* data, size_t length) override { + ASSERT(active_request_.has_value()); + ASSERT(active_request_->request_headers_ != nullptr); + active_request_->request_headers_->setPath(absl::string_view(data, length)); + } + void onStatusImpl(const char*, size_t) override {} + Http::Http1::CallbackResult onHeadersCompleteImpl() override; + Http::Http1::CallbackResult onMessageCompleteImpl() override; + + Http::HeaderMap& headerMap() override { return *active_request_->request_headers_; } + + void setCodecCallbacks(ServerCodecCallbacks& callbacks) override { callbacks_ = &callbacks; } + void decode(Envoy::Buffer::Instance& buffer, bool) override { + if (!decodeBuffer(buffer)) { + callbacks_->onDecodingFailure(); + } + } + void encode(const StreamFrame& frame, EncodingCallbacks& callbacks) override; + ResponsePtr respond(absl::Status status, absl::string_view data, const Request&) override { + auto response = Http::ResponseHeaderMapImpl::create(); + response->setStatus(std::to_string(Utility::statusToHttpStatus(status.code()))); + response->setContentLength(data.size()); + response->addCopy(Http::LowerCaseString("reason"), status.message()); + auto response_frame = std::make_unique(std::move(response), true); + response_frame->optionalBuffer().add(data.data(), data.size()); + return response_frame; + } + + void onDecodingSuccess(StreamFramePtr&& frame) override { + if (callbacks_->connection().has_value()) { + callbacks_->onDecodingSuccess(std::move(frame)); + } + + // Connection may have been closed by the callback. + if (!callbacks_->connection().has_value() || + callbacks_->connection()->state() != Network::Connection::State::Open) { + parser_->pause(); + } + } + + // TODO(wbpcode): send 400 bad request to client as response and then call the callback. + void onDecodingFailure() override { callbacks_->onDecodingFailure(); } + + absl::optional active_request_; + ServerCodecCallbacks* callbacks_{}; +}; + +class Http1ClientCodec : public Http1CodecBase, public ClientCodec { +public: + Http1ClientCodec(bool single_frame_mode, uint32_t max_buffer_size) + : Http1CodecBase(single_frame_mode, max_buffer_size, false) {} + + Http::Http1::CallbackResult onMessageBeginImpl() override; + void onUrlImpl(const char*, size_t) override {} + void onStatusImpl(const char*, size_t) override {} + Http::Http1::CallbackResult onHeadersCompleteImpl() override; + Http::Http1::CallbackResult onMessageCompleteImpl() override; + + Http::HeaderMap& headerMap() override { return *expect_response_->response_headers_; } + + void setCodecCallbacks(ClientCodecCallbacks& callbacks) override { callbacks_ = &callbacks; } + void decode(Envoy::Buffer::Instance& buffer, bool) override { + if (!decodeBuffer(buffer)) { + callbacks_->onDecodingFailure(); + } + } + void encode(const StreamFrame& frame, EncodingCallbacks& callbacks) override; + + void onDecodingSuccess(StreamFramePtr&& frame) override { + if (callbacks_->connection().has_value()) { + callbacks_->onDecodingSuccess(std::move(frame)); + } + + // Connection may have been closed by the callback. + if (!callbacks_->connection().has_value() || + callbacks_->connection()->state() != Network::Connection::State::Open) { + parser_->pause(); + } + } + void onDecodingFailure() override { callbacks_->onDecodingFailure(); } + + absl::optional expect_response_; + + ClientCodecCallbacks* callbacks_{}; +}; + +class Http1CodecFactory : public CodecFactory { +public: + Http1CodecFactory(bool single_frame_mode, uint32_t max_buffer_size) + : single_frame_mode_(single_frame_mode), max_buffer_size_(max_buffer_size) {} + + ClientCodecPtr createClientCodec() const override { + return std::make_unique(single_frame_mode_, max_buffer_size_); + } + + ServerCodecPtr createServerCodec() const override { + return std::make_unique(single_frame_mode_, max_buffer_size_); + } + +private: + const bool single_frame_mode_{}; + const uint32_t max_buffer_size_{}; +}; + +class Http1CodecFactoryConfig : public CodecFactoryConfig { +public: + // CodecFactoryConfig + CodecFactoryPtr + createCodecFactory(const Envoy::Protobuf::Message& config, + Envoy::Server::Configuration::FactoryContext& context) override; + std::string name() const override { return "envoy.generic_proxy.codecs.http1"; } + Envoy::ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique(); + } +}; + +} // namespace Http1 +} // namespace Codec +} // namespace GenericProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/contrib/generic_proxy/filters/network/source/router/router.cc b/contrib/generic_proxy/filters/network/source/router/router.cc index 2a7cbd69900b..47f3eb5fa5e0 100644 --- a/contrib/generic_proxy/filters/network/source/router/router.cc +++ b/contrib/generic_proxy/filters/network/source/router/router.cc @@ -31,21 +31,15 @@ constexpr absl::string_view RouterFilterName = "envoy.filters.generic.router"; } // namespace void GenericUpstream::writeToConnection(Buffer::Instance& buffer) { - if (is_cleaned_up_) { - return; - } - - if (owned_conn_data_ != nullptr) { - ASSERT(owned_conn_data_->connection().state() == Network::Connection::State::Open); + if (owned_conn_data_ != nullptr && + owned_conn_data_->connection().state() == Network::Connection::State::Open) { owned_conn_data_->connection().write(buffer, false); } } OptRef GenericUpstream::connection() { - if (is_cleaned_up_) { - return {}; - } - if (owned_conn_data_ != nullptr) { + if (owned_conn_data_ != nullptr && + owned_conn_data_->connection().state() == Network::Connection::State::Open) { return {owned_conn_data_->connection()}; } return {}; @@ -301,10 +295,10 @@ void UpstreamRequest::startStream() { } void UpstreamRequest::resetStream(StreamResetReason reason) { - if (stream_reset_) { + if (reset_or_response_complete_) { return; } - stream_reset_ = true; + reset_or_response_complete_ = true; ENVOY_LOG(debug, "generic proxy upstream request: reset upstream request"); @@ -329,9 +323,10 @@ void UpstreamRequest::resetStream(StreamResetReason reason) { void UpstreamRequest::clearStream(bool close_connection) { // Set the upstream response complete flag to true first to ensure the possible // connection close event will not be handled. - response_complete_ = true; + reset_or_response_complete_ = true; - ENVOY_LOG(debug, "generic proxy upstream request: complete upstream request"); + ENVOY_LOG(debug, "generic proxy upstream request: complete upstream request ()", + close_connection); if (span_ != nullptr) { TraceContextBridge trace_context{*parent_.request_stream_}; @@ -463,12 +458,22 @@ void UpstreamRequest::onDecodingSuccess(StreamFramePtr response) { } } -void UpstreamRequest::onDecodingFailure() { resetStream(StreamResetReason::ProtocolError); } +void UpstreamRequest::onDecodingFailure() { + // Decoding failure after the response is complete, close the connection. + // This should only happen when some special cases, for example: + // The HTTP response is complete but the request is not fully sent. + // The codec will throw an error after the response is complete. + if (reset_or_response_complete_) { + generic_upstream_->cleanUp(true); + return; + } + resetStream(StreamResetReason::ProtocolError); +} void UpstreamRequest::onConnectionClose(Network::ConnectionEvent event) { // If the upstream response is complete or the upstream request is reset then // ignore the connection close event. - if (response_complete_ || stream_reset_) { + if (reset_or_response_complete_) { return; } diff --git a/contrib/generic_proxy/filters/network/source/router/router.h b/contrib/generic_proxy/filters/network/source/router/router.h index 64f41bba5a51..205908b6cdf8 100644 --- a/contrib/generic_proxy/filters/network/source/router/router.h +++ b/contrib/generic_proxy/filters/network/source/router/router.h @@ -183,8 +183,7 @@ class UpstreamRequest : public LinkedObject, absl::optional connecting_start_time_; // One of these flags should be set to true when the request is complete. - bool stream_reset_{}; - bool response_complete_{}; + bool reset_or_response_complete_{}; bool expects_response_{}; diff --git a/contrib/generic_proxy/filters/network/source/upstream.cc b/contrib/generic_proxy/filters/network/source/upstream.cc index 3af4c0b712a1..8124e03f0738 100644 --- a/contrib/generic_proxy/filters/network/source/upstream.cc +++ b/contrib/generic_proxy/filters/network/source/upstream.cc @@ -6,11 +6,14 @@ namespace NetworkFilters { namespace GenericProxy { UpstreamConnection::~UpstreamConnection() { - // Do clean up here again to ensure the cleanUp is called. This is safe to call - // multiple times because of the is_cleand_up_ flag. - // TODO(wbpcode): Clarify/resolve bypassing of virtual dispatch - // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall) - this->cleanUp(true); + // In case we doesn't clean up the pending connecting request. + if (tcp_pool_handle_ != nullptr) { + // Clear the data first. + auto local_handle = tcp_pool_handle_; + tcp_pool_handle_ = nullptr; + + local_handle->cancel(Tcp::ConnectionPool::CancelPolicy::Default); + } } void UpstreamConnection::initialize() { @@ -21,34 +24,33 @@ void UpstreamConnection::initialize() { } void UpstreamConnection::cleanUp(bool close_connection) { - // If the cleanUp is called multiple times, just return. - if (is_cleaned_up_) { - return; - } - - ENVOY_LOG(debug, "generic proxy upstream manager: clean up upstream connection"); - // Set is_cleaned_up_ flag to true to avoid double clean up. - is_cleaned_up_ = true; + ENVOY_LOG(debug, "generic proxy upstream manager: clean up upstream (close: {})", + close_connection); if (close_connection && owned_conn_data_ != nullptr) { ENVOY_LOG(debug, "generic proxy upstream request: close upstream connection"); ASSERT(tcp_pool_handle_ == nullptr); - owned_conn_data_->connection().close(Network::ConnectionCloseType::FlushWrite); + + // Clear the data first to avoid re-entering this function in the close callback. + auto local_data = std::move(owned_conn_data_); + owned_conn_data_.reset(); + + local_data->connection().close(Network::ConnectionCloseType::FlushWrite); } - owned_conn_data_.reset(); if (tcp_pool_handle_ != nullptr) { ENVOY_LOG(debug, "generic proxy upstream manager: cacel upstream connection"); - ASSERT(owned_conn_data_ == nullptr); - tcp_pool_handle_->cancel(Tcp::ConnectionPool::CancelPolicy::Default); + + // Clear the data first. + auto local_handle = tcp_pool_handle_; tcp_pool_handle_ = nullptr; + + local_handle->cancel(Tcp::ConnectionPool::CancelPolicy::Default); } } void UpstreamConnection::onUpstreamData(Buffer::Instance& data, bool end_stream) { - ASSERT(!is_cleaned_up_); - if (data.length() == 0) { return; } diff --git a/contrib/generic_proxy/filters/network/source/upstream.h b/contrib/generic_proxy/filters/network/source/upstream.h index dc88b29d72cf..8e4e1f062dbf 100644 --- a/contrib/generic_proxy/filters/network/source/upstream.h +++ b/contrib/generic_proxy/filters/network/source/upstream.h @@ -50,7 +50,6 @@ class UpstreamConnection : public Envoy::Event::DeferredDeletable, Upstream::TcpPoolData tcp_pool_data_; ClientCodecPtr client_codec_; - bool is_cleaned_up_{}; // Whether the upstream connection is created. This will be set to true when the initialize() // is called. bool initialized_{}; diff --git a/contrib/generic_proxy/filters/network/test/codecs/http1/BUILD b/contrib/generic_proxy/filters/network/test/codecs/http1/BUILD new file mode 100644 index 000000000000..d13a77613895 --- /dev/null +++ b/contrib/generic_proxy/filters/network/test/codecs/http1/BUILD @@ -0,0 +1,21 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_contrib_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_contrib_package() + +envoy_cc_test( + name = "config_test", + srcs = [ + "config_test.cc", + ], + deps = [ + "//contrib/generic_proxy/filters/network/source/codecs/http1:config", + "//contrib/generic_proxy/filters/network/test/mocks:codec_mocks", + "//test/mocks/server:factory_context_mocks", + ], +) diff --git a/contrib/generic_proxy/filters/network/test/codecs/http1/config_test.cc b/contrib/generic_proxy/filters/network/test/codecs/http1/config_test.cc new file mode 100644 index 000000000000..118c48d760c0 --- /dev/null +++ b/contrib/generic_proxy/filters/network/test/codecs/http1/config_test.cc @@ -0,0 +1,1482 @@ +#include +#include + +#include "test/mocks/server/factory_context.h" + +#include "contrib/generic_proxy/filters/network/source/codecs/http1/config.h" +#include "contrib/generic_proxy/filters/network/test/mocks/codec.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace GenericProxy { +namespace Codec { +namespace Http1 { +namespace { + +using testing::NiceMock; + +TEST(Http1MessageFrameTest, Http1MessageFrameTest) { + // Protocol. + { + auto headers = Http::RequestHeaderMapImpl::create(); + HttpRequestFrame frame(std::move(headers), true); + + EXPECT_EQ(frame.protocol(), "http1"); + } + + // ForEach. + { + auto headers = Http::RequestHeaderMapImpl::create(); + // Add some headers. + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + // Add some custom headers. + headers->addCopy(Http::LowerCaseString("custom"), "value"); + + HttpRequestFrame frame(std::move(headers), true); + + // Check that the headers are iterated correctly. + size_t count = 0; + frame.forEach([&count](absl::string_view, absl::string_view) -> bool { + count++; + return true; + }); + + EXPECT_EQ(count, 4); + } + + // Get. + { + auto headers = Http::RequestHeaderMapImpl::create(); + // Add some headers. + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + // Add some custom headers. + headers->addCopy(Http::LowerCaseString("custom"), "value"); + + HttpRequestFrame frame(std::move(headers), true); + + // Check that the headers are retrieved correctly. + EXPECT_EQ(frame.get("host").value(), "host"); + EXPECT_EQ(frame.get(":authority").value(), "host"); + EXPECT_EQ(frame.get(":path").value(), "/path"); + EXPECT_EQ(frame.get(":method").value(), "GET"); + EXPECT_EQ(frame.get("custom").value(), "value"); + } + + // Set. + { + auto headers = Http::RequestHeaderMapImpl::create(); + HttpRequestFrame frame(std::move(headers), true); + + // Set some headers. + frame.set("host", "host"); + frame.set(":path", "/path"); + frame.set(":method", "POST"); + frame.set("custom", "value"); + + // Check that the headers are set correctly. + EXPECT_EQ(frame.get("host").value(), "host"); + EXPECT_EQ(frame.get(":authority").value(), "host"); + EXPECT_EQ(frame.get(":path").value(), "/path"); + EXPECT_EQ(frame.get(":method").value(), "POST"); + EXPECT_EQ(frame.get("custom").value(), "value"); + } + + // Erase. + { + auto headers = Http::RequestHeaderMapImpl::create(); + // Add some headers. + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + // Add some custom headers. + headers->addCopy(Http::LowerCaseString("custom"), "value"); + + HttpRequestFrame frame(std::move(headers), true); + + // Erase some headers. + frame.erase("host"); + frame.erase(":path"); + frame.erase(":method"); + frame.erase("custom"); + + // Check that the headers are erased correctly. + EXPECT_EQ(frame.get("host"), absl::nullopt); + EXPECT_EQ(frame.get(":authority"), absl::nullopt); + EXPECT_EQ(frame.get(":path"), absl::nullopt); + EXPECT_EQ(frame.get(":method"), absl::nullopt); + EXPECT_EQ(frame.get("custom"), absl::nullopt); + } + + // Request FrameFlags. + { + auto headers = Http::RequestHeaderMapImpl::create(); + HttpRequestFrame frame(std::move(headers), true); + + EXPECT_EQ(true, frame.frameFlags().endStream()); + + auto headers2 = Http::RequestHeaderMapImpl::create(); + HttpRequestFrame frame2(std::move(headers2), false); + + EXPECT_EQ(false, frame2.frameFlags().endStream()); + } + + // Response FrameFlags. + { + auto headers = Http::ResponseHeaderMapImpl::create(); + HttpResponseFrame frame(std::move(headers), true); + + EXPECT_EQ(true, frame.frameFlags().endStream()); + EXPECT_EQ(false, frame.frameFlags().streamFlags().drainClose()); + + auto headers2 = Http::ResponseHeaderMapImpl::create(); + headers2->setConnection("close"); + HttpResponseFrame frame2(std::move(headers2), false); + + EXPECT_EQ(false, frame2.frameFlags().endStream()); + EXPECT_EQ(true, frame2.frameFlags().streamFlags().drainClose()); + } + + // Request FrameType. + { + auto headers = Http::RequestHeaderMapImpl::create(); + + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + + HttpRequestFrame frame(std::move(headers), true); + + EXPECT_EQ(frame.host(), "host"); + EXPECT_EQ(frame.path(), "/path"); + EXPECT_EQ(frame.method(), "GET"); + } + + // Response FrameType. + { + // 200 + auto headers = Http::ResponseHeaderMapImpl::create(); + headers->setStatus(200); + HttpResponseFrame frame(std::move(headers), true); + + EXPECT_EQ(frame.status().code(), 200); + EXPECT_TRUE(frame.status().ok()); + + // 404 + auto headers2 = Http::ResponseHeaderMapImpl::create(); + headers2->setStatus(404); + HttpResponseFrame frame2(std::move(headers2), true); + + EXPECT_EQ(frame2.status().code(), 404); + EXPECT_TRUE(frame2.status().ok()); + + // 500 + auto headers3 = Http::ResponseHeaderMapImpl::create(); + headers3->setStatus(500); + HttpResponseFrame frame3(std::move(headers3), true); + + EXPECT_EQ(frame3.status().code(), 500); + EXPECT_FALSE(frame3.status().ok()); + + // 503 + auto headers4 = Http::ResponseHeaderMapImpl::create(); + headers4->setStatus(503); + HttpResponseFrame frame4(std::move(headers4), true); + + EXPECT_EQ(frame4.status().code(), 503); + EXPECT_FALSE(frame4.status().ok()); + + // Invalid status code value. + auto headers5 = Http::ResponseHeaderMapImpl::create(); + headers5->addCopy(Http::Headers::get().Status, "xxx"); + HttpResponseFrame frame5(std::move(headers5), true); + + EXPECT_EQ(frame5.status().code(), -1); + EXPECT_FALSE(frame5.status().ok()); + } + + // Raw body frame. + { + Buffer::OwnedImpl buffer("body"); + + HttpRawBodyFrame frame(buffer, true); + + EXPECT_EQ(frame.frameFlags().endStream(), true); + + EXPECT_EQ(frame.buffer().length(), 4); + EXPECT_EQ(frame.buffer().toString(), "body"); + + Buffer::OwnedImpl new_buffer; + new_buffer.move(frame.buffer()); + + EXPECT_EQ(new_buffer.toString(), "body"); + + EXPECT_EQ(frame.buffer().length(), 0); + } +} + +class Http1ServerCodecTest : public testing::Test { +public: + Http1ServerCodecTest() { initializeCodec(); } + + void initializeCodec(bool single_frame_mode = false, uint32_t max_buffer_size = 8 * 1024 * 1024) { + codec_ = std::make_unique(single_frame_mode, max_buffer_size); + codec_->setCodecCallbacks(codec_callbacks_); + } + + NiceMock codec_callbacks_; + NiceMock mock_connection; + std::unique_ptr codec_; +}; + +TEST_F(Http1ServerCodecTest, HeaderOnlyRequestDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Content-Length: 0\r\n" + "custom: value\r\n" + "\r\n"); + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).WillOnce(Invoke([](StreamFramePtr frame) { + EXPECT_EQ(frame->frameFlags().endStream(), true); + + auto* request = dynamic_cast(frame.get()); + + EXPECT_EQ(request->host(), "host"); + EXPECT_EQ(request->path(), "/"); + EXPECT_EQ(request->method(), "GET"); + EXPECT_EQ(request->get("host").value(), "host"); + EXPECT_EQ(request->get(":authority").value(), "host"); + EXPECT_EQ(request->get(":path").value(), "/"); + EXPECT_EQ(request->get(":method").value(), "GET"); + EXPECT_EQ(request->get("custom").value(), "value"); + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ServerCodecTest, RequestDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Content-Length: 4\r\n" + "custom: value\r\n" + "\r\n" + "body"); + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)) + .Times(2) + .WillRepeatedly(Invoke([](StreamFramePtr frame) { + auto* request = dynamic_cast(frame.get()); + if (request != nullptr) { + EXPECT_EQ(frame->frameFlags().endStream(), false); + + EXPECT_EQ(request->host(), "host"); + EXPECT_EQ(request->path(), "/"); + EXPECT_EQ(request->method(), "GET"); + EXPECT_EQ(request->get("host").value(), "host"); + EXPECT_EQ(request->get(":authority").value(), "host"); + EXPECT_EQ(request->get(":path").value(), "/"); + EXPECT_EQ(request->get(":method").value(), "GET"); + EXPECT_EQ(request->get("custom").value(), "value"); + } + + auto* body = dynamic_cast(frame.get()); + if (body != nullptr) { + EXPECT_EQ(frame->frameFlags().endStream(), true); + + EXPECT_EQ(body->buffer().length(), 4); + EXPECT_EQ(body->buffer().toString(), "body"); + } + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ServerCodecTest, ChunkedRequestDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)) + .Times(3) // One for headers and one for body, and one for the last chunk. + .WillRepeatedly(Invoke([](StreamFramePtr frame) { + auto* request = dynamic_cast(frame.get()); + if (request != nullptr) { + EXPECT_EQ(frame->frameFlags().endStream(), false); + + EXPECT_EQ(request->host(), "host"); + EXPECT_EQ(request->path(), "/"); + EXPECT_EQ(request->method(), "GET"); + EXPECT_EQ(request->get("host").value(), "host"); + EXPECT_EQ(request->get(":authority").value(), "host"); + EXPECT_EQ(request->get(":path").value(), "/"); + EXPECT_EQ(request->get(":method").value(), "GET"); + EXPECT_EQ(request->get("custom").value(), "value"); + } + + auto* body = dynamic_cast(frame.get()); + if (body != nullptr) { + if (!frame->frameFlags().endStream()) { + EXPECT_EQ(body->buffer().length(), 4); + EXPECT_EQ(body->buffer().toString(), "body"); + } else { + EXPECT_EQ(body->buffer().length(), 0); + } + } + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ServerCodecTest, MultipleBufferChunkedRequestDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n"); // Chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)) + .Times(2) + .WillRepeatedly(Invoke([](StreamFramePtr frame) { + auto* request = dynamic_cast(frame.get()); + if (request != nullptr) { + EXPECT_EQ(frame->frameFlags().endStream(), false); + + EXPECT_EQ(request->host(), "host"); + EXPECT_EQ(request->path(), "/"); + EXPECT_EQ(request->method(), "GET"); + EXPECT_EQ(request->get("host").value(), "host"); + EXPECT_EQ(request->get(":authority").value(), "host"); + EXPECT_EQ(request->get(":path").value(), "/"); + EXPECT_EQ(request->get(":method").value(), "GET"); + EXPECT_EQ(request->get("custom").value(), "value"); + } else { + auto* body = dynamic_cast(frame.get()); + EXPECT_EQ(frame->frameFlags().endStream(), false); + EXPECT_EQ(body->buffer().length(), 4); + EXPECT_EQ(body->buffer().toString(), "body"); + } + })); + + codec_->decode(buffer, false); + + EXPECT_EQ(buffer.length(), 0); + + buffer.add("0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).WillOnce(Invoke([](StreamFramePtr frame) { + auto* body = dynamic_cast(frame.get()); + EXPECT_EQ(frame->frameFlags().endStream(), true); + EXPECT_EQ(body->buffer().length(), 0); + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ServerCodecTest, UnexpectedRequestTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + // Connect. + { + initializeCodec(); + + Buffer::OwnedImpl buffer; + + buffer.add("CONNECT host:443 HTTP/1.1\r\n" + "custom: value\r\n" + "\r\n"); + + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer, false); + } + + // Transfer-Encoding and Content-Length are set at same time. + { + initializeCodec(); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Length: 4\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer, false); + } + + // Unknown Transfer-Encoding. + { + initializeCodec(); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Transfer-Encoding: gzip, chunked\r\n" // Only 'chunked' is supported. + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer, false); + } + + // Lost required request headers. + { + initializeCodec(); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "custom: value\r\n" + "\r\n"); + + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer, false); + } + + // HTTP1.0 request. + { + initializeCodec(); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.0\r\n" + "Host: host\r\n" + "custom: value\r\n" + "\r\n"); + + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer, false); + } +} + +TEST_F(Http1ServerCodecTest, RespondTest) { + // Create a request. + auto headers = Http::RequestHeaderMapImpl::create(); + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + headers->addCopy(Http::LowerCaseString("custom"), "value"); + + HttpRequestFrame request(std::move(headers), true); + + // Respond with a response. + { + for (int i = 0; i <= 16; i++) { + auto response = + codec_->respond(absl::Status(static_cast(i), ""), "", request); + + EXPECT_NE(response, nullptr); + EXPECT_NE(dynamic_cast(response.get())->response_, nullptr); + + auto* response_headers = dynamic_cast(response.get())->response_.get(); + EXPECT_EQ(response_headers->getStatusValue(), + std::to_string(Utility::statusToHttpStatus(static_cast(i)))); + } + } +} + +TEST_F(Http1ServerCodecTest, HeaderOnlyResponseEncodingTest) { + // Create a response. + auto headers = Http::ResponseHeaderMapImpl::create(); + headers->setStatus(200); + + HttpResponseFrame response(std::move(headers), true); + + NiceMock encoding_callbacks; + + // Encode the response. + { + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, true)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "HTTP/1.1 200 OK\r\n" + "\r\n"); + })); + + codec_->encode(response, encoding_callbacks); + } +} + +TEST_F(Http1ServerCodecTest, ResponseEncodingTest) { + // Create a response. + auto headers = Http::ResponseHeaderMapImpl::create(); + headers->setStatus(200); + headers->addCopy(Http::Headers::get().ContentLength, "4"); + + HttpResponseFrame response(std::move(headers), false); + + Buffer::OwnedImpl body_buffer("body"); + HttpRawBodyFrame body(body_buffer, true); + + NiceMock encoding_callbacks; + + // Encode the response. + { + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, false)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "HTTP/1.1 200 OK\r\n" + "content-length: 4\r\n" + "\r\n"); + buffer.drain(buffer.length()); + })); + + codec_->encode(response, encoding_callbacks); + + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, true)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "body"); + + buffer.drain(buffer.length()); + })); + + codec_->encode(body, encoding_callbacks); + } +} + +TEST_F(Http1ServerCodecTest, ChunkedResponseEncodingTest) { + // Create a response. + auto headers = Http::ResponseHeaderMapImpl::create(); + headers->setStatus(200); + headers->addCopy(Http::Headers::get().TransferEncoding, "chunked"); + + HttpResponseFrame response(std::move(headers), false); + + Buffer::OwnedImpl body_buffer("body"); + HttpRawBodyFrame body(body_buffer, true); + + NiceMock encoding_callbacks; + + // Encode the response. + { + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, false)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "HTTP/1.1 200 OK\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"); + buffer.drain(buffer.length()); + })); + + codec_->encode(response, encoding_callbacks); + + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, true)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + buffer.drain(buffer.length()); + })); + + codec_->encode(body, encoding_callbacks); + } +} + +TEST_F(Http1ServerCodecTest, RequestAndResponseTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + // Do repeated request and response. + for (size_t i = 0; i < 100; i++) { + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).Times(3); + + codec_->decode(buffer, false); + + // Create a response. + auto headers = Http::ResponseHeaderMapImpl::create(); + headers->setStatus(200); + headers->addCopy(Http::Headers::get().TransferEncoding, "chunked"); + + HttpResponseFrame response(std::move(headers), false); + + Buffer::OwnedImpl body_buffer("body"); + HttpRawBodyFrame body(body_buffer, true); + + NiceMock encoding_callbacks; + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, _)).Times(2); + + // Encode the response. + codec_->encode(response, encoding_callbacks); + codec_->encode(body, encoding_callbacks); + } +} + +TEST_F(Http1ServerCodecTest, ResponseCompleteBeforeRequestCompleteTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n"); // Chunk footer. No last chunk header and footer, this is an incomplete + // request. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).Times(2); + + codec_->decode(buffer, false); + + // Create a response. + auto headers = Http::ResponseHeaderMapImpl::create(); + headers->setStatus(200); + headers->addCopy(Http::Headers::get().TransferEncoding, "chunked"); + + HttpResponseFrame response(std::move(headers), false); + + Buffer::OwnedImpl body_buffer("body"); + HttpRawBodyFrame body(body_buffer, true); + + NiceMock encoding_callbacks; + + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, false)); + // Encode the response. + codec_->encode(response, encoding_callbacks); + + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, true)); + // Response is complete, but request is not complete, so the codec should close the connection. + EXPECT_CALL(mock_connection, close(Network::ConnectionCloseType::FlushWrite)); + + codec_->encode(body, encoding_callbacks); +} + +TEST_F(Http1ServerCodecTest, NewRequestBeforeFirstRequestCompleteTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + Buffer::OwnedImpl buffer2; + buffer2.add(buffer); + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).Times(3); + + codec_->decode(buffer, false); + + // First request is not complete, so the codec should close the connection. + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer2, false); +} + +TEST_F(Http1ServerCodecTest, SingleFrameModeRequestDecodingTest) { + initializeCodec(true, 8 * 1024 * 1024); + + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Content-Length: 4\r\n" + "custom: value\r\n" + "\r\n" + "body"); + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).WillOnce(Invoke([](StreamFramePtr frame) { + auto* request = dynamic_cast(frame.get()); + + EXPECT_EQ(frame->frameFlags().endStream(), true); + + EXPECT_EQ(request->host(), "host"); + EXPECT_EQ(request->path(), "/"); + EXPECT_EQ(request->method(), "GET"); + EXPECT_EQ(request->get("host").value(), "host"); + EXPECT_EQ(request->get(":authority").value(), "host"); + EXPECT_EQ(request->get(":path").value(), "/"); + EXPECT_EQ(request->get(":method").value(), "GET"); + EXPECT_EQ(request->get("custom").value(), "value"); + + EXPECT_EQ(request->optionalBuffer().length(), 4); + EXPECT_EQ(request->optionalBuffer().toString(), "body"); + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ServerCodecTest, SingleFrameModeRequestTooLargeTest) { + initializeCodec(true, 4); + + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Content-Length: 4\r\n" + "custom: value\r\n" + "\r\n" + "body~"); + + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer, false); +} + +TEST_F(Http1ServerCodecTest, SingleFrameModeChunkedRequestDecodingTest) { + initializeCodec(true, 8 * 1024 * 1024); + + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).WillOnce(Invoke([](StreamFramePtr frame) { + auto* request = dynamic_cast(frame.get()); + + EXPECT_EQ(frame->frameFlags().endStream(), true); + + EXPECT_EQ(request->host(), "host"); + EXPECT_EQ(request->path(), "/"); + EXPECT_EQ(request->method(), "GET"); + EXPECT_EQ(request->get("host").value(), "host"); + EXPECT_EQ(request->get(":authority").value(), "host"); + EXPECT_EQ(request->get(":path").value(), "/"); + EXPECT_EQ(request->get(":method").value(), "GET"); + EXPECT_EQ(request->get("custom").value(), "value"); + + EXPECT_EQ(request->optionalBuffer().length(), 4); + EXPECT_EQ(request->optionalBuffer().toString(), "body"); + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ServerCodecTest, SingleFrameModeMultipleBufferChunkedRequestDecodingTest) { + initializeCodec(true, 8 * 1024 * 1024); + + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + Buffer::OwnedImpl buffer; + + buffer.add("GET / HTTP/1.1\r\n" + "Host: host\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n"); // Chunk footer. + + codec_->decode(buffer, false); + + EXPECT_EQ(buffer.length(), 0); + + buffer.add("0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).WillOnce(Invoke([](StreamFramePtr frame) { + auto* request = dynamic_cast(frame.get()); + + EXPECT_EQ(frame->frameFlags().endStream(), true); + + EXPECT_EQ(request->host(), "host"); + EXPECT_EQ(request->path(), "/"); + EXPECT_EQ(request->method(), "GET"); + EXPECT_EQ(request->get("host").value(), "host"); + EXPECT_EQ(request->get(":authority").value(), "host"); + EXPECT_EQ(request->get(":path").value(), "/"); + EXPECT_EQ(request->get(":method").value(), "GET"); + EXPECT_EQ(request->get("custom").value(), "value"); + + EXPECT_EQ(request->optionalBuffer().length(), 4); + EXPECT_EQ(request->optionalBuffer().toString(), "body"); + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ServerCodecTest, SingleFrameModeResponseEncodingTest) { + // Create a response. + auto headers = Http::ResponseHeaderMapImpl::create(); + headers->setStatus(200); + headers->setContentLength(4); + + HttpResponseFrame response(std::move(headers), true); + response.optionalBuffer().add("body"); + + NiceMock encoding_callbacks; + + // Encode the response. + { + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, true)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "HTTP/1.1 200 OK\r\n" + "content-length: 4\r\n" + "\r\n" + "body"); + buffer.drain(buffer.length()); + })); + codec_->encode(response, encoding_callbacks); + } +} + +class Http1ClientCodecTest : public testing::Test { +public: + Http1ClientCodecTest() { initializeCodec(); } + + void initializeCodec(bool single_frame_mode = false, uint32_t max_buffer_size = 8 * 1024 * 1024) { + codec_ = std::make_unique(single_frame_mode, max_buffer_size); + codec_->setCodecCallbacks(codec_callbacks_); + } + + void encodingOneRequest() { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + // Create a request. + auto headers = Http::RequestHeaderMapImpl::create(); + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + headers->addCopy(Http::Headers::get().ContentLength, "4"); + + HttpRequestFrame request(std::move(headers), false); + + Buffer::OwnedImpl body_buffer("body"); + HttpRawBodyFrame body(body_buffer, true); + + NiceMock encoding_callbacks; + + // Encode the request. + { + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, false)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "GET /path HTTP/1.1\r\n" + "host: host\r\n" + "content-length: 4\r\n" + "\r\n"); + buffer.drain(buffer.length()); + })); + + codec_->encode(request, encoding_callbacks); + + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, true)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "body"); + buffer.drain(buffer.length()); + })); + + codec_->encode(body, encoding_callbacks); + } + } + + NiceMock codec_callbacks_; + NiceMock mock_connection; + std::unique_ptr codec_; +}; + +TEST_F(Http1ClientCodecTest, HeaderOnlyResponseDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + encodingOneRequest(); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "content-length: 0\r\n" + "\r\n"); + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).WillOnce(Invoke([](StreamFramePtr frame) { + EXPECT_EQ(frame->frameFlags().endStream(), true); + + auto* response = dynamic_cast(frame.get()); + + EXPECT_EQ(response->response_->getStatusValue(), "200"); + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ClientCodecTest, ResponseDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + encodingOneRequest(); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Content-Length: 4\r\n" + "custom: value\r\n" + "\r\n" + "body"); + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)) + .Times(2) + .WillRepeatedly(Invoke([](StreamFramePtr frame) { + auto* response = dynamic_cast(frame.get()); + if (response != nullptr) { + EXPECT_EQ(frame->frameFlags().endStream(), false); + EXPECT_EQ(response->response_->getStatusValue(), "200"); + EXPECT_EQ(response->response_->getContentLengthValue(), "4"); + EXPECT_EQ(response->get("custom").value(), "value"); + } + + auto* body = dynamic_cast(frame.get()); + if (body != nullptr) { + EXPECT_EQ(frame->frameFlags().endStream(), true); + + EXPECT_EQ(body->buffer().length(), 4); + EXPECT_EQ(body->buffer().toString(), "body"); + } + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ClientCodecTest, ChunkedResponseDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + encodingOneRequest(); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)) + .Times(3) // One for headers and one for body, and one for the last chunk. + .WillRepeatedly(Invoke([](StreamFramePtr frame) { + auto* response = dynamic_cast(frame.get()); + if (response != nullptr) { + EXPECT_EQ(frame->frameFlags().endStream(), false); + + EXPECT_EQ(response->response_->getStatusValue(), "200"); + EXPECT_EQ(response->get("custom").value(), "value"); + } + + auto* body = dynamic_cast(frame.get()); + if (body != nullptr) { + if (!frame->frameFlags().endStream()) { + EXPECT_EQ(body->buffer().length(), 4); + EXPECT_EQ(body->buffer().toString(), "body"); + } else { + EXPECT_EQ(body->buffer().length(), 0); + } + } + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ClientCodecTest, MultipleBufferChunkedResponseDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + encodingOneRequest(); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n"); // Chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)) + .Times(2) + .WillRepeatedly(Invoke([](StreamFramePtr frame) { + auto* response = dynamic_cast(frame.get()); + if (response != nullptr) { + EXPECT_EQ(frame->frameFlags().endStream(), false); + + EXPECT_EQ(response->response_->getStatusValue(), "200"); + EXPECT_EQ(response->get("custom").value(), "value"); + } else { + auto* body = dynamic_cast(frame.get()); + EXPECT_EQ(frame->frameFlags().endStream(), false); + EXPECT_EQ(body->buffer().length(), 4); + EXPECT_EQ(body->buffer().toString(), "body"); + } + })); + + codec_->decode(buffer, false); + + EXPECT_EQ(buffer.length(), 0); + + buffer.add("0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).WillOnce(Invoke([](StreamFramePtr frame) { + auto* body = dynamic_cast(frame.get()); + EXPECT_EQ(frame->frameFlags().endStream(), true); + EXPECT_EQ(body->buffer().length(), 0); + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ClientCodecTest, UnexpectedResponseTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + // Transfer-Encoding and Content-Length are set at same time. + { + initializeCodec(); + + encodingOneRequest(); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Length: 4\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer, false); + } + + // Unknown Transfer-Encoding. + { + initializeCodec(); + + encodingOneRequest(); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: gzip, chunked\r\n" // Only 'chunked' is supported. + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer, false); + } +} + +TEST_F(Http1ClientCodecTest, HeaderOnlyRequestEncodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + // Create a request. + auto headers = Http::RequestHeaderMapImpl::create(); + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + headers->addCopy(Http::LowerCaseString("custom"), "value"); + + HttpRequestFrame request(std::move(headers), true); + + NiceMock encoding_callbacks; + + // Encode the request. + { + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, true)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "GET /path HTTP/1.1\r\n" + "host: host\r\n" + "custom: value\r\n" + "\r\n"); + })); + + codec_->encode(request, encoding_callbacks); + } +} + +TEST_F(Http1ClientCodecTest, RequestEncodingTest) { encodingOneRequest(); } + +TEST_F(Http1ClientCodecTest, ChunkedRequestEncodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + // Create a request. + auto headers = Http::RequestHeaderMapImpl::create(); + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + headers->addCopy(Http::Headers::get().TransferEncoding, "chunked"); + + HttpRequestFrame request(std::move(headers), false); + + Buffer::OwnedImpl body_buffer("body"); + HttpRawBodyFrame body(body_buffer, true); + + NiceMock encoding_callbacks; + + // Encode the request. + { + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, false)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "GET /path HTTP/1.1\r\n" + "host: host\r\n" + "transfer-encoding: chunked\r\n" + "\r\n"); + buffer.drain(buffer.length()); + })); + + codec_->encode(request, encoding_callbacks); + + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, true)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + buffer.drain(buffer.length()); + })); + + codec_->encode(body, encoding_callbacks); + } +} + +TEST_F(Http1ClientCodecTest, RequestAndResponseTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + // Do repeated request and response. + for (size_t i = 0; i < 100; i++) { + // Create a request. + auto headers = Http::RequestHeaderMapImpl::create(); + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + headers->addCopy(Http::Headers::get().TransferEncoding, "chunked"); + + HttpRequestFrame request(std::move(headers), false); + + Buffer::OwnedImpl body_buffer("body"); + HttpRawBodyFrame body(body_buffer, true); + + NiceMock encoding_callbacks; + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, _)).Times(2); + + // Encode the request. + codec_->encode(request, encoding_callbacks); + codec_->encode(body, encoding_callbacks); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + // Decode the response. + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).Times(3); + codec_->decode(buffer, false); + } +} + +TEST_F(Http1ClientCodecTest, ResponseCompleteBeforeRequestCompleteTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + // Create a request. + auto headers = Http::RequestHeaderMapImpl::create(); + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + headers->addCopy(Http::Headers::get().TransferEncoding, "chunked"); + + HttpRequestFrame request(std::move(headers), false); + + NiceMock encoding_callbacks; + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, _)); + + // Encode the request. Only the headers are encoded and the body is not encoded. + codec_->encode(request, encoding_callbacks); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + // Decode the response. + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).Times(3); + // Finally, the onDecodingFailure() is called because the request is not complete and the + // response is complete. + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer, false); +} + +TEST_F(Http1ClientCodecTest, SingleFrameModeRequestEncodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + initializeCodec(true, 8 * 1024 * 1024); + + // Create a request. + auto headers = Http::RequestHeaderMapImpl::create(); + headers->addCopy(Http::Headers::get().HostLegacy, "host"); + headers->addCopy(Http::Headers::get().Path, "/path"); + headers->addCopy(Http::Headers::get().Method, "GET"); + headers->setContentLength(4); + + HttpRequestFrame request(std::move(headers), true); + request.optionalBuffer().add("body"); + + NiceMock encoding_callbacks; + + // Encode the request. + { + EXPECT_CALL(encoding_callbacks, onEncodingSuccess(_, true)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { + EXPECT_EQ(buffer.toString(), "GET /path HTTP/1.1\r\n" + "host: host\r\n" + "content-length: 4\r\n" + "\r\n" + "body"); + })); + codec_->encode(request, encoding_callbacks); + } +} + +TEST_F(Http1ClientCodecTest, SingleFrameModeResponseDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + initializeCodec(true, 8 * 1024 * 1024); + + encodingOneRequest(); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Content-Length: 4\r\n" + "custom: value\r\n" + "\r\n" + "body"); + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).WillOnce(Invoke([](StreamFramePtr frame) { + auto* response = dynamic_cast(frame.get()); + + EXPECT_EQ(frame->frameFlags().endStream(), true); + EXPECT_EQ(response->response_->getStatusValue(), "200"); + EXPECT_EQ(response->response_->getContentLengthValue(), "4"); + EXPECT_EQ(response->get("custom").value(), "value"); + + EXPECT_EQ(response->optionalBuffer().length(), 4); + EXPECT_EQ(response->optionalBuffer().toString(), "body"); + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ClientCodecTest, SingleFrameModeResponseTooLargeTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + initializeCodec(true, 4); + + encodingOneRequest(); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Content-Length: 5\r\n" + "custom: value\r\n" + "\r\n" + "body~"); + + EXPECT_CALL(codec_callbacks_, onDecodingFailure()); + codec_->decode(buffer, false); +} + +TEST_F(Http1ClientCodecTest, SingleFrameModeChunkedResponseDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + initializeCodec(true, 8 * 1024 * 1024); + + encodingOneRequest(); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n" // Chunk footer. + "0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).WillOnce(Invoke([](StreamFramePtr frame) { + auto* response = dynamic_cast(frame.get()); + + EXPECT_EQ(frame->frameFlags().endStream(), true); + + EXPECT_EQ(response->response_->getStatusValue(), "200"); + EXPECT_EQ(response->get("custom").value(), "value"); + + EXPECT_EQ(response->optionalBuffer().length(), 4); + EXPECT_EQ(response->optionalBuffer().toString(), "body"); + })); + + codec_->decode(buffer, false); +} + +TEST_F(Http1ClientCodecTest, SingleFrameModeMultipleBufferChunkedResponseDecodingTest) { + ON_CALL(codec_callbacks_, connection()) + .WillByDefault(testing::Return(makeOptRef(mock_connection))); + + initializeCodec(true, 8 * 1024 * 1024); + + encodingOneRequest(); + + Buffer::OwnedImpl buffer; + + buffer.add("HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "custom: value\r\n" + "\r\n" + "4\r\n" // Chunk header. + "body" // Chunk body. + "\r\n"); // Chunk footer. + + codec_->decode(buffer, false); + + EXPECT_EQ(buffer.length(), 0); + + buffer.add("0\r\n" // Last chunk header. + "\r\n"); // Last chunk footer. + + EXPECT_CALL(codec_callbacks_, onDecodingSuccess(_)).WillOnce(Invoke([](StreamFramePtr frame) { + auto* response = dynamic_cast(frame.get()); + + EXPECT_EQ(frame->frameFlags().endStream(), true); + + EXPECT_EQ(response->response_->getStatusValue(), "200"); + EXPECT_EQ(response->get("custom").value(), "value"); + + EXPECT_EQ(response->optionalBuffer().length(), 4); + EXPECT_EQ(response->optionalBuffer().toString(), "body"); + })); + + codec_->decode(buffer, false); +} + +} // namespace +} // namespace Http1 +} // namespace Codec +} // namespace GenericProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/tools/proto_format/format_api.py b/tools/proto_format/format_api.py index 8306a1f51515..d096f0677376 100644 --- a/tools/proto_format/format_api.py +++ b/tools/proto_format/format_api.py @@ -32,6 +32,7 @@ 'envoy.extensions.filters.network.client_ssl_auth.v3', 'envoy.extensions.filters.network.generic_proxy.action.v3', 'envoy.extensions.filters.network.generic_proxy.codecs.dubbo.v3', + 'envoy.extensions.filters.network.generic_proxy.codecs.http1.v3', 'envoy.extensions.filters.network.generic_proxy.codecs.kafka.v3', 'envoy.extensions.filters.network.generic_proxy.matcher.v3', 'envoy.extensions.filters.network.generic_proxy.router.v3', From 1e475b3fa5ba2facb424fe09dc7ca0678787fe07 Mon Sep 17 00:00:00 2001 From: code Date: Tue, 19 Mar 2024 21:58:19 +0800 Subject: [PATCH 07/11] dubbo: refactor the message implementation and generic proxy codecs (#32768) * dirty commit Signed-off-by: wbpcode * dirty commit Signed-off-by: wbpcode * complete refactoring and tests Signed-off-by: wbpcode * resolve coverage problem Signed-off-by: wbpcode * handle heartbeat and pipeline requests Signed-off-by: wbpcode --------- Signed-off-by: wbpcode --- .../network/source/codecs/dubbo/config.cc | 76 +-- .../network/source/codecs/dubbo/config.h | 46 +- .../network/test/codecs/dubbo/config_test.cc | 59 +- source/extensions/common/dubbo/BUILD | 3 +- source/extensions/common/dubbo/codec.cc | 15 +- .../common/dubbo/hessian2_serializer_impl.cc | 132 ++-- .../common/dubbo/hessian2_serializer_impl.h | 2 +- source/extensions/common/dubbo/message.cc | 440 ++++++++++++ source/extensions/common/dubbo/message.h | 161 ++++- .../extensions/common/dubbo/message_impl.cc | 95 --- source/extensions/common/dubbo/message_impl.h | 138 ---- source/extensions/common/dubbo/metadata.h | 6 +- test/extensions/common/dubbo/BUILD | 1 - test/extensions/common/dubbo/codec_test.cc | 68 +- .../dubbo/hessian2_serializer_impl_test.cc | 303 ++------- test/extensions/common/dubbo/message_test.cc | 626 +++++++++++++++--- test/extensions/common/dubbo/metadata_test.cc | 10 +- 17 files changed, 1327 insertions(+), 854 deletions(-) create mode 100644 source/extensions/common/dubbo/message.cc delete mode 100644 source/extensions/common/dubbo/message_impl.cc delete mode 100644 source/extensions/common/dubbo/message_impl.h diff --git a/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.cc b/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.cc index 4bbfa68e1b06..588a40191c1b 100644 --- a/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.cc +++ b/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.cc @@ -4,7 +4,7 @@ #include "envoy/registry/registry.h" -#include "source/extensions/common/dubbo/message_impl.h" +#include "source/extensions/common/dubbo/message.h" namespace Envoy { namespace Extensions { @@ -31,20 +31,9 @@ Common::Dubbo::ResponseStatus genericStatusToStatus(StatusCode code) { } // namespace void DubboRequest::forEach(IterateCallback callback) const { - const auto* typed_request = - dynamic_cast(&inner_metadata_->mutableRequest()); - ASSERT(typed_request != nullptr); - - for (const auto& pair : typed_request->attachment().attachment()) { - ASSERT(pair.first != nullptr && pair.second != nullptr); - - if (pair.first->type() == Hessian2::Object::Type::String && - pair.second->type() == Hessian2::Object::Type::String) { - ASSERT(pair.first->toString().has_value() && pair.second->toString().has_value()); - - if (!callback(pair.first->toString().value().get(), pair.second->toString().value().get())) { - break; - } + for (const auto& [key, val] : inner_metadata_->request().content().attachments()) { + if (!callback(key, val)) { + break; } } } @@ -53,27 +42,21 @@ absl::optional DubboRequest::get(absl::string_view key) const if (key == VERSION_KEY) { return inner_metadata_->request().serviceVersion(); } - const auto* typed_request = - dynamic_cast(&inner_metadata_->mutableRequest()); - ASSERT(typed_request != nullptr); - return typed_request->attachment().lookup(key); + auto it = inner_metadata_->request().content().attachments().find(key); + if (it == inner_metadata_->request().content().attachments().end()) { + return absl::nullopt; + } + + return absl::string_view{it->second}; } void DubboRequest::set(absl::string_view key, absl::string_view val) { - auto* typed_request = - dynamic_cast(&inner_metadata_->mutableRequest()); - ASSERT(typed_request != nullptr); - - typed_request->mutableAttachment()->insert(key, val); + inner_metadata_->request().content().setAttachment(key, val); } void DubboRequest::erase(absl::string_view key) { - auto* typed_request = - dynamic_cast(&inner_metadata_->mutableRequest()); - ASSERT(typed_request != nullptr); - - typed_request->mutableAttachment()->remove(key); + inner_metadata_->request().content().delAttachment(key); } void DubboResponse::refreshStatus() { @@ -85,19 +68,19 @@ void DubboResponse::refreshStatus() { const auto status = inner_metadata_->context().responseStatus(); const auto optional_type = inner_metadata_->response().responseType(); - // The final status is not ok if the response status is not ResponseStatus::Ok - // anyway. - bool response_ok = (status == Common::Dubbo::ResponseStatus::Ok); + if (status != Common::Dubbo::ResponseStatus::Ok) { + status_ = StreamStatus(static_cast(status), false); + return; + } + bool response_ok = true; // The final status is not ok if the response type is ResponseWithException or // ResponseWithExceptionWithAttachments even if the response status is Ok. - if (status == Common::Dubbo::ResponseStatus::Ok) { - ASSERT(optional_type.has_value()); - auto type = optional_type.value_or(RpcResponseType::ResponseWithException); - if (type == RpcResponseType::ResponseWithException || - type == RpcResponseType::ResponseWithExceptionWithAttachments) { - response_ok = false; - } + ASSERT(optional_type.has_value()); + auto type = optional_type.value_or(RpcResponseType::ResponseWithException); + if (type == RpcResponseType::ResponseWithException || + type == RpcResponseType::ResponseWithExceptionWithAttachments) { + response_ok = false; } status_ = StreamStatus(static_cast(status), response_ok); @@ -105,7 +88,7 @@ void DubboResponse::refreshStatus() { DubboCodecBase::DubboCodecBase(Common::Dubbo::DubboCodecPtr codec) : codec_(std::move(codec)) {} -ResponsePtr DubboServerCodec::respond(Status status, absl::string_view, +ResponsePtr DubboServerCodec::respond(Status status, absl::string_view data, const Request& origin_request) { const auto* typed_request = dynamic_cast(&origin_request); ASSERT(typed_request != nullptr); @@ -113,17 +96,20 @@ ResponsePtr DubboServerCodec::respond(Status status, absl::string_view, Common::Dubbo::ResponseStatus response_status = genericStatusToStatus(status.code()); absl::optional optional_type; - absl::string_view content; if (response_status == Common::Dubbo::ResponseStatus::Ok) { optional_type.emplace(Common::Dubbo::RpcResponseType::ResponseWithException); - content = "exception_via_proxy"; + } + auto response = Common::Dubbo::DirectResponseUtil::localResponse( + *typed_request->inner_metadata_, response_status, optional_type, data); + + if (!status.ok()) { + response->mutableResponse().content().setAttachment("reason", status.message()); } else { - content = status.message(); + response->mutableResponse().content().setAttachment("reason", "envoy_response"); } - return std::make_unique(Common::Dubbo::DirectResponseUtil::localResponse( - *typed_request->inner_metadata_, response_status, optional_type, content)); + return std::make_unique(std::move(response)); } CodecFactoryPtr diff --git a/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h b/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h index 86acc6f2aa9c..840cb61a733c 100644 --- a/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h +++ b/contrib/generic_proxy/filters/network/source/codecs/dubbo/config.h @@ -35,9 +35,9 @@ class DubboRequest : public Request { void forEach(IterateCallback callback) const override; absl::optional get(absl::string_view key) const override; void set(absl::string_view key, absl::string_view val) override; - absl::string_view host() const override { return inner_metadata_->request().serviceName(); } - absl::string_view path() const override { return inner_metadata_->request().serviceName(); } - absl::string_view method() const override { return inner_metadata_->request().methodName(); } + absl::string_view host() const override { return inner_metadata_->request().service(); } + absl::string_view path() const override { return inner_metadata_->request().service(); } + absl::string_view method() const override { return inner_metadata_->request().method(); } void erase(absl::string_view key) override; // StreamFrame @@ -87,7 +87,7 @@ class DubboDecoderBase : public DubboCodecBase, public CodecType { void setCodecCallbacks(CallBackType& callback) override { callback_ = &callback; } - void decode(Buffer::Instance& buffer, bool) override { + Common::Dubbo::DecodeStatus decodeOne(Buffer::Instance& buffer) { if (metadata_ == nullptr) { metadata_ = std::make_shared(); } @@ -109,27 +109,59 @@ class DubboDecoderBase : public DubboCodecBase, public CodecType { ENVOY_LOG(warn, "Dubbo codec: unexpected decoding error"); metadata_.reset(); callback_->onDecodingFailure(); - return; + return Common::Dubbo::DecodeStatus::Failure; } if (decode_status == Common::Dubbo::DecodeStatus::Waiting) { ENVOY_LOG(debug, "Dubbo codec: waiting for more input data"); - return; + return Common::Dubbo::DecodeStatus::Waiting; } ASSERT(decode_status == Common::Dubbo::DecodeStatus::Success); + if (metadata_->context().heartbeat()) { + Buffer::OwnedImpl heartbeat_response; + + ENVOY_LOG(debug, "Dubbo codec: heartbeat from downstream/upstream"); + constexpr char first_four_bytes[] = {'\xda', '\xbb', '\x22', 20}; + heartbeat_response.add(first_four_bytes, 4); + + heartbeat_response.writeBEInt(metadata_->requestId()); + heartbeat_response.writeBEInt(1); + heartbeat_response.writeByte('N'); + + metadata_.reset(); + callback_->writeToConnection(heartbeat_response); + + return Common::Dubbo::DecodeStatus::Success; + } + auto message = std::make_unique(metadata_); message->stream_frame_flags_ = {{static_cast(metadata_->requestId()), !metadata_->context().isTwoWay(), false, metadata_->context().heartbeat()}, true}; - callback_->onDecodingSuccess(std::move(message)); metadata_.reset(); + callback_->onDecodingSuccess(std::move(message)); + + return Common::Dubbo::DecodeStatus::Success; } catch (const EnvoyException& error) { ENVOY_LOG(warn, "Dubbo codec: decoding error: {}", error.what()); + metadata_.reset(); callback_->onDecodingFailure(); + + return Common::Dubbo::DecodeStatus::Failure; + } + } + + void decode(Buffer::Instance& buffer, bool) override { + while (buffer.length() > 0) { + // Continue decoding if the buffer has more data and the previous decoding is + // successful. + if (decodeOne(buffer) != Common::Dubbo::DecodeStatus::Success) { + break; + } } } diff --git a/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc b/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc index 6a4e3abb8614..f689f79024e2 100644 --- a/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc +++ b/contrib/generic_proxy/filters/network/test/codecs/dubbo/config_test.cc @@ -1,7 +1,7 @@ #include #include -#include "source/extensions/common/dubbo/message_impl.h" +#include "source/extensions/common/dubbo/message.h" #include "test/extensions/common/dubbo/mocks.h" #include "test/mocks/server/factory_context.h" @@ -25,26 +25,15 @@ using testing::Return; using namespace Common::Dubbo; MessageMetadataSharedPtr createDubboRequst(bool one_way_request) { - auto request = std::make_unique(); - request->setServiceName("fake_service"); - request->setMethodName("fake_method"); - request->setServiceVersion("fake_version"); - request->setParametersLazyCallback([]() -> RpcRequestImpl::ParametersPtr { - return std::make_unique(); - }); - request->setAttachmentLazyCallback([]() -> RpcRequestImpl::AttachmentPtr { - auto map = std::make_unique(); - Hessian2::ObjectPtr key_o = std::make_unique("group"); - Hessian2::ObjectPtr val_o = std::make_unique("fake_group"); - - map->toMutableUntypedMap().value().get().emplace(std::move(key_o), std::move(val_o)); - return std::make_unique(std::move(map), 0); - }); + auto request = std::make_unique("fake_dubbo_version", "fake_service", "fake_version", + "fake_method"); + + request->content().initialize("", {}, {}); + request->content().setAttachment("group", "fake_group"); auto context = std::make_unique(); context->setMessageType(one_way_request ? MessageType::Oneway : MessageType::Request); context->setRequestId(123456); - context->setSerializeType(SerializeType::Hessian2); auto metadata = std::make_shared(); metadata->setContext(std::move(context)); @@ -245,7 +234,7 @@ TEST(DubboServerCodecTest, DubboServerCodecTest) { buffer.add("anything"); EXPECT_CALL(*raw_serializer, deserializeRpcRequest(_, _)) - .WillOnce(Return(ByMove(std::make_unique()))); + .WillOnce(Return(ByMove(std::make_unique("a", "b", "c", "d")))); EXPECT_CALL(callbacks, onDecodingSuccess(_)); server_codec.decode(buffer, false); @@ -269,42 +258,44 @@ TEST(DubboServerCodecTest, DubboServerCodecTest) { Status status = absl::OkStatus(); DubboRequest request(createDubboRequst(false)); - auto response = server_codec.respond(status, "", request); + auto response = server_codec.respond(status, "anything", request); auto* typed_response = static_cast(response.get()); - auto* typed_inner_response = - static_cast(&typed_response->inner_metadata_->mutableResponse()); + auto& typed_inner_response = typed_response->inner_metadata_->mutableResponse(); EXPECT_EQ(ResponseStatus::Ok, typed_response->inner_metadata_->responseStatus()); - EXPECT_EQ(RpcResponseType::ResponseWithException, typed_inner_response->responseType().value()); - EXPECT_EQ("exception_via_proxy", typed_inner_response->localRawMessage().value()); + EXPECT_EQ(RpcResponseType::ResponseWithException, typed_inner_response.responseType().value()); + EXPECT_EQ("anything", typed_inner_response.content().result()->toString().value().get()); + EXPECT_EQ("envoy_response", typed_inner_response.content().attachments().at("reason")); } { Status status(StatusCode::kInvalidArgument, "test_message"); DubboRequest request(createDubboRequst(false)); - auto response = server_codec.respond(status, "", request); + auto response = server_codec.respond(status, "anything", request); auto* typed_response = static_cast(response.get()); - auto* typed_inner_response = - static_cast(&typed_response->inner_metadata_->mutableResponse()); + auto& typed_inner_response = typed_response->inner_metadata_->mutableResponse(); EXPECT_EQ(ResponseStatus::BadRequest, typed_response->inner_metadata_->responseStatus()); - EXPECT_EQ(false, typed_inner_response->responseType().has_value()); - EXPECT_EQ("test_message", typed_inner_response->localRawMessage().value()); + EXPECT_EQ(false, typed_inner_response.responseType().has_value()); + + EXPECT_EQ("anything", typed_inner_response.content().result()->toString().value().get()); + EXPECT_EQ("test_message", typed_inner_response.content().attachments().at("reason")); } { Status status(StatusCode::kAborted, "test_message2"); DubboRequest request(createDubboRequst(false)); - auto response = server_codec.respond(status, "", request); + auto response = server_codec.respond(status, "anything", request); auto* typed_response = static_cast(response.get()); - auto* typed_inner_response = - static_cast(&typed_response->inner_metadata_->mutableResponse()); + auto& typed_inner_response = typed_response->inner_metadata_->mutableResponse(); EXPECT_EQ(ResponseStatus::ServerError, typed_response->inner_metadata_->responseStatus()); - EXPECT_EQ(false, typed_inner_response->responseType().has_value()); - EXPECT_EQ("test_message2", typed_inner_response->localRawMessage().value()); + EXPECT_EQ(false, typed_inner_response.responseType().has_value()); + + EXPECT_EQ("anything", typed_inner_response.content().result()->toString().value().get()); + EXPECT_EQ("test_message2", typed_inner_response.content().attachments().at("reason")); } } @@ -365,7 +356,7 @@ TEST(DubboClientCodecTest, DubboClientCodecTest) { buffer.writeBEInt(8); buffer.add("anything"); - auto response = std::make_unique(); + auto response = std::make_unique(); response->setResponseType(RpcResponseType::ResponseWithValue); EXPECT_CALL(*raw_serializer, deserializeRpcResponse(_, _)) diff --git a/source/extensions/common/dubbo/BUILD b/source/extensions/common/dubbo/BUILD index e409d8e43339..d3c9340fdb89 100644 --- a/source/extensions/common/dubbo/BUILD +++ b/source/extensions/common/dubbo/BUILD @@ -48,11 +48,10 @@ envoy_cc_library( envoy_cc_library( name = "message_lib", srcs = [ - "message_impl.cc", + "message.cc", ], hdrs = [ "message.h", - "message_impl.h", ], deps = [ ":hessian2_utils_lib", diff --git a/source/extensions/common/dubbo/codec.cc b/source/extensions/common/dubbo/codec.cc index 5fdb34843cdf..d02012b88506 100644 --- a/source/extensions/common/dubbo/codec.cc +++ b/source/extensions/common/dubbo/codec.cc @@ -8,7 +8,6 @@ #include "source/common/common/assert.h" #include "source/extensions/common/dubbo/hessian2_serializer_impl.h" #include "source/extensions/common/dubbo/message.h" -#include "source/extensions/common/dubbo/message_impl.h" #include "source/extensions/common/dubbo/metadata.h" namespace Envoy { @@ -33,7 +32,7 @@ void encodeHeader(Buffer::Instance& buffer, Context& context, uint32_t body_size buffer.writeBEInt(MagicNumber); // Serialize type and flag. - uint8_t flag = static_cast(context.serializeType()); + uint8_t flag = static_cast(SerializeType::Hessian2); switch (context.messageType()) { case MessageType::Response: @@ -167,7 +166,6 @@ DecodeStatus DubboCodec::decodeHeader(Buffer::Instance& buffer, MessageMetadata& absl::StrCat("invalid dubbo message serialization type ", static_cast::type>(serialize_type))); } - context->setSerializeType(serialize_type); // Initial basic type of message. MessageType type = @@ -277,7 +275,6 @@ MessageMetadataSharedPtr DirectResponseUtil::heartbeatResponse(MessageMetadata& auto context = std::make_unique(); // Set context. - context->setSerializeType(request_context.serializeType()); context->setMessageType(MessageType::HeartbeatResponse); context->setResponseStatus(ResponseStatus::Ok); context->setRequestId(request_context.requestId()); @@ -299,7 +296,6 @@ MessageMetadataSharedPtr DirectResponseUtil::localResponse(MessageMetadata& requ auto context = std::make_unique(); // Set context. - context->setSerializeType(request_context.serializeType()); if (status != ResponseStatus::Ok) { context->setMessageType(MessageType::Exception); } else if (type.has_value() && @@ -309,16 +305,17 @@ MessageMetadataSharedPtr DirectResponseUtil::localResponse(MessageMetadata& requ } else { context->setMessageType(MessageType::Response); } + context->setResponseStatus(status); context->setRequestId(request_context.requestId()); // Set response. - auto response = std::make_unique(); - if (status == ResponseStatus::Ok && type.has_value()) { + auto response = std::make_unique(); + if (status == ResponseStatus::Ok) { // No response type for non-Ok response. - response->setResponseType(type.value()); + response->setResponseType(type.value_or(RpcResponseType::ResponseWithValue)); } - response->setLocalRawMessage(content); + response->content().initialize(std::make_unique(content), {}); auto metadata = std::make_shared(); metadata->setContext(std::move(context)); diff --git a/source/extensions/common/dubbo/hessian2_serializer_impl.cc b/source/extensions/common/dubbo/hessian2_serializer_impl.cc index 506e315c8d94..0e49a62bd4f8 100644 --- a/source/extensions/common/dubbo/hessian2_serializer_impl.cc +++ b/source/extensions/common/dubbo/hessian2_serializer_impl.cc @@ -8,7 +8,6 @@ #include "source/common/common/macros.h" #include "source/extensions/common/dubbo/hessian2_utils.h" #include "source/extensions/common/dubbo/message.h" -#include "source/extensions/common/dubbo/message_impl.h" #include "source/extensions/common/dubbo/metadata.h" #include "hessian2/object.hpp" @@ -32,7 +31,6 @@ RpcRequestPtr Hessian2SerializerImpl::deserializeRpcRequest(Buffer::Instance& bu ASSERT(context.messageType() == MessageType::Request || context.messageType() == MessageType::Oneway); ASSERT(context.bodySize() <= buffer.length()); - auto request = std::make_unique(); Hessian2::Decoder decoder(std::make_unique(buffer)); @@ -42,9 +40,11 @@ RpcRequestPtr Hessian2SerializerImpl::deserializeRpcRequest(Buffer::Instance& bu auto service_version = decoder.decode(); auto method_name = decoder.decode(); - if (context.bodySize() < decoder.offset()) { - throw EnvoyException(fmt::format("RpcRequest size({}) larger than body size({})", - decoder.offset(), context.bodySize())); + const auto decoded_size = decoder.offset(); + + if (context.bodySize() < decoded_size) { + throw EnvoyException(fmt::format("RpcRequest size({}) larger than body size({})", decoded_size, + context.bodySize())); } if (dubbo_version == nullptr || service_name == nullptr || service_version == nullptr || @@ -52,46 +52,11 @@ RpcRequestPtr Hessian2SerializerImpl::deserializeRpcRequest(Buffer::Instance& bu throw EnvoyException(fmt::format("RpcRequest has no request metadata")); } - request->setServiceName(*service_name); - request->setServiceVersion(*service_version); - request->setMethodName(*method_name); - - // Move original request message to the raw buffer to delay the decoding of complex body. - request->messageBuffer().move(buffer, context.bodySize()); - const size_t parsed_size = decoder.offset(); - auto delayed_decoder = std::make_shared( - std::make_unique(request->messageBuffer(), parsed_size)); - - request->setParametersLazyCallback([delayed_decoder]() -> RpcRequestImpl::ParametersPtr { - auto params = std::make_unique(); - - if (auto types = delayed_decoder->decode(); types != nullptr && !types->empty()) { - uint32_t number = Hessian2Utils::getParametersNumber(*types); - for (uint32_t i = 0; i < number; i++) { - if (auto result = delayed_decoder->decode(); result != nullptr) { - params->push_back(std::move(result)); - } else { - throw EnvoyException("Cannot parse RpcRequest parameter from buffer"); - } - } - } - return params; - }); - - request->setAttachmentLazyCallback([delayed_decoder]() -> RpcRequestImpl::AttachmentPtr { - size_t offset = delayed_decoder->offset(); - - auto result = delayed_decoder->decode(); - if (result != nullptr && result->type() == Hessian2::Object::Type::UntypedMap) { - return std::make_unique( - RpcRequestImpl::Attachment::MapPtr{ - dynamic_cast(result.release())}, - offset); - } else { - return std::make_unique( - std::make_unique(), offset); - } - }); + buffer.drain(decoded_size); + + auto request = std::make_unique(std::move(*dubbo_version), std::move(*service_name), + std::move(*service_version), std::move(*method_name)); + request->content().initialize(buffer, context.bodySize() - decoded_size); return request; } @@ -106,29 +71,29 @@ RpcResponsePtr Hessian2SerializerImpl::deserializeRpcResponse(Buffer::Instance& return nullptr; } + ASSERT(context.hasResponseStatus()); + // Handle normal response or exception response. ASSERT(context.messageType() == MessageType::Response || context.messageType() == MessageType::Exception); - auto response = std::make_unique(); + + ASSERT(context.hasResponseStatus()); + auto response = std::make_unique(); // Non `Ok` response body has no response type info and skip deserialization. if (context.messageType() == MessageType::Exception) { - ASSERT(context.hasResponseStatus()); ASSERT(context.responseStatus() != ResponseStatus::Ok); - response->messageBuffer().move(buffer, context.bodySize()); + response->content().initialize(buffer, context.bodySize()); return response; } - bool has_value = true; - Hessian2::Decoder decoder(std::make_unique(buffer)); auto type_value = decoder.decode(); if (type_value == nullptr) { throw EnvoyException(fmt::format("Cannot parse RpcResponse type from buffer")); } - RpcResponseType type = static_cast(*type_value); - response->setResponseType(type); + const RpcResponseType type = static_cast(*type_value); switch (type) { case RpcResponseType::ResponseWithException: @@ -136,8 +101,6 @@ RpcResponsePtr Hessian2SerializerImpl::deserializeRpcResponse(Buffer::Instance& context.setMessageType(MessageType::Exception); break; case RpcResponseType::ResponseWithNullValue: - has_value = false; - FALLTHRU; case RpcResponseType::ResponseNullValueWithAttachments: case RpcResponseType::ResponseWithValue: case RpcResponseType::ResponseValueWithAttachments: @@ -146,26 +109,25 @@ RpcResponsePtr Hessian2SerializerImpl::deserializeRpcResponse(Buffer::Instance& throw EnvoyException(fmt::format("not supported return type {}", static_cast(type))); } - size_t total_size = decoder.offset(); + const auto decoded_size = decoder.offset(); - if (context.bodySize() < total_size) { - throw EnvoyException(fmt::format("RpcResponse size({}) large than body size({})", total_size, + if (context.bodySize() < decoded_size) { + throw EnvoyException(fmt::format("RpcResponse size({}) large than body size({})", decoded_size, context.bodySize())); } - if (!has_value && context.bodySize() != total_size) { - throw EnvoyException( - fmt::format("RpcResponse is no value, but the rest of the body size({}) not equal 0", - (context.bodySize() - total_size))); - } + buffer.drain(decoded_size); - response->messageBuffer().move(buffer, context.bodySize()); + response->setResponseType(type); + response->content().initialize(buffer, context.bodySize() - decoded_size); return response; } void Hessian2SerializerImpl::serializeRpcResponse(Buffer::Instance& buffer, MessageMetadata& metadata) { ASSERT(metadata.hasContext()); + ASSERT(metadata.context().hasResponseStatus()); + const auto& context = metadata.context(); if (context.heartbeat()) { @@ -173,22 +135,13 @@ void Hessian2SerializerImpl::serializeRpcResponse(Buffer::Instance& buffer, return; } - ASSERT(dynamic_cast(&metadata.mutableResponse()) != nullptr); - auto* typed_response_info = dynamic_cast(&metadata.mutableResponse()); - - if (typed_response_info->localRawMessage().has_value()) { - // Local direct response. - Hessian2::Encoder encoder(std::make_unique(buffer)); - - if (typed_response_info->responseType().has_value()) { - encoder.encode(static_cast(typed_response_info->responseType().value())); - } - encoder.encode(typed_response_info->localRawMessage().value()); - } else { - // Update of dubbo response is not supported for now. And there is no retry for response - // encoding. So, it is safe to move the data directly. - buffer.move(typed_response_info->messageBuffer()); + ASSERT(metadata.hasResponse()); + if (auto type = metadata.response().responseType(); type.has_value()) { + ASSERT(metadata.context().responseStatus() == ResponseStatus::Ok); + buffer.writeByte(0x90 + static_cast(type.value())); } + + buffer.add(metadata.response().content().buffer()); } void Hessian2SerializerImpl::serializeRpcRequest(Buffer::Instance& buffer, @@ -206,25 +159,14 @@ void Hessian2SerializerImpl::serializeRpcRequest(Buffer::Instance& buffer, ASSERT(metadata.context().messageType() == MessageType::Request || metadata.context().messageType() == MessageType::Oneway); - ASSERT(dynamic_cast(&metadata.mutableRequest()) != nullptr); - auto* typed_request_info = dynamic_cast(&metadata.mutableRequest()); + Hessian2::Encoder encoder(std::make_unique(buffer)); - // Create a copy for possible retry. - Buffer::OwnedImpl copy_of_message = typed_request_info->messageBuffer(); + encoder.encode(metadata.request().version()); + encoder.encode(metadata.request().service()); + encoder.encode(metadata.request().serviceVersion()); + encoder.encode(metadata.request().method()); - if (typed_request_info->hasAttachment() && typed_request_info->attachment().attachmentUpdated()) { - const size_t attachment_offset = typed_request_info->attachment().attachmentOffset(); - ASSERT(attachment_offset <= copy_of_message.length()); - - // Move the parameters to buffer. - buffer.move(copy_of_message, attachment_offset); - - // Re-encode the dubbo attachment. - Hessian2::Encoder encoder(std::make_unique(buffer)); - encoder.encode(typed_request_info->attachment().attachment()); - } else { - buffer.move(copy_of_message); - } + buffer.add(metadata.request().content().buffer()); } } // namespace Dubbo diff --git a/source/extensions/common/dubbo/hessian2_serializer_impl.h b/source/extensions/common/dubbo/hessian2_serializer_impl.h index 1f9354ff93d2..974f41ac4dc6 100644 --- a/source/extensions/common/dubbo/hessian2_serializer_impl.h +++ b/source/extensions/common/dubbo/hessian2_serializer_impl.h @@ -1,6 +1,6 @@ #pragma once -#include "source/extensions/common/dubbo/message_impl.h" +#include "source/extensions/common/dubbo/message.h" #include "source/extensions/common/dubbo/serializer.h" namespace Envoy { diff --git a/source/extensions/common/dubbo/message.cc b/source/extensions/common/dubbo/message.cc new file mode 100644 index 000000000000..a85a0d28cb50 --- /dev/null +++ b/source/extensions/common/dubbo/message.cc @@ -0,0 +1,440 @@ +#include "source/extensions/common/dubbo/message.h" + +#include "source/common/common/logger.h" +#include "source/extensions/common/dubbo/hessian2_utils.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Dubbo { + +void RequestContent::initialize(Buffer::Instance& buffer, uint64_t length) { + ASSERT(content_buffer_.length() == 0, "content buffer has been initialized"); + + content_buffer_.move(buffer, length); + + // Clear the types, arguments and attachments. + types_.clear(); + argvs_.clear(); + attachs_.clear(); + + // Set both decoded and updated to false since the content has been initialized + // by raw buffer. + decoded_ = false; + updated_ = false; +} + +void RequestContent::initialize(std::string&& types, ArgumentVec&& argvs, Attachments&& attachs) { + ASSERT(content_buffer_.length() == 0, "content buffer has been initialized"); + + // Set the types, arguments and attachments. + types_ = std::move(types); + argvs_ = std::move(argvs); + attachs_ = std::move(attachs); + + // Encode the types, arguments and attachments into the content buffer. + encodeEverything(); + + // Set decoded to true since the content has been initialized by types, + // arguments and attachments. + decoded_ = true; + updated_ = false; +} + +const Buffer::Instance& RequestContent::buffer() { + // Ensure the attachments in the buffer is latest. + + if (content_buffer_.length() == 0) { + encodeEverything(); + } else { + encodeAttachments(); + } + + return content_buffer_; +} + +void RequestContent::bufferMoveTo(Buffer::Instance& buffer) { buffer.move(content_buffer_); } + +const ArgumentVec& RequestContent::arguments() { + lazyDecode(); + + return argvs_; +} + +const Attachments& RequestContent::attachments() { + lazyDecode(); + + return attachs_; +} + +void RequestContent::setAttachment(absl::string_view key, absl::string_view val) { + lazyDecode(); + + updated_ = true; + attachs_[key] = val; +} + +void RequestContent::delAttachment(absl::string_view key) { + lazyDecode(); + + updated_ = true; + attachs_.erase(key); +} + +void RequestContent::lazyDecode() { + if (decoded_) { + return; + } + decoded_ = true; + + // Decode the content buffer into types, arguments and attachments. + Hessian2::Decoder decoder(std::make_unique(content_buffer_)); + + // Handle the types and arguments. + if (auto element = decoder.decode(); element != nullptr) { + uint32_t number = 0; + + if (element->type() == Hessian2::Object::Type::Integer) { + ASSERT(element->toInteger().has_value()); + if (int32_t direct_num = element->toInteger().value(); direct_num == -1) { + if (auto types = decoder.decode(); types != nullptr) { + types_ = *types; + number = Hessian2Utils::getParametersNumber(types_); + } else { + ENVOY_LOG(error, "Cannot parse RpcInvocation parameter types from buffer"); + handleBrokenValue(); + return; + } + } else if (direct_num >= 0) { + number = direct_num; + } else { + ENVOY_LOG(error, "Invalid RpcInvocation parameter number {}", direct_num); + handleBrokenValue(); + return; + } + } else if (element->type() == Hessian2::Object::Type::String) { + ASSERT(element->toString().has_value()); + types_ = element->toString().value().get(); + number = Hessian2Utils::getParametersNumber(types_); + } + + for (uint32_t i = 0; i < number; i++) { + if (auto result = decoder.decode(); result != nullptr) { + argvs_.push_back(std::move(result)); + } else { + ENVOY_LOG(error, "Cannot parse RpcInvocation parameter from buffer"); + handleBrokenValue(); + return; + } + } + } else { + ENVOY_LOG(error, "Cannot parse RpcInvocation from buffer"); + handleBrokenValue(); + return; + } + + // Record the size of the arguments in the content buffer. This is useful for + // re-encoding the attachments. + argvs_size_ = decoder.offset(); + + // Handle the attachments. + auto map = decoder.decode(); + if (map == nullptr || map->type() != Hessian2::Object::Type::UntypedMap) { + return; + } + + for (auto& [key, val] : map->toMutableUntypedMap().value().get()) { + if (key->type() != Hessian2::Object::Type::String || + val->type() != Hessian2::Object::Type::String) { + continue; + } + attachs_.emplace(std::move(key->toMutableString().value().get()), + std::move(val->toMutableString().value().get())); + } +} + +void RequestContent::encodeAttachments() { + // Do nothing if the attachments have not been updated. + if (!updated_) { + return; + } + + // Ensure the content has been decoded before re-encoding it. + lazyDecode(); + + const uint64_t buffer_length = content_buffer_.length(); + ASSERT(buffer_length > 0, "content buffer is empty"); + + // The size of arguments will be set when doing lazyDecode() or encodeEverything(). + if (buffer_length < argvs_size_) { + ENVOY_LOG(error, "arguments size {} is larger than content buffer {}", argvs_size_, + buffer_length); + handleBrokenValue(); + return; + } + + // Create a new buffer to hold the re-encoded content. + + Buffer::OwnedImpl new_content_buffer; + // Copy the types and arguments into the new buffer. + new_content_buffer.move(content_buffer_, argvs_size_); + + // Encode the attachments into the new buffer. + Hessian2::Encoder encoder(std::make_unique(new_content_buffer)); + new_content_buffer.writeByte('H'); + for (auto& [key, val] : attachs_) { + encoder.encode(key); + encoder.encode(val); + } + new_content_buffer.writeByte('Z'); + + // Clear the content buffer and move the new buffer into it. + content_buffer_.drain(content_buffer_.length()); + content_buffer_.move(new_content_buffer); + + updated_ = false; +} + +void RequestContent::encodeEverything() { + ASSERT(content_buffer_.length() == 0, "content buffer contains something"); + + // Encode the types, arguments and attachments into the content buffer. + Hessian2::Encoder encoder(std::make_unique(content_buffer_)); + + // Encode the types into the content buffer first. + if (!types_.empty()) { + encoder.encode(types_); + } else if (!argvs_.empty()) { + encoder.encode(static_cast(argvs_.size())); + } else { + encoder.encode(types_); + } + + // Encode the arguments into the content buffer. + for (auto& arg : argvs_) { + encoder.encode(*arg); + } + + // Record the size of the arguments in the content buffer. This is useful for + // re-encoding the attachments. + argvs_size_ = content_buffer_.length(); + + // Encode the attachments into the content buffer. + content_buffer_.writeByte('H'); + for (auto& [key, val] : attachs_) { + encoder.encode(key); + encoder.encode(val); + } + content_buffer_.writeByte('Z'); + + updated_ = false; +} + +void RequestContent::handleBrokenValue() { + // Because the lazy decoding is used, Envoy cannot reject the message with broken + // content. Instead, it will reset the whole content to an empty state. + + // Clear everything. + content_buffer_.drain(content_buffer_.length()); + types_.clear(); + argvs_.clear(); + attachs_.clear(); + + // Encode everything. + encodeEverything(); + + decoded_ = true; + updated_ = false; +} + +void ResponseContent::initialize(Buffer::Instance& buffer, uint64_t length) { + ASSERT(content_buffer_.length() == 0, "content buffer has been initialized"); + content_buffer_.move(buffer, length); + + // Clear the result and attachments. + result_ = nullptr; + attachs_.clear(); + + // Set both decoded and updated to false since the content has been initialized + // by raw buffer. + decoded_ = false; + updated_ = false; +} + +void ResponseContent::initialize(Hessian2::ObjectPtr&& value, Attachments&& attachs) { + ASSERT(content_buffer_.length() == 0, "content buffer has been initialized"); + + // Set the result and attachments. + result_ = std::move(value); + attachs_ = std::move(attachs); + + // Encode the result and attachments into the content buffer. + encodeEverything(); + + // Set decoded to true since the content has been initialized by result and attachments. + decoded_ = true; + updated_ = false; +} + +const Buffer::Instance& ResponseContent::buffer() { + // Ensure the attachments in the buffer is latest. + if (content_buffer_.length() == 0) { + encodeEverything(); + } else { + encodeAttachments(); + } + + return content_buffer_; +} + +void ResponseContent::bufferMoveTo(Buffer::Instance& buffer) { buffer.move(content_buffer_); } + +const Hessian2::Object* ResponseContent::result() { + lazyDecode(); + + return result_.get(); +} + +const Attachments& ResponseContent::attachments() { + lazyDecode(); + + return attachs_; +} + +void ResponseContent::setAttachment(absl::string_view key, absl::string_view val) { + lazyDecode(); + + updated_ = true; + attachs_[key] = val; +} + +void ResponseContent::delAttachment(absl::string_view key) { + lazyDecode(); + + updated_ = true; + attachs_.erase(key); +} + +void ResponseContent::lazyDecode() { + if (decoded_) { + return; + } + decoded_ = true; + + // Decode the content buffer into result and attachments. + Hessian2::Decoder decoder(std::make_unique(content_buffer_)); + + // Handle the result. + result_ = decoder.decode(); + + if (result_ == nullptr) { + ENVOY_LOG(error, "Cannot parse RpcResult from buffer"); + handleBrokenValue(); + return; + } + + // Record the size of the result in the content buffer. This is useful for + // re-encoding the attachments. + result_size_ = decoder.offset(); + + // Handle the attachments. + auto map = decoder.decode(); + if (map == nullptr || map->type() != Hessian2::Object::Type::UntypedMap) { + return; + } + + for (auto& [key, val] : map->toMutableUntypedMap().value().get()) { + if (key->type() != Hessian2::Object::Type::String || + val->type() != Hessian2::Object::Type::String) { + continue; + } + attachs_.emplace(std::move(key->toMutableString().value().get()), + std::move(val->toMutableString().value().get())); + } +} + +void ResponseContent::encodeAttachments() { + if (!updated_) { + return; + } + + // Ensure the content has been decoded before re-encoding it. + lazyDecode(); + + const uint64_t buffer_length = content_buffer_.length(); + ASSERT(buffer_length > 0, "content buffer is empty"); + + if (buffer_length < result_size_) { + ENVOY_LOG(error, "result size {} is larger than content buffer {}", result_size_, + buffer_length); + handleBrokenValue(); + return; + } + + // Create a new buffer to hold the re-encoded content. + Buffer::OwnedImpl new_content_buffer; + + // Copy the result into the new buffer. + new_content_buffer.move(content_buffer_, result_size_); + + // Encode the attachments into the new buffer. + Hessian2::Encoder encoder(std::make_unique(new_content_buffer)); + new_content_buffer.writeByte('H'); + for (auto& [key, val] : attachs_) { + encoder.encode(key); + encoder.encode(val); + } + new_content_buffer.writeByte('Z'); + + // Clear the content buffer and move the new buffer into it. + content_buffer_.drain(content_buffer_.length()); + content_buffer_.move(new_content_buffer); + + updated_ = false; +} + +void ResponseContent::encodeEverything() { + ASSERT(content_buffer_.length() == 0, "content buffer contains something"); + + // Encode the result and attachments into the content buffer. + Hessian2::Encoder encoder(std::make_unique(content_buffer_)); + if (result_ == nullptr) { + content_buffer_.writeByte('N'); + } else { + encoder.encode(*result_); + } + + // Record the size of the result in the content buffer. This is useful for + // re-encoding the attachments. + result_size_ = content_buffer_.length(); + + content_buffer_.writeByte('H'); + for (auto& [key, val] : attachs_) { + encoder.encode(key); + encoder.encode(val); + } + content_buffer_.writeByte('Z'); + + updated_ = false; +} + +void ResponseContent::handleBrokenValue() { + // Because the lazy decoding is used, Envoy cannot reject the message with broken + // content. Instead, it will reset the whole content to an empty state. + + // Clear everything. + content_buffer_.drain(content_buffer_.length()); + result_ = nullptr; + attachs_.clear(); + + // Encode everything. + encodeEverything(); + + decoded_ = true; + updated_ = false; +} + +} // namespace Dubbo +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/common/dubbo/message.h b/source/extensions/common/dubbo/message.h index 6eeff49bd189..5808e496a5cd 100644 --- a/source/extensions/common/dubbo/message.h +++ b/source/extensions/common/dubbo/message.h @@ -7,6 +7,7 @@ #include "envoy/common/pure.h" #include "source/common/buffer/buffer_impl.h" +#include "source/extensions/common/dubbo/hessian2_utils.h" #include "absl/container/node_hash_map.h" #include "absl/types/optional.h" @@ -84,29 +85,175 @@ enum class RpcResponseType : uint8_t { ResponseNullValueWithAttachments = 5, }; +using Attachments = absl::flat_hash_map; +using ArgumentVec = absl::InlinedVector; + +class RequestContent : Envoy::Logger::Loggable { +public: + // Initialize the content buffer with the given buffer and length. + void initialize(Buffer::Instance& buffer, uint64_t length); + + // Initialize the content buffer with the given types and arguments and attachments. + // The initialize() call will also encode these types and arguments into the + // content buffer. + void initialize(std::string&& types, ArgumentVec&& argvs, Attachments&& attachs); + + // Underlying content buffer. This may re-encode the result and attachments into the + // content buffer to ensure the returned buffer is up-to-date. + const Buffer::Instance& buffer(); + + // Move the content buffer to the given buffer. This only does the move and does not + // re-encode the result and attachments. + void bufferMoveTo(Buffer::Instance& buffer); + + // Get all the arguments of the request. + const ArgumentVec& arguments(); + + // Get all the attachments of the request. + const Attachments& attachments(); + + // Set the attachment with the given key and value. If the key already exists, the + // value will be updated. Otherwise, a new key-value pair will be added. + void setAttachment(absl::string_view key, absl::string_view val); + + // Remove the attachment with the given key. + void delAttachment(absl::string_view key); + +private: + // Decode the content buffer into types, arguments and attachments. The decoding is + // lazy and will be triggered when the content is accessed. + void lazyDecode(); + + // Re-encode the attachments into the content buffer. + void encodeAttachments(); + + // Re-encode the types, arguments and attachments into the content buffer. + void encodeEverything(); + + // Called when the content is broken. The whole content will be reset to an empty + // state. + void handleBrokenValue(); + + Buffer::OwnedImpl content_buffer_; + + // If the content has been decoded. This ensures the decoding is only performed once. + bool decoded_{false}; + + // If the attachments has been updated. This ensures the re-encoding is only + // when the attachment has been modified. + bool updated_{false}; + + uint64_t argvs_size_{0}; + + std::string types_; + ArgumentVec argvs_; + Attachments attachs_; +}; + /** * RpcRequest represent an rpc call. */ class RpcRequest { public: - virtual ~RpcRequest() = default; + RpcRequest(std::string&& version, std::string&& service, std::string&& service_version, + std::string&& method) + : version_(std::move(version)), service_(std::move(service)), + service_version_(std::move(service_version)), method_(std::move(method)) {} + + absl::string_view version() const { return version_; } + absl::string_view service() const { return service_; } + absl::string_view serviceVersion() const { return service_version_; } + absl::string_view method() const { return method_; } + + RequestContent& content() const { return content_; } - virtual absl::string_view serviceName() const PURE; - virtual absl::string_view methodName() const PURE; - virtual absl::string_view serviceVersion() const PURE; - virtual absl::optional serviceGroup() const PURE; +private: + std::string version_; + std::string service_; + std::string service_version_; + std::string method_; + + mutable RequestContent content_; }; using RpcRequestPtr = std::unique_ptr; +class ResponseContent : public Envoy::Logger::Loggable { +public: + // Initialize the content buffer with the given buffer and length. + void initialize(Buffer::Instance& buffer, uint64_t length); + + // Initialize the content buffer with the given result and attachments. The initialize() + // call will also encode the result and attachments into the content buffer. + void initialize(Hessian2::ObjectPtr&& value, Attachments&& attachs); + + // Underlying content buffer. This may re-encode the result and attachments into the + // content buffer to ensure the returned buffer is up-to-date. + const Buffer::Instance& buffer(); + + // Move the content buffer to the given buffer. This only does the move and does not + // re-encode the result and attachments. + void bufferMoveTo(Buffer::Instance& buffer); + + // Get the result of the response. If the content has not been decoded, the decoding + // will be triggered. + const Hessian2::Object* result(); + + // Get all the attachments of the response. + const Attachments& attachments(); + + // Set the attachment with the given key and value. If the key already exists, the + // value will be updated. Otherwise, a new key-value pair will be added. + void setAttachment(absl::string_view key, absl::string_view val); + + // Remove the attachment with the given key. + void delAttachment(absl::string_view key); + +private: + // Decode the content buffer into value and attachments. The decoding is lazy and will + // be triggered when the content is accessed. + void lazyDecode(); + + // Re-encode the attachments into the content buffer. + void encodeAttachments(); + + // Re-encode the result and attachments into the content buffer. + void encodeEverything(); + + // Called when the content is broken. The whole content will be reset to an empty + // state. + void handleBrokenValue(); + + Buffer::OwnedImpl content_buffer_; + + // If the content has been decoded. This ensures the decoding is only performed once. + bool decoded_{false}; + + // If the attachments has been updated. This ensures the re-encoding is only + // when the attachment has been modified. + bool updated_{false}; + + uint64_t result_size_{0}; + + Hessian2::ObjectPtr result_; + Attachments attachs_; +}; + /** * RpcResponse represent the result of an rpc call. */ class RpcResponse { public: - virtual ~RpcResponse() = default; + RpcResponse() = default; + + void setResponseType(RpcResponseType response_type) { response_type_ = response_type; } + absl::optional responseType() const { return response_type_; } + + ResponseContent& content() const { return content_; } - virtual absl::optional responseType() const PURE; +private: + absl::optional response_type_{}; + mutable ResponseContent content_; }; using RpcResponsePtr = std::unique_ptr; diff --git a/source/extensions/common/dubbo/message_impl.cc b/source/extensions/common/dubbo/message_impl.cc deleted file mode 100644 index 89b30dd92b4b..000000000000 --- a/source/extensions/common/dubbo/message_impl.cc +++ /dev/null @@ -1,95 +0,0 @@ -#include "source/extensions/common/dubbo/message_impl.h" - -#include - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Dubbo { - -RpcRequestImpl::Attachment::Attachment(MapPtr&& value, size_t offset) - : attachment_(std::move(value)), attachment_offset_(offset) { - ASSERT(attachment_ != nullptr); - ASSERT(attachment_->toMutableUntypedMap().has_value()); -} - -void RpcRequestImpl::Attachment::insert(absl::string_view key, absl::string_view value) { - ASSERT(attachment_->toMutableUntypedMap().has_value()); - - attachment_updated_ = true; - - Hessian2::ObjectPtr key_o = std::make_unique(key); - Hessian2::ObjectPtr val_o = std::make_unique(value); - attachment_->toMutableUntypedMap().value().get().insert_or_assign(std::move(key_o), - std::move(val_o)); -} - -void RpcRequestImpl::Attachment::remove(absl::string_view key) { - ASSERT(attachment_->toMutableUntypedMap().has_value()); - - attachment_updated_ = true; - attachment_->toMutableUntypedMap().value().get().erase(key); -} - -absl::optional RpcRequestImpl::Attachment::lookup(absl::string_view key) const { - ASSERT(attachment_->toMutableUntypedMap().has_value()); - - auto& map = attachment_->toMutableUntypedMap().value().get(); - auto result = map.find(key); - if (result != map.end() && result->second->type() == Hessian2::Object::Type::String) { - ASSERT(result->second->toString().has_value()); - return absl::make_optional(result->second->toString().value().get()); - } - return absl::nullopt; -} - -void RpcRequestImpl::assignParametersIfNeed() const { - ASSERT(parameters_lazy_callback_ != nullptr); - if (parameters_ == nullptr) { - parameters_ = parameters_lazy_callback_(); - } -} - -void RpcRequestImpl::assignAttachmentIfNeed() const { - ASSERT(attachment_lazy_callback_ != nullptr); - if (attachment_ != nullptr) { - return; - } - - assignParametersIfNeed(); - attachment_ = attachment_lazy_callback_(); - - if (auto g = attachment_->lookup("group"); g.has_value()) { - const_cast(this)->group_ = std::string(g.value()); - } -} - -absl::optional RpcRequestImpl::serviceGroup() const { - assignAttachmentIfNeed(); - return RpcRequestBase::serviceGroup(); -} - -const RpcRequestImpl::Attachment& RpcRequestImpl::attachment() const { - assignAttachmentIfNeed(); - return *attachment_; -} - -RpcRequestImpl::AttachmentPtr& RpcRequestImpl::mutableAttachment() const { - assignAttachmentIfNeed(); - return attachment_; -} - -const RpcRequestImpl::Parameters& RpcRequestImpl::parameters() const { - assignParametersIfNeed(); - return *parameters_; -} - -RpcRequestImpl::ParametersPtr& RpcRequestImpl::mutableParameters() const { - assignParametersIfNeed(); - return parameters_; -} - -} // namespace Dubbo -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/dubbo/message_impl.h b/source/extensions/common/dubbo/message_impl.h deleted file mode 100644 index 877cb5dba626..000000000000 --- a/source/extensions/common/dubbo/message_impl.h +++ /dev/null @@ -1,138 +0,0 @@ -#pragma once - -#include - -#include "source/extensions/common/dubbo/hessian2_utils.h" -#include "source/extensions/common/dubbo/message.h" - -namespace Envoy { -namespace Extensions { -namespace Common { -namespace Dubbo { - -class RpcRequestBase : public RpcRequest { -public: - void setServiceName(absl::string_view name) { service_name_ = std::string(name); } - void setMethodName(absl::string_view name) { method_name_ = std::string(name); } - void setServiceVersion(absl::string_view version) { service_version_ = std::string(version); } - void setServiceGroup(absl::string_view group) { group_ = std::string(group); } - - // RpcRequest - absl::string_view serviceName() const override { return service_name_; } - absl::string_view methodName() const override { return method_name_; } - absl::string_view serviceVersion() const override { return service_version_; } - absl::optional serviceGroup() const override { - return group_.has_value() ? absl::make_optional(group_.value()) - : absl::nullopt; - } - -protected: - std::string service_name_; - std::string method_name_; - std::string service_version_; - absl::optional group_; -}; - -class RpcRequestImpl : public RpcRequestBase { -public: - // Each parameter consists of a parameter binary size and Hessian2::Object. - using Parameters = std::vector; - using ParametersPtr = std::unique_ptr; - - class Attachment { - public: - using Map = Hessian2::UntypedMapObject; - using MapPtr = std::unique_ptr; - using String = Hessian2::StringObject; - - Attachment(MapPtr&& value, size_t offset); - - const Map& attachment() const { return *attachment_; } - - void insert(absl::string_view key, absl::string_view value); - void remove(absl::string_view key); - absl::optional lookup(absl::string_view key) const; - - // Whether the attachment should be re-serialized. - bool attachmentUpdated() const { return attachment_updated_; } - - size_t attachmentOffset() const { return attachment_offset_; } - - private: - bool attachment_updated_{false}; - - MapPtr attachment_; - - // The binary offset of attachment in the original message. Retaining this value can help - // subsequent re-serialization of the attachment without re-serializing the parameters. - size_t attachment_offset_{}; - }; - using AttachmentPtr = std::unique_ptr; - - using AttachmentLazyCallback = std::function; - using ParametersLazyCallback = std::function; - - bool hasParameters() const { return parameters_ != nullptr; } - const Parameters& parameters() const; - ParametersPtr& mutableParameters() const; - - bool hasAttachment() const { return attachment_ != nullptr; } - const Attachment& attachment() const; - AttachmentPtr& mutableAttachment() const; - - void setParametersLazyCallback(ParametersLazyCallback&& callback) { - parameters_lazy_callback_ = std::move(callback); - } - - void setAttachmentLazyCallback(AttachmentLazyCallback&& callback) { - attachment_lazy_callback_ = std::move(callback); - } - - absl::optional serviceGroup() const override; - - Buffer::Instance& messageBuffer() { return message_buffer_; } - -private: - void assignParametersIfNeed() const; - void assignAttachmentIfNeed() const; - - // Original request message from downstream. - Buffer::OwnedImpl message_buffer_; - - AttachmentLazyCallback attachment_lazy_callback_; - ParametersLazyCallback parameters_lazy_callback_; - - mutable ParametersPtr parameters_{}; - mutable AttachmentPtr attachment_{}; -}; - -class RpcResponseImpl : public RpcResponse { -public: - void setResponseType(RpcResponseType type) { response_type_ = type; } - - // RpcResponse - absl::optional responseType() const override { return response_type_; } - - Buffer::Instance& messageBuffer() { return message_buffer_; } - - void setLocalRawMessage(absl::string_view val) { local_raw_message_ = std::string(val); } - absl::optional localRawMessage() { - return local_raw_message_.has_value() - ? absl::make_optional(local_raw_message_.value()) - : absl::nullopt; - } - -private: - // Original response message from upstream. - Buffer::OwnedImpl message_buffer_; - - // Optional raw content for local direct response. - absl::optional local_raw_message_; - - absl::optional response_type_; -}; - -} // namespace Dubbo -} // namespace Common -} // namespace Extensions -} // namespace Envoy diff --git a/source/extensions/common/dubbo/metadata.h b/source/extensions/common/dubbo/metadata.h index 774b73054ce1..6c2790c0ce8f 100644 --- a/source/extensions/common/dubbo/metadata.h +++ b/source/extensions/common/dubbo/metadata.h @@ -7,6 +7,7 @@ #include "source/extensions/common/dubbo/message.h" #include "absl/types/optional.h" +#include "hessian2/object.hpp" namespace Envoy { namespace Extensions { @@ -18,9 +19,6 @@ namespace Dubbo { */ class Context { public: - void setSerializeType(SerializeType type) { serialize_type_ = type; } - SerializeType serializeType() const { return serialize_type_; } - void setMessageType(MessageType type) { message_type_ = type; } MessageType messageType() const { return message_type_; } @@ -47,12 +45,10 @@ class Context { } private: - SerializeType serialize_type_{SerializeType::Hessian2}; MessageType message_type_{MessageType::Request}; absl::optional response_status_{}; int64_t request_id_{}; - size_t body_size_{}; }; diff --git a/test/extensions/common/dubbo/BUILD b/test/extensions/common/dubbo/BUILD index 67e35e09e460..bd3c0a8acc67 100644 --- a/test/extensions/common/dubbo/BUILD +++ b/test/extensions/common/dubbo/BUILD @@ -13,7 +13,6 @@ envoy_cc_test( name = "message_test", srcs = ["message_test.cc"], deps = [ - "//source/extensions/common/dubbo:codec_lib", "//source/extensions/common/dubbo:message_lib", "//test/test_common:printers_lib", "//test/test_common:utility_lib", diff --git a/test/extensions/common/dubbo/codec_test.cc b/test/extensions/common/dubbo/codec_test.cc index 0ec32de8d4e0..89eb86df8c6f 100644 --- a/test/extensions/common/dubbo/codec_test.cc +++ b/test/extensions/common/dubbo/codec_test.cc @@ -1,7 +1,7 @@ #include #include "source/extensions/common/dubbo/codec.h" -#include "source/extensions/common/dubbo/message_impl.h" +#include "source/extensions/common/dubbo/message.h" #include "test/extensions/common/dubbo/mocks.h" #include "test/test_common/printers.h" @@ -227,7 +227,6 @@ TEST(DubboCodecTest, DecodeDataTest) { context->setMessageType(MessageType::Request); context->setRequestId(1); context->setBodySize(buffer.length() + 1); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -245,12 +244,12 @@ TEST(DubboCodecTest, DecodeDataTest) { context->setMessageType(MessageType::Request); context->setRequestId(1); context->setBodySize(buffer.length()); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); EXPECT_CALL(*raw_serializer, deserializeRpcRequest(_, _)) - .WillOnce(testing::Return(testing::ByMove(std::make_unique()))); + .WillOnce( + testing::Return(testing::ByMove(std::make_unique("a", "b", "c", "d")))); EXPECT_EQ(DecodeStatus::Success, codec.decodeData(buffer, metadata)); EXPECT_EQ(true, metadata.hasRequest()); @@ -267,7 +266,6 @@ TEST(DubboCodecTest, DecodeDataTest) { context->setMessageType(MessageType::HeartbeatRequest); context->setRequestId(1); context->setBodySize(buffer.length()); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -290,12 +288,11 @@ TEST(DubboCodecTest, DecodeDataTest) { context->setRequestId(1); context->setBodySize(buffer.length()); context->setResponseStatus(ResponseStatus::Ok); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); EXPECT_CALL(*raw_serializer, deserializeRpcResponse(_, _)) - .WillOnce(testing::Return(testing::ByMove(std::make_unique()))); + .WillOnce(testing::Return(testing::ByMove(std::make_unique()))); EXPECT_EQ(DecodeStatus::Success, codec.decodeData(buffer, metadata)); EXPECT_EQ(true, metadata.hasResponse()); @@ -313,7 +310,6 @@ TEST(DubboCodecTest, DecodeDataTest) { context->setRequestId(1); context->setBodySize(buffer.length()); context->setResponseStatus(ResponseStatus::Ok); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -336,7 +332,6 @@ TEST(DubboCodecTest, DecodeDataTest) { context->setRequestId(1); context->setBodySize(buffer.length()); context->setResponseStatus(ResponseStatus::Ok); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -360,7 +355,6 @@ TEST(DubboCodecTest, EncodeTest) { auto context = std::make_unique(); context->setMessageType(MessageType::Request); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -397,7 +391,6 @@ TEST(DubboCodecTest, EncodeTest) { auto context = std::make_unique(); context->setMessageType(MessageType::Oneway); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -433,7 +426,6 @@ TEST(DubboCodecTest, EncodeTest) { auto context = std::make_unique(); context->setMessageType(MessageType::HeartbeatRequest); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -470,7 +462,6 @@ TEST(DubboCodecTest, EncodeTest) { context->setMessageType(MessageType::Response); context->setResponseStatus(ResponseStatus::Ok); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -507,7 +498,6 @@ TEST(DubboCodecTest, EncodeTest) { context->setMessageType(MessageType::Exception); context->setResponseStatus(ResponseStatus::BadRequest); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -544,7 +534,6 @@ TEST(DubboCodecTest, EncodeTest) { context->setMessageType(MessageType::Exception); context->setResponseStatus(ResponseStatus::ServerError); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -581,7 +570,6 @@ TEST(DubboCodecTest, EncodeTest) { context->setMessageType(MessageType::HeartbeatResponse); context->setResponseStatus(ResponseStatus::Ok); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -618,7 +606,6 @@ TEST(DubboCodecTest, EncodeTest) { context->setMessageType(static_cast(6)); context->setResponseStatus(ResponseStatus::Ok); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -637,7 +624,6 @@ TEST(DubboCodecTest, EncodeHeaderForTestTest) { context->setMessageType(static_cast(6)); context->setResponseStatus(ResponseStatus::Ok); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); EXPECT_DEATH(codec.encodeHeaderForTest(buffer, *context), ".*panic: corrupted enum.*"); } @@ -650,7 +636,6 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { auto context = std::make_unique(); context->setMessageType(MessageType::HeartbeatRequest); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -658,7 +643,6 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { EXPECT_EQ(MessageType::HeartbeatResponse, response->messageType()); EXPECT_EQ(12345, response->requestId()); - EXPECT_EQ(SerializeType::Hessian2, response->context().serializeType()); EXPECT_EQ(ResponseStatus::Ok, response->responseStatus()); EXPECT_EQ(true, response->context().heartbeat()); } @@ -669,7 +653,6 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { auto context = std::make_unique(); context->setMessageType(MessageType::Request); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -678,14 +661,12 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { EXPECT_EQ(MessageType::Response, response->messageType()); EXPECT_EQ(12345, response->requestId()); - EXPECT_EQ(SerializeType::Hessian2, response->context().serializeType()); EXPECT_EQ(ResponseStatus::Ok, response->responseStatus()); EXPECT_EQ(false, response->context().heartbeat()); EXPECT_EQ(RpcResponseType::ResponseWithValue, response->response().responseType().value()); - auto typed_response = dynamic_cast(&response->mutableResponse()); - EXPECT_NE(nullptr, typed_response); - EXPECT_EQ("anything", typed_response->localRawMessage().value()); + auto& typed_response = response->mutableResponse(); + EXPECT_EQ("anything", typed_response.content().result()->toString().value().get()); } // Local normal response without response type. @@ -694,7 +675,6 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { auto context = std::make_unique(); context->setMessageType(MessageType::Request); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -703,14 +683,13 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { EXPECT_EQ(MessageType::Response, response->messageType()); EXPECT_EQ(12345, response->requestId()); - EXPECT_EQ(SerializeType::Hessian2, response->context().serializeType()); EXPECT_EQ(ResponseStatus::Ok, response->responseStatus()); EXPECT_EQ(false, response->context().heartbeat()); - EXPECT_EQ(false, response->response().responseType().has_value()); + EXPECT_EQ(true, response->response().responseType().has_value()); + EXPECT_EQ(RpcResponseType::ResponseWithValue, response->response().responseType().value()); - auto typed_response = dynamic_cast(&response->mutableResponse()); - EXPECT_NE(nullptr, typed_response); - EXPECT_EQ("anything", typed_response->localRawMessage().value()); + auto& typed_response = response->mutableResponse(); + EXPECT_EQ("anything", typed_response.content().result()->toString().value().get()); } // Local normal response with exception type. @@ -719,7 +698,6 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { auto context = std::make_unique(); context->setMessageType(MessageType::Request); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -728,14 +706,12 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { EXPECT_EQ(MessageType::Exception, response->messageType()); EXPECT_EQ(12345, response->requestId()); - EXPECT_EQ(SerializeType::Hessian2, response->context().serializeType()); EXPECT_EQ(ResponseStatus::Ok, response->responseStatus()); EXPECT_EQ(false, response->context().heartbeat()); EXPECT_EQ(RpcResponseType::ResponseWithException, response->response().responseType().value()); - auto typed_response = dynamic_cast(&response->mutableResponse()); - EXPECT_NE(nullptr, typed_response); - EXPECT_EQ("anything", typed_response->localRawMessage().value()); + auto& typed_response = response->mutableResponse(); + EXPECT_EQ("anything", typed_response.content().result()->toString().value().get()); } // Local normal response with exception type. @@ -744,7 +720,6 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { auto context = std::make_unique(); context->setMessageType(MessageType::Request); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -754,15 +729,13 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { EXPECT_EQ(MessageType::Exception, response->messageType()); EXPECT_EQ(12345, response->requestId()); - EXPECT_EQ(SerializeType::Hessian2, response->context().serializeType()); EXPECT_EQ(ResponseStatus::Ok, response->responseStatus()); EXPECT_EQ(false, response->context().heartbeat()); EXPECT_EQ(RpcResponseType::ResponseWithExceptionWithAttachments, response->response().responseType().value()); - auto typed_response = dynamic_cast(&response->mutableResponse()); - EXPECT_NE(nullptr, typed_response); - EXPECT_EQ("anything", typed_response->localRawMessage().value()); + auto& typed_response = response->mutableResponse(); + EXPECT_EQ("anything", typed_response.content().result()->toString().value().get()); } // Local exception response. @@ -771,7 +744,6 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { auto context = std::make_unique(); context->setMessageType(MessageType::Request); context->setRequestId(12345); - context->setSerializeType(SerializeType::Hessian2); metadata.setContext(std::move(context)); @@ -780,15 +752,13 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { EXPECT_EQ(MessageType::Exception, response->messageType()); EXPECT_EQ(12345, response->requestId()); - EXPECT_EQ(SerializeType::Hessian2, response->context().serializeType()); EXPECT_EQ(ResponseStatus::BadRequest, response->responseStatus()); EXPECT_EQ(false, response->context().heartbeat()); // Response type will be ignored for non-Ok response. EXPECT_EQ(false, response->response().responseType().has_value()); - auto typed_response = dynamic_cast(&response->mutableResponse()); - EXPECT_NE(nullptr, typed_response); - EXPECT_EQ("anything", typed_response->localRawMessage().value()); + auto& typed_response = response->mutableResponse(); + EXPECT_EQ("anything", typed_response.content().result()->toString().value().get()); } // Local response without request context. @@ -800,15 +770,13 @@ TEST(DirectResponseUtilTest, DirectResponseUtilTest) { EXPECT_EQ(MessageType::Exception, response->messageType()); EXPECT_EQ(0, response->requestId()); - EXPECT_EQ(SerializeType::Hessian2, response->context().serializeType()); EXPECT_EQ(ResponseStatus::BadRequest, response->responseStatus()); EXPECT_EQ(false, response->context().heartbeat()); // Response type will be ignored for non-Ok response. EXPECT_EQ(false, response->response().responseType().has_value()); - auto typed_response = dynamic_cast(&response->mutableResponse()); - EXPECT_NE(nullptr, typed_response); - EXPECT_EQ("anything", typed_response->localRawMessage().value()); + auto& typed_response = response->mutableResponse(); + EXPECT_EQ("anything", typed_response.content().result()->toString().value().get()); } } diff --git a/test/extensions/common/dubbo/hessian2_serializer_impl_test.cc b/test/extensions/common/dubbo/hessian2_serializer_impl_test.cc index ef78accf9773..067782a2bf37 100644 --- a/test/extensions/common/dubbo/hessian2_serializer_impl_test.cc +++ b/test/extensions/common/dubbo/hessian2_serializer_impl_test.cc @@ -35,8 +35,8 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequest) { auto result = serializer.deserializeRpcRequest(buffer, *context); ASSERT(result != nullptr); - EXPECT_EQ("test", result->methodName()); - EXPECT_EQ("test", result->serviceName()); + EXPECT_EQ("test", result->method()); + EXPECT_EQ("test", result->service()); EXPECT_EQ("0.0.0", result->serviceVersion()); } @@ -75,12 +75,17 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequest) { TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { Hessian2SerializerImpl serializer; - RpcRequestImpl::Attachment attach(std::make_unique(), 0); - attach.insert("test1", "test_value1"); - attach.insert("test2", "test_value2"); - attach.insert("test3", "test_value3"); + Hessian2::Object::UntypedMap untyped_map; + untyped_map.emplace(Hessian2::ObjectPtr{new Hessian2::StringObject("test1")}, + Hessian2::ObjectPtr{new Hessian2::StringObject("test_value1")}); + untyped_map.emplace(Hessian2::ObjectPtr{new Hessian2::StringObject("test2")}, + Hessian2::ObjectPtr{new Hessian2::StringObject("test_value2")}); + untyped_map.emplace(Hessian2::ObjectPtr{new Hessian2::StringObject("test3")}, + Hessian2::ObjectPtr{new Hessian2::StringObject("test_value3")}); - RpcRequestImpl::Parameters params; + auto map_object = std::make_unique(std::move(untyped_map)); + + ArgumentVec params; params.push_back(std::make_unique("test_string")); @@ -124,12 +129,10 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { encoder.encode(*param); } // Encode an untyped map object as fourth parameter. - encoder.encode(attach.attachment()); - - size_t expected_attachment_offset = buffer.length(); + encoder.encode(*map_object); // Encode attachment - encoder.encode(attach.attachment()); + encoder.encode(*map_object); auto context = std::make_unique(); context->setBodySize(buffer.length()); @@ -137,28 +140,18 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { auto result = serializer.deserializeRpcRequest(buffer, *context); EXPECT_NE(nullptr, result); - auto invo = dynamic_cast(result.get()); - // All data be moved to buffer in the request. EXPECT_EQ(0, buffer.length()); - EXPECT_EQ(context->bodySize(), invo->messageBuffer().length()); - - EXPECT_EQ(false, invo->hasAttachment()); - EXPECT_EQ(false, invo->hasParameters()); - - auto& result_params = invo->mutableParameters(); - // When parsing parameters, attachment will not be parsed. - EXPECT_EQ(false, invo->hasAttachment()); - EXPECT_EQ(true, invo->hasParameters()); + auto& result_params = result->content().arguments(); - EXPECT_EQ(4, result_params->size()); + EXPECT_EQ(4, result_params.size()); - EXPECT_EQ("test_string", result_params->at(0)->toString().value().get()); - EXPECT_EQ(4, result_params->at(1)->toBinary().value().get().at(4)); - EXPECT_EQ(233333, *result_params->at(2)->toLong()); - EXPECT_EQ(3, result_params->at(3)->toUntypedMap().value().get().size()); - EXPECT_EQ("test_value2", result_params->at(3) + EXPECT_EQ("test_string", result_params.at(0)->toString().value().get()); + EXPECT_EQ(4, result_params.at(1)->toBinary().value().get().at(4)); + EXPECT_EQ(233333, *result_params.at(2)->toLong()); + EXPECT_EQ(3, result_params.at(3)->toUntypedMap().value().get().size()); + EXPECT_EQ("test_value2", result_params.at(3) ->toUntypedMap() .value() .get() @@ -167,76 +160,9 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { .value() .get()); - auto& result_attach = invo->mutableAttachment(); - EXPECT_EQ("test_value2", result_attach->attachment() - .toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); - - EXPECT_EQ(expected_attachment_offset, result_attach->attachmentOffset()); + EXPECT_EQ("test_value2", result->content().attachments().at("test2")); } - { - Buffer::OwnedImpl buffer; - buffer.add(std::string({ - 0x05, '2', '.', '0', '.', '2', // Dubbo version - 0x04, 't', 'e', 's', 't', // Service name - 0x05, '0', '.', '0', '.', '0', // Service version - 0x04, 't', 'e', 's', 't', // method name - })); - - Hessian2::Encoder encoder(std::make_unique(buffer)); - - encoder.encode(parameters_type); - - for (const auto& param : params) { - encoder.encode(*param); - } - // Encode an untyped map object as fourth parameter. - encoder.encode(attach.attachment()); - - // Encode attachment - encoder.encode(attach.attachment()); - - auto context = std::make_unique(); - context->setBodySize(buffer.length()); - - auto result = serializer.deserializeRpcRequest(buffer, *context); - EXPECT_NE(nullptr, result); - auto invo = dynamic_cast(result.get()); - - EXPECT_EQ(false, invo->hasAttachment()); - EXPECT_EQ(false, invo->hasParameters()); - - auto& result_attach = invo->mutableAttachment(); - - // When parsing attachment, parameters will also be parsed. - EXPECT_EQ(true, invo->hasAttachment()); - EXPECT_EQ(true, invo->hasParameters()); - - EXPECT_EQ("test_value2", result_attach->attachment() - .toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); - - auto& result_params = invo->parameters(); - EXPECT_EQ("test_value2", result_params.at(3) - ->toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); - } // Test case that request only have parameters. { Buffer::OwnedImpl buffer; @@ -255,7 +181,7 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { encoder.encode(*param); } // Encode an untyped map object as fourth parameter. - encoder.encode(attach.attachment()); + encoder.encode(*map_object); auto context = std::make_unique(); context->setBodySize(buffer.length()); @@ -263,28 +189,8 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { auto result = serializer.deserializeRpcRequest(buffer, *context); EXPECT_NE(nullptr, result); - auto invo = dynamic_cast(result.get()); - - EXPECT_EQ(false, invo->hasAttachment()); - EXPECT_EQ(false, invo->hasParameters()); - - auto& result_attach = invo->mutableAttachment(); - - // When parsing attachment, parameters will also be parsed. - EXPECT_EQ(true, invo->hasAttachment()); - EXPECT_EQ(true, invo->hasParameters()); - - auto& result_params = invo->parameters(); - EXPECT_EQ("test_value2", result_params.at(3) - ->toUntypedMap() - .value() - .get() - .find("test2") - ->second->toString() - .value() - .get()); - - EXPECT_EQ(true, result_attach->attachment().toUntypedMap().value().get().empty()); + EXPECT_EQ(4, result->content().arguments().size()); + EXPECT_EQ(true, result->content().attachments().empty()); } // Test the case where there are not enough parameters in the request buffer. { @@ -311,45 +217,9 @@ TEST(Hessian2ProtocolTest, deserializeRpcRequestWithParametersOrAttachment) { auto result = serializer.deserializeRpcRequest(buffer, *context); EXPECT_NE(nullptr, result); - auto invo = dynamic_cast(result.get()); - - // There are not enough parameters and throws an exception. - EXPECT_THROW_WITH_MESSAGE(invo->mutableParameters(), EnvoyException, - "Cannot parse RpcRequest parameter from buffer"); - } - // Test for incorrect attachment types. - { - Buffer::OwnedImpl buffer; - buffer.add(std::string({ - 0x05, '2', '.', '0', '.', '2', // Dubbo version - 0x04, 't', 'e', 's', 't', // Service name - 0x05, '0', '.', '0', '.', '0', // Service version - 0x04, 't', 'e', 's', 't', // method name - })); - - Hessian2::Encoder encoder(std::make_unique(buffer)); - - encoder.encode(parameters_type); - - for (const auto& param : params) { - encoder.encode(*param); - } - // Encode an untyped map object as fourth parameter. - encoder.encode(attach.attachment()); - - // Encode a string object as attachment. - encoder.encode(*params[0]); - - auto context = std::make_unique(); - context->setBodySize(buffer.length()); - - auto result = serializer.deserializeRpcRequest(buffer, *context); - EXPECT_NE(nullptr, result); - - auto invo = dynamic_cast(result.get()); - - auto& result_attach = invo->mutableAttachment(); - EXPECT_EQ(true, result_attach->attachment().toUntypedMap().value().get().empty()); + // The request will be reset to an empty state. + EXPECT_EQ(true, result->content().arguments().empty()); + EXPECT_EQ(true, result->content().attachments().empty()); } } @@ -560,26 +430,6 @@ TEST(Hessian2ProtocolTest, deserializeRpcResponse) { EXPECT_THROW_WITH_MESSAGE(serializer.deserializeRpcResponse(buffer, *context), EnvoyException, "not supported return type 6"); } - - // incorrect value size - { - Buffer::OwnedImpl buffer; - buffer.add(std::string({ - '\x92', // without the value of the return type - 0x04, 't', 'e', 's', 't', // return body - })); - std::string exception_string = - fmt::format("RpcResponse is no value, but the rest of the body size({}) not equal 0", - buffer.length() - 1); - - auto context = std::make_unique(); - context->setMessageType(MessageType::Response); - context->setResponseStatus(ResponseStatus::Ok); - context->setBodySize(buffer.length()); - - EXPECT_THROW_WITH_MESSAGE(serializer.deserializeRpcResponse(buffer, *context), EnvoyException, - exception_string); - } } TEST(Hessian2ProtocolTest, serializeRpcRequest) { @@ -604,11 +454,12 @@ TEST(Hessian2ProtocolTest, serializeRpcRequest) { auto context = std::make_unique(); context->setMessageType(MessageType::Request); - auto request = std::make_unique(); - request->setServiceName("test.service"); - request->setMethodName("test.method"); - request->setServiceVersion("test.version"); - request->messageBuffer().add("anything_for_no_attachment_update"); + auto request = std::make_unique("v", "v", "v", "v"); + + ArgumentVec args; + args.push_back(std::make_unique(true)); + + request->content().initialize("Z", std::move(args), {}); auto metadata = std::make_shared(); metadata->setContext(std::move(context)); @@ -617,9 +468,9 @@ TEST(Hessian2ProtocolTest, serializeRpcRequest) { Buffer::OwnedImpl buffer; serializer.serializeRpcRequest(buffer, *metadata); - // No attachment update and the message buffer will be used directly to - // accelerate the encoding. - EXPECT_EQ("anything_for_no_attachment_update", buffer.toString()); + EXPECT_EQ( + std::string({'\x1', 'v', '\x1', 'v', '\x1', 'v', '\x1', 'v', '\x1', 'Z', 'T', 'H', 'Z'}), + buffer.toString()); } // Normal request with attachment update. @@ -628,23 +479,13 @@ TEST(Hessian2ProtocolTest, serializeRpcRequest) { auto context = std::make_unique(); context->setMessageType(MessageType::Request); - auto request = std::make_unique(); - request->setServiceName("test.service"); - request->setMethodName("test.method"); - request->setServiceVersion("test.version"); - request->messageBuffer().add("anything_for_attachment_update"); - - size_t fake_attachment_offset = 8; + auto request = std::make_unique("v", "v", "v", "v"); - request->setParametersLazyCallback([]() -> RpcRequestImpl::ParametersPtr { - return std::make_unique(); - }); + ArgumentVec args; + args.push_back(std::make_unique(true)); - request->setAttachmentLazyCallback([fake_attachment_offset]() -> RpcRequestImpl::AttachmentPtr { - return std::make_unique( - std::make_unique(), fake_attachment_offset); - }); - request->mutableAttachment()->insert("key", "value"); + request->content().initialize("Z", std::move(args), {}); + request->content().setAttachment("key", "value"); auto metadata = std::make_shared(); metadata->setContext(std::move(context)); @@ -653,9 +494,6 @@ TEST(Hessian2ProtocolTest, serializeRpcRequest) { Buffer::OwnedImpl buffer; serializer.serializeRpcRequest(buffer, *metadata); - // 8 (fake_attachment_offset) bytes for original parameters and 12 bytes for updated attachment. - EXPECT_EQ(20, buffer.length()); - EXPECT_EQ(true, absl::StrContains(buffer.toString(), "value")); } } @@ -684,28 +522,13 @@ TEST(Hessian2ProtocolTest, serializeRpcResponse) { context->setMessageType(MessageType::Response); context->setResponseStatus(ResponseStatus::Ok); - auto response = std::make_unique(); - response->messageBuffer().add("anything"); - - auto metadata = std::make_shared(); - metadata->setContext(std::move(context)); - metadata->setResponse(std::move(response)); - - Buffer::OwnedImpl buffer; - serializer.serializeRpcResponse(buffer, *metadata); - - // The data in message buffer will be used directly for normal response. - EXPECT_EQ("anything", buffer.toString()); - } - - // Local response without response type. - { - auto context = std::make_unique(); - context->setMessageType(MessageType::Response); - context->setResponseStatus(ResponseStatus::Ok); + auto response = std::make_unique(); + response->setResponseType(RpcResponseType::ResponseWithValue); - auto response = std::make_unique(); - response->setLocalRawMessage("anything"); + Buffer::OwnedImpl response_content; + response_content.writeByte('\x08'); + response_content.add("anything"); + response->content().initialize(response_content, 9); auto metadata = std::make_shared(); metadata->setContext(std::move(context)); @@ -714,34 +537,8 @@ TEST(Hessian2ProtocolTest, serializeRpcResponse) { Buffer::OwnedImpl buffer; serializer.serializeRpcResponse(buffer, *metadata); - auto buffer_string = buffer.toString(); - - EXPECT_EQ(0x08, static_cast(buffer_string[0])); // Check length. - EXPECT_EQ("anything", buffer_string.substr(1)); - } - - // Local response with response type. - { - auto context = std::make_unique(); - context->setMessageType(MessageType::Response); - context->setResponseStatus(ResponseStatus::Ok); - - auto response = std::make_unique(); - response->setResponseType(RpcResponseType::ResponseWithException); - response->setLocalRawMessage("anything"); - - auto metadata = std::make_shared(); - metadata->setContext(std::move(context)); - metadata->setResponse(std::move(response)); - - Buffer::OwnedImpl buffer; - serializer.serializeRpcResponse(buffer, *metadata); - - auto buffer_string = buffer.toString(); - - EXPECT_EQ(0x90, static_cast(buffer_string[0])); // Check response type. - EXPECT_EQ(0x08, static_cast(buffer_string[1])); // Check length. - EXPECT_EQ("anything", buffer_string.substr(2)); + // The data in message buffer will be used directly for normal response. + EXPECT_EQ("anything", buffer.toString().substr(2)); } } diff --git a/test/extensions/common/dubbo/message_test.cc b/test/extensions/common/dubbo/message_test.cc index faa933638736..ebe04176d896 100644 --- a/test/extensions/common/dubbo/message_test.cc +++ b/test/extensions/common/dubbo/message_test.cc @@ -1,4 +1,4 @@ -#include "source/extensions/common/dubbo/message_impl.h" +#include "source/extensions/common/dubbo/message.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" @@ -12,136 +12,552 @@ namespace Common { namespace Dubbo { namespace { -TEST(RpcRequestImplTest, RpcRequestAttachmentTest) { - auto map = std::make_unique(); +TEST(RpcRequestTest, SimpleSetAndGetTest) { + RpcRequest request("a", "b", "c", "d"); - map->emplace(std::make_unique("group"), - std::make_unique("fake_group")); - map->emplace(std::make_unique("fake_key"), - std::make_unique("fake_value")); + EXPECT_EQ("a", request.version()); - map->emplace(std::make_unique(), std::make_unique(0)); + EXPECT_EQ("b", request.service()); - map->emplace(std::make_unique("map_key"), - std::make_unique()); + EXPECT_EQ("c", request.serviceVersion()); - RpcRequestImpl::Attachment attachment(std::move(map), 23333); - - EXPECT_EQ(4, attachment.attachment().toUntypedMap().value().get().size()); - - // Test lookup. - EXPECT_EQ(absl::nullopt, attachment.lookup("map_key")); - EXPECT_EQ("fake_group", *attachment.lookup("group")); - - EXPECT_FALSE(attachment.attachmentUpdated()); - - // Test remove. Remove a normal string type key/value pair. - EXPECT_EQ("fake_value", *attachment.lookup("fake_key")); - attachment.remove("fake_key"); - EXPECT_EQ(absl::nullopt, attachment.lookup("fake_key")); - - EXPECT_EQ(3, attachment.attachment().toUntypedMap().value().get().size()); - - // Test remove. Delete a key/value pair whose value type is map. - attachment.remove("map_key"); - EXPECT_EQ(2, attachment.attachment().toUntypedMap().value().get().size()); - - // Test insert. - attachment.insert("test", "test_value"); - EXPECT_EQ(3, attachment.attachment().toUntypedMap().value().get().size()); - - EXPECT_EQ("test_value", *attachment.lookup("test")); - - EXPECT_TRUE(attachment.attachmentUpdated()); - EXPECT_EQ(23333, attachment.attachmentOffset()); + EXPECT_EQ("d", request.method()); } -TEST(RpcRequestImplTest, RpcRequestImplTest) { - RpcRequestImpl request; - - request.setServiceName("fake_service"); - EXPECT_EQ("fake_service", request.serviceName()); +TEST(RpcRequestTest, InitializeWithBufferTest) { + // Empty buffer. + { + RpcRequest request("a", "b", "c", "d"); + + // Initialize the request with an empty buffer. + Buffer::OwnedImpl buffer; + request.content().initialize(buffer, buffer.length()); + + // Try get the argument vector. + EXPECT_EQ(request.content().arguments().size(), 0); + + // Try get the attachment group. + EXPECT_FALSE(request.content().attachments().contains("group")); + + // attachments() call will make the content to be decoded. + // And the content is empty, so the content will be treated as broken. + // So the content will be reset to an empty state: + // empty types, empty arguments, empty attachments. + EXPECT_EQ(request.content().buffer().toString(), std::string({0x0, 'H', 'Z'})); + + // Set the group. + request.content().setAttachment("group", "group"); + EXPECT_EQ("group", request.content().attachments().at("group")); + + // buffer() call will re-encode the attachments into the content buffer. + EXPECT_EQ(request.content().buffer().toString(), + std::string({ + 0x0, // Empty string types + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); + } + + // Buffer no argument but has attachment. + { + RpcRequest request("a", "b", "c", "d"); + + Buffer::OwnedImpl buffer(std::string({ + 0x0, // Empty string types + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z', // Attachments end + 'x' // Anything that make no sense + })); + + // Initialize the request with an empty buffer. + request.content().initialize(buffer, buffer.length() - 1); + + EXPECT_EQ(1, buffer.length()); // Other data should be consumed. + EXPECT_EQ(buffer.toString(), "x"); + + // Try get the argument vector. + EXPECT_EQ(request.content().arguments().size(), 0); + + // Try get the attachment group. + EXPECT_EQ("group", request.content().attachments().at("group")); + + EXPECT_EQ(request.content().buffer().toString(), + std::string({ + 0x0, // Empty string types + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); + + // Overwrite the group. + request.content().setAttachment("group", "groupx"); + + // buffer() call will re-encode the attachments into the content buffer. + EXPECT_EQ(request.content().buffer().toString(), + std::string({ + 0x0, // Empty string types + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x6, 'g', 'r', 'o', 'u', 'p', 'x', // Value + 'Z' // Attachments end + })); + + request.content().delAttachment("group"); + + // buffer() call will re-encode the attachments into the content buffer. + EXPECT_EQ(request.content().buffer().toString(), + std::string({ + 0x0, // Empty string types + 'H', // Attachments start + 'Z' // Attachments end + })); + } + + // Broken buffer where -1 is used to tell the arguments number but + // no following types string. + { + RpcRequest request("a", "b", "c", "d"); + Buffer::OwnedImpl buffer(std::string({ + '\x8f', // -1 means the following bytes is types string + 'H', + 'Z', + })); + + // Initialize the request with the broken buffer. + request.content().initialize(buffer, buffer.length()); + + EXPECT_EQ(0, request.content().arguments().size()); + + // The content is broken, so the content will be reset to an empty state: + // empty types, empty arguments, empty attachments. + EXPECT_EQ(request.content().buffer().toString(), std::string({0x0, 'H', 'Z'})); + } + + // Broken buffer where -2 is used to tell the arguments number. This is + // unexpected number. + { + RpcRequest request("a", "b", "c", "d"); + Buffer::OwnedImpl buffer(std::string({ + '\x8e', // -2 is unexpected + 'H', + 'Z', + })); + + // Initialize the request with the broken buffer. + request.content().initialize(buffer, buffer.length()); + + EXPECT_EQ(0, request.content().arguments().size()); + + // The content is broken, so the content will be reset to an empty state: + // empty types, empty arguments, empty attachments. + EXPECT_EQ(request.content().buffer().toString(), std::string({0x0, 'H', 'Z'})); + } + + // Correct buffer where -1 is used to tell the arguments number. + { + RpcRequest request("a", "b", "c", "d"); + Buffer::OwnedImpl buffer(std::string({ + '\x8f', // -1 means the following bytes is types string + 0x2, 'Z', 'Z', // The types string + 'T', 'F', // true and false + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); + + // Initialize the request with the correct buffer. + request.content().initialize(buffer, buffer.length()); + + // Get the argument vector. + const auto& args = request.content().arguments(); + + // There are 2 arguments. + EXPECT_EQ(2, args.size()); + + // The first argument is true. + EXPECT_EQ(true, args[0]->toBoolean().value().get()); + // The second argument is false. + EXPECT_EQ(false, args[1]->toBoolean().value().get()); + + // Get the attachment group. + EXPECT_EQ("group", request.content().attachments().at("group")); + + EXPECT_EQ(request.content().buffer().toString(), + std::string({ + '\x8f', // -1 means the following bytes is types string + 0x2, 'Z', 'Z', // The types string + 'T', 'F', // true and false + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); + + // Move the buffer. + Buffer::OwnedImpl buffer2; + request.content().bufferMoveTo(buffer2); + + // The buffer is moved, and if we call buffer() again, everything will be + // re-encoded. + // Note: -1 is removed because the single types string is enough. + EXPECT_EQ(request.content().buffer().toString(), std::string({ + 0x2, 'Z', 'Z', // The types string + 'T', 'F', // true and false + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); + } + + // Correct buffer with non-negative integer for the arguments number. + { + RpcRequest request("a", "b", "c", "d"); + Buffer::OwnedImpl buffer(std::string({ + '\x92', // 2 means the following are two arguments + 'T', 'F', // true and false + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); + + // Initialize the request with the correct buffer. + request.content().initialize(buffer, buffer.length()); + + // Get the argument vector. + const auto& args = request.content().arguments(); + + // There are 2 arguments. + EXPECT_EQ(2, args.size()); + + // The first argument is true. + EXPECT_EQ(true, args[0]->toBoolean().value().get()); + // The second argument is false. + EXPECT_EQ(false, args[1]->toBoolean().value().get()); + + // Get the attachment group. + EXPECT_EQ("group", request.content().attachments().at("group")); + + // Move the buffer. + Buffer::OwnedImpl buffer2; + request.content().bufferMoveTo(buffer2); + + // No types string is provided and the arguments are not empty. + // So the number of arguments will be used directly. + EXPECT_EQ(request.content().buffer().toString(), + std::string({ + '\x92', // 2 means the following are two arguments + 'T', 'F', // true and false + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); + } + + // Normal correct buffer with normal types string. + { + RpcRequest request("a", "b", "c", "d"); + Buffer::OwnedImpl buffer(std::string({ + 0x2, 'Z', 'Z', // The types string + 'T', 'F', // true and false + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'T', // Key + 'F', // Value + 'Z' // Attachments end + })); + + // Initialize the request with the correct buffer. + request.content().initialize(buffer, buffer.length()); + + // Get the argument vector. + const auto& args = request.content().arguments(); + + // There are 2 arguments. + EXPECT_EQ(2, args.size()); + + // The first argument is true. + EXPECT_EQ(true, args[0]->toBoolean().value().get()); + // The second argument is false. + EXPECT_EQ(false, args[1]->toBoolean().value().get()); + + // Only string key and value are used. + EXPECT_EQ(1, request.content().attachments().size()); + + // Get the attachment group. + EXPECT_EQ("group", request.content().attachments().at("group")); + + // Move the buffer. + Buffer::OwnedImpl buffer2; + request.content().bufferMoveTo(buffer2); + + EXPECT_EQ(request.content().buffer().toString(), std::string({ + 0x2, 'Z', 'Z', // The types string + 'T', 'F', // true and false + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z', // Attachments end + })); + } + + // Buffer without attachments. + { + RpcRequest request("a", "b", "c", "d"); + Buffer::OwnedImpl buffer(std::string({ + 0x2, 'Z', 'Z', // The types string + 'T', 'F', // true and false + })); + + // Initialize the request with the correct buffer. + request.content().initialize(buffer, buffer.length()); + + // Get the argument vector. + const auto& args = request.content().arguments(); + + // There are 2 arguments. + EXPECT_EQ(2, args.size()); + + // The first argument is true. + EXPECT_EQ(true, args[0]->toBoolean().value().get()); + // The second argument is false. + EXPECT_EQ(false, args[1]->toBoolean().value().get()); + + EXPECT_TRUE(request.content().attachments().empty()); + + // Move the buffer. + Buffer::OwnedImpl buffer2; + request.content().bufferMoveTo(buffer2); + + // Empty attachments will be encoded to the buffer when buffer() is called + // and the underlying buffer is empty. + EXPECT_EQ(request.content().buffer().toString(), std::string({ + 0x2, 'Z', 'Z', // The types string + 'T', 'F', // true and false + 'H', // Attachments start + 'Z', // Attachments end + })); + + // Set the attachment. + request.content().setAttachment("group", "group"); + + // buffer() call will re-encode the attachments into the content buffer. + EXPECT_EQ(request.content().buffer().toString(), std::string({ + 0x2, 'Z', 'Z', // The types string + 'T', 'F', // true and false + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); + } +} - request.setMethodName("fake_method"); - EXPECT_EQ("fake_method", request.methodName()); +TEST(RpcRequestTest, InitializeWithDecodedValuesTest) { + RpcRequest request("a", "b", "c", "d"); - request.setServiceVersion("fake_version"); - EXPECT_EQ("fake_version", request.serviceVersion()); + ArgumentVec args; + args.push_back(std::make_unique(true)); + args.push_back(std::make_unique(false)); - bool set_parameters{false}; - bool set_attachment{false}; + Attachments attachs; + attachs.emplace("group", "group"); - request.setParametersLazyCallback([&set_parameters]() -> RpcRequestImpl::ParametersPtr { - set_parameters = true; - return std::make_unique(); - }); + // Initialize the request with the given types and arguments and attachments. + request.content().initialize(std::string("ZZ"), std::move(args), std::move(attachs)); - request.setAttachmentLazyCallback([&set_attachment]() -> RpcRequestImpl::AttachmentPtr { - auto map = std::make_unique(); + // Get the argument vector. + const auto& args2 = request.content().arguments(); - map->emplace(std::make_unique("group"), - std::make_unique("fake_group")); + // There are 2 arguments. + EXPECT_EQ(2, args2.size()); - auto attach = std::make_unique(std::move(map), 0); + // The first argument is true. + EXPECT_EQ(true, args2[0]->toBoolean().value().get()); + // The second argument is false. + EXPECT_EQ(false, args2[1]->toBoolean().value().get()); - set_attachment = true; + // Get the attachment group. + EXPECT_EQ("group", request.content().attachments().at("group")); - return attach; - }); + // Get the buffer. + EXPECT_EQ(request.content().buffer().toString(), std::string({ + 0x2, 'Z', 'Z', // The types string + 'T', 'F', // true and false + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); +} - EXPECT_EQ(false, request.hasParameters()); - EXPECT_EQ(false, request.hasAttachment()); +TEST(RpcResponseTest, SimpleSetAndGetTest) { + RpcResponse response; - // When parsing attachment, parameters will also be parsed. - EXPECT_NE(nullptr, request.mutableAttachment()); - request.attachment(); - EXPECT_EQ(true, set_parameters); - EXPECT_EQ(true, set_attachment); - EXPECT_EQ(true, request.hasParameters()); - EXPECT_EQ(true, request.hasAttachment()); - EXPECT_EQ("fake_group", request.serviceGroup().value()); + EXPECT_EQ(absl::nullopt, response.responseType()); - request.setServiceGroup("new_fake_group"); - EXPECT_EQ("new_fake_group", request.serviceGroup().value()); + // Set the response type and validate we can get it. + response.setResponseType(RpcResponseType::ResponseNullValueWithAttachments); + EXPECT_EQ(RpcResponseType::ResponseNullValueWithAttachments, response.responseType().value()); +} - // If parameters and attachment have values, the callback function will not be executed. - set_parameters = false; - set_attachment = false; - EXPECT_NE(nullptr, request.mutableParameters()); - EXPECT_NE(nullptr, request.mutableAttachment()); - EXPECT_EQ(false, set_parameters); - EXPECT_EQ(false, set_attachment); +TEST(RpcResponseTest, InitializeWithBufferTest) { + // Empty buffer. + { + RpcResponse response; + + // Initialize the response with an empty buffer. + Buffer::OwnedImpl buffer; + response.content().initialize(buffer, buffer.length()); + + // Try get the result. + EXPECT_EQ(response.content().result(), nullptr); + + // Try get the attachments. + EXPECT_FALSE(response.content().attachments().contains("group")); + + // attachments() call will make the content to be decoded. + // And the content is empty, so the content will be treated as broken. + // So the content will be reset to an empty state: + // empty result, empty attachments. + EXPECT_EQ(response.content().buffer().toString(), std::string({'N', 'H', 'Z'})); + + // Set the group. + response.content().setAttachment("group", "group"); + EXPECT_EQ("group", response.content().attachments().at("group")); + + // buffer() call will re-encode the attachments into the content buffer. + EXPECT_EQ(response.content().buffer().toString(), std::string({ + 'N', + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); + } + + // Buffer with result but no attachments. + { + RpcResponse response; + + Buffer::OwnedImpl buffer(std::string({ + 'T', // true + })); + + // Initialize the response with the buffer. + response.content().initialize(buffer, buffer.length()); + + // Get the result. + const auto* result = response.content().result(); + + // The result is a boolean. + EXPECT_EQ(true, result->toBoolean().value().get()); + + // Try get the attachments. + EXPECT_FALSE(response.content().attachments().contains("group")); + + // Move the buffer. + Buffer::OwnedImpl buffer2; + response.content().bufferMoveTo(buffer2); + + // Empty attachments will be encoded to the buffer when buffer() is called + // and the underlying buffer is empty. + EXPECT_EQ(response.content().buffer().toString(), std::string({ + 'T', // true + 'H', // Attachments start + 'Z', // Attachments end + })); + + // Set the attachment. + response.content().setAttachment("group", "group"); + + // buffer() call will re-encode the attachments into the content buffer. + EXPECT_EQ(response.content().buffer().toString(), std::string({ + 'T', // true + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); + + // Remove the attachment. + response.content().delAttachment("group"); + + // buffer() call will re-encode the attachments into the content buffer. + EXPECT_EQ(response.content().buffer().toString(), std::string({ + 'T', // true + 'H', // Attachments start + 'Z' // Attachments end + })); + } + + // Buffer with result and attachments. + { + RpcResponse response; + + Buffer::OwnedImpl buffer(std::string({ + 'T', // true + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'T', // Key + 'F', // Value + 'Z' // Attachments end + })); + + // Initialize the response with the buffer. + response.content().initialize(buffer, buffer.length()); + + // Get the result. + const auto* result = response.content().result(); + + // The result is a boolean. + EXPECT_EQ(true, result->toBoolean().value().get()); + + // Only string key and value are used. + EXPECT_EQ(1, response.content().attachments().size()); + + // Get the attachment group. + EXPECT_EQ("group", response.content().attachments().at("group")); + } +} - // Reset attachment and parameters. - request.mutableParameters() = nullptr; - request.mutableAttachment() = nullptr; +TEST(RpcResponseTest, InitializeWithDecodedValuesTest) { + RpcResponse response; - // When parsing parameters, attachment will not be parsed. - request.mutableParameters(); - EXPECT_EQ(true, set_parameters); - EXPECT_EQ(false, set_attachment); - EXPECT_EQ(true, request.hasParameters()); - EXPECT_EQ(false, request.hasAttachment()); + Hessian2::ObjectPtr result = std::make_unique(true); + Attachments attachs; + attachs.emplace("group", "group"); - request.messageBuffer().add("abcdefg"); - EXPECT_EQ("abcdefg", request.messageBuffer().toString()); -} + // Initialize the response with the given result and attachments. + response.content().initialize(std::move(result), std::move(attachs)); -TEST(RpcResponseImplTest, RpcResponseImplTest) { - RpcResponseImpl result; + // Get the result. + const auto* result2 = response.content().result(); - EXPECT_EQ(false, result.responseType().has_value()); - result.setResponseType(RpcResponseType::ResponseWithValue); - EXPECT_EQ(true, result.responseType().has_value()); - EXPECT_EQ(RpcResponseType::ResponseWithValue, result.responseType().value()); + // The result is a boolean. + EXPECT_EQ(true, result2->toBoolean().value().get()); - EXPECT_EQ(false, result.localRawMessage().has_value()); - result.setLocalRawMessage("abcdefg"); - EXPECT_EQ(true, result.localRawMessage().has_value()); - EXPECT_EQ("abcdefg", result.localRawMessage().value()); + // Get the attachment group. + EXPECT_EQ("group", response.content().attachments().at("group")); - result.messageBuffer().add("abcdefg"); - EXPECT_EQ("abcdefg", result.messageBuffer().toString()); + // Get the buffer. + EXPECT_EQ(response.content().buffer().toString(), std::string({ + 'T', // true + 'H', // Attachments start + 0x5, 'g', 'r', 'o', 'u', 'p', // Key + 0x5, 'g', 'r', 'o', 'u', 'p', // Value + 'Z' // Attachments end + })); } } // namespace diff --git a/test/extensions/common/dubbo/metadata_test.cc b/test/extensions/common/dubbo/metadata_test.cc index f4cf7b15869d..4f45a8e7fdd0 100644 --- a/test/extensions/common/dubbo/metadata_test.cc +++ b/test/extensions/common/dubbo/metadata_test.cc @@ -1,6 +1,6 @@ #include -#include "source/extensions/common/dubbo/message_impl.h" +#include "source/extensions/common/dubbo/message.h" #include "source/extensions/common/dubbo/metadata.h" #include "gtest/gtest.h" @@ -14,10 +14,6 @@ namespace { TEST(ContextTest, ContextTest) { Context context; - // Simple set and get of serialize type. - context.setSerializeType(SerializeType::Hessian2); - EXPECT_EQ(SerializeType::Hessian2, context.serializeType()); - // Simple set and get of message type. context.setMessageType(MessageType::HeartbeatResponse); EXPECT_EQ(MessageType::HeartbeatResponse, context.messageType()); @@ -63,10 +59,10 @@ TEST(MessageMetadataTest, MessageMetadataTest) { auto context = std::make_unique(); auto raw_context = context.get(); - auto request = std::make_unique(); + auto request = std::make_unique("a", "b", "c", "d"); auto raw_request = request.get(); - auto response = std::make_unique(); + auto response = std::make_unique(); auto raw_response = response.get(); // Simple set and get of context. From aa19504d52ad11906075063c2bf6ef48d7c9b27c Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Tue, 19 Mar 2024 10:25:21 -0400 Subject: [PATCH 08/11] fuzz: throwing an exception in case of an absl Status error upstream address (#32956) This is similar to #32546, but I missed the other field that may trigger a similar error. Risk Level: low - fuzz only Testing: Add corpus entry Signed-off-by: Adi Suissa-Peleg --- .../header_parser_corpus/wrong_upstream_address | 8 ++++++++ test/fuzz/utility.h | 14 +++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 test/common/router/header_parser_corpus/wrong_upstream_address diff --git a/test/common/router/header_parser_corpus/wrong_upstream_address b/test/common/router/header_parser_corpus/wrong_upstream_address new file mode 100644 index 000000000000..b45638c9ab38 --- /dev/null +++ b/test/common/router/header_parser_corpus/wrong_upstream_address @@ -0,0 +1,8 @@ +stream_info { + upstream_local_address { + socket_address { + address: "s" + named_port: "" + } + } +} diff --git a/test/fuzz/utility.h b/test/fuzz/utility.h index a450ac92e837..499f429e1e57 100644 --- a/test/fuzz/utility.h +++ b/test/fuzz/utility.h @@ -177,11 +177,15 @@ inline std::unique_ptr fromStreamInfo(const test::fuzz::StreamIn } else { address = Network::Utility::resolveUrl("tcp://10.0.0.1:443"); } - auto upstream_local_address = - stream_info.has_upstream_local_address() - ? Envoy::Network::Address::resolveProtoAddress(stream_info.upstream_local_address()) - .value() - : Network::Utility::resolveUrl("tcp://10.0.0.1:10000"); + Envoy::Network::Address::InstanceConstSharedPtr upstream_local_address; + if (stream_info.has_upstream_local_address()) { + auto upstream_local_address_or_error = + Envoy::Network::Address::resolveProtoAddress(stream_info.upstream_local_address()); + THROW_IF_STATUS_NOT_OK(upstream_local_address_or_error, throw); + upstream_local_address = upstream_local_address_or_error.value(); + } else { + upstream_local_address = Network::Utility::resolveUrl("tcp://10.0.0.1:10000"); + } test_stream_info->upstreamInfo()->setUpstreamLocalAddress(upstream_local_address); test_stream_info->downstream_connection_info_provider_ = std::make_shared(address, address); From 79d5a6d96a9121da655593c19cb17185b03b047d Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:40:16 +0200 Subject: [PATCH 09/11] fluentd_access_logger: add retry and backoff options (#32682) Signed-off-by: ohadvano --- .../access_loggers/fluentd/v3/BUILD | 5 +- .../access_loggers/fluentd/v3/fluentd.proto | 20 +- changelogs/current.yaml | 5 + .../observability/access_log/stats.rst | 1 + source/common/common/BUILD | 2 + source/common/common/backoff_strategy.h | 21 ++ .../access_loggers/fluentd/config.cc | 11 + .../fluentd/fluentd_access_log_impl.cc | 103 +++++-- .../fluentd/fluentd_access_log_impl.h | 28 +- test/common/common/backoff_strategy_test.cc | 25 ++ .../fluentd/fluentd_access_log_impl_test.cc | 278 ++++++++++++++++-- .../fluentd_access_log_integration_test.cc | 81 ++++- test/mocks/common.h | 11 + 13 files changed, 529 insertions(+), 62 deletions(-) diff --git a/api/envoy/extensions/access_loggers/fluentd/v3/BUILD b/api/envoy/extensions/access_loggers/fluentd/v3/BUILD index 29ebf0741406..09a37ad16b83 100644 --- a/api/envoy/extensions/access_loggers/fluentd/v3/BUILD +++ b/api/envoy/extensions/access_loggers/fluentd/v3/BUILD @@ -5,5 +5,8 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") licenses(["notice"]) # Apache 2 api_proto_package( - deps = ["@com_github_cncf_xds//udpa/annotations:pkg"], + deps = [ + "//envoy/config/core/v3:pkg", + "@com_github_cncf_xds//udpa/annotations:pkg", + ], ) diff --git a/api/envoy/extensions/access_loggers/fluentd/v3/fluentd.proto b/api/envoy/extensions/access_loggers/fluentd/v3/fluentd.proto index e6b2adfdc9c0..ce68c79e9d91 100644 --- a/api/envoy/extensions/access_loggers/fluentd/v3/fluentd.proto +++ b/api/envoy/extensions/access_loggers/fluentd/v3/fluentd.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package envoy.extensions.access_loggers.fluentd.v3; +import "envoy/config/core/v3/backoff.proto"; + import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; @@ -22,8 +24,19 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // the Fluentd Forward Protocol as described in: `Fluentd Forward Protocol Specification // `_. // [#extension: envoy.access_loggers.fluentd] -// [#next-free-field: 7] +// [#next-free-field: 8] message FluentdAccessLogConfig { + message RetryOptions { + // The number of times the logger will attempt to connect to the upstream during reconnects. + // By default, there is no limit. The logger will attempt to reconnect to the upstream each time + // connecting to the upstream failed or the upstream connection had been closed for any reason. + google.protobuf.UInt32Value max_connect_attempts = 1; + + // Sets the backoff strategy. If this value is not set, the default base backoff interval is 500 + // milliseconds and the default max backoff interval is 5 seconds (10 times the base interval). + config.core.v3.BackoffStrategy backoff_options = 2; + } + // The upstream cluster to connect to for streaming the Fluentd messages. string cluster = 1 [(validate.rules).string = {min_len: 1}]; @@ -67,4 +80,9 @@ message FluentdAccessLogConfig { // "message": "My error message" // } google.protobuf.Struct record = 6 [(validate.rules).message = {required: true}]; + + // Optional retry, in case upstream connection has failed. If this field is not set, the default values will be applied, + // as specified in the :ref:`RetryOptions ` + // configuration. + RetryOptions retry_options = 7; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index aa29fe080592..e95e090f0ed4 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -313,6 +313,11 @@ new_features: added a :ref:`configuration option ` to add ``x-envoy-local-overloaded`` header when Overload Manager is triggered. +- area: access_loggers + change: | + Added :ref:`retry options + ` to Fluentd Access Logger to + support upstream reconnect options, backoff intervals. - area: tracing change: | Added support to configure a Dynatrace sampler for the OpenTelemetry tracer. diff --git a/docs/root/configuration/observability/access_log/stats.rst b/docs/root/configuration/observability/access_log/stats.rst index 0a61df4e4518..5bc23c23ef67 100644 --- a/docs/root/configuration/observability/access_log/stats.rst +++ b/docs/root/configuration/observability/access_log/stats.rst @@ -46,4 +46,5 @@ The Fluentd access log has statistics rooted at the *access_logs.fluentd.(proto_config), context.serverFactoryContext().threadLocal(), + context.serverFactoryContext().api().randomGenerator(), getAccessLoggerCacheSingleton(context.serverFactoryContext())); } diff --git a/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.cc b/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.cc index ce1a96694d90..a6f312d66d96 100644 --- a/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.cc +++ b/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.cc @@ -12,17 +12,26 @@ namespace Fluentd { using MessagePackBuffer = msgpack::sbuffer; using MessagePackPacker = msgpack::packer; -FluentdAccessLoggerImpl::FluentdAccessLoggerImpl(Tcp::AsyncTcpClientPtr client, +FluentdAccessLoggerImpl::FluentdAccessLoggerImpl(Upstream::ThreadLocalCluster& cluster, + Tcp::AsyncTcpClientPtr client, Event::Dispatcher& dispatcher, const FluentdAccessLogConfig& config, + BackOffStrategyPtr backoff_strategy, Stats::Scope& parent_scope) : tag_(config.tag()), id_(dispatcher.name()), + max_connect_attempts_( + config.has_retry_options() && config.retry_options().has_max_connect_attempts() + ? absl::optional(config.retry_options().max_connect_attempts().value()) + : absl::nullopt), stats_scope_(parent_scope.createScope(config.stat_prefix())), fluentd_stats_( {ACCESS_LOG_FLUENTD_STATS(POOL_COUNTER(*stats_scope_), POOL_GAUGE(*stats_scope_))}), - client_(std::move(client)), - buffer_flush_interval_msec_(PROTOBUF_GET_MS_OR_DEFAULT(config, buffer_flush_interval, 1000)), - max_buffer_size_bytes_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, buffer_size_bytes, 16384)), + cluster_(cluster), backoff_strategy_(std::move(backoff_strategy)), client_(std::move(client)), + buffer_flush_interval_msec_( + PROTOBUF_GET_MS_OR_DEFAULT(config, buffer_flush_interval, DefaultBufferFlushIntervalMs)), + max_buffer_size_bytes_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, buffer_size_bytes, DefaultMaxBufferSize)), + retry_timer_(dispatcher.createTimer([this]() -> void { onBackoffCallback(); })), flush_timer_(dispatcher.createTimer([this]() { flush(); flush_timer_->enableTimer(buffer_flush_interval_msec_); @@ -35,27 +44,21 @@ void FluentdAccessLoggerImpl::onEvent(Network::ConnectionEvent event) { connecting_ = false; if (event == Network::ConnectionEvent::Connected) { + backoff_strategy_->reset(); + retry_timer_->disableTimer(); flush(); } else if (event == Network::ConnectionEvent::LocalClose || event == Network::ConnectionEvent::RemoteClose) { ENVOY_LOG(debug, "upstream connection was closed"); - // TODO(ohadvano): add an option to reconnect to the upstream, if configured - fluentd_stats_.connections_closed_.inc(); - disconnected_ = true; - clearBuffer(); - - ASSERT(flush_timer_ != nullptr); - flush_timer_->disableTimer(); + maybeReconnect(); } } void FluentdAccessLoggerImpl::log(EntryPtr&& entry) { - if (disconnected_) { + if (disconnected_ || approximate_message_size_bytes_ >= max_buffer_size_bytes_) { fluentd_stats_.entries_lost_.inc(); // We will lose the data deliberately so the buffer doesn't grow infinitely. - // Since the client is disconnected, there's nothing much we can do with the data anyway. - // TODO(ohadvano): add an option to reconnect to the upstream, if configured return; } @@ -63,6 +66,8 @@ void FluentdAccessLoggerImpl::log(EntryPtr&& entry) { entries_.push_back(std::move(entry)); fluentd_stats_.entries_buffered_.inc(); if (approximate_message_size_bytes_ >= max_buffer_size_bytes_) { + // If we exceeded the buffer limit, immediately flush the logs instead of waiting for + // the next flush interval, to allow new logs to be buffered. flush(); } } @@ -76,8 +81,7 @@ void FluentdAccessLoggerImpl::flush() { } if (!client_->connected()) { - connecting_ = true; - client_->connect(); + connect(); return; } @@ -102,6 +106,42 @@ void FluentdAccessLoggerImpl::flush() { clearBuffer(); } +void FluentdAccessLoggerImpl::connect() { + connect_attempts_++; + if (!client_->connect()) { + ENVOY_LOG(debug, "no healthy upstream"); + maybeReconnect(); + return; + } + + connecting_ = true; +} + +void FluentdAccessLoggerImpl::maybeReconnect() { + if (max_connect_attempts_.has_value() && connect_attempts_ >= max_connect_attempts_) { + ENVOY_LOG(debug, "max connection attempts reached"); + cluster_.info()->trafficStats()->upstream_cx_connect_attempts_exceeded_.inc(); + setDisconnected(); + return; + } + + uint64_t next_backoff_ms = backoff_strategy_->nextBackOffMs(); + retry_timer_->enableTimer(std::chrono::milliseconds(next_backoff_ms)); + ENVOY_LOG(debug, "reconnect attempt scheduled for {} ms", next_backoff_ms); +} + +void FluentdAccessLoggerImpl::onBackoffCallback() { + fluentd_stats_.reconnect_attempts_.inc(); + this->connect(); +} + +void FluentdAccessLoggerImpl::setDisconnected() { + disconnected_ = true; + clearBuffer(); + ASSERT(flush_timer_ != nullptr); + flush_timer_->disableTimer(); +} + void FluentdAccessLoggerImpl::clearBuffer() { entries_.clear(); approximate_message_size_bytes_ = 0; @@ -117,7 +157,8 @@ FluentdAccessLoggerCacheImpl::FluentdAccessLoggerCacheImpl( } FluentdAccessLoggerSharedPtr -FluentdAccessLoggerCacheImpl::getOrCreateLogger(const FluentdAccessLogConfigSharedPtr config) { +FluentdAccessLoggerCacheImpl::getOrCreateLogger(const FluentdAccessLogConfigSharedPtr config, + Random::RandomGenerator& random) { auto& cache = tls_slot_->getTyped(); const auto cache_key = MessageUtil::hash(*config); const auto it = cache.access_loggers_.find(cache_key); @@ -125,25 +166,41 @@ FluentdAccessLoggerCacheImpl::getOrCreateLogger(const FluentdAccessLogConfigShar return it->second.lock(); } + auto* cluster = cluster_manager_.getThreadLocalCluster(config->cluster()); auto client = - cluster_manager_.getThreadLocalCluster(config->cluster()) - ->tcpAsyncClient(nullptr, std::make_shared(false)); + cluster->tcpAsyncClient(nullptr, std::make_shared(false)); + + uint64_t base_interval_ms = DefaultBaseBackoffIntervalMs; + uint64_t max_interval_ms = base_interval_ms * DefaultMaxBackoffIntervalFactor; + + if (config->has_retry_options() && config->retry_options().has_backoff_options()) { + base_interval_ms = PROTOBUF_GET_MS_OR_DEFAULT(config->retry_options().backoff_options(), + base_interval, DefaultBaseBackoffIntervalMs); + max_interval_ms = + PROTOBUF_GET_MS_OR_DEFAULT(config->retry_options().backoff_options(), max_interval, + base_interval_ms * DefaultMaxBackoffIntervalFactor); + } + + BackOffStrategyPtr backoff_strategy = std::make_unique( + base_interval_ms, max_interval_ms, random); const auto logger = std::make_shared( - std::move(client), cache.dispatcher_, *config, *stats_scope_); + *cluster, std::move(client), cache.dispatcher_, *config, std::move(backoff_strategy), + *stats_scope_); cache.access_loggers_.emplace(cache_key, logger); return logger; } FluentdAccessLog::FluentdAccessLog(AccessLog::FilterPtr&& filter, FluentdFormatterPtr&& formatter, const FluentdAccessLogConfigSharedPtr config, - ThreadLocal::SlotAllocator& tls, + ThreadLocal::SlotAllocator& tls, Random::RandomGenerator& random, FluentdAccessLoggerCacheSharedPtr access_logger_cache) : ImplBase(std::move(filter)), formatter_(std::move(formatter)), tls_slot_(tls.allocateSlot()), config_(config), access_logger_cache_(access_logger_cache) { tls_slot_->set( - [config = config_, access_logger_cache = access_logger_cache_](Event::Dispatcher&) { - return std::make_shared(access_logger_cache->getOrCreateLogger(config)); + [config = config_, &random, access_logger_cache = access_logger_cache_](Event::Dispatcher&) { + return std::make_shared( + access_logger_cache->getOrCreateLogger(config, random)); }); } diff --git a/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h b/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h index 11a03530d32d..95e84632177f 100644 --- a/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h +++ b/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h @@ -18,6 +18,11 @@ using FluentdAccessLogConfig = envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig; using FluentdAccessLogConfigSharedPtr = std::shared_ptr; +static constexpr uint64_t DefaultBaseBackoffIntervalMs = 500; +static constexpr uint64_t DefaultMaxBackoffIntervalFactor = 10; +static constexpr uint64_t DefaultBufferFlushIntervalMs = 1000; +static constexpr uint64_t DefaultMaxBufferSize = 16384; + // Entry represents a single Fluentd message, msgpack format based, as specified in: // https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1#entry class Entry { @@ -49,6 +54,7 @@ using FluentdAccessLoggerSharedPtr = std::shared_ptr; COUNTER(entries_lost) \ COUNTER(entries_buffered) \ COUNTER(events_sent) \ + COUNTER(reconnect_attempts) \ COUNTER(connections_closed) struct AccessLogFluentdStats { @@ -59,8 +65,9 @@ class FluentdAccessLoggerImpl : public Tcp::AsyncTcpClientCallbacks, public FluentdAccessLogger, public Logger::Loggable { public: - FluentdAccessLoggerImpl(Tcp::AsyncTcpClientPtr client, Event::Dispatcher& dispatcher, - const FluentdAccessLogConfig& config, Stats::Scope& parent_scope); + FluentdAccessLoggerImpl(Upstream::ThreadLocalCluster& cluster, Tcp::AsyncTcpClientPtr client, + Event::Dispatcher& dispatcher, const FluentdAccessLogConfig& config, + BackOffStrategyPtr backoff_strategy, Stats::Scope& parent_scope); // Tcp::AsyncTcpClientCallbacks void onEvent(Network::ConnectionEvent event) override; @@ -73,19 +80,28 @@ class FluentdAccessLoggerImpl : public Tcp::AsyncTcpClientCallbacks, private: void flush(); + void connect(); + void maybeReconnect(); + void onBackoffCallback(); + void setDisconnected(); void clearBuffer(); bool disconnected_ = false; bool connecting_ = false; std::string tag_; std::string id_; + uint32_t connect_attempts_{0}; + absl::optional max_connect_attempts_{}; const Stats::ScopeSharedPtr stats_scope_; AccessLogFluentdStats fluentd_stats_; std::vector entries_; uint64_t approximate_message_size_bytes_ = 0; + Upstream::ThreadLocalCluster& cluster_; + const BackOffStrategyPtr backoff_strategy_; const Tcp::AsyncTcpClientPtr client_; const std::chrono::milliseconds buffer_flush_interval_msec_; const uint64_t max_buffer_size_bytes_; + const Event::TimerPtr retry_timer_; const Event::TimerPtr flush_timer_; }; @@ -98,7 +114,8 @@ class FluentdAccessLoggerCache { * @return FluentdAccessLoggerSharedPtr ready for logging requests. */ virtual FluentdAccessLoggerSharedPtr - getOrCreateLogger(const FluentdAccessLogConfigSharedPtr config) PURE; + getOrCreateLogger(const FluentdAccessLogConfigSharedPtr config, + Random::RandomGenerator& random) PURE; }; using FluentdAccessLoggerCacheSharedPtr = std::shared_ptr; @@ -108,8 +125,8 @@ class FluentdAccessLoggerCacheImpl : public Singleton::Instance, public FluentdA FluentdAccessLoggerCacheImpl(Upstream::ClusterManager& cluster_manager, Stats::Scope& parent_scope, ThreadLocal::SlotAllocator& tls); - FluentdAccessLoggerSharedPtr - getOrCreateLogger(const FluentdAccessLogConfigSharedPtr config) override; + FluentdAccessLoggerSharedPtr getOrCreateLogger(const FluentdAccessLogConfigSharedPtr config, + Random::RandomGenerator& random) override; private: /** @@ -135,6 +152,7 @@ class FluentdAccessLog : public Common::ImplBase { public: FluentdAccessLog(AccessLog::FilterPtr&& filter, FluentdFormatterPtr&& formatter, const FluentdAccessLogConfigSharedPtr config, ThreadLocal::SlotAllocator& tls, + Random::RandomGenerator& random, FluentdAccessLoggerCacheSharedPtr access_logger_cache); private: diff --git a/test/common/common/backoff_strategy_test.cc b/test/common/common/backoff_strategy_test.cc index a5111809bf44..6dcb6a04aeaf 100644 --- a/test/common/common/backoff_strategy_test.cc +++ b/test/common/common/backoff_strategy_test.cc @@ -114,4 +114,29 @@ TEST(FixedBackOffStrategyTest, FixedBackOffBasicReset) { EXPECT_EQ(20, fixed_back_off.nextBackOffMs()); } +TEST(BackOffStrategyUtilsTest, InvalidConfig) { + { + // Valid config. + envoy::config::core::v3::BackoffStrategy backoff_strategy; + backoff_strategy.mutable_base_interval()->set_seconds(2); + backoff_strategy.mutable_max_interval()->set_seconds(3); + EXPECT_TRUE(BackOffStrategyUtils::validateBackOffStrategyConfig(backoff_strategy, 1, 10).ok()); + } + + { + // Max interval is lower than base interval. + envoy::config::core::v3::BackoffStrategy backoff_strategy; + backoff_strategy.mutable_base_interval()->set_seconds(3); + backoff_strategy.mutable_max_interval()->set_seconds(2); + EXPECT_TRUE(!BackOffStrategyUtils::validateBackOffStrategyConfig(backoff_strategy, 1, 10).ok()); + } + + { + // Max interval is lower than base interval. + envoy::config::core::v3::BackoffStrategy backoff_strategy; + backoff_strategy.mutable_max_interval()->set_nanos(2000000); + EXPECT_TRUE(!BackOffStrategyUtils::validateBackOffStrategyConfig(backoff_strategy, 3, 10).ok()); + } +} + } // namespace Envoy diff --git a/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc b/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc index a058c22dcf07..14c7bd089fca 100644 --- a/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc +++ b/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc @@ -31,16 +31,25 @@ class FluentdAccessLoggerImplTest : public testing::Test { public: FluentdAccessLoggerImplTest() : async_client_(new Tcp::AsyncClient::MockAsyncTcpClient()), - timer_(new Event::MockTimer(&dispatcher_)) {} + backoff_strategy_(new MockBackOffStrategy()), + flush_timer_(new Event::MockTimer(&dispatcher_)), + retry_timer_(new Event::MockTimer(&dispatcher_)) {} - void init(int buffer_size_bytes = 0) { + void init(int buffer_size_bytes = 1, absl::optional max_connect_attempts = absl::nullopt) { EXPECT_CALL(*async_client_, setAsyncTcpClientCallbacks(_)); - EXPECT_CALL(*timer_, enableTimer(_, _)); + EXPECT_CALL(*flush_timer_, enableTimer(_, _)); config_.set_tag(tag_); + + if (max_connect_attempts.has_value()) { + config_.mutable_retry_options()->mutable_max_connect_attempts()->set_value( + max_connect_attempts.value()); + } + config_.mutable_buffer_size_bytes()->set_value(buffer_size_bytes); logger_ = std::make_unique( - Tcp::AsyncTcpClientPtr{async_client_}, dispatcher_, config_, *stats_store_.rootScope()); + cluster_, Tcp::AsyncTcpClientPtr{async_client_}, dispatcher_, config_, + BackOffStrategyPtr{backoff_strategy_}, *stats_store_.rootScope()); } std::string getExpectedMsgpackPayload(int entries_count) { @@ -62,10 +71,13 @@ class FluentdAccessLoggerImplTest : public testing::Test { std::string tag_ = "test.tag"; uint64_t time_ = 123; std::vector data_ = {10, 20}; + NiceMock cluster_; Tcp::AsyncClient::MockAsyncTcpClient* async_client_; + MockBackOffStrategy* backoff_strategy_; Stats::IsolatedStoreImpl stats_store_; Event::MockDispatcher dispatcher_; - Event::MockTimer* timer_; + Event::MockTimer* flush_timer_; + Event::MockTimer* retry_timer_; std::unique_ptr logger_; envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config_; }; @@ -87,8 +99,8 @@ TEST_F(FluentdAccessLoggerImplTest, NoWriteOnLogIfBufferLimitNotPassed) { } TEST_F(FluentdAccessLoggerImplTest, NoWriteOnLogIfDisconnectedByRemote) { - init(); - EXPECT_CALL(*timer_, disableTimer()); + init(1, 1); + EXPECT_CALL(*flush_timer_, disableTimer()); EXPECT_CALL(*async_client_, write(_, _)).Times(0); EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)); EXPECT_CALL(*async_client_, connect()).WillOnce(Invoke([this]() -> bool { @@ -100,8 +112,8 @@ TEST_F(FluentdAccessLoggerImplTest, NoWriteOnLogIfDisconnectedByRemote) { } TEST_F(FluentdAccessLoggerImplTest, NoWriteOnLogIfDisconnectedByLocal) { - init(); - EXPECT_CALL(*timer_, disableTimer()); + init(1, 1); + EXPECT_CALL(*flush_timer_, disableTimer()); EXPECT_CALL(*async_client_, write(_, _)).Times(0); EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)); EXPECT_CALL(*async_client_, connect()).WillOnce(Invoke([this]() -> bool { @@ -114,6 +126,8 @@ TEST_F(FluentdAccessLoggerImplTest, NoWriteOnLogIfDisconnectedByLocal) { TEST_F(FluentdAccessLoggerImplTest, LogSingleEntry) { init(); // Default buffer limit is 0 so single entry should be flushed immediately. + EXPECT_CALL(*backoff_strategy_, reset()); + EXPECT_CALL(*retry_timer_, disableTimer()); EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)).WillOnce(Return(true)); EXPECT_CALL(*async_client_, connect()).WillOnce(Invoke([this]() -> bool { logger_->onEvent(Network::ConnectionEvent::Connected); @@ -133,6 +147,8 @@ TEST_F(FluentdAccessLoggerImplTest, LogTwoEntries) { init(12); // First entry is 10 bytes, so first entry should not cause the logger to flush. // First log should not be flushed. + EXPECT_CALL(*backoff_strategy_, reset()); + EXPECT_CALL(*retry_timer_, disableTimer()); EXPECT_CALL(*async_client_, connected()).Times(0); EXPECT_CALL(*async_client_, write(_, _)).Times(0); logger_->log(std::make_unique(time_, std::move(data_))); @@ -164,11 +180,123 @@ TEST_F(FluentdAccessLoggerImplTest, CallbacksTest) { EXPECT_NO_THROW(logger_->onData(buffer, false)); } +TEST_F(FluentdAccessLoggerImplTest, SuccessfulReconnect) { + init(1, 2); + EXPECT_CALL(*backoff_strategy_, nextBackOffMs()).WillOnce(Return(1)); + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)).WillOnce(Return(true)); + EXPECT_CALL(*async_client_, connect()) + .WillOnce(Invoke([this]() -> bool { + EXPECT_CALL(*backoff_strategy_, reset()).Times(0); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(1), _)); + EXPECT_CALL(*retry_timer_, disableTimer()).Times(0); + logger_->onEvent(Network::ConnectionEvent::LocalClose); + return true; + })) + .WillOnce(Invoke([this]() -> bool { + EXPECT_CALL(*backoff_strategy_, reset()); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)).Times(0); + EXPECT_CALL(*retry_timer_, disableTimer()); + logger_->onEvent(Network::ConnectionEvent::Connected); + return true; + })); + EXPECT_CALL(*async_client_, write(_, _)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool end_stream) { + EXPECT_FALSE(end_stream); + std::string expected_payload = getExpectedMsgpackPayload(1); + EXPECT_EQ(expected_payload, buffer.toString()); + })); + + logger_->log(std::make_unique(time_, std::move(data_))); + retry_timer_->invokeCallback(); +} + +TEST_F(FluentdAccessLoggerImplTest, ReconnectFailure) { + init(1, 2); + + EXPECT_CALL(*backoff_strategy_, nextBackOffMs()).WillOnce(Return(1)); + EXPECT_CALL(*backoff_strategy_, reset()).Times(0); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(1), _)); + EXPECT_CALL(*retry_timer_, disableTimer()).Times(0); + + EXPECT_CALL(*flush_timer_, disableTimer()); + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)); + EXPECT_CALL(*async_client_, connect()) + .WillOnce(Invoke([this]() -> bool { + logger_->onEvent(Network::ConnectionEvent::LocalClose); + return true; + })) + .WillOnce(Invoke([this]() -> bool { + logger_->onEvent(Network::ConnectionEvent::LocalClose); + return true; + })); + + logger_->log(std::make_unique(time_, std::move(data_))); + retry_timer_->invokeCallback(); +} + +TEST_F(FluentdAccessLoggerImplTest, TwoReconnects) { + init(1, 3); + + EXPECT_CALL(*backoff_strategy_, nextBackOffMs()).WillOnce(Return(1)).WillOnce(Return(1)); + EXPECT_CALL(*backoff_strategy_, reset()).Times(0); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(1), _)).Times(2); + EXPECT_CALL(*retry_timer_, disableTimer()).Times(0); + + EXPECT_CALL(*flush_timer_, disableTimer()); + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)); + EXPECT_CALL(*async_client_, connect()) + .WillOnce(Invoke([this]() -> bool { + logger_->onEvent(Network::ConnectionEvent::LocalClose); + return true; + })) + .WillOnce(Invoke([this]() -> bool { + logger_->onEvent(Network::ConnectionEvent::LocalClose); + return true; + })) + .WillOnce(Invoke([this]() -> bool { + logger_->onEvent(Network::ConnectionEvent::LocalClose); + return true; + })); + + logger_->log(std::make_unique(time_, std::move(data_))); + retry_timer_->invokeCallback(); + retry_timer_->invokeCallback(); +} + +TEST_F(FluentdAccessLoggerImplTest, RetryOnNoHealthyUpstream) { + init(); + + EXPECT_CALL(*backoff_strategy_, nextBackOffMs()).WillOnce(Return(1)); + EXPECT_CALL(*backoff_strategy_, reset()).Times(0); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(1), _)); + EXPECT_CALL(*retry_timer_, disableTimer()).Times(0); + + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)); + EXPECT_CALL(*async_client_, connect()).WillOnce(Return(false)); + logger_->log(std::make_unique(time_, std::move(data_))); +} + +TEST_F(FluentdAccessLoggerImplTest, NoWriteOnBufferFull) { + // Setting the buffer to 0 so new log will be thrown. + init(0); + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + EXPECT_CALL(*async_client_, connect()).Times(0); + EXPECT_CALL(*async_client_, connected()).Times(0); + logger_->log(std::make_unique(time_, std::move(data_))); +} + class FluentdAccessLoggerCacheImplTest : public testing::Test { public: - FluentdAccessLoggerCacheImplTest() : logger_cache_(cluster_manager_, scope_, tls_) {} - void init(bool second_logger = false) { + tls_.setDispatcher(&dispatcher_); + flush_timer_ = new Event::MockTimer(&dispatcher_); + retry_timer_ = new Event::MockTimer(&dispatcher_); + EXPECT_CALL(*flush_timer_, enableTimer(_, _)); + async_client1_ = new Tcp::AsyncClient::MockAsyncTcpClient(); EXPECT_CALL(*async_client1_, setAsyncTcpClientCallbacks(_)); @@ -176,9 +304,16 @@ class FluentdAccessLoggerCacheImplTest : public testing::Test { async_client2_ = new Tcp::AsyncClient::MockAsyncTcpClient(); EXPECT_CALL(*async_client2_, setAsyncTcpClientCallbacks(_)); } + + logger_cache_ = std::make_unique(cluster_manager_, scope_, tls_); } std::string cluster_name_ = "test_cluster"; + uint64_t time_ = 123; + std::vector data_ = {10, 20}; + Event::MockTimer* flush_timer_; + Event::MockTimer* retry_timer_; + Event::MockDispatcher dispatcher_; NiceMock cluster_; NiceMock cluster_manager_; Tcp::AsyncClient::MockAsyncTcpClient* async_client1_; @@ -186,7 +321,8 @@ class FluentdAccessLoggerCacheImplTest : public testing::Test { NiceMock store_; Stats::Scope& scope_{*store_.rootScope()}; NiceMock tls_; - FluentdAccessLoggerCacheImpl logger_cache_; + NiceMock random_; + std::unique_ptr logger_cache_; }; TEST_F(FluentdAccessLoggerCacheImplTest, CreateNonExistingLogger) { @@ -200,7 +336,8 @@ TEST_F(FluentdAccessLoggerCacheImplTest, CreateNonExistingLogger) { config.set_cluster(cluster_name_); config.set_tag("test.tag"); config.mutable_buffer_size_bytes()->set_value(123); - auto logger = logger_cache_.getOrCreateLogger(std::make_shared(config)); + auto logger = + logger_cache_->getOrCreateLogger(std::make_shared(config), random_); EXPECT_TRUE(logger != nullptr); } @@ -215,14 +352,16 @@ TEST_F(FluentdAccessLoggerCacheImplTest, CreateTwoLoggersSameHash) { config1.set_cluster(cluster_name_); config1.set_tag("test.tag"); config1.mutable_buffer_size_bytes()->set_value(123); - auto logger1 = logger_cache_.getOrCreateLogger(std::make_shared(config1)); + auto logger1 = + logger_cache_->getOrCreateLogger(std::make_shared(config1), random_); EXPECT_TRUE(logger1 != nullptr); envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config2; config2.set_cluster(cluster_name_); // config hash will be different than config1 config2.set_tag("test.tag"); config2.mutable_buffer_size_bytes()->set_value(123); - auto logger2 = logger_cache_.getOrCreateLogger(std::make_shared(config2)); + auto logger2 = + logger_cache_->getOrCreateLogger(std::make_shared(config2), random_); EXPECT_TRUE(logger2 != nullptr); // Make sure we got the same logger @@ -243,20 +382,60 @@ TEST_F(FluentdAccessLoggerCacheImplTest, CreateTwoLoggersDifferentHash) { config1.set_cluster(cluster_name_); config1.set_tag("test.tag"); config1.mutable_buffer_size_bytes()->set_value(123); - auto logger1 = logger_cache_.getOrCreateLogger(std::make_shared(config1)); + auto logger1 = + logger_cache_->getOrCreateLogger(std::make_shared(config1), random_); EXPECT_TRUE(logger1 != nullptr); + Event::MockTimer* flush_timer2 = new Event::MockTimer(&dispatcher_); + Event::MockTimer* retry_timer2 = new Event::MockTimer(&dispatcher_); + UNREFERENCED_PARAMETER(retry_timer2); + EXPECT_CALL(*flush_timer2, enableTimer(_, _)); + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config2; config2.set_cluster("different_cluster"); // config hash will be different than config1 config2.set_tag("test.tag"); config2.mutable_buffer_size_bytes()->set_value(123); - auto logger2 = logger_cache_.getOrCreateLogger(std::make_shared(config2)); + auto logger2 = + logger_cache_->getOrCreateLogger(std::make_shared(config2), random_); EXPECT_TRUE(logger2 != nullptr); // Make sure we got two different loggers EXPECT_NE(logger1, logger2); } +TEST_F(FluentdAccessLoggerCacheImplTest, JitteredExponentialBackOffStrategyConfig) { + init(); + + EXPECT_CALL(cluster_manager_, getThreadLocalCluster(cluster_name_)).WillOnce(Return(&cluster_)); + EXPECT_CALL(*async_client1_, connected()).WillOnce(Return(false)); + EXPECT_CALL(*async_client1_, connect()).WillRepeatedly(Return(false)); + EXPECT_CALL(cluster_, tcpAsyncClient(_, _)).WillOnce(Invoke([&] { + return Tcp::AsyncTcpClientPtr{async_client1_}; + })); + + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config; + config.set_cluster(cluster_name_); + config.set_tag("test.tag"); + config.mutable_buffer_size_bytes()->set_value(1); + config.mutable_retry_options()->mutable_backoff_options()->mutable_base_interval()->set_nanos( + 7000000); + config.mutable_retry_options()->mutable_backoff_options()->mutable_max_interval()->set_nanos( + 20000000); + + auto logger = + logger_cache_->getOrCreateLogger(std::make_shared(config), random_); + ASSERT_TRUE(logger != nullptr); + + // Setting random so it doesn't add jitter + EXPECT_CALL(random_, random()).WillOnce(Return(6)).WillOnce(Return(13)).WillOnce(Return(19)); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(6), _)); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(13), _)); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(19), _)); + logger->log(std::make_unique(time_, std::move(data_))); + retry_timer_->invokeCallback(); + retry_timer_->invokeCallback(); +} + class MockFluentdAccessLogger : public FluentdAccessLogger { public: MOCK_METHOD(void, log, (EntryPtr &&)); @@ -265,7 +444,7 @@ class MockFluentdAccessLogger : public FluentdAccessLogger { class MockFluentdAccessLoggerCache : public FluentdAccessLoggerCache { public: MOCK_METHOD(FluentdAccessLoggerSharedPtr, getOrCreateLogger, - (const FluentdAccessLogConfigSharedPtr)); + (const FluentdAccessLogConfigSharedPtr, Random::RandomGenerator&)); }; class MockFluentdFormatter : public FluentdFormatter { @@ -280,31 +459,32 @@ using FilterPtr = Envoy::AccessLog::FilterPtr; class FluentdAccessLogTest : public testing::Test { public: - FluentdAccessLogTest() { - ON_CALL(*filter_, evaluate(_, _)).WillByDefault(Return(true)); - EXPECT_CALL(*logger_cache_, getOrCreateLogger(_)).WillOnce(Return(logger_)); - } + FluentdAccessLogTest() { ON_CALL(*filter_, evaluate(_, _)).WillByDefault(Return(true)); } AccessLog::MockFilter* filter_{new NiceMock()}; NiceMock tls_; + NiceMock random_; + NiceMock context_; envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config_; - MockFluentdFormatter* formatter_{new NiceMock()}; - std::shared_ptr logger_{new MockFluentdAccessLogger()}; - std::shared_ptr logger_cache_{new MockFluentdAccessLoggerCache()}; }; TEST_F(FluentdAccessLogTest, CreateAndLog) { - auto access_log = - FluentdAccessLog(AccessLog::FilterPtr{filter_}, FluentdFormatterPtr{formatter_}, - std::make_shared(config_), tls_, logger_cache_); + auto* formatter = new NiceMock(); + auto logger = std::make_shared(); + auto logger_cache = std::make_shared(); + + EXPECT_CALL(*logger_cache, getOrCreateLogger(_, _)).WillOnce(Return(logger)); + auto access_log = FluentdAccessLog(AccessLog::FilterPtr{filter_}, FluentdFormatterPtr{formatter}, + std::make_shared(config_), tls_, + random_, logger_cache); MockTimeSystem time_system; EXPECT_CALL(time_system, systemTime).WillOnce(Return(SystemTime(std::chrono::seconds(200)))); NiceMock stream_info; EXPECT_CALL(stream_info, timeSource()).WillOnce(ReturnRef(time_system)); - EXPECT_CALL(*formatter_, format(_, _)).WillOnce(Return(std::vector{10, 20})); - EXPECT_CALL(*logger_, log(_)).WillOnce(Invoke([](EntryPtr&& entry) { + EXPECT_CALL(*formatter, format(_, _)).WillOnce(Return(std::vector{10, 20})); + EXPECT_CALL(*logger, log(_)).WillOnce(Invoke([](EntryPtr&& entry) { EXPECT_EQ(200, entry->time_); ASSERT_EQ(2, entry->record_.size()); EXPECT_EQ(uint8_t(10), entry->record_[0]); @@ -314,6 +494,44 @@ TEST_F(FluentdAccessLogTest, CreateAndLog) { access_log.log({}, stream_info); } +TEST_F(FluentdAccessLogTest, UnknownCluster) { + FluentdAccessLogFactory factory; + + config_.set_cluster("unknown"); + config_.set_tag("tag"); + config_.set_stat_prefix("prefix"); + auto* record = config_.mutable_record(); + (*record->mutable_fields())["Message"].set_string_value("SomeValue"); + + EXPECT_CALL(context_.server_factory_context_.cluster_manager_, + checkActiveStaticCluster("unknown")) + .WillOnce(Return(absl::InvalidArgumentError("no cluster"))); + + EXPECT_THROW_WITH_MESSAGE( + factory.createAccessLogInstance(config_, AccessLog::FilterPtr{filter_}, context_), + EnvoyException, "cluster 'unknown' was not found"); +} + +TEST_F(FluentdAccessLogTest, InvalidBackoffConfig) { + FluentdAccessLogFactory factory; + + config_.set_cluster("unknown"); + config_.set_tag("tag"); + config_.set_stat_prefix("prefix"); + auto* record = config_.mutable_record(); + (*record->mutable_fields())["Message"].set_string_value("SomeValue"); + auto* retry_options = config_.mutable_retry_options(); + retry_options->mutable_backoff_options()->mutable_base_interval()->set_seconds(3); + retry_options->mutable_backoff_options()->mutable_max_interval()->set_seconds(2); + + EXPECT_CALL(context_.server_factory_context_.cluster_manager_, checkActiveStaticCluster(_)) + .WillOnce(Return(absl::OkStatus())); + + EXPECT_THROW_WITH_MESSAGE( + factory.createAccessLogInstance(config_, AccessLog::FilterPtr{filter_}, context_), + EnvoyException, "max_backoff_interval must be greater or equal to base_backoff_interval"); +} + } // namespace } // namespace Fluentd } // namespace AccessLoggers diff --git a/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc b/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc index 933ac3fe1369..6c386c9b979b 100644 --- a/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc +++ b/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc @@ -32,7 +32,10 @@ class FluentdAccessLogIntegrationTest : public testing::Test, public BaseIntegra void init(const std::string cluster_name = default_cluster_name, bool flush_access_log_on_connected = false, - absl::optional buffer_size_bytes = absl::nullopt) { + absl::optional buffer_size_bytes = absl::nullopt, + absl::optional max_connect_attempts = 1, + absl::optional base_backoff_interval = absl::nullopt, + absl::optional max_backoff_interval = absl::nullopt) { setUpstreamCount(2); config_helper_.renameListener("tcp_proxy"); config_helper_.addConfigModifier( @@ -64,6 +67,25 @@ class FluentdAccessLogIntegrationTest : public testing::Test, public BaseIntegra access_log_config.mutable_buffer_size_bytes()->set_value(buffer_size_bytes.value()); } + if (max_connect_attempts.has_value()) { + access_log_config.mutable_retry_options()->mutable_max_connect_attempts()->set_value( + max_connect_attempts.value()); + } + + if (base_backoff_interval.has_value()) { + access_log_config.mutable_retry_options() + ->mutable_backoff_options() + ->mutable_base_interval() + ->set_nanos(base_backoff_interval.value() * 1000000); + } + + if (max_backoff_interval.has_value()) { + access_log_config.mutable_retry_options() + ->mutable_backoff_options() + ->mutable_max_interval() + ->set_nanos(max_backoff_interval.value() * 1000000); + } + auto* record = access_log_config.mutable_record(); (*record->mutable_fields())["Message"].set_string_value("SomeValue"); (*record->mutable_fields())["LogType"].set_string_value("%ACCESS_LOG_TYPE%"); @@ -137,6 +159,18 @@ TEST_F(FluentdAccessLogIntegrationTest, UnknownCluster) { EXPECT_DEATH(init("unknown_cluster"), ""); } +TEST_F(FluentdAccessLogIntegrationTest, InvalidBackoffConfig) { + // Invalid config: min interval set to 30, max interval is set to 20. + EXPECT_DEATH(init(default_cluster_name, false, 1, 1, 30, 20), ""); +} + +TEST_F(FluentdAccessLogIntegrationTest, LogLostOnBufferFull) { + init(default_cluster_name, false, /* max_buffer_size = */ 0); + sendBidirectionalData(); + + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_lost", 1); +} + TEST_F(FluentdAccessLogIntegrationTest, SingleEntrySingleRecord) { init(); sendBidirectionalData(); @@ -145,6 +179,9 @@ TEST_F(FluentdAccessLogIntegrationTest, SingleEntrySingleRecord) { test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); + test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); + test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 1); + EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { bool validated = false; validateFluentdPayload(tcp_data, &validated, @@ -161,6 +198,9 @@ TEST_F(FluentdAccessLogIntegrationTest, SingleEntryTwoRecords) { test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); + test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); + test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 1); + EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { bool validated = false; validateFluentdPayload(tcp_data, &validated, @@ -171,13 +211,16 @@ TEST_F(FluentdAccessLogIntegrationTest, SingleEntryTwoRecords) { } TEST_F(FluentdAccessLogIntegrationTest, TwoEntries) { - init(default_cluster_name, /*flush_access_log_on_connected = */ true, /*buffer_size_bytes = */ 0); + init(default_cluster_name, /*flush_access_log_on_connected = */ true, /*buffer_size_bytes = */ 1); sendBidirectionalData(); test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 2); test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 2); ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); + test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); + test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 1); + EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { bool validated = false; validateFluentdPayload(tcp_data, &validated, @@ -195,6 +238,9 @@ TEST_F(FluentdAccessLogIntegrationTest, UpstreamConnectionClosed) { test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); + test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); + test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 1); + EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { bool validated = false; validateFluentdPayload(tcp_data, &validated, @@ -204,11 +250,42 @@ TEST_F(FluentdAccessLogIntegrationTest, UpstreamConnectionClosed) { ASSERT_TRUE(fake_access_log_connection_->close()); test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.connections_closed", 1); + test_server_->waitForGaugeEq("cluster.fluentd_cluster.upstream_cx_active", 0); // New access log would be discarded because the connection is closed. sendBidirectionalData(); test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_lost", 1); } +TEST_F(FluentdAccessLogIntegrationTest, UpstreamConnectionClosedWithMultipleReconnects) { + init(default_cluster_name, false, {}, /* max_reconnect_attempts = */ 3); + sendBidirectionalData(); + + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 1); + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); + + ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); + test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 1); + ASSERT_TRUE(fake_access_log_connection_->close()); + + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.connections_closed", 1); + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.reconnect_attempts", 1); + FakeRawConnectionPtr fake_access_log_connection_2; + ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_2)); + test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 2); + ASSERT_TRUE(fake_access_log_connection_2->close()); + + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.connections_closed", 2); + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.reconnect_attempts", 2); + FakeRawConnectionPtr fake_access_log_connection_3; + ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_3)); + test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_total", 3); + ASSERT_TRUE(fake_access_log_connection_3->close()); + + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.connections_closed", 3); + test_server_->waitForCounterEq("cluster.fluentd_cluster.upstream_cx_connect_attempts_exceeded", + 1); +} + } // namespace } // namespace Envoy diff --git a/test/mocks/common.h b/test/mocks/common.h index 455dc2f59523..246aec0f0068 100644 --- a/test/mocks/common.h +++ b/test/mocks/common.h @@ -2,6 +2,7 @@ #include +#include "envoy/common/backoff_strategy.h" #include "envoy/common/conn_pool.h" #include "envoy/common/key_value_store.h" #include "envoy/common/random_generator.h" @@ -67,6 +68,16 @@ class MockTimeSystem : public Event::TestTimeSystem { Event::TestRealTimeSystem real_time_; // NO_CHECK_FORMAT(real_time) }; +class MockBackOffStrategy : public BackOffStrategy { +public: + ~MockBackOffStrategy() override = default; + + MOCK_METHOD(uint64_t, nextBackOffMs, ()); + MOCK_METHOD(void, reset, ()); + MOCK_METHOD(void, reset, (uint64_t base_interval)); + MOCK_METHOD(bool, isOverTimeLimit, (uint64_t interval_ms), (const)); +}; + // Captures absl::string_view parameters into temp strings, for use // with gmock's SaveArg. Providing an absl::string_view compiles, // but fails because by the time you examine the saved value, its From 8365ca889dcaf5694e306e5a331835c3b6e4b383 Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Tue, 19 Mar 2024 10:12:07 -0500 Subject: [PATCH 10/11] mobile: Clean up .bazelrc for Swift (#32971) Signed-off-by: Fredy Wijaya --- mobile/.bazelrc | 2 -- 1 file changed, 2 deletions(-) diff --git a/mobile/.bazelrc b/mobile/.bazelrc index 0fc992ca2e46..f60cad86caa8 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -295,9 +295,7 @@ build:mobile-remote-ci-macos-kotlin --@com_envoyproxy_protoc_gen_validate//bazel build:mobile-remote-ci-macos-swift --config=mobile-remote-ci-macos build:mobile-remote-ci-macos-swift --config=mobile-test-ios -build:mobile-remote-ci-macos-swift --config=mobile-remote-ci-macos build:mobile-remote-ci-macos-swift --define=envoy_mobile_request_compression=disabled -build:mobile-remote-ci-macos-swift --define=envoy_mobile_swift_cxx_interop=disabled build:mobile-remote-ci-macos-swift --define=google_grpc=disabled build:mobile-remote-ci-macos-swift --define=envoy_mobile_xds=disabled build:mobile-remote-ci-macos-swift --@envoy//bazel:http3=False From 04835afdae38f7e609ed37e489685634c83c5d1f Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 19 Mar 2024 12:27:03 -0400 Subject: [PATCH 11/11] Revert "Support upstream http filters with tcp tunneling (#27183)" (#32980) This reverts commit 50e9108c22c2e46d4ad04a977d24d89201aa0c62. Signed-off-by: Alyssa Wilk --- envoy/router/router.h | 11 - envoy/tcp/BUILD | 1 - envoy/tcp/upstream.h | 15 +- source/common/http/conn_manager_impl.h | 1 - source/common/http/filter_manager.cc | 12 +- source/common/http/filter_manager.h | 5 - source/common/router/router.cc | 10 +- source/common/router/upstream_request.cc | 12 +- source/common/router/upstream_request.h | 23 +- source/common/runtime/runtime_features.cc | 3 - source/common/runtime/runtime_features.h | 2 - source/common/tcp_proxy/BUILD | 7 - source/common/tcp_proxy/tcp_proxy.cc | 122 ++------ source/common/tcp_proxy/tcp_proxy.h | 118 +------- source/common/tcp_proxy/upstream.cc | 223 +-------------- source/common/tcp_proxy/upstream.h | 184 +----------- .../upstreams/http/http/upstream_request.h | 1 - .../upstreams/http/tcp/upstream_request.h | 1 - .../upstreams/http/udp/upstream_request.h | 1 - source/extensions/upstreams/tcp/generic/BUILD | 1 - .../upstreams/tcp/generic/config.cc | 4 +- .../extensions/upstreams/tcp/generic/config.h | 2 - test/common/router/upstream_request_test.cc | 5 +- test/common/tcp_proxy/BUILD | 4 - test/common/tcp_proxy/tcp_proxy_test.cc | 193 ++++--------- test/common/tcp_proxy/tcp_proxy_test_base.h | 5 +- test/common/tcp_proxy/upstream_test.cc | 264 ++---------------- test/extensions/filters/http/cache/BUILD | 1 - test/extensions/filters/http/csrf/BUILD | 1 - .../filters/http/custom_response/BUILD | 2 +- test/extensions/filters/http/fault/BUILD | 1 - .../filters/http/health_check/BUILD | 1 - test/extensions/filters/http/jwt_authn/BUILD | 2 +- test/extensions/filters/http/rbac/BUILD | 2 +- .../http/tcp/upstream_request_test.cc | 2 +- .../http/udp/upstream_request_test.cc | 1 - test/extensions/upstreams/tcp/generic/BUILD | 1 - .../upstreams/tcp/generic/config_test.cc | 46 ++- test/integration/BUILD | 13 +- test/integration/base_integration_test.cc | 1 - test/integration/http_protocol_integration.cc | 24 +- test/integration/http_protocol_integration.h | 4 - test/mocks/http/mocks.cc | 1 - test/mocks/http/mocks.h | 2 - test/mocks/router/BUILD | 9 - test/mocks/router/upstream_request.cc | 13 - test/mocks/router/upstream_request.h | 21 -- 47 files changed, 193 insertions(+), 1185 deletions(-) delete mode 100644 test/mocks/router/upstream_request.cc delete mode 100644 test/mocks/router/upstream_request.h diff --git a/envoy/router/router.h b/envoy/router/router.h index 8d559c280d33..299cb25c2960 100644 --- a/envoy/router/router.h +++ b/envoy/router/router.h @@ -1526,17 +1526,6 @@ class GenericUpstream { * @param trailers supplies the trailers to encode. */ virtual void encodeTrailers(const Http::RequestTrailerMap& trailers) PURE; - - // TODO(vikaschoudhary16): Remove this api. - // This api is only used to enable half-close semantics on the upstream connection. - // This ideally should be done via calling connection.enableHalfClose() but since TcpProxy - // does not have access to the upstream connection, it is done via this api for now. - /** - * Enable half-close semantics on the upstream connection. Reading a remote half-close - * will not fully close the connection. This is off by default. - * @param enabled Whether to set half-close semantics as enabled or disabled. - */ - virtual void enableHalfClose() PURE; /** * Enable/disable further data from this stream. */ diff --git a/envoy/tcp/BUILD b/envoy/tcp/BUILD index 5c3bdca0d9ef..86d70b1774c6 100644 --- a/envoy/tcp/BUILD +++ b/envoy/tcp/BUILD @@ -26,7 +26,6 @@ envoy_cc_library( "//envoy/http:header_evaluator", "//envoy/tcp:conn_pool_interface", "//envoy/upstream:upstream_interface", - "//source/common/router:router_lib", "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], ) diff --git a/envoy/tcp/upstream.h b/envoy/tcp/upstream.h index f6191a27513b..200ec7fc9ea7 100644 --- a/envoy/tcp/upstream.h +++ b/envoy/tcp/upstream.h @@ -2,14 +2,11 @@ #include "envoy/buffer/buffer.h" #include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" -#include "envoy/http/filter.h" #include "envoy/http/header_evaluator.h" #include "envoy/stream_info/stream_info.h" #include "envoy/tcp/conn_pool.h" #include "envoy/upstream/upstream.h" -#include "source/common/router/router.h" - namespace Envoy { namespace Upstream { @@ -51,17 +48,14 @@ class TunnelingConfigHelper { virtual void propagateResponseTrailers(Http::ResponseTrailerMapPtr&& trailers, const StreamInfo::FilterStateSharedPtr& filter_state) const PURE; - virtual const Envoy::Router::FilterConfig& routerFilterConfig() const PURE; - virtual Server::Configuration::ServerFactoryContext& serverFactoryContext() const PURE; }; using TunnelingConfigHelperOptConstRef = OptRef; // An API for wrapping either a TCP or an HTTP connection pool. -class GenericConnPool : public Event::DeferredDeletable, - public Logger::Loggable { +class GenericConnPool : public Logger::Loggable { public: - ~GenericConnPool() override = default; + virtual ~GenericConnPool() = default; /** * Called to create a TCP connection or HTTP stream for "CONNECT" streams. @@ -111,9 +105,9 @@ class GenericConnectionPoolCallbacks { // Interface for a generic Upstream, which can communicate with a TCP or HTTP // upstream. -class GenericUpstream : public Event::DeferredDeletable { +class GenericUpstream { public: - ~GenericUpstream() override = default; + virtual ~GenericUpstream() = default; /** * Enable/disable further data from this stream. @@ -181,7 +175,6 @@ class GenericConnPoolFactory : public Envoy::Config::TypedFactory { TunnelingConfigHelperOptConstRef config, Upstream::LoadBalancerContext* context, Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, - Http::StreamDecoderFilterCallbacks& stream_decoder_callbacks, StreamInfo::StreamInfo& downstream_info) const PURE; }; diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index c8d731afdb3d..11151b157bf5 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -314,7 +314,6 @@ class ConnectionManagerImpl : Logger::Loggable, OptRef tracingConfig() const override; const ScopeTrackedObject& scope() override; OptRef downstreamCallbacks() override { return *this; } - bool isHalfCloseEnabled() override { return false; } // DownstreamStreamFilterCallbacks void setRoute(Router::RouteConstSharedPtr route) override; diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 070440572660..187bb7b58561 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -901,17 +901,9 @@ FilterManager::commonEncodePrefix(ActiveStreamEncoderFilter* filter, bool end_st FilterIterationStartState filter_iteration_start_state) { // Only do base state setting on the initial call. Subsequent calls for filtering do not touch // the base state. - ENVOY_STREAM_LOG(trace, "commonEncodePrefix end_stream: {}, isHalfCloseEnabled: {}", *this, - end_stream, filter_manager_callbacks_.isHalfCloseEnabled()); if (filter == nullptr) { - // half close is enabled in case tcp proxying is done with http1 encoder. In this case, we - // should not set the local_complete_ flag to true when end_stream is true. - // setting local_complete_ to true will cause any data sent in the upstream direction to be - // dropped. - if (end_stream && !filter_manager_callbacks_.isHalfCloseEnabled()) { - ASSERT(!state_.local_complete_); - state_.local_complete_ = true; - } + ASSERT(!state_.local_complete_); + state_.local_complete_ = end_stream; return encoder_filters_.begin(); } diff --git a/source/common/http/filter_manager.h b/source/common/http/filter_manager.h index da5ba20033bd..6a671ab99e9b 100644 --- a/source/common/http/filter_manager.h +++ b/source/common/http/filter_manager.h @@ -545,11 +545,6 @@ class FilterManagerCallbacks { * Returns the DownstreamStreamFilterCallbacks for downstream HTTP filters. */ virtual OptRef downstreamCallbacks() { return {}; } - /** - * Returns if close from the upstream is to be handled with half-close semantics. - * This is used for HTTP/1.1 codec. - */ - virtual bool isHalfCloseEnabled() PURE; }; /** diff --git a/source/common/router/router.cc b/source/common/router/router.cc index c3baed7b839e..a41e1c3374dc 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -744,9 +744,8 @@ Http::FilterHeadersStatus Filter::decodeHeaders(Http::RequestHeaderMap& headers, // will never transition from false to true. bool can_use_http3 = !transport_socket_options_ || !transport_socket_options_->http11ProxyInfo().has_value(); - UpstreamRequestPtr upstream_request = - std::make_unique(*this, std::move(generic_conn_pool), can_send_early_data, - can_use_http3, false /*enable_half_close*/); + UpstreamRequestPtr upstream_request = std::make_unique( + *this, std::move(generic_conn_pool), can_send_early_data, can_use_http3); LinkedList::moveIntoList(std::move(upstream_request), upstream_requests_); upstream_requests_.front()->acceptHeadersFromRouter(end_stream); if (streaming_shadows_) { @@ -1988,9 +1987,8 @@ void Filter::doRetry(bool can_send_early_data, bool can_use_http3, TimeoutRetry cleanup(); return; } - UpstreamRequestPtr upstream_request = - std::make_unique(*this, std::move(generic_conn_pool), can_send_early_data, - can_use_http3, false /*enable_tcp_tunneling*/); + UpstreamRequestPtr upstream_request = std::make_unique( + *this, std::move(generic_conn_pool), can_send_early_data, can_use_http3); if (include_attempt_count_in_request_) { downstream_headers_->setEnvoyAttemptCount(attempt_count_); diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index bb0803df3be5..7c9079e58a44 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -80,8 +80,7 @@ class UpstreamFilterManager : public Http::FilterManager { UpstreamRequest::UpstreamRequest(RouterFilterInterface& parent, std::unique_ptr&& conn_pool, - bool can_send_early_data, bool can_use_http3, - bool enable_half_close) + bool can_send_early_data, bool can_use_http3) : parent_(parent), conn_pool_(std::move(conn_pool)), stream_info_(parent_.callbacks()->dispatcher().timeSource(), nullptr), start_time_(parent_.callbacks()->dispatcher().timeSource().monotonicTime()), @@ -94,8 +93,7 @@ UpstreamRequest::UpstreamRequest(RouterFilterInterface& parent, cleaned_up_(false), had_upstream_(false), stream_options_({can_send_early_data, can_use_http3}), grpc_rq_success_deferred_(false), upstream_wait_for_response_headers_before_disabling_read_(Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.upstream_wait_for_response_headers_before_disabling_read")), - enable_half_close_(enable_half_close) { + "envoy.reloadable_features.upstream_wait_for_response_headers_before_disabling_read")) { if (auto tracing_config = parent_.callbacks()->tracingConfig(); tracing_config.has_value()) { if (tracing_config->spawnUpstreamSpan() || parent_.config().start_child_span_) { span_ = parent_.callbacks()->activeSpan().spawnChild( @@ -260,8 +258,7 @@ void UpstreamRequest::decode1xxHeaders(Http::ResponseHeaderMapPtr&& headers) { // on to the router. void UpstreamRequest::decodeHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_stream) { ASSERT(headers.get()); - ENVOY_STREAM_LOG(trace, "end_stream: {}, upstream response headers:\n{}", *parent_.callbacks(), - end_stream, *headers); + ENVOY_STREAM_LOG(trace, "upstream response headers:\n{}", *parent_.callbacks(), *headers); ScopeTrackerScopeState scope(&parent_.callbacks()->scope(), parent_.callbacks()->dispatcher()); resetPerTryIdleTimer(); @@ -584,9 +581,6 @@ void UpstreamRequest::onPoolReady(std::unique_ptr&& upstream, had_upstream_ = true; // Have the upstream use the account of the downstream. upstream_->setAccount(parent_.callbacks()->account()); - if (enable_half_close_) { - upstream_->enableHalfClose(); - } host->outlierDetector().putResult(Upstream::Outlier::Result::LocalOriginConnectSuccess); diff --git a/source/common/router/upstream_request.h b/source/common/router/upstream_request.h index c6e7a4ec4c94..9e88bfefda31 100644 --- a/source/common/router/upstream_request.h +++ b/source/common/router/upstream_request.h @@ -62,27 +62,29 @@ class UpstreamCodecFilter; * UpstreamCodecFilter. This is accomplished via the UpstreamStreamFilterCallbacks * interface, with the UpstreamFilterManager acting as intermediary. * + * UpstreamRequest is marked as final because no subclasses are expected. + * This class is intended to be used as-is without any specialized inheritance. */ -class UpstreamRequest : public Logger::Loggable, - public UpstreamToDownstream, - public LinkedObject, - public GenericConnectionPoolCallbacks, - public Event::DeferredDeletable { +class UpstreamRequest final : public Logger::Loggable, + public UpstreamToDownstream, + public LinkedObject, + public GenericConnectionPoolCallbacks, + public Event::DeferredDeletable { public: UpstreamRequest(RouterFilterInterface& parent, std::unique_ptr&& conn_pool, - bool can_send_early_data, bool can_use_http3, bool enable_half_close); + bool can_send_early_data, bool can_use_http3); ~UpstreamRequest() override; void deleteIsPending() override { cleanUp(); } // To be called from the destructor, or prior to deferred delete. void cleanUp(); - virtual void acceptHeadersFromRouter(bool end_stream); - virtual void acceptDataFromRouter(Buffer::Instance& data, bool end_stream); + void acceptHeadersFromRouter(bool end_stream); + void acceptDataFromRouter(Buffer::Instance& data, bool end_stream); void acceptTrailersFromRouter(Http::RequestTrailerMap& trailers); void acceptMetadataFromRouter(Http::MetadataMapPtr&& metadata_map_ptr); - virtual void resetStream(); + void resetStream(); void setupPerTryTimeout(); void maybeEndDecode(bool end_stream); void onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host, bool pool_success); @@ -256,7 +258,6 @@ class UpstreamRequest : public Logger::Loggable, Http::ConnectionPool::Instance::StreamOptions stream_options_; bool grpc_rq_success_deferred_ : 1; bool upstream_wait_for_response_headers_before_disabling_read_ : 1; - bool enable_half_close_ : 1; }; class UpstreamRequestFilterManagerCallbacks : public Http::FilterManagerCallbacks, @@ -370,7 +371,7 @@ class UpstreamRequestFilterManagerCallbacks : public Http::FilterManagerCallback void setUpstreamToDownstream(UpstreamToDownstream& upstream_to_downstream_interface) override { upstream_request_.upstream_interface_ = upstream_to_downstream_interface; } - bool isHalfCloseEnabled() override { return upstream_request_.enable_half_close_; } + Http::RequestTrailerMapPtr trailers_; Http::ResponseHeaderMapPtr informational_headers_; Http::ResponseHeaderMapPtr response_headers_; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index ec9e7f6a40e3..40e526641425 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -130,9 +130,6 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_runtime_initialized); FALSE_RUNTIME_GUARD(envoy_reloadable_features_always_use_v6); // TODO(wbpcode) complete remove this feature is no one use it. FALSE_RUNTIME_GUARD(envoy_reloadable_features_refresh_rtt_after_request); -// TODO(vikaschoudhary16) flip this to true only after all the -// TcpProxy::Filter::HttpStreamDecoderFilterCallbacks are implemented or commented as unnecessary -FALSE_RUNTIME_GUARD(envoy_restart_features_upstream_http_filters_with_tcp_proxy); // TODO(danzh) false deprecate it once QUICHE has its own enable/disable flag. FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_reject_all); // TODO(suniltheta): Once the newly added http async technique is stabilized move it under diff --git a/source/common/runtime/runtime_features.h b/source/common/runtime/runtime_features.h index e698064a5971..0c2fb2d9b9e7 100644 --- a/source/common/runtime/runtime_features.h +++ b/source/common/runtime/runtime_features.h @@ -26,8 +26,6 @@ void maybeSetRuntimeGuard(absl::string_view name, bool value); void maybeSetDeprecatedInts(absl::string_view name, uint32_t value); constexpr absl::string_view defer_processing_backedup_streams = "envoy.reloadable_features.defer_processing_backedup_streams"; -constexpr absl::string_view upstream_http_filters_with_tcp_proxy = - "envoy.restart_features.upstream_http_filters_with_tcp_proxy"; } // namespace Runtime } // namespace Envoy diff --git a/source/common/tcp_proxy/BUILD b/source/common/tcp_proxy/BUILD index c6883b43aec3..966088ed4a08 100644 --- a/source/common/tcp_proxy/BUILD +++ b/source/common/tcp_proxy/BUILD @@ -17,22 +17,15 @@ envoy_cc_library( "upstream.h", ], deps = [ - "//envoy/http:header_map_interface", - "//envoy/router:router_ratelimit_interface", "//envoy/tcp:conn_pool_interface", "//envoy/tcp:upstream_interface", "//envoy/upstream:cluster_manager_interface", "//envoy/upstream:load_balancer_interface", - "//source/common/http:async_client_lib", "//source/common/http:codec_client_lib", - "//source/common/http:hash_policy_lib", "//source/common/http:header_map_lib", "//source/common/http:headers_lib", "//source/common/http:utility_lib", - "//source/common/network:utility_lib", "//source/common/router:header_parser_lib", - "//source/common/router:router_lib", - "//source/common/router:shadow_writer_lib", ], ) diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index 21483aca5463..f60f31ec44db 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -32,10 +32,8 @@ #include "source/common/network/upstream_server_name.h" #include "source/common/network/upstream_socket_options_filter_state.h" #include "source/common/router/metadatamatchcriteria_impl.h" -#include "source/common/router/shadow_writer_impl.h" #include "source/common/stream_info/stream_id_provider_impl.h" #include "source/common/stream_info/uint64_accessor_impl.h" -#include "source/common/tracing/http_tracer_impl.h" namespace Envoy { namespace TcpProxy { @@ -112,7 +110,7 @@ Config::SharedConfig::SharedConfig( } if (config.has_tunneling_config()) { tunneling_config_helper_ = - std::make_unique(*stats_scope_.get(), config, context); + std::make_unique(config.tunneling_config(), context); } if (config.has_max_downstream_connection_duration()) { const uint64_t connection_duration = @@ -232,10 +230,8 @@ UpstreamDrainManager& Config::drainManager() { } Filter::Filter(ConfigSharedPtr config, Upstream::ClusterManager& cluster_manager) - : tracing_config_(Tracing::EgressConfig::get()), config_(config), - cluster_manager_(cluster_manager), downstream_callbacks_(*this), - upstream_callbacks_(new UpstreamCallbacks(this)), - upstream_decoder_filter_callbacks_(HttpStreamDecoderFilterCallbacks(this)) { + : config_(config), cluster_manager_(cluster_manager), downstream_callbacks_(*this), + upstream_callbacks_(new UpstreamCallbacks(this)) { ASSERT(config != nullptr); } @@ -293,11 +289,9 @@ void Filter::onInitFailure(UpstreamFailureReason reason) { // not have started attempting to connect to an upstream and there is no // connection pool callback latency to record. if (initial_upstream_connection_start_time_.has_value()) { - if (!getStreamInfo().upstreamInfo()->upstreamTiming().connectionPoolCallbackLatency()) { - getStreamInfo().upstreamInfo()->upstreamTiming().recordConnectionPoolCallbackLatency( - initial_upstream_connection_start_time_.value(), - read_callbacks_->connection().dispatcher().timeSource()); - } + getStreamInfo().upstreamInfo()->upstreamTiming().recordConnectionPoolCallbackLatency( + initial_upstream_connection_start_time_.value(), + read_callbacks_->connection().dispatcher().timeSource()); } read_callbacks_->connection().close( Network::ConnectionCloseType::NoFlush, @@ -558,16 +552,9 @@ bool Filter::maybeTunnel(Upstream::ThreadLocalCluster& cluster) { if (!factory) { return false; } - if (Runtime::runtimeFeatureEnabled( - "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { - // TODO(vikaschoudhary16): Initialize route_ once per cluster. - upstream_decoder_filter_callbacks_.route_ = std::make_shared( - cluster.info()->name(), - *std::unique_ptr{new Router::RetryPolicyImpl()}); - } - generic_conn_pool_ = factory->createGenericConnPool( - cluster, config_->tunnelingConfigHelper(), this, *upstream_callbacks_, - upstream_decoder_filter_callbacks_, getStreamInfo()); + + generic_conn_pool_ = factory->createGenericConnPool(cluster, config_->tunnelingConfigHelper(), + this, *upstream_callbacks_, getStreamInfo()); if (generic_conn_pool_) { connecting_ = true; connect_attempts_++; @@ -583,19 +570,7 @@ bool Filter::maybeTunnel(Upstream::ThreadLocalCluster& cluster) { void Filter::onGenericPoolFailure(ConnectionPool::PoolFailureReason reason, absl::string_view failure_reason, Upstream::HostDescriptionConstSharedPtr host) { - if (Runtime::runtimeFeatureEnabled( - "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { - // generic_conn_pool_ is an instance of TcpProxy::HttpConnPool. - // generic_conn_pool_->newStream() is called in maybeTunnel() which initializes an instance of - // Router::UpstreamRequest. If Router::UpstreamRequest receives headers from the upstream which - // results in end_stream=true, then via callbacks passed to Router::UpstreamRequest, - // TcpProxy::Filter::onGenericPoolFailure() gets invoked. If we do not do deferredDelete here, - // then the same instance of UpstreamRequest which is under execution will go out of scope. - read_callbacks_->connection().dispatcher().deferredDelete(std::move(generic_conn_pool_)); - } else { - generic_conn_pool_.reset(); - } - + generic_conn_pool_.reset(); read_callbacks_->upstreamHost(host); getStreamInfo().upstreamInfo()->setUpstreamHost(host); getStreamInfo().upstreamInfo()->setUpstreamTransportFailureReason(failure_reason); @@ -619,30 +594,23 @@ void Filter::onGenericPoolReady(StreamInfo::StreamInfo* info, Upstream::HostDescriptionConstSharedPtr& host, const Network::ConnectionInfoProvider& address_provider, Ssl::ConnectionInfoConstSharedPtr ssl_info) { - StreamInfo::UpstreamInfo& upstream_info = *getStreamInfo().upstreamInfo(); - if (!upstream_info.upstreamTiming().connectionPoolCallbackLatency()) { - upstream_info.upstreamTiming().recordConnectionPoolCallbackLatency( - initial_upstream_connection_start_time_.value(), - read_callbacks_->connection().dispatcher().timeSource()); - } upstream_ = std::move(upstream); generic_conn_pool_.reset(); read_callbacks_->upstreamHost(host); - // No need to set information using address_provider in case routing via Router::UpstreamRequest - // because in that case, information is already set by the - // Router::UpstreamRequest::onPoolReady() method before reaching here. - if (upstream_info.upstreamLocalAddress() == nullptr) { - upstream_info.setUpstreamLocalAddress(address_provider.localAddress()); - upstream_info.setUpstreamRemoteAddress(address_provider.remoteAddress()); - } + StreamInfo::UpstreamInfo& upstream_info = *getStreamInfo().upstreamInfo(); + upstream_info.upstreamTiming().recordConnectionPoolCallbackLatency( + initial_upstream_connection_start_time_.value(), + read_callbacks_->connection().dispatcher().timeSource()); upstream_info.setUpstreamHost(host); + upstream_info.setUpstreamLocalAddress(address_provider.localAddress()); + upstream_info.setUpstreamRemoteAddress(address_provider.remoteAddress()); upstream_info.setUpstreamSslConnection(ssl_info); onUpstreamConnection(); read_callbacks_->continueReading(); if (info) { upstream_info.setUpstreamFilterState(info->filterState()); } -} // namespace TcpProxy +} const Router::MetadataMatchCriteria* Filter::metadataMatchCriteria() { const Router::MetadataMatchCriteria* route_criteria = @@ -672,30 +640,14 @@ const std::string& TunnelResponseTrailers::key() { } TunnelingConfigHelperImpl::TunnelingConfigHelperImpl( - Stats::Scope& stats_scope, - const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy& config_message, + const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig& + config_message, Server::Configuration::FactoryContext& context) - : use_post_(config_message.tunneling_config().use_post()), - header_parser_(Envoy::Router::HeaderParser::configure( - config_message.tunneling_config().headers_to_add())), - propagate_response_headers_(config_message.tunneling_config().propagate_response_headers()), - propagate_response_trailers_(config_message.tunneling_config().propagate_response_trailers()), - post_path_(config_message.tunneling_config().post_path()), - route_stat_name_storage_("tcpproxy_tunneling", context.scope().symbolTable()), - // TODO(vikaschoudhary16): figure out which of the following router_config_ members are - // not required by tcp_proxy and move them to a different class - router_config_(route_stat_name_storage_.statName(), - context.serverFactoryContext().localInfo(), stats_scope, - context.serverFactoryContext().clusterManager(), - context.serverFactoryContext().runtime(), - context.serverFactoryContext().api().randomGenerator(), - std::make_unique( - context.serverFactoryContext().clusterManager()), - true, false, false, false, false, false, {}, - context.serverFactoryContext().api().timeSource(), - context.serverFactoryContext().httpContext(), - context.serverFactoryContext().routerContext()), - server_factory_context_(context.serverFactoryContext()) { + : use_post_(config_message.use_post()), + header_parser_(Envoy::Router::HeaderParser::configure(config_message.headers_to_add())), + propagate_response_headers_(config_message.propagate_response_headers()), + propagate_response_trailers_(config_message.propagate_response_trailers()), + post_path_(config_message.post_path()) { if (!post_path_.empty() && !use_post_) { throw EnvoyException("Can't set a post path when POST method isn't used"); } @@ -703,7 +655,7 @@ TunnelingConfigHelperImpl::TunnelingConfigHelperImpl( envoy::config::core::v3::SubstitutionFormatString substitution_format_config; substitution_format_config.mutable_text_format_source()->set_inline_string( - config_message.tunneling_config().hostname()); + config_message.hostname()); hostname_fmt_ = Formatter::SubstitutionFormatStringUtils::fromProtoConfig( substitution_format_config, context); } @@ -745,8 +697,8 @@ void Filter::onConnectTimeout() { } Network::FilterStatus Filter::onData(Buffer::Instance& data, bool end_stream) { - ENVOY_CONN_LOG(trace, "downstream connection received {} bytes, end_stream={}, has upstream {}", - read_callbacks_->connection(), data.length(), end_stream, upstream_ != nullptr); + ENVOY_CONN_LOG(trace, "downstream connection received {} bytes, end_stream={}", + read_callbacks_->connection(), data.length(), end_stream); getStreamInfo().getDownstreamBytesMeter()->addWireBytesReceived(data.length()); if (upstream_) { getStreamInfo().getUpstreamBytesMeter()->addWireBytesSent(data.length()); @@ -847,23 +799,14 @@ void Filter::onUpstreamEvent(Network::ConnectionEvent event) { if (event == Network::ConnectionEvent::RemoteClose || event == Network::ConnectionEvent::LocalClose) { - if (Runtime::runtimeFeatureEnabled( - "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { - read_callbacks_->connection().dispatcher().deferredDelete(std::move(upstream_)); - } else { - upstream_.reset(); - } + upstream_.reset(); disableIdleTimer(); if (connecting) { if (event == Network::ConnectionEvent::RemoteClose) { - getStreamInfo().setResponseFlag(StreamInfo::UpstreamConnectionFailure); - // upstreamHost can be nullptr if we received a disconnect from the upstream before - // receiving any response - if (read_callbacks_->upstreamHost() != nullptr) { - read_callbacks_->upstreamHost()->outlierDetector().putResult( - Upstream::Outlier::Result::LocalOriginConnectFailed); - } + getStreamInfo().setResponseFlag(StreamInfo::CoreResponseFlag::UpstreamConnectionFailure); + read_callbacks_->upstreamHost()->outlierDetector().putResult( + Upstream::Outlier::Result::LocalOriginConnectFailed); } if (!downstream_closed_) { route_ = pickRoute(); @@ -987,9 +930,6 @@ void Filter::disableIdleTimer() { } } -Filter::HttpStreamDecoderFilterCallbacks::HttpStreamDecoderFilterCallbacks(Filter* parent) - : parent_(parent), request_trailer_map_(Http::RequestTrailerMapImpl::create()) {} - UpstreamDrainManager::~UpstreamDrainManager() { // If connections aren't closed before they are destructed an ASSERT fires, // so cancel all pending drains, which causes the connections to be closed. diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index 4019ea1211ef..45f34b901405 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -10,7 +10,6 @@ #include "envoy/common/random_generator.h" #include "envoy/event/timer.h" #include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" -#include "envoy/http/codec.h" #include "envoy/http/header_evaluator.h" #include "envoy/network/connection.h" #include "envoy/network/filter.h" @@ -23,7 +22,6 @@ #include "envoy/upstream/cluster_manager.h" #include "envoy/upstream/upstream.h" -#include "source/common/common/assert.h" #include "source/common/common/logger.h" #include "source/common/formatter/substitution_format_string.h" #include "source/common/http/header_map_impl.h" @@ -145,29 +143,24 @@ class TunnelResponseTrailers : public Http::TunnelResponseHeadersOrTrailersImpl private: const Http::ResponseTrailerMapPtr response_trailers_; }; -class Config; + class TunnelingConfigHelperImpl : public TunnelingConfigHelper, protected Logger::Loggable { public: TunnelingConfigHelperImpl( - Stats::Scope& scope, - const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy& config_message, + const envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig& + config_message, Server::Configuration::FactoryContext& context); std::string host(const StreamInfo::StreamInfo& stream_info) const override; bool usePost() const override { return use_post_; } const std::string& postPath() const override { return post_path_; } Envoy::Http::HeaderEvaluator& headerEvaluator() const override { return *header_parser_; } - - const Envoy::Router::FilterConfig& routerFilterConfig() const override { return router_config_; } void propagateResponseHeaders(Http::ResponseHeaderMapPtr&& headers, const StreamInfo::FilterStateSharedPtr& filter_state) const override; void propagateResponseTrailers(Http::ResponseTrailerMapPtr&& trailers, const StreamInfo::FilterStateSharedPtr& filter_state) const override; - Server::Configuration::ServerFactoryContext& serverFactoryContext() const override { - return server_factory_context_; - } private: const bool use_post_; @@ -176,9 +169,6 @@ class TunnelingConfigHelperImpl : public TunnelingConfigHelper, const bool propagate_response_headers_; const bool propagate_response_trailers_; std::string post_path_; - Stats::StatNameManagedStorage route_stat_name_storage_; - const Router::FilterConfig router_config_; - Server::Configuration::ServerFactoryContext& server_factory_context_; }; /** @@ -471,107 +461,6 @@ class Filter : public Network::ReadFilter, }; StreamInfo::StreamInfo& getStreamInfo(); - class HttpStreamDecoderFilterCallbacks : public Http::StreamDecoderFilterCallbacks, - public ScopeTrackedObject { - public: - HttpStreamDecoderFilterCallbacks(Filter* parent); - // Http::StreamDecoderFilterCallbacks - OptRef connection() override { - return parent_->read_callbacks_->connection(); - } - StreamInfo::StreamInfo& streamInfo() override { return parent_->getStreamInfo(); } - const ScopeTrackedObject& scope() override { return *this; } - Event::Dispatcher& dispatcher() override { - return parent_->read_callbacks_->connection().dispatcher(); - } - void resetStream(Http::StreamResetReason, absl::string_view) override { - IS_ENVOY_BUG("Not implemented. Unexpected call to resetStream()"); - }; - Router::RouteConstSharedPtr route() override { return route_; } - Upstream::ClusterInfoConstSharedPtr clusterInfo() override { - return parent_->cluster_manager_.getThreadLocalCluster(parent_->route_->clusterName()) - ->info(); - } - uint64_t streamId() const override { - auto sip = parent_->getStreamInfo().getStreamIdProvider(); - if (sip) { - return sip->toInteger().value(); - } - return 0; - } - Tracing::Span& activeSpan() override { return parent_->active_span_; } - OptRef tracingConfig() const override { - return makeOptRef(parent_->tracing_config_); - } - void continueDecoding() override {} - void addDecodedData(Buffer::Instance&, bool) override {} - void injectDecodedDataToFilterChain(Buffer::Instance&, bool) override {} - Http::RequestTrailerMap& addDecodedTrailers() override { return *request_trailer_map_; } - Http::MetadataMapVector& addDecodedMetadata() override { - static Http::MetadataMapVector metadata_map_vector; - return metadata_map_vector; - } - const Buffer::Instance* decodingBuffer() override { return nullptr; } - void modifyDecodingBuffer(std::function) override {} - void sendLocalReply(Http::Code, absl::string_view, - std::function, - const absl::optional, - absl::string_view) override {} - void encode1xxHeaders(Http::ResponseHeaderMapPtr&&) override {} - Http::ResponseHeaderMapOptRef informationalHeaders() override { return {}; } - void encodeHeaders(Http::ResponseHeaderMapPtr&&, bool, absl::string_view) override {} - Http::ResponseHeaderMapOptRef responseHeaders() override { return {}; } - void encodeData(Buffer::Instance&, bool) override {} - Http::RequestHeaderMapOptRef requestHeaders() override { return {}; } - Http::RequestTrailerMapOptRef requestTrailers() override { return {}; } - void encodeTrailers(Http::ResponseTrailerMapPtr&&) override {} - Http::ResponseTrailerMapOptRef responseTrailers() override { return {}; } - void encodeMetadata(Http::MetadataMapPtr&&) override {} - // TODO(vikaschoudhary16): Implement watermark callbacks and test through flow control e2es. - void onDecoderFilterAboveWriteBufferHighWatermark() override {} - void onDecoderFilterBelowWriteBufferLowWatermark() override {} - void addDownstreamWatermarkCallbacks(Http::DownstreamWatermarkCallbacks&) override {} - void removeDownstreamWatermarkCallbacks(Http::DownstreamWatermarkCallbacks&) override {} - void setDecoderBufferLimit(uint32_t) override {} - uint32_t decoderBufferLimit() override { return 0; } - bool recreateStream(const Http::ResponseHeaderMap*) override { return false; } - void addUpstreamSocketOptions(const Network::Socket::OptionsSharedPtr&) override {} - Network::Socket::OptionsSharedPtr getUpstreamSocketOptions() const override { return nullptr; } - const Router::RouteSpecificFilterConfig* mostSpecificPerFilterConfig() const override { - return nullptr; - } - Buffer::BufferMemoryAccountSharedPtr account() const override { return nullptr; } - void setUpstreamOverrideHost(Upstream::LoadBalancerContext::OverrideHost) override {} - absl::optional - upstreamOverrideHost() const override { - return absl::nullopt; - } - void restoreContextOnContinue(ScopeTrackedObjectStack& tracked_object_stack) override { - tracked_object_stack.add(*this); - } - void traversePerFilterConfig( - std::function) const override {} - Http::Http1StreamEncoderOptionsOptRef http1StreamEncoderOptions() override { return {}; } - OptRef downstreamCallbacks() override { return {}; } - OptRef upstreamCallbacks() override { return {}; } - void resetIdleTimer() override {} - // absl::optional upstreamOverrideHost() const override { - // return absl::nullopt; - // } - absl::string_view filterConfigName() const override { return ""; } - - // ScopeTrackedObject - void dumpState(std::ostream& os, int indent_level) const override { - const char* spaces = spacesForLevel(indent_level); - os << spaces << "TcpProxy " << this << DUMP_MEMBER(streamId()) << "\n"; - DUMP_DETAILS(parent_->getStreamInfo().upstreamInfo()); - } - Filter* parent_{}; - Http::RequestTrailerMapPtr request_trailer_map_; - std::shared_ptr route_; - }; - Tracing::NullSpan active_span_; - const Tracing::Config& tracing_config_; protected: struct DownstreamCallbacks : public Network::ConnectionCallbacks { @@ -656,7 +545,6 @@ class Filter : public Network::ReadFilter, uint32_t connect_attempts_{}; bool connecting_{}; bool downstream_closed_{}; - HttpStreamDecoderFilterCallbacks upstream_decoder_filter_callbacks_; }; // This class deals with an upstream connection that needs to finish flushing, when the downstream diff --git a/source/common/tcp_proxy/upstream.cc b/source/common/tcp_proxy/upstream.cc index 01e99426b9f6..5e4eaa35338d 100644 --- a/source/common/tcp_proxy/upstream.cc +++ b/source/common/tcp_proxy/upstream.cc @@ -1,19 +1,16 @@ #include "source/common/tcp_proxy/upstream.h" -#include "envoy/http/header_map.h" #include "envoy/upstream/cluster_manager.h" #include "source/common/http/codec_client.h" #include "source/common/http/codes.h" #include "source/common/http/header_map_impl.h" #include "source/common/http/headers.h" -#include "source/common/http/null_route_impl.h" #include "source/common/http/utility.h" #include "source/common/runtime/runtime_features.h" namespace Envoy { namespace TcpProxy { - using TunnelingConfig = envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig; @@ -197,7 +194,6 @@ void HttpUpstream::resetEncoder(Network::ConnectionEvent event, bool inform_down conn_pool_callbacks_->onFailure(); return; } - if (inform_downstream) { upstream_callbacks_.onEvent(event); } @@ -233,7 +229,7 @@ TcpConnPool::~TcpConnPool() { void TcpConnPool::newStream(GenericConnectionPoolCallbacks& callbacks) { callbacks_ = &callbacks; - // Given this function is re-entrant, make sure we only reset the upstream_handle_ if given a + // Given this function is reentrant, make sure we only reset the upstream_handle_ if given a // valid connection handle. If newConnection fails inline it may result in attempting to // select a new host, and a recursive call to establishUpstreamConnection. In this case the // first call to newConnection will return null and the inner call will persist. @@ -274,67 +270,29 @@ HttpConnPool::HttpConnPool(Upstream::ThreadLocalCluster& thread_local_cluster, Upstream::LoadBalancerContext* context, const TunnelingConfigHelper& config, Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, - Http::StreamDecoderFilterCallbacks& stream_decoder_callbacks, Http::CodecType type, StreamInfo::StreamInfo& downstream_info) - : config_(config), type_(type), decoder_filter_callbacks_(&stream_decoder_callbacks), - upstream_callbacks_(upstream_callbacks), downstream_info_(downstream_info) { + : config_(config), type_(type), upstream_callbacks_(upstream_callbacks), + downstream_info_(downstream_info) { absl::optional protocol; if (type_ == Http::CodecType::HTTP3) { protocol = Http::Protocol::Http3; } else if (type_ == Http::CodecType::HTTP2) { protocol = Http::Protocol::Http2; } - if (Runtime::runtimeFeatureEnabled( - "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { - absl::optional upstream_protocol = protocol; - generic_conn_pool_ = createConnPool(thread_local_cluster, context, upstream_protocol); - return; - } conn_pool_data_ = thread_local_cluster.httpConnPool(Upstream::ResourcePriority::Default, protocol, context); } -std::unique_ptr -HttpConnPool::createConnPool(Upstream::ThreadLocalCluster& cluster, - Upstream::LoadBalancerContext* context, - absl::optional protocol) { - Router::GenericConnPoolFactory* factory = nullptr; - factory = Envoy::Config::Utility::getFactoryByName( - "envoy.filters.connection_pools.http.generic"); - if (!factory) { - return nullptr; - } - - return factory->createGenericConnPool( - cluster, Envoy::Router::GenericConnPoolFactory::UpstreamProtocol::HTTP, - decoder_filter_callbacks_->route()->routeEntry()->priority(), protocol, context); -} - HttpConnPool::~HttpConnPool() { if (upstream_handle_ != nullptr) { // Because HTTP connections are generally shorter lived and have a higher probability of use // before going idle, they are closed with Default rather than CloseExcess. upstream_handle_->cancel(ConnectionPool::CancelPolicy::Default); } - if (combined_upstream_ != nullptr) { - combined_upstream_->onDownstreamEvent(Network::ConnectionEvent::LocalClose); - } } void HttpConnPool::newStream(GenericConnectionPoolCallbacks& callbacks) { callbacks_ = &callbacks; - if (Runtime::runtimeFeatureEnabled( - "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { - combined_upstream_ = std::make_unique( - *this, upstream_callbacks_, *decoder_filter_callbacks_, config_, downstream_info_); - RouterUpstreamRequestPtr upstream_request = std::make_unique( - *combined_upstream_, std::move(generic_conn_pool_), /*can_send_early_data_=*/false, - /*can_use_http3_=*/true, true /*enable_tcp_tunneling*/); - combined_upstream_->setRouterUpstreamRequest(std::move(upstream_request)); - combined_upstream_->newStream(callbacks); - return; - } - upstream_ = std::make_unique(upstream_callbacks_, config_, downstream_info_, type_); Tcp::ConnectionPool::Cancellable* handle = conn_pool_data_.value().newStream(upstream_->responseDecoder(), *this, @@ -352,15 +310,6 @@ void HttpConnPool::onPoolFailure(ConnectionPool::PoolFailureReason reason, callbacks_->onGenericPoolFailure(reason, failure_reason, host); } -void HttpConnPool::onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host, - bool pool_success) { - if (!pool_success) { - return; - } - combined_upstream_->setConnPoolCallbacks(std::make_unique( - *this, host, downstream_info_.downstreamAddressProvider().sslConnection())); -} - void HttpConnPool::onPoolReady(Http::RequestEncoder& request_encoder, Upstream::HostDescriptionConstSharedPtr host, StreamInfo::StreamInfo& info, absl::optional) { @@ -383,174 +332,8 @@ void HttpConnPool::onPoolReady(Http::RequestEncoder& request_encoder, void HttpConnPool::onGenericPoolReady(Upstream::HostDescriptionConstSharedPtr& host, const Network::ConnectionInfoProvider& address_provider, Ssl::ConnectionInfoConstSharedPtr ssl_info) { - if (Runtime::runtimeFeatureEnabled( - "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { - - callbacks_->onGenericPoolReady(nullptr, std::move(combined_upstream_), host, address_provider, - ssl_info); - return; - } callbacks_->onGenericPoolReady(nullptr, std::move(upstream_), host, address_provider, ssl_info); } -CombinedUpstream::CombinedUpstream(HttpConnPool& http_conn_pool, - Tcp::ConnectionPool::UpstreamCallbacks& callbacks, - Http::StreamDecoderFilterCallbacks& decoder_callbacks, - const TunnelingConfigHelper& config, - StreamInfo::StreamInfo& downstream_info) - : config_(config), downstream_info_(downstream_info), parent_(http_conn_pool), - decoder_filter_callbacks_(decoder_callbacks), response_decoder_(*this), - upstream_callbacks_(callbacks) { - auto is_ssl = downstream_info_.downstreamAddressProvider().sslConnection(); - downstream_headers_ = Http::createHeaderMap({ - {Http::Headers::get().Method, config_.usePost() ? "POST" : "CONNECT"}, - {Http::Headers::get().Host, config_.host(downstream_info_)}, - }); - - if (config_.usePost()) { - const std::string& scheme = - is_ssl ? Http::Headers::get().SchemeValues.Https : Http::Headers::get().SchemeValues.Http; - downstream_headers_->addReference(Http::Headers::get().Path, config_.postPath()); - downstream_headers_->addReference(Http::Headers::get().Scheme, scheme); - } - - config_.headerEvaluator().evaluateHeaders( - *downstream_headers_, {downstream_info_.getRequestHeaders()}, downstream_info_); -} - -void CombinedUpstream::setRouterUpstreamRequest( - Router::UpstreamRequestPtr router_upstream_request) { - LinkedList::moveIntoList(std::move(router_upstream_request), upstream_requests_); -} - -void CombinedUpstream::newStream(GenericConnectionPoolCallbacks&) { - upstream_requests_.front()->acceptHeadersFromRouter(false); -} - -void CombinedUpstream::encodeData(Buffer::Instance& data, bool end_stream) { - if (upstream_requests_.empty()) { - return; - } - upstream_requests_.front()->acceptDataFromRouter(data, end_stream); - if (end_stream) { - doneWriting(); - } -} - -bool CombinedUpstream::readDisable(bool disable) { - if (upstream_requests_.empty()) { - return false; - } - if (disable) { - upstream_requests_.front()->onAboveWriteBufferHighWatermark(); - } - return true; -} - -Tcp::ConnectionPool::ConnectionData* -CombinedUpstream::onDownstreamEvent(Network::ConnectionEvent event) { - if (upstream_requests_.empty()) { - return nullptr; - } - - if (event == Network::ConnectionEvent::LocalClose || - event == Network::ConnectionEvent::RemoteClose) { - upstream_requests_.front()->resetStream(); - } - return nullptr; -} - -bool CombinedUpstream::isValidResponse(const Http::ResponseHeaderMap& headers) { - switch (parent_.codecType()) { - case Http::CodecType::HTTP1: - // According to RFC7231 any 2xx response indicates that the connection is - // established. - // Any 'Content-Length' or 'Transfer-Encoding' header fields MUST be ignored. - // https://tools.ietf.org/html/rfc7231#section-4.3.6 - return Http::CodeUtility::is2xx(Http::Utility::getResponseStatus(headers)); - case Http::CodecType::HTTP2: - case Http::CodecType::HTTP3: - if (Http::Utility::getResponseStatus(headers) != 200) { - return false; - } - return true; - } - return true; -} - -void CombinedUpstream::onResetEncoder(Network::ConnectionEvent event, bool inform_downstream) { - if (event == Network::ConnectionEvent::LocalClose || - event == Network::ConnectionEvent::RemoteClose) { - if (!upstream_requests_.empty()) { - upstream_requests_.front()->resetStream(); - } - } - - // If we did not receive a valid CONNECT response yet we treat this as a pool - // failure, otherwise we forward the event downstream. - if (conn_pool_callbacks_ != nullptr) { - conn_pool_callbacks_->onFailure(); - return; - } - - if (inform_downstream) { - upstream_callbacks_.onEvent(event); - } -} - -// Router::RouterFilterInterface -void CombinedUpstream::onUpstreamHeaders([[maybe_unused]] uint64_t response_code, - Http::ResponseHeaderMapPtr&& headers, - [[maybe_unused]] UpstreamRequest& upstream_request, - bool end_stream) { - responseDecoder().decodeHeaders(std::move(headers), end_stream); -} - -void CombinedUpstream::onUpstreamData(Buffer::Instance& data, - [[maybe_unused]] UpstreamRequest& upstream_request, - bool end_stream) { - responseDecoder().decodeData(data, end_stream); -} - -void CombinedUpstream::onUpstreamTrailers(Http::ResponseTrailerMapPtr&& trailers, - UpstreamRequest&) { - responseDecoder().decodeTrailers(std::move(trailers)); -} - -Http::RequestHeaderMap* CombinedUpstream::downstreamHeaders() { return downstream_headers_.get(); } - -void CombinedUpstream::doneReading() { - read_half_closed_ = true; - if (write_half_closed_) { - onResetEncoder(Network::ConnectionEvent::LocalClose); - } -} - -void CombinedUpstream::onUpstreamReset(Http::StreamResetReason, absl::string_view, - UpstreamRequest&) { - upstream_callbacks_.onEvent(Network::ConnectionEvent::RemoteClose); -} - -void CombinedUpstream::doneWriting() { - write_half_closed_ = true; - if (read_half_closed_) { - onResetEncoder(Network::ConnectionEvent::LocalClose); - } -} - -void CombinedUpstream::onResetStream(Http::StreamResetReason, absl::string_view) { - read_half_closed_ = true; - write_half_closed_ = true; - onResetEncoder(Network::ConnectionEvent::LocalClose); -} - -void CombinedUpstream::onAboveWriteBufferHighWatermark() { - upstream_callbacks_.onAboveWriteBufferHighWatermark(); -} - -void CombinedUpstream::onBelowWriteBufferLowWatermark() { - upstream_callbacks_.onBelowWriteBufferLowWatermark(); -} - } // namespace TcpProxy } // namespace Envoy diff --git a/source/common/tcp_proxy/upstream.h b/source/common/tcp_proxy/upstream.h index 23af532b37cb..d16126547cc5 100644 --- a/source/common/tcp_proxy/upstream.h +++ b/source/common/tcp_proxy/upstream.h @@ -1,11 +1,7 @@ #pragma once -#include - #include "envoy/http/conn_pool.h" -#include "envoy/http/header_map.h" #include "envoy/network/connection.h" -#include "envoy/router/router_ratelimit.h" #include "envoy/tcp/conn_pool.h" #include "envoy/tcp/upstream.h" #include "envoy/upstream/load_balancer.h" @@ -15,13 +11,7 @@ #include "source/common/buffer/buffer_impl.h" #include "source/common/common/dump_state_utils.h" #include "source/common/http/codec_client.h" -#include "source/common/http/hash_policy.h" -#include "source/common/http/null_route_impl.h" -#include "source/common/network/utility.h" -#include "source/common/router/config_impl.h" #include "source/common/router/header_parser.h" -#include "source/common/router/router.h" -#include "source/extensions/early_data/default_early_data_policy.h" namespace Envoy { namespace TcpProxy { @@ -57,25 +47,16 @@ class TcpConnPool : public GenericConnPool, public Tcp::ConnectionPool::Callback }; class HttpUpstream; -class CombinedUpstream; class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callbacks { public: HttpConnPool(Upstream::ThreadLocalCluster& thread_local_cluster, Upstream::LoadBalancerContext* context, const TunnelingConfigHelper& config, - Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, - Http::StreamDecoderFilterCallbacks&, Http::CodecType type, + Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, Http::CodecType type, StreamInfo::StreamInfo& downstream_info); - - using RouterUpstreamRequest = Router::UpstreamRequest; - using RouterUpstreamRequestPtr = std::unique_ptr; ~HttpConnPool() override; - bool valid() const { return conn_pool_data_.has_value() || generic_conn_pool_; } - Http::CodecType codecType() const { return type_; } - std::unique_ptr createConnPool(Upstream::ThreadLocalCluster&, - Upstream::LoadBalancerContext* context, - absl::optional protocol); + bool valid() const { return conn_pool_data_.has_value(); } // GenericConnPool void newStream(GenericConnectionPoolCallbacks& callbacks) override; @@ -88,32 +69,16 @@ class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callba Upstream::HostDescriptionConstSharedPtr host, StreamInfo::StreamInfo& info, absl::optional) override; - void onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr, bool); - void onHttpPoolReady(Upstream::HostDescriptionConstSharedPtr& host, - Ssl::ConnectionInfoConstSharedPtr ssl_info); - class Callbacks { public: Callbacks(HttpConnPool& conn_pool, Upstream::HostDescriptionConstSharedPtr host, Ssl::ConnectionInfoConstSharedPtr ssl_info) : conn_pool_(&conn_pool), host_(host), ssl_info_(ssl_info) {} virtual ~Callbacks() = default; - virtual void onSuccess(Http::RequestEncoder* request_encoder) { + virtual void onSuccess(Http::RequestEncoder& request_encoder) { ASSERT(conn_pool_ != nullptr); - if (!Runtime::runtimeFeatureEnabled( - "envoy.restart_features.upstream_http_filters_with_tcp_proxy")) { - ASSERT(request_encoder != nullptr); - conn_pool_->onGenericPoolReady(host_, request_encoder->getStream().connectionInfoProvider(), - ssl_info_); - return; - } - - Network::ConnectionInfoProviderSharedPtr local_connection_info_provider( - std::make_shared( - Network::Utility::getCanonicalIpv4LoopbackAddress(), - Network::Utility::getCanonicalIpv4LoopbackAddress())); - - conn_pool_->onGenericPoolReady(host_, *local_connection_info_provider.get(), ssl_info_); + conn_pool_->onGenericPoolReady(host_, request_encoder.getStream().connectionInfoProvider(), + ssl_info_); } virtual void onFailure() { ASSERT(conn_pool_ != nullptr); @@ -139,12 +104,9 @@ class HttpConnPool : public GenericConnPool, public Http::ConnectionPool::Callba absl::optional conn_pool_data_{}; Http::ConnectionPool::Cancellable* upstream_handle_{}; GenericConnectionPoolCallbacks* callbacks_{}; - Http::StreamDecoderFilterCallbacks* decoder_filter_callbacks_; Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks_; std::unique_ptr upstream_; - std::unique_ptr combined_upstream_; StreamInfo::StreamInfo& downstream_info_; - std::unique_ptr generic_conn_pool_; }; class TcpUpstream : public GenericUpstream { @@ -168,7 +130,6 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { public: using TunnelingConfig = envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig; - HttpUpstream(Tcp::ConnectionPool::UpstreamCallbacks& callbacks, const TunnelingConfigHelper& config, StreamInfo::StreamInfo& downstream_info, Http::CodecType type); @@ -194,7 +155,7 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { void onAboveWriteBufferHighWatermark() override; void onBelowWriteBufferLowWatermark() override; - virtual void setRequestEncoder(Http::RequestEncoder& request_encoder, bool is_ssl); + void setRequestEncoder(Http::RequestEncoder& request_encoder, bool is_ssl); void setConnPoolCallbacks(std::unique_ptr&& callbacks) { conn_pool_callbacks_ = std::move(callbacks); } @@ -209,13 +170,12 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { const TunnelingConfigHelper& config_; // The downstream info that is owned by the downstream connection. StreamInfo::StreamInfo& downstream_info_; - std::unique_ptr downstream_headers_; private: - Upstream::ClusterInfoConstSharedPtr cluster_; class DecoderShim : public Http::ResponseDecoder { public: DecoderShim(HttpUpstream& parent) : parent_(parent) {} + // Http::ResponseDecoder void decode1xxHeaders(Http::ResponseHeaderMapPtr&&) override {} void decodeHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_stream) override { bool is_valid_response = parent_.isValidResponse(*headers); @@ -224,7 +184,7 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { if (!is_valid_response || end_stream) { parent_.resetEncoder(Network::ConnectionEvent::LocalClose); } else if (parent_.conn_pool_callbacks_ != nullptr) { - parent_.conn_pool_callbacks_->onSuccess(parent_.request_encoder_); + parent_.conn_pool_callbacks_->onSuccess(*parent_.request_encoder_); parent_.conn_pool_callbacks_.reset(); } } @@ -264,133 +224,5 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { std::unique_ptr conn_pool_callbacks_; }; -class CombinedUpstream : public GenericUpstream, - protected Http::StreamCallbacks, - public Envoy::Router::RouterFilterInterface { -public: - CombinedUpstream(HttpConnPool& http_conn_pool, Tcp::ConnectionPool::UpstreamCallbacks& callbacks, - Http::StreamDecoderFilterCallbacks& decoder_callbacks, - const TunnelingConfigHelper& config, StreamInfo::StreamInfo& downstream_info); - ~CombinedUpstream() override = default; - using UpstreamRequest = Router::UpstreamRequest; - Http::ResponseDecoder& responseDecoder() { return response_decoder_; } - void doneReading(); - void doneWriting(); - using UpstreamRequestPtr = std::unique_ptr; - void setRouterUpstreamRequest(UpstreamRequestPtr); - void newStream(GenericConnectionPoolCallbacks& callbacks); - void encodeData(Buffer::Instance& data, bool end_stream) override; - Tcp::ConnectionPool::ConnectionData* onDownstreamEvent(Network::ConnectionEvent event) override; - bool isValidResponse(const Http::ResponseHeaderMap&); - bool readDisable(bool disable) override; - void setConnPoolCallbacks(std::unique_ptr&& callbacks) { - conn_pool_callbacks_ = std::move(callbacks); - } - void addBytesSentCallback(Network::Connection::BytesSentCb) override{}; - // HTTP upstream must not implement converting upstream transport - // socket from non-secure to secure mode. - bool startUpstreamSecureTransport() override { return false; } - Ssl::ConnectionInfoConstSharedPtr getUpstreamConnectionSslInfo() override { return nullptr; } - - // Http::StreamCallbacks - void onResetStream(Http::StreamResetReason reason, - absl::string_view transport_failure_reason) override; - void onAboveWriteBufferHighWatermark() override; - void onBelowWriteBufferLowWatermark() override; - - // Router::RouterFilterInterface - void onUpstreamHeaders(uint64_t response_code, Http::ResponseHeaderMapPtr&& headers, - UpstreamRequest& upstream_request, bool end_stream) override; - void onUpstreamData(Buffer::Instance& data, UpstreamRequest& upstream_request, - bool end_stream) override; - void onUpstream1xxHeaders(Http::ResponseHeaderMapPtr&&, UpstreamRequest&) override {} - void onUpstreamTrailers(Http::ResponseTrailerMapPtr&&, UpstreamRequest&) override; - void onUpstreamMetadata(Http::MetadataMapPtr&&) override {} - void onUpstreamReset(Http::StreamResetReason stream_reset_reason, - absl::string_view transport_failure_reason, UpstreamRequest&) override; - void onUpstreamHostSelected(Upstream::HostDescriptionConstSharedPtr host, - bool pool_success) override { - parent_.onUpstreamHostSelected(host, pool_success); - } - void onPerTryTimeout(UpstreamRequest&) override {} - void onPerTryIdleTimeout(UpstreamRequest&) override {} - void onStreamMaxDurationReached(UpstreamRequest&) override {} - Http::StreamDecoderFilterCallbacks* callbacks() override { return &decoder_filter_callbacks_; } - Upstream::ClusterInfoConstSharedPtr cluster() override { - return decoder_filter_callbacks_.clusterInfo(); - } - Router::FilterConfig& config() override { - return const_cast(config_.routerFilterConfig()); - } - Router::TimeoutData timeout() override { return {}; } - absl::optional dynamicMaxStreamDuration() const override { - return absl::nullopt; - } - Http::RequestHeaderMap* downstreamHeaders() override; - Http::RequestTrailerMap* downstreamTrailers() override { return nullptr; } - bool downstreamResponseStarted() const override { return false; } - bool downstreamEndStream() const override { return false; } - uint32_t attemptCount() const override { return 0; } - -protected: - void onResetEncoder(Network::ConnectionEvent event, bool inform_downstream = true); - - // The config object that is owned by the downstream network filter chain factory. - const TunnelingConfigHelper& config_; - // The downstream info that is owned by the downstream connection. - StreamInfo::StreamInfo& downstream_info_; - std::list upstream_requests_; - std::unique_ptr downstream_headers_; - HttpConnPool& parent_; - -private: - Http::StreamDecoderFilterCallbacks& decoder_filter_callbacks_; - class DecoderShim : public Http::ResponseDecoder { - public: - DecoderShim(CombinedUpstream& parent) : parent_(parent) {} - // Http::ResponseDecoder - void decode1xxHeaders(Http::ResponseHeaderMapPtr&&) override {} - void decodeHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_stream) override { - bool is_valid_response = parent_.isValidResponse(*headers); - parent_.config_.propagateResponseHeaders(std::move(headers), - parent_.downstream_info_.filterState()); - if (!is_valid_response || end_stream) { - parent_.onResetEncoder(Network::ConnectionEvent::LocalClose); - } else if (parent_.conn_pool_callbacks_ != nullptr) { - parent_.conn_pool_callbacks_->onSuccess(nullptr /*parent_.request_encoder_*/); - parent_.conn_pool_callbacks_.reset(); - } - } - void decodeData(Buffer::Instance& data, bool end_stream) override { - parent_.upstream_callbacks_.onUpstreamData(data, end_stream); - if (end_stream) { - parent_.doneReading(); - } - } - void decodeTrailers(Http::ResponseTrailerMapPtr&& trailers) override { - parent_.config_.propagateResponseTrailers(std::move(trailers), - parent_.downstream_info_.filterState()); - if (Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.tcp_tunneling_send_downstream_fin_on_upstream_trailers")) { - Buffer::OwnedImpl data; - parent_.upstream_callbacks_.onUpstreamData(data, /* end_stream = */ true); - } - parent_.doneReading(); - } - void decodeMetadata(Http::MetadataMapPtr&&) override {} - void dumpState(std::ostream& os, int indent_level) const override { - DUMP_STATE_UNIMPLEMENTED(DecoderShim); - } - - private: - CombinedUpstream& parent_; - }; - DecoderShim response_decoder_; - Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks_; - std::unique_ptr conn_pool_callbacks_; - bool read_half_closed_{}; - bool write_half_closed_{}; -}; - } // namespace TcpProxy } // namespace Envoy diff --git a/source/extensions/upstreams/http/http/upstream_request.h b/source/extensions/upstreams/http/http/upstream_request.h index db8adfc2cc14..d7b29d7c8d53 100644 --- a/source/extensions/upstreams/http/http/upstream_request.h +++ b/source/extensions/upstreams/http/http/upstream_request.h @@ -73,7 +73,6 @@ class HttpUpstream : public Router::GenericUpstream, public Envoy::Http::StreamC void encodeTrailers(const Envoy::Http::RequestTrailerMap& trailers) override { request_encoder_->encodeTrailers(trailers); } - void enableHalfClose() override { request_encoder_->enableTcpTunneling(); } void readDisable(bool disable) override { request_encoder_->getStream().readDisable(disable); } diff --git a/source/extensions/upstreams/http/tcp/upstream_request.h b/source/extensions/upstreams/http/tcp/upstream_request.h index 8c7431250c1e..87e9431fbd30 100644 --- a/source/extensions/upstreams/http/tcp/upstream_request.h +++ b/source/extensions/upstreams/http/tcp/upstream_request.h @@ -72,7 +72,6 @@ class TcpUpstream : public Router::GenericUpstream, void encodeMetadata(const Envoy::Http::MetadataMapVector&) override {} Envoy::Http::Status encodeHeaders(const Envoy::Http::RequestHeaderMap&, bool end_stream) override; void encodeTrailers(const Envoy::Http::RequestTrailerMap&) override; - void enableHalfClose() override {} void readDisable(bool disable) override; void resetStream() override; void setAccount(Buffer::BufferMemoryAccountSharedPtr) override {} diff --git a/source/extensions/upstreams/http/udp/upstream_request.h b/source/extensions/upstreams/http/udp/upstream_request.h index b5f6e25e6540..4d4a132411e1 100644 --- a/source/extensions/upstreams/http/udp/upstream_request.h +++ b/source/extensions/upstreams/http/udp/upstream_request.h @@ -77,7 +77,6 @@ class UdpUpstream : public Router::GenericUpstream, void encodeTrailers(const Envoy::Http::RequestTrailerMap&) override {} void readDisable(bool) override {} void resetStream() override; - void enableHalfClose() override {} void setAccount(Buffer::BufferMemoryAccountSharedPtr) override {} const StreamInfo::BytesMeterSharedPtr& bytesMeter() override { return bytes_meter_; } diff --git a/source/extensions/upstreams/tcp/generic/BUILD b/source/extensions/upstreams/tcp/generic/BUILD index ea02233167e3..a29fa7133934 100644 --- a/source/extensions/upstreams/tcp/generic/BUILD +++ b/source/extensions/upstreams/tcp/generic/BUILD @@ -18,7 +18,6 @@ envoy_cc_extension( ], visibility = ["//visibility:public"], deps = [ - "//envoy/http:filter_interface", "//envoy/stream_info:bool_accessor_interface", "//envoy/stream_info:filter_state_interface", "//source/common/http:codec_client_lib", diff --git a/source/extensions/upstreams/tcp/generic/config.cc b/source/extensions/upstreams/tcp/generic/config.cc index d3e33fdb83f8..e688ab84a510 100644 --- a/source/extensions/upstreams/tcp/generic/config.cc +++ b/source/extensions/upstreams/tcp/generic/config.cc @@ -18,7 +18,6 @@ TcpProxy::GenericConnPoolPtr GenericConnPoolFactory::createGenericConnPool( Upstream::ThreadLocalCluster& thread_local_cluster, TcpProxy::TunnelingConfigHelperOptConstRef config, Upstream::LoadBalancerContext* context, Envoy::Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, - Http::StreamDecoderFilterCallbacks& stream_decoder_callbacks, StreamInfo::StreamInfo& downstream_info) const { if (config.has_value() && !disableTunnelingByFilterState(downstream_info)) { Http::CodecType pool_type; @@ -31,8 +30,7 @@ TcpProxy::GenericConnPoolPtr GenericConnPoolFactory::createGenericConnPool( pool_type = Http::CodecType::HTTP1; } auto ret = std::make_unique( - thread_local_cluster, context, *config, upstream_callbacks, stream_decoder_callbacks, - pool_type, downstream_info); + thread_local_cluster, context, *config, upstream_callbacks, pool_type, downstream_info); return (ret->valid() ? std::move(ret) : nullptr); } auto ret = std::make_unique(thread_local_cluster, context, diff --git a/source/extensions/upstreams/tcp/generic/config.h b/source/extensions/upstreams/tcp/generic/config.h index e7e87a0145c3..c4fee935ef96 100644 --- a/source/extensions/upstreams/tcp/generic/config.h +++ b/source/extensions/upstreams/tcp/generic/config.h @@ -1,7 +1,6 @@ #pragma once #include "envoy/extensions/upstreams/tcp/generic/v3/generic_connection_pool.pb.h" -#include "envoy/http/filter.h" #include "envoy/registry/registry.h" #include "envoy/tcp/upstream.h" @@ -23,7 +22,6 @@ class GenericConnPoolFactory : public TcpProxy::GenericConnPoolFactory { TcpProxy::TunnelingConfigHelperOptConstRef config, Upstream::LoadBalancerContext* context, Envoy::Tcp::ConnectionPool::UpstreamCallbacks& upstream_callbacks, - Http::StreamDecoderFilterCallbacks& stream_decoder_callbacks, StreamInfo::StreamInfo& downstream_info) const override; ProtobufTypes::MessagePtr createEmptyConfigProto() override { diff --git a/test/common/router/upstream_request_test.cc b/test/common/router/upstream_request_test.cc index bb9a8185d4b4..67a6b003cd36 100644 --- a/test/common/router/upstream_request_test.cc +++ b/test/common/router/upstream_request_test.cc @@ -33,9 +33,8 @@ class UpstreamRequestTest : public testing::Test { void initialize() { auto conn_pool = std::make_unique>(); conn_pool_ = conn_pool.get(); - upstream_request_ = - std::make_unique(router_filter_interface_, std::move(conn_pool), false, - true, false /*enable_tcp_tunneling*/); + upstream_request_ = std::make_unique(router_filter_interface_, + std::move(conn_pool), false, true); } Http::FilterFactoryCb createDecoderFilterFactoryCb(Http::StreamDecoderFilterSharedPtr filter) { return [filter](Http::FilterChainFactoryCallbacks& callbacks) { diff --git a/test/common/tcp_proxy/BUILD b/test/common/tcp_proxy/BUILD index d6c47b8a9cbc..0630b106fd5c 100644 --- a/test/common/tcp_proxy/BUILD +++ b/test/common/tcp_proxy/BUILD @@ -79,13 +79,9 @@ envoy_cc_test( deps = [ "//source/common/tcp_proxy", "//test/mocks/http:http_mocks", - "//test/mocks/router:router_filter_interface", - "//test/mocks/router:upstream_request", "//test/mocks/server:factory_context_mocks", - "//test/mocks/stats:stats_mocks", "//test/mocks/tcp:tcp_mocks", "//test/mocks/upstream:cluster_manager_mocks", - "//test/mocks/upstream:load_balancer_context_mock", "//test/test_common:test_runtime_lib", ], ) diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 144e50f2fbbc..41ad163dba41 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -171,7 +171,7 @@ class TcpProxyTest : public TcpProxyTestBase { std::shared_ptr> mock_access_logger_; }; -TEST_P(TcpProxyTest, ExplicitCluster) { +TEST_F(TcpProxyTest, ExplicitCluster) { configure(defaultConfig()); NiceMock connection; @@ -179,7 +179,7 @@ TEST_P(TcpProxyTest, ExplicitCluster) { } // Tests that half-closes are proxied and don't themselves cause any connection to be closed. -TEST_P(TcpProxyTest, HalfCloseProxy) { +TEST_F(TcpProxyTest, HalfCloseProxy) { setup(1); EXPECT_CALL(filter_callbacks_.connection_, close(_)).Times(0); @@ -200,7 +200,7 @@ TEST_P(TcpProxyTest, HalfCloseProxy) { } // Test with an explicitly configured upstream. -TEST_P(TcpProxyTest, ExplicitFactory) { +TEST_F(TcpProxyTest, ExplicitFactory) { // Explicitly configure an HTTP upstream, to test factory creation. auto& info = factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_ .cluster_.info_; @@ -224,7 +224,7 @@ TEST_P(TcpProxyTest, ExplicitFactory) { } // Test nothing bad happens if an invalid factory is configured. -TEST_P(TcpProxyTest, BadFactory) { +TEST_F(TcpProxyTest, BadFactory) { auto& info = factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_ .cluster_.info_; info->upstream_config_ = std::make_unique(); @@ -262,7 +262,7 @@ TEST_P(TcpProxyTest, BadFactory) { } // Test that downstream is closed after an upstream LocalClose. -TEST_P(TcpProxyTest, UpstreamLocalDisconnect) { +TEST_F(TcpProxyTest, UpstreamLocalDisconnect) { setup(1); raiseEventUpstreamConnected(0); @@ -280,7 +280,7 @@ TEST_P(TcpProxyTest, UpstreamLocalDisconnect) { } // Test that downstream is closed after an upstream RemoteClose. -TEST_P(TcpProxyTest, UpstreamRemoteDisconnect) { +TEST_F(TcpProxyTest, UpstreamRemoteDisconnect) { setup(1); timeSystem().advanceTimeWait(std::chrono::microseconds(20)); @@ -304,7 +304,7 @@ TEST_P(TcpProxyTest, UpstreamRemoteDisconnect) { } // Test that reconnect is attempted after a local connect failure -TEST_P(TcpProxyTest, ConnectAttemptsUpstreamLocalFail) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamLocalFail) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); @@ -326,7 +326,7 @@ TEST_P(TcpProxyTest, ConnectAttemptsUpstreamLocalFail) { } // Make sure that the tcp proxy code handles reentrant calls to onPoolFailure. -TEST_P(TcpProxyTest, ConnectAttemptsUpstreamLocalFailReentrant) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamLocalFailReentrant) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); @@ -350,7 +350,7 @@ TEST_P(TcpProxyTest, ConnectAttemptsUpstreamLocalFailReentrant) { } // Test that reconnect is attempted after a remote connect failure -TEST_P(TcpProxyTest, ConnectAttemptsUpstreamRemoteFail) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamRemoteFail) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); setup(2, config); @@ -364,7 +364,7 @@ TEST_P(TcpProxyTest, ConnectAttemptsUpstreamRemoteFail) { } // Test that reconnect is attempted after a connect timeout. -TEST_P(TcpProxyTest, ConnectAttemptsUpstreamTimeout) { +TEST_F(TcpProxyTest, ConnectAttemptsUpstreamTimeout) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(2); setup(2, config); @@ -378,7 +378,7 @@ TEST_P(TcpProxyTest, ConnectAttemptsUpstreamTimeout) { } // Test that only the configured number of connect attempts occur -TEST_P(TcpProxyTest, ConnectAttemptsLimit) { +TEST_F(TcpProxyTest, ConnectAttemptsLimit) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = accessLogConfig("%RESPONSE_FLAGS%"); config.mutable_max_connect_attempts()->set_value(3); @@ -409,7 +409,7 @@ TEST_P(TcpProxyTest, ConnectAttemptsLimit) { EXPECT_EQ(access_log_data_, "UF,URX"); } -TEST_P(TcpProxyTest, ConnectedNoOp) { +TEST_F(TcpProxyTest, ConnectedNoOp) { setup(1); raiseEventUpstreamConnected(0); @@ -419,7 +419,7 @@ TEST_P(TcpProxyTest, ConnectedNoOp) { } // Test that the tcp proxy sends the correct notifications to the outlier detector -TEST_P(TcpProxyTest, OutlierDetection) { +TEST_F(TcpProxyTest, OutlierDetection) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_max_connect_attempts()->set_value(3); setup(3, config); @@ -437,7 +437,7 @@ TEST_P(TcpProxyTest, OutlierDetection) { raiseEventUpstreamConnected(2); } -TEST_P(TcpProxyTest, UpstreamDisconnectDownstreamFlowControl) { +TEST_F(TcpProxyTest, UpstreamDisconnectDownstreamFlowControl) { setup(1); raiseEventUpstreamConnected(0); @@ -459,7 +459,7 @@ TEST_P(TcpProxyTest, UpstreamDisconnectDownstreamFlowControl) { filter_callbacks_.connection_.runLowWatermarkCallbacks(); } -TEST_P(TcpProxyTest, DownstreamDisconnectRemote) { +TEST_F(TcpProxyTest, DownstreamDisconnectRemote) { setup(1); raiseEventUpstreamConnected(0); @@ -476,7 +476,7 @@ TEST_P(TcpProxyTest, DownstreamDisconnectRemote) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); } -TEST_P(TcpProxyTest, DownstreamDisconnectLocal) { +TEST_F(TcpProxyTest, DownstreamDisconnectLocal) { setup(1); raiseEventUpstreamConnected(0); @@ -493,7 +493,7 @@ TEST_P(TcpProxyTest, DownstreamDisconnectLocal) { filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::LocalClose); } -TEST_P(TcpProxyTest, UpstreamConnectTimeout) { +TEST_F(TcpProxyTest, UpstreamConnectTimeout) { setup(1, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush, _)); @@ -503,7 +503,7 @@ TEST_P(TcpProxyTest, UpstreamConnectTimeout) { EXPECT_EQ(access_log_data_, "UF,URX"); } -TEST_P(TcpProxyTest, UpstreamClusterNotFound) { +TEST_F(TcpProxyTest, UpstreamClusterNotFound) { setup(0, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_CALL(factory_context_.server_factory_context_.cluster_manager_, getThreadLocalCluster(_)) @@ -514,7 +514,7 @@ TEST_P(TcpProxyTest, UpstreamClusterNotFound) { EXPECT_EQ(access_log_data_.value(), "NC"); } -TEST_P(TcpProxyTest, NoHost) { +TEST_F(TcpProxyTest, NoHost) { EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush, _)); setup(0, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onNewConnection()); @@ -522,78 +522,7 @@ TEST_P(TcpProxyTest, NoHost) { EXPECT_EQ(access_log_data_, "UH"); } -// Tests StreamDecoderFilterCallbacks interface implementation -TEST_P(TcpProxyTest, StreamDecoderFilterCallbacks) { - envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = - accessLogConfig("%RESPONSE_FLAGS%"); - config.mutable_tunneling_config()->set_hostname("www.example.com"); - configure(config); - NiceMock thread_local_cluster_; - auto cluster_info = std::make_shared>(); - // EXPECT_CALL(factory_context_.serverFactoryContext().clusterManager(), getThreadLocalCluster(_)) - // .WillRepeatedly(Return(&thread_local_cluster_)); - EXPECT_CALL(thread_local_cluster_, info()).WillRepeatedly(Return(cluster_info)); - filter_ = - std::make_unique(config_, factory_context_.serverFactoryContext().clusterManager()); - filter_->initializeReadFilterCallbacks(filter_callbacks_); - auto stream_decoder_callbacks = Filter::HttpStreamDecoderFilterCallbacks(filter_.get()); - EXPECT_NO_THROW(stream_decoder_callbacks.streamId()); - EXPECT_NO_THROW(stream_decoder_callbacks.connection()); - EXPECT_NO_THROW(stream_decoder_callbacks.dispatcher()); - EXPECT_ENVOY_BUG( - { stream_decoder_callbacks.resetStream(Http::StreamResetReason::RemoteReset, ""); }, - "Not implemented"); - EXPECT_NO_THROW(stream_decoder_callbacks.streamInfo()); - EXPECT_NO_THROW(stream_decoder_callbacks.scope()); - EXPECT_NO_THROW(stream_decoder_callbacks.route()); - EXPECT_NO_THROW(stream_decoder_callbacks.continueDecoding()); - EXPECT_NO_THROW(stream_decoder_callbacks.requestHeaders()); - EXPECT_NO_THROW(stream_decoder_callbacks.requestTrailers()); - EXPECT_NO_THROW(stream_decoder_callbacks.responseHeaders()); - EXPECT_NO_THROW(stream_decoder_callbacks.responseTrailers()); - EXPECT_NO_THROW(stream_decoder_callbacks.encodeMetadata(nullptr)); - EXPECT_NO_THROW(stream_decoder_callbacks.onDecoderFilterAboveWriteBufferHighWatermark()); - EXPECT_NO_THROW(stream_decoder_callbacks.onDecoderFilterBelowWriteBufferLowWatermark()); - EXPECT_NO_THROW(stream_decoder_callbacks.setDecoderBufferLimit(uint32_t{0})); - EXPECT_NO_THROW(stream_decoder_callbacks.decoderBufferLimit()); - EXPECT_NO_THROW(stream_decoder_callbacks.recreateStream(nullptr)); - EXPECT_NO_THROW(stream_decoder_callbacks.getUpstreamSocketOptions()); - Network::Socket::OptionsSharedPtr sock_options = - Network::SocketOptionFactory::buildIpTransparentOptions(); - EXPECT_NO_THROW(stream_decoder_callbacks.addUpstreamSocketOptions(sock_options)); - EXPECT_NO_THROW(stream_decoder_callbacks.mostSpecificPerFilterConfig()); - EXPECT_NO_THROW(stream_decoder_callbacks.account()); - EXPECT_NO_THROW(stream_decoder_callbacks.setUpstreamOverrideHost( - Upstream::LoadBalancerContext::OverrideHost(std::make_pair("foo", true)))); - EXPECT_NO_THROW(stream_decoder_callbacks.http1StreamEncoderOptions()); - EXPECT_NO_THROW(stream_decoder_callbacks.downstreamCallbacks()); - EXPECT_NO_THROW(stream_decoder_callbacks.upstreamCallbacks()); - EXPECT_NO_THROW(stream_decoder_callbacks.upstreamOverrideHost()); - EXPECT_NO_THROW(stream_decoder_callbacks.resetIdleTimer()); - EXPECT_NO_THROW(stream_decoder_callbacks.filterConfigName()); - EXPECT_NO_THROW(stream_decoder_callbacks.activeSpan()); - EXPECT_NO_THROW(stream_decoder_callbacks.tracingConfig()); - Buffer::OwnedImpl inject_data; - EXPECT_NO_THROW(stream_decoder_callbacks.addDecodedData(inject_data, false)); - EXPECT_NO_THROW(stream_decoder_callbacks.injectDecodedDataToFilterChain(inject_data, false)); - EXPECT_NO_THROW(stream_decoder_callbacks.addDecodedData(inject_data, false)); - EXPECT_NO_THROW(stream_decoder_callbacks.addDecodedTrailers()); - EXPECT_NO_THROW(stream_decoder_callbacks.addDecodedMetadata()); - EXPECT_NO_THROW(stream_decoder_callbacks.decodingBuffer()); - auto func = [](Buffer::Instance&) {}; - EXPECT_NO_THROW(stream_decoder_callbacks.modifyDecodingBuffer(func)); - EXPECT_NO_THROW(stream_decoder_callbacks.encode1xxHeaders(nullptr)); - EXPECT_NO_THROW(stream_decoder_callbacks.informationalHeaders()); - EXPECT_NO_THROW(stream_decoder_callbacks.encodeHeaders(nullptr, false, "")); - EXPECT_NO_THROW(stream_decoder_callbacks.encodeData(inject_data, false)); - EXPECT_NO_THROW(stream_decoder_callbacks.encodeTrailers(nullptr)); - EXPECT_NO_THROW(stream_decoder_callbacks.setDecoderBufferLimit(0)); - std::array buffer; - OutputBufferStream ostream{buffer.data(), buffer.size()}; - EXPECT_NO_THROW(stream_decoder_callbacks.dumpState(ostream, 0)); -} - -TEST_P(TcpProxyTest, RouteWithMetadataMatch) { +TEST_F(TcpProxyTest, RouteWithMetadataMatch) { auto v1 = ProtobufWkt::Value(); v1.set_string_value("v1"); auto v2 = ProtobufWkt::Value(); @@ -633,7 +562,7 @@ TEST_P(TcpProxyTest, RouteWithMetadataMatch) { // Tests that the endpoint selector of a weighted cluster gets included into the // LoadBalancerContext. -TEST_P(TcpProxyTest, WeightedClusterWithMetadataMatch) { +TEST_F(TcpProxyTest, WeightedClusterWithMetadataMatch) { const std::string yaml = R"EOF( stat_prefix: name weighted_clusters: @@ -730,7 +659,7 @@ TEST_P(TcpProxyTest, WeightedClusterWithMetadataMatch) { } // Test that metadata match criteria provided on the StreamInfo is used. -TEST_P(TcpProxyTest, StreamInfoDynamicMetadata) { +TEST_F(TcpProxyTest, StreamInfoDynamicMetadata) { configure(defaultConfig()); ProtobufWkt::Value val; @@ -768,7 +697,7 @@ TEST_P(TcpProxyTest, StreamInfoDynamicMetadata) { // Test that if both streamInfo and configuration add metadata match criteria, they // are merged. -TEST_P(TcpProxyTest, StreamInfoDynamicMetadataAndConfigMerged) { +TEST_F(TcpProxyTest, StreamInfoDynamicMetadataAndConfigMerged) { const std::string yaml = R"EOF( stat_prefix: name weighted_clusters: @@ -829,7 +758,7 @@ TEST_P(TcpProxyTest, StreamInfoDynamicMetadataAndConfigMerged) { EXPECT_EQ(hv2, effective_criterions[2]->value()); } -TEST_P(TcpProxyTest, DisconnectBeforeData) { +TEST_F(TcpProxyTest, DisconnectBeforeData) { configure(defaultConfig()); filter_ = std::make_unique(config_, factory_context_.server_factory_context_.cluster_manager_); @@ -840,7 +769,7 @@ TEST_P(TcpProxyTest, DisconnectBeforeData) { // Test that if the downstream connection is closed before the upstream connection // is established, the upstream connection is cancelled. -TEST_P(TcpProxyTest, RemoteClosedBeforeUpstreamConnected) { +TEST_F(TcpProxyTest, RemoteClosedBeforeUpstreamConnected) { setup(1); EXPECT_CALL(*conn_pool_handles_.at(0), cancel(Tcp::ConnectionPool::CancelPolicy::CloseExcess)); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -848,13 +777,13 @@ TEST_P(TcpProxyTest, RemoteClosedBeforeUpstreamConnected) { // Test that if the downstream connection is closed before the upstream connection // is established, the upstream connection is cancelled. -TEST_P(TcpProxyTest, LocalClosedBeforeUpstreamConnected) { +TEST_F(TcpProxyTest, LocalClosedBeforeUpstreamConnected) { setup(1); EXPECT_CALL(*conn_pool_handles_.at(0), cancel(Tcp::ConnectionPool::CancelPolicy::CloseExcess)); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::LocalClose); } -TEST_P(TcpProxyTest, UpstreamConnectFailure) { +TEST_F(TcpProxyTest, UpstreamConnectFailure) { setup(1, accessLogConfig("%RESPONSE_FLAGS%")); EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush, _)); @@ -870,7 +799,7 @@ TEST_P(TcpProxyTest, UpstreamConnectFailure) { EXPECT_EQ(access_log_data_, "UF,URX"); } -TEST_P(TcpProxyTest, UpstreamConnectionLimit) { +TEST_F(TcpProxyTest, UpstreamConnectionLimit) { configure(accessLogConfig("%RESPONSE_FLAGS%")); factory_context_.server_factory_context_.cluster_manager_.thread_local_cluster_.cluster_.info_ ->resetResourceManager(0, 0, 0, 0, 0); @@ -887,7 +816,7 @@ TEST_P(TcpProxyTest, UpstreamConnectionLimit) { EXPECT_EQ(access_log_data_, "UO"); } -TEST_P(TcpProxyTest, IdleTimeoutObjectFactory) { +TEST_F(TcpProxyTest, IdleTimeoutObjectFactory) { const std::string name = "envoy.tcp_proxy.per_connection_idle_timeout_ms"; auto* factory = Registry::FactoryRegistry::getFactory(name); @@ -899,7 +828,7 @@ TEST_P(TcpProxyTest, IdleTimeoutObjectFactory) { EXPECT_EQ(duration_in_milliseconds, object->serializeAsString()); } -TEST_P(TcpProxyTest, InvalidIdleTimeoutObjectFactory) { +TEST_F(TcpProxyTest, InvalidIdleTimeoutObjectFactory) { const std::string name = "envoy.tcp_proxy.per_connection_idle_timeout_ms"; auto* factory = Registry::FactoryRegistry::getFactory(name); @@ -908,7 +837,7 @@ TEST_P(TcpProxyTest, InvalidIdleTimeoutObjectFactory) { ASSERT_EQ(nullptr, factory->createFromBytes("not_a_number")); } -TEST_P(TcpProxyTest, IdleTimeoutWithFilterStateOverride) { +TEST_F(TcpProxyTest, IdleTimeoutWithFilterStateOverride) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -948,7 +877,7 @@ TEST_P(TcpProxyTest, IdleTimeoutWithFilterStateOverride) { // Tests that the idle timer closes both connections, and gets updated when either // connection has activity. -TEST_P(TcpProxyTest, IdleTimeout) { +TEST_F(TcpProxyTest, IdleTimeout) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -978,7 +907,7 @@ TEST_P(TcpProxyTest, IdleTimeout) { } // Tests that the idle timer is disabled when the downstream connection is closed. -TEST_P(TcpProxyTest, IdleTimerDisabledDownstreamClose) { +TEST_F(TcpProxyTest, IdleTimerDisabledDownstreamClose) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -992,7 +921,7 @@ TEST_P(TcpProxyTest, IdleTimerDisabledDownstreamClose) { } // Tests that the idle timer is disabled when the upstream connection is closed. -TEST_P(TcpProxyTest, IdleTimerDisabledUpstreamClose) { +TEST_F(TcpProxyTest, IdleTimerDisabledUpstreamClose) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1006,7 +935,7 @@ TEST_P(TcpProxyTest, IdleTimerDisabledUpstreamClose) { } // Tests that flushing data during an idle timeout doesn't cause problems. -TEST_P(TcpProxyTest, IdleTimeoutWithOutstandingDataFlushed) { +TEST_F(TcpProxyTest, IdleTimeoutWithOutstandingDataFlushed) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1058,7 +987,7 @@ TEST_P(TcpProxyTest, IdleTimeoutWithOutstandingDataFlushed) { // Checks that %UPSTREAM_WIRE_BYTES_SENT%, %UPSTREAM_WIRE_BYTES_RECEIVED%, // %DOWNSTREAM_WIRE_BYTES_SENT%, and %DOWNSTREAM_WIRE_BYTES_RECEIVED% are // correctly logged. -TEST_P(TcpProxyTest, AccessLogBytesMeterData) { +TEST_F(TcpProxyTest, AccessLogBytesMeterData) { setup(1, accessLogConfig("%UPSTREAM_WIRE_BYTES_SENT% %UPSTREAM_WIRE_BYTES_RECEIVED% " "%DOWNSTREAM_WIRE_BYTES_SENT% %DOWNSTREAM_WIRE_BYTES_RECEIVED%")); raiseEventUpstreamConnected(0); @@ -1076,7 +1005,7 @@ TEST_P(TcpProxyTest, AccessLogBytesMeterData) { // Test that access log fields %UPSTREAM_HOST% and %UPSTREAM_CLUSTER% are correctly logged with the // observability name. -TEST_P(TcpProxyTest, AccessLogUpstreamHost) { +TEST_F(TcpProxyTest, AccessLogUpstreamHost) { setup(1, accessLogConfig("%UPSTREAM_HOST% %UPSTREAM_CLUSTER%")); raiseEventUpstreamConnected(0); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1085,7 +1014,7 @@ TEST_P(TcpProxyTest, AccessLogUpstreamHost) { } // Test that access log field %UPSTREAM_LOCAL_ADDRESS% is correctly logged. -TEST_P(TcpProxyTest, AccessLogUpstreamLocalAddress) { +TEST_F(TcpProxyTest, AccessLogUpstreamLocalAddress) { setup(1, accessLogConfig("%UPSTREAM_LOCAL_ADDRESS%")); raiseEventUpstreamConnected(0); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1094,7 +1023,7 @@ TEST_P(TcpProxyTest, AccessLogUpstreamLocalAddress) { } // Test that access log fields %DOWNSTREAM_PEER_URI_SAN% is correctly logged. -TEST_P(TcpProxyTest, AccessLogPeerUriSan) { +TEST_F(TcpProxyTest, AccessLogPeerUriSan) { filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress( Network::Utility::resolveUrl("tcp://1.1.1.2:20000")); filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -1112,7 +1041,7 @@ TEST_P(TcpProxyTest, AccessLogPeerUriSan) { } // Test that access log fields %DOWNSTREAM_TLS_SESSION_ID% is correctly logged. -TEST_P(TcpProxyTest, AccessLogTlsSessionId) { +TEST_F(TcpProxyTest, AccessLogTlsSessionId) { filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress( Network::Utility::resolveUrl("tcp://1.1.1.2:20000")); filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -1132,7 +1061,7 @@ TEST_P(TcpProxyTest, AccessLogTlsSessionId) { // Test that access log fields %DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT% and // %DOWNSTREAM_LOCAL_ADDRESS% are correctly logged. -TEST_P(TcpProxyTest, AccessLogDownstreamAddress) { +TEST_F(TcpProxyTest, AccessLogDownstreamAddress) { filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setLocalAddress( Network::Utility::resolveUrl("tcp://1.1.1.2:20000")); filter_callbacks_.connection_.stream_info_.downstream_connection_info_provider_->setRemoteAddress( @@ -1144,7 +1073,7 @@ TEST_P(TcpProxyTest, AccessLogDownstreamAddress) { } // Test that intermediate log entry by field %ACCESS_LOG_TYPE%. -TEST_P(TcpProxyTest, IntermediateLogEntry) { +TEST_F(TcpProxyTest, IntermediateLogEntry) { auto config = accessLogConfig("%ACCESS_LOG_TYPE%"); config.mutable_access_log_options()->mutable_access_log_flush_interval()->set_seconds(1); config.mutable_idle_timeout()->set_seconds(0); @@ -1200,7 +1129,7 @@ TEST_P(TcpProxyTest, IntermediateLogEntry) { AccessLogType_Name(AccessLog::AccessLogType::TcpConnectionEnd)); } -TEST_P(TcpProxyTest, TestAccessLogOnUpstreamConnected) { +TEST_F(TcpProxyTest, TestAccessLogOnUpstreamConnected) { auto config = accessLogConfig("%UPSTREAM_HOST% %ACCESS_LOG_TYPE%"); config.mutable_access_log_options()->set_flush_access_log_on_connected(true); @@ -1222,7 +1151,7 @@ TEST_P(TcpProxyTest, TestAccessLogOnUpstreamConnected) { AccessLogType_Name(AccessLog::AccessLogType::TcpConnectionEnd))); } -TEST_P(TcpProxyTest, AccessLogUpstreamSSLConnection) { +TEST_F(TcpProxyTest, AccessLogUpstreamSSLConnection) { setup(1); NiceMock stream_info; @@ -1239,7 +1168,7 @@ TEST_P(TcpProxyTest, AccessLogUpstreamSSLConnection) { } // Tests that upstream flush works properly with no idle timeout configured. -TEST_P(TcpProxyTest, UpstreamFlushNoTimeout) { +TEST_F(TcpProxyTest, UpstreamFlushNoTimeout) { setup(1); raiseEventUpstreamConnected(0); @@ -1263,7 +1192,7 @@ TEST_P(TcpProxyTest, UpstreamFlushNoTimeout) { // Tests that upstream flush works with an idle timeout configured, but the connection // finishes draining before the timer expires. -TEST_P(TcpProxyTest, UpstreamFlushTimeoutConfigured) { +TEST_F(TcpProxyTest, UpstreamFlushTimeoutConfigured) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1294,7 +1223,7 @@ TEST_P(TcpProxyTest, UpstreamFlushTimeoutConfigured) { } // Tests that upstream flush closes the connection when the idle timeout fires. -TEST_P(TcpProxyTest, UpstreamFlushTimeoutExpired) { +TEST_F(TcpProxyTest, UpstreamFlushTimeoutExpired) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); config.mutable_idle_timeout()->set_seconds(1); setup(1, config); @@ -1322,7 +1251,7 @@ TEST_P(TcpProxyTest, UpstreamFlushTimeoutExpired) { // Tests that upstream flush will close a connection if it reads data from the upstream // connection after the downstream connection is closed (nowhere to send it). -TEST_P(TcpProxyTest, UpstreamFlushReceiveUpstreamData) { +TEST_F(TcpProxyTest, UpstreamFlushReceiveUpstreamData) { setup(1); raiseEventUpstreamConnected(0); @@ -1341,13 +1270,13 @@ TEST_P(TcpProxyTest, UpstreamFlushReceiveUpstreamData) { upstream_callbacks_->onUpstreamData(buffer, false); } -TEST_P(TcpProxyTest, UpstreamSocketOptionsReturnedEmpty) { +TEST_F(TcpProxyTest, UpstreamSocketOptionsReturnedEmpty) { setup(1); auto options = filter_->upstreamSocketOptions(); EXPECT_EQ(options, nullptr); } -TEST_P(TcpProxyTest, TcpProxySetRedirectRecordsToUpstream) { +TEST_F(TcpProxyTest, TcpProxySetRedirectRecordsToUpstream) { setup(1, true); EXPECT_TRUE(filter_->upstreamSocketOptions()); auto iterator = std::find_if( @@ -1367,7 +1296,7 @@ TEST_P(TcpProxyTest, TcpProxySetRedirectRecordsToUpstream) { } // Tests that downstream connection can access upstream connections filter state. -TEST_P(TcpProxyTest, ShareFilterState) { +TEST_F(TcpProxyTest, ShareFilterState) { setup(1); upstream_connections_.at(0)->streamInfo().filterState()->setData( @@ -1383,7 +1312,7 @@ TEST_P(TcpProxyTest, ShareFilterState) { } // Tests that filter callback can access downstream and upstream address and ssl properties. -TEST_P(TcpProxyTest, AccessDownstreamAndUpstreamProperties) { +TEST_F(TcpProxyTest, AccessDownstreamAndUpstreamProperties) { setup(1); raiseEventUpstreamConnected(0); @@ -1396,7 +1325,7 @@ TEST_P(TcpProxyTest, AccessDownstreamAndUpstreamProperties) { upstream_connections_.at(0)->streamInfo().downstreamAddressProvider().sslConnection()); } -TEST_P(TcpProxyTest, PickClusterOnUpstreamFailure) { +TEST_F(TcpProxyTest, PickClusterOnUpstreamFailure) { auto config = defaultConfig(); set2Cluster(config); config.mutable_max_connect_attempts()->set_value(2); @@ -1425,7 +1354,7 @@ TEST_P(TcpProxyTest, PickClusterOnUpstreamFailure) { } // Verify that odcds callback does not re-pick cluster. Upstream connect failure does. -TEST_P(TcpProxyTest, OnDemandCallbackStickToTheSelectedCluster) { +TEST_F(TcpProxyTest, OnDemandCallbackStickToTheSelectedCluster) { auto config = onDemandConfig(); set2Cluster(config); config.mutable_max_connect_attempts()->set_value(2); @@ -1484,7 +1413,7 @@ TEST_P(TcpProxyTest, OnDemandCallbackStickToTheSelectedCluster) { } // Verify the on demand api is not invoked when the target thread local cluster is present. -TEST_P(TcpProxyTest, OdcdsIsIgnoredIfClusterExists) { +TEST_F(TcpProxyTest, OdcdsIsIgnoredIfClusterExists) { auto config = onDemandConfig(); setup(1, config); @@ -1503,7 +1432,7 @@ TEST_P(TcpProxyTest, OdcdsIsIgnoredIfClusterExists) { } // Verify the on demand request is cancelled if the tcp downstream connection is closed. -TEST_P(TcpProxyTest, OdcdsCancelIfConnectionClose) { +TEST_F(TcpProxyTest, OdcdsCancelIfConnectionClose) { auto config = onDemandConfig(); mock_odcds_api_handle_ = Upstream::MockOdCdsApiHandle::create().release(); @@ -1525,7 +1454,7 @@ TEST_P(TcpProxyTest, OdcdsCancelIfConnectionClose) { } // Verify a request can be served after a successful on demand cluster request. -TEST_P(TcpProxyTest, OdcdsBasicDownstreamLocalClose) { +TEST_F(TcpProxyTest, OdcdsBasicDownstreamLocalClose) { auto config = onDemandConfig(); mock_odcds_api_handle_ = Upstream::MockOdCdsApiHandle::create().release(); @@ -1570,7 +1499,7 @@ TEST_P(TcpProxyTest, OdcdsBasicDownstreamLocalClose) { } // Verify the connection is closed after the cluster missing callback is triggered. -TEST_P(TcpProxyTest, OdcdsClusterMissingCauseConnectionClose) { +TEST_F(TcpProxyTest, OdcdsClusterMissingCauseConnectionClose) { auto config = onDemandConfig(); mock_odcds_api_handle_ = Upstream::MockOdCdsApiHandle::create().release(); @@ -1599,7 +1528,7 @@ TEST_P(TcpProxyTest, OdcdsClusterMissingCauseConnectionClose) { } // Test that upstream transport failure message is reflected in access logs. -TEST_P(TcpProxyTest, UpstreamConnectFailureStreamInfoAccessLog) { +TEST_F(TcpProxyTest, UpstreamConnectFailureStreamInfoAccessLog) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); setup(1, accessLogConfig("%UPSTREAM_TRANSPORT_FAILURE_REASON%")); @@ -1616,7 +1545,7 @@ TEST_P(TcpProxyTest, UpstreamConnectFailureStreamInfoAccessLog) { // Test that call to tcp_proxy filter's startUpstreamSecureTransport results // in upstream's startUpstreamSecureTransport call. -TEST_P(TcpProxyTest, UpstreamStartSecureTransport) { +TEST_F(TcpProxyTest, UpstreamStartSecureTransport) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); setup(1, config); @@ -1625,8 +1554,6 @@ TEST_P(TcpProxyTest, UpstreamStartSecureTransport) { filter_->startUpstreamSecureTransport(); } -INSTANTIATE_TEST_SUITE_P(WithOrWithoutUpstream, TcpProxyTest, ::testing::Bool()); - TEST(PerConnectionCluster, ObjectFactory) { const std::string name = "envoy.tcp_proxy.cluster"; auto* factory = diff --git a/test/common/tcp_proxy/tcp_proxy_test_base.h b/test/common/tcp_proxy/tcp_proxy_test_base.h index a7a7770d218a..9816df3871d9 100644 --- a/test/common/tcp_proxy/tcp_proxy_test_base.h +++ b/test/common/tcp_proxy/tcp_proxy_test_base.h @@ -57,11 +57,9 @@ inline Config constructConfigFromYaml(const std::string& yaml, return {tcp_proxy, context}; } -class TcpProxyTestBase : public testing::TestWithParam { +class TcpProxyTestBase : public testing::Test { public: TcpProxyTestBase() { - scoped_runtime_.mergeValues({{"envoy.restart_features.upstream_http_filters_with_tcp_proxy", - GetParam() ? "true" : "false"}}); ON_CALL(*factory_context_.server_factory_context_.access_log_manager_.file_, write(_)) .WillByDefault(SaveArg<0>(&access_log_data_)); ON_CALL(filter_callbacks_.connection_.stream_info_, setUpstreamClusterInfo(_)) @@ -177,7 +175,6 @@ class TcpProxyTestBase : public testing::TestWithParam { Upstream::HostDescriptionConstSharedPtr upstream_host_{}; Upstream::ClusterInfoConstSharedPtr upstream_cluster_{}; std::string redirect_records_data_ = "some data"; - TestScopedRuntime scoped_runtime_; }; } // namespace TcpProxy diff --git a/test/common/tcp_proxy/upstream_test.cc b/test/common/tcp_proxy/upstream_test.cc index feca11eb4a60..d48d9690c602 100644 --- a/test/common/tcp_proxy/upstream_test.cc +++ b/test/common/tcp_proxy/upstream_test.cc @@ -6,11 +6,8 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/http/stream_encoder.h" -#include "test/mocks/router/router_filter_interface.h" -#include "test/mocks/router/upstream_request.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/tcp/mocks.h" -#include "test/mocks/upstream/load_balancer_context.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/test_runtime.h" @@ -26,7 +23,7 @@ using testing::Return; namespace Envoy { namespace TcpProxy { namespace { -using envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy; +using envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig; class HttpUpstreamTest : public testing::TestWithParam { public: @@ -40,11 +37,11 @@ class HttpUpstreamTest : public testing::TestWithParam { .WillByDefault(Return(Http::Http1StreamEncoderOptionsOptRef(stream_encoder_options_))); } EXPECT_CALL(stream_encoder_options_, enableHalfClose()).Times(AnyNumber()); - tcp_proxy_.mutable_tunneling_config()->set_hostname("default.host.com:443"); + config_message_.set_hostname("default.host.com:443"); } void setupUpstream() { - config_ = std::make_unique(scope_, tcp_proxy_, context_); + config_ = std::make_unique(config_message_, context_); upstream_ = std::make_unique(callbacks_, *this->config_, downstream_stream_info_, GetParam()); upstream_->setRequestEncoder(encoder_, true); @@ -54,9 +51,7 @@ class HttpUpstreamTest : public testing::TestWithParam { Http::MockRequestEncoder encoder_; Http::MockHttp1StreamEncoderOptions stream_encoder_options_; NiceMock callbacks_; - TcpProxy tcp_proxy_; - NiceMock store_; - Stats::MockScope& scope_{store_.mockScope()}; + TcpProxy_TunnelingConfig config_message_; std::unique_ptr config_; std::unique_ptr upstream_; NiceMock context_; @@ -151,7 +146,7 @@ TEST_P(HttpUpstreamTest, UpstreamWatermarks) { class MockHttpConnPoolCallbacks : public HttpConnPool::Callbacks { public: - MOCK_METHOD(void, onSuccess, (Http::RequestEncoder * request_encoder)); + MOCK_METHOD(void, onSuccess, (Http::RequestEncoder & request_encoder)); MOCK_METHOD(void, onFailure, ()); }; @@ -240,11 +235,11 @@ class HttpUpstreamRequestEncoderTest : public testing::TestWithParamset_hostname("default.host.com:443"); + config_message_.set_hostname("default.host.com:443"); } void setupUpstream() { - config_ = std::make_unique(scope_, tcp_proxy_, context_); + config_ = std::make_unique(config_message_, context_); upstream_ = std::make_unique(callbacks_, *this->config_, this->downstream_stream_info_, GetParam()); } @@ -264,9 +259,7 @@ class HttpUpstreamRequestEncoderTest : public testing::TestWithParam context_; std::unique_ptr upstream_; - TcpProxy tcp_proxy_; - NiceMock store_; - Stats::MockScope& scope_{store_.mockScope()}; + TcpProxy_TunnelingConfig config_message_; std::unique_ptr config_; bool is_http2_ = true; }; @@ -288,7 +281,7 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoder) { } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderUsePost) { - this->tcp_proxy_.mutable_tunneling_config()->set_use_post(true); + this->config_message_.set_use_post(true); this->setupUpstream(); std::unique_ptr expected_headers; expected_headers = Http::createHeaderMap({ @@ -307,8 +300,8 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderUsePost) { } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderUsePostWithCustomPath) { - this->tcp_proxy_.mutable_tunneling_config()->set_use_post(true); - this->tcp_proxy_.mutable_tunneling_config()->set_post_path("/test"); + this->config_message_.set_use_post(true); + this->config_message_.set_post_path("/test"); this->setupUpstream(); std::unique_ptr expected_headers; expected_headers = Http::createHeaderMap({ @@ -327,25 +320,25 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderUsePostWithCustomPath) { } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderConnectWithCustomPath) { - this->tcp_proxy_.mutable_tunneling_config()->set_use_post(false); - this->tcp_proxy_.mutable_tunneling_config()->set_post_path("/test"); + this->config_message_.set_use_post(false); + this->config_message_.set_post_path("/test"); EXPECT_THROW_WITH_MESSAGE(this->setupUpstream(), EnvoyException, "Can't set a post path when POST method isn't used"); } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHeaders) { - auto* header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); + auto* header = this->config_message_.add_headers_to_add(); auto* hdr = header->mutable_header(); hdr->set_key("header0"); hdr->set_value("value0"); - header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); + header = this->config_message_.add_headers_to_add(); hdr = header->mutable_header(); hdr->set_key("header1"); hdr->set_value("value1"); header->set_append_action(envoy::config::core::v3::HeaderValueOption::APPEND_IF_EXISTS_OR_ADD); - header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); + header = this->config_message_.add_headers_to_add(); hdr = header->mutable_header(); hdr->set_key("header1"); hdr->set_value("value2"); @@ -367,13 +360,13 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHeaders) { } TEST_P(HttpUpstreamRequestEncoderTest, ConfigReuse) { - auto* header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); + auto* header = this->config_message_.add_headers_to_add(); auto* hdr = header->mutable_header(); hdr->set_key("key"); hdr->set_value("value1"); header->set_append_action(envoy::config::core::v3::HeaderValueOption::APPEND_IF_EXISTS_OR_ADD); - header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); + header = this->config_message_.add_headers_to_add(); hdr = header->mutable_header(); hdr->set_key("key"); hdr->set_value("value2"); @@ -411,12 +404,12 @@ TEST_P(HttpUpstreamRequestEncoderTest, ConfigReuse) { } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHeadersWithDownstreamInfo) { - auto* header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); + auto* header = this->config_message_.add_headers_to_add(); auto* hdr = header->mutable_header(); hdr->set_key("header0"); hdr->set_value("value0"); - header = this->tcp_proxy_.mutable_tunneling_config()->add_headers_to_add(); + header = this->config_message_.add_headers_to_add(); hdr = header->mutable_header(); hdr->set_key("downstream_local_port"); hdr->set_value("%DOWNSTREAM_LOCAL_PORT%"); @@ -445,7 +438,7 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHeadersWithDownstreamInfo) TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHostnameWithDownstreamInfoRequestedServerName) { - this->tcp_proxy_.mutable_tunneling_config()->set_hostname("%REQUESTED_SERVER_NAME%:443"); + this->config_message_.set_hostname("%REQUESTED_SERVER_NAME%:443"); this->setupUpstream(); std::unique_ptr expected_headers; @@ -469,8 +462,7 @@ TEST_P(HttpUpstreamRequestEncoderTest, } TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHostnameWithDownstreamInfoDynamicMetadata) { - this->tcp_proxy_.mutable_tunneling_config()->set_hostname( - "%DYNAMIC_METADATA(tunnel:address)%:443"); + this->config_message_.set_hostname("%DYNAMIC_METADATA(tunnel:address)%:443"); this->setupUpstream(); std::unique_ptr expected_headers; @@ -490,218 +482,6 @@ TEST_P(HttpUpstreamRequestEncoderTest, RequestEncoderHostnameWithDownstreamInfoD EXPECT_CALL(this->encoder_, encodeHeaders(HeaderMapEqualRef(expected_headers.get()), false)); this->upstream_->setRequestEncoder(this->encoder_, false); } - -class CombinedUpstreamTest : public testing::Test { -public: - CombinedUpstreamTest() { - EXPECT_CALL(encoder_, getStream()).Times(AnyNumber()); - EXPECT_CALL(encoder_, http1StreamEncoderOptions()).Times(AnyNumber()); - EXPECT_CALL(encoder_, enableTcpTunneling()).Times(AnyNumber()); - EXPECT_CALL(stream_encoder_options_, enableHalfClose()).Times(AnyNumber()); - tcp_proxy_.mutable_tunneling_config()->set_hostname("default.host.com:443"); - } - - void setup() { - tunnel_config_ = std::make_unique(scope_, tcp_proxy_, context_); - conn_pool_ = std::make_unique(cluster_, &lb_context_, *tunnel_config_, callbacks_, - decoder_callbacks_, Http::CodecType::HTTP2, - downstream_stream_info_); - upstream_ = std::make_unique(*conn_pool_, callbacks_, decoder_callbacks_, - *tunnel_config_, downstream_stream_info_); - auto mock_conn_pool = std::make_unique>(); - std::unique_ptr generic_conn_pool = std::move(mock_conn_pool); - config_ = std::make_shared(tcp_proxy_, factory_context_); - filter_ = - std::make_unique(config_, factory_context_.serverFactoryContext().clusterManager()); - filter_->initializeReadFilterCallbacks(filter_callbacks_); - auto mock_upst = std::make_unique>( - *upstream_, std::move(generic_conn_pool)); - mock_router_upstream_request_ = mock_upst.get(); - upstream_->setRouterUpstreamRequest(std::move(mock_upst)); - EXPECT_CALL(*mock_router_upstream_request_, acceptHeadersFromRouter(false)); - EXPECT_NO_THROW(tunnel_config_->serverFactoryContext()); - upstream_->newStream(*filter_); - } - - Router::MockUpstreamRequest* mock_router_upstream_request_{}; - NiceMock router_filter_interface_; - NiceMock factory_context_; - ConfigSharedPtr config_; - NiceMock filter_callbacks_; - std::unique_ptr filter_; - - NiceMock downstream_stream_info_; - Http::MockRequestEncoder encoder_; - Http::MockHttp1StreamEncoderOptions stream_encoder_options_; - NiceMock callbacks_; - TcpProxy tcp_proxy_; - NiceMock decoder_callbacks_; - NiceMock cluster_; - NiceMock lb_context_; - std::unique_ptr conn_pool_; - NiceMock store_; - Stats::MockScope& scope_{store_.mockScope()}; - std::unique_ptr tunnel_config_; - std::unique_ptr upstream_; - NiceMock context_; -}; -TEST_F(CombinedUpstreamTest, RouterFilterInterface) { - this->setup(); - EXPECT_EQ(this->upstream_->startUpstreamSecureTransport(), false); - EXPECT_EQ(this->upstream_->getUpstreamConnectionSslInfo(), nullptr); - auto mock_conn_pool = std::make_unique>(); - std::unique_ptr generic_conn_pool = std::move(mock_conn_pool); - auto mock_upst = std::make_unique>( - *this->upstream_, std::move(generic_conn_pool)); - EXPECT_NO_THROW(this->upstream_->onUpstream1xxHeaders(nullptr, *mock_upst.get())); - EXPECT_NO_THROW(this->upstream_->onUpstreamMetadata(nullptr)); - EXPECT_NO_THROW(this->upstream_->onPerTryTimeout(*mock_upst.get())); - EXPECT_NO_THROW(this->upstream_->onPerTryIdleTimeout(*mock_upst.get())); - EXPECT_NO_THROW(this->upstream_->onStreamMaxDurationReached(*mock_upst.get())); - EXPECT_EQ(this->upstream_->dynamicMaxStreamDuration(), absl::nullopt); - EXPECT_EQ(this->upstream_->downstreamTrailers(), nullptr); - EXPECT_EQ(this->upstream_->downstreamResponseStarted(), false); - EXPECT_EQ(this->upstream_->downstreamEndStream(), false); - EXPECT_EQ(this->upstream_->attemptCount(), 0); -} - -TEST_F(CombinedUpstreamTest, WriteUpstream) { - this->setup(); - EXPECT_CALL(*this->mock_router_upstream_request_, - acceptDataFromRouter(BufferStringEqual("foo"), false /*end_stream*/)); - Buffer::OwnedImpl buffer1("foo"); - this->upstream_->encodeData(buffer1, false); - - EXPECT_CALL(*this->mock_router_upstream_request_, - acceptDataFromRouter(BufferStringEqual("bar"), true /*end_stream*/)); - Buffer::OwnedImpl buffer2("bar"); - this->upstream_->encodeData(buffer2, true); - - // New upstream with no encoder. - this->upstream_ = std::make_unique(*conn_pool_, callbacks_, decoder_callbacks_, - *tunnel_config_, downstream_stream_info_); - this->upstream_->encodeData(buffer2, true); -} - -TEST_F(CombinedUpstreamTest, WriteDownstream) { - this->setup(); - EXPECT_CALL(this->callbacks_, onUpstreamData(BufferStringEqual("foo"), false)); - Buffer::OwnedImpl buffer1("foo"); - this->upstream_->responseDecoder().decodeData(buffer1, false); - - EXPECT_CALL(this->callbacks_, onUpstreamData(BufferStringEqual("bar"), true)); - Buffer::OwnedImpl buffer2("bar"); - this->upstream_->responseDecoder().decodeData(buffer2, true); -} - -TEST_F(CombinedUpstreamTest, InvalidUpgradeWithEarlyFin) { - this->setup(); - EXPECT_CALL(this->callbacks_, onEvent(_)); - Http::ResponseHeaderMapPtr headers{new Http::TestResponseHeaderMapImpl{{":status", "200"}}}; - this->upstream_->responseDecoder().decodeHeaders(std::move(headers), true); -} - -TEST_F(CombinedUpstreamTest, InvalidUpgradeWithNon200) { - this->setup(); - EXPECT_CALL(this->callbacks_, onEvent(_)); - Http::ResponseHeaderMapPtr headers{new Http::TestResponseHeaderMapImpl{{":status", "301"}}}; - this->upstream_->responseDecoder().decodeHeaders(std::move(headers), false); -} - -TEST_F(CombinedUpstreamTest, ReadDisable) { - this->setup(); - EXPECT_CALL(*this->mock_router_upstream_request_, onAboveWriteBufferHighWatermark()); - EXPECT_TRUE(this->upstream_->readDisable(true)); - - EXPECT_CALL(*this->mock_router_upstream_request_, onAboveWriteBufferHighWatermark()).Times(0); - EXPECT_TRUE(this->upstream_->readDisable(false)); - - // New upstream with no encoder. - this->upstream_ = std::make_unique(*conn_pool_, callbacks_, decoder_callbacks_, - *tunnel_config_, downstream_stream_info_); - EXPECT_FALSE(this->upstream_->readDisable(true)); -} - -TEST_F(CombinedUpstreamTest, AddBytesSentCallbackForCoverage) { - this->setup(); - this->upstream_->addBytesSentCallback([&](uint64_t) { return true; }); -} - -TEST_F(CombinedUpstreamTest, DownstreamDisconnect) { - this->setup(); - EXPECT_CALL(*this->mock_router_upstream_request_, resetStream()); - EXPECT_CALL(this->callbacks_, onEvent(_)).Times(0); - EXPECT_TRUE(this->upstream_->onDownstreamEvent(Network::ConnectionEvent::LocalClose) == nullptr); -} - -TEST_F(CombinedUpstreamTest, UpstreamReset) { - this->setup(); - EXPECT_CALL(*this->mock_router_upstream_request_, resetStream()); - EXPECT_CALL(this->callbacks_, onEvent(_)); - this->upstream_->onResetStream(Http::StreamResetReason::ConnectionTermination, ""); -} - -TEST_F(CombinedUpstreamTest, UpstreamWatermarks) { - this->setup(); - EXPECT_CALL(this->callbacks_, onAboveWriteBufferHighWatermark()); - this->upstream_->onAboveWriteBufferHighWatermark(); - - EXPECT_CALL(this->callbacks_, onBelowWriteBufferLowWatermark()); - this->upstream_->onBelowWriteBufferLowWatermark(); -} - -TEST_F(CombinedUpstreamTest, OnSuccessCalledOnValidResponse) { - this->setup(); - auto conn_pool_callbacks = std::make_unique(); - auto conn_pool_callbacks_raw = conn_pool_callbacks.get(); - this->upstream_->setConnPoolCallbacks(std::move(conn_pool_callbacks)); - EXPECT_CALL(*conn_pool_callbacks_raw, onFailure()).Times(0); - EXPECT_CALL(*conn_pool_callbacks_raw, onSuccess(_)); - Http::ResponseHeaderMapPtr headers{new Http::TestResponseHeaderMapImpl{{":status", "200"}}}; - this->upstream_->responseDecoder().decodeHeaders(std::move(headers), false); -} - -TEST_F(CombinedUpstreamTest, OnFailureCalledOnInvalidResponse) { - this->setup(); - auto conn_pool_callbacks = std::make_unique(); - auto conn_pool_callbacks_raw = conn_pool_callbacks.get(); - this->upstream_->setConnPoolCallbacks(std::move(conn_pool_callbacks)); - EXPECT_CALL(*conn_pool_callbacks_raw, onFailure()); - EXPECT_CALL(*conn_pool_callbacks_raw, onSuccess(_)).Times(0); - Http::ResponseHeaderMapPtr headers{new Http::TestResponseHeaderMapImpl{{":status", "404"}}}; - this->upstream_->responseDecoder().decodeHeaders(std::move(headers), false); -} - -TEST_F(CombinedUpstreamTest, DumpsResponseDecoderWithoutAllocatingMemory) { - std::array buffer; - OutputBufferStream ostream{buffer.data(), buffer.size()}; - this->setup(); - - Stats::TestUtil::MemoryTest memory_test; - this->upstream_->responseDecoder().dumpState(ostream, 1); - EXPECT_EQ(memory_test.consumedBytes(), 0); - EXPECT_THAT(ostream.contents(), EndsWith("has not implemented dumpState\n")); -} -TEST_F(CombinedUpstreamTest, UpstreamTrailersMarksDoneReading) { - this->setup(); - EXPECT_CALL(*this->mock_router_upstream_request_, resetStream()); - this->upstream_->doneWriting(); - Http::ResponseTrailerMapPtr trailers{new Http::TestResponseTrailerMapImpl{{"key", "value"}}}; - this->upstream_->responseDecoder().decodeTrailers(std::move(trailers)); -} - -TEST_F(CombinedUpstreamTest, UpstreamTrailersDontPropagateFinDownstreamWhenFeatureDisabled) { - TestScopedRuntime scoped_runtime; - scoped_runtime.mergeValues( - {{"envoy.reloadable_features.tcp_tunneling_send_downstream_fin_on_upstream_trailers", - "false"}}); - this->setup(); - EXPECT_CALL(*this->mock_router_upstream_request_, resetStream()); - upstream_->doneWriting(); - EXPECT_CALL(callbacks_, onUpstreamData(_, _)).Times(0); - Http::ResponseTrailerMapPtr trailers{new Http::TestResponseTrailerMapImpl{{"key", "value"}}}; - upstream_->responseDecoder().decodeTrailers(std::move(trailers)); -} } // namespace } // namespace TcpProxy } // namespace Envoy diff --git a/test/extensions/filters/http/cache/BUILD b/test/extensions/filters/http/cache/BUILD index ce6100368be6..1d1f5f0ab270 100644 --- a/test/extensions/filters/http/cache/BUILD +++ b/test/extensions/filters/http/cache/BUILD @@ -124,7 +124,6 @@ envoy_extension_cc_test( "cache_filter_integration_test.cc", ], extension_names = ["envoy.filters.http.cache"], - shard_count = 2, deps = [ "//source/extensions/filters/http/cache:config", "//source/extensions/filters/http/cache:http_cache_lib", diff --git a/test/extensions/filters/http/csrf/BUILD b/test/extensions/filters/http/csrf/BUILD index 2a3b5ef8c3a4..5a958bce60e8 100644 --- a/test/extensions/filters/http/csrf/BUILD +++ b/test/extensions/filters/http/csrf/BUILD @@ -33,7 +33,6 @@ envoy_extension_cc_test( size = "large", srcs = ["csrf_filter_integration_test.cc"], extension_names = ["envoy.filters.http.csrf"], - shard_count = 2, deps = [ "//source/extensions/filters/http/csrf:config", "//test/config:utility_lib", diff --git a/test/extensions/filters/http/custom_response/BUILD b/test/extensions/filters/http/custom_response/BUILD index 01c7daba1d5c..704c20d197f4 100644 --- a/test/extensions/filters/http/custom_response/BUILD +++ b/test/extensions/filters/http/custom_response/BUILD @@ -81,7 +81,7 @@ envoy_extension_cc_test( "custom_response_integration_test.cc", ], extension_names = ["envoy.filters.http.custom_response"], - shard_count = 4, + shard_count = 2, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/fault/BUILD b/test/extensions/filters/http/fault/BUILD index 55c19ce038bf..a7413da5feaf 100644 --- a/test/extensions/filters/http/fault/BUILD +++ b/test/extensions/filters/http/fault/BUILD @@ -55,7 +55,6 @@ envoy_extension_cc_test( size = "large", srcs = ["fault_filter_integration_test.cc"], extension_names = ["envoy.filters.http.fault"], - shard_count = 4, deps = [ "//source/extensions/filters/http/fault:config", "//test/integration:http_protocol_integration_lib", diff --git a/test/extensions/filters/http/health_check/BUILD b/test/extensions/filters/http/health_check/BUILD index 7023b7b02bf6..8d7a622ebf1d 100644 --- a/test/extensions/filters/http/health_check/BUILD +++ b/test/extensions/filters/http/health_check/BUILD @@ -45,7 +45,6 @@ envoy_extension_cc_test( "health_check_integration_test.cc", ], extension_names = ["envoy.filters.http.health_check"], - shard_count = 2, deps = [ "//source/extensions/filters/http/buffer:config", "//source/extensions/filters/http/health_check:config", diff --git a/test/extensions/filters/http/jwt_authn/BUILD b/test/extensions/filters/http/jwt_authn/BUILD index 321ebccf2c83..9b84849c432f 100644 --- a/test/extensions/filters/http/jwt_authn/BUILD +++ b/test/extensions/filters/http/jwt_authn/BUILD @@ -162,7 +162,7 @@ envoy_extension_cc_test( "envoy.filters.http.jwt_authn", "envoy.filters.http.set_filter_state", ], - shard_count = 8, + shard_count = 4, tags = [ "cpu:3", ], diff --git a/test/extensions/filters/http/rbac/BUILD b/test/extensions/filters/http/rbac/BUILD index 33538419eebd..4c397d1e0389 100644 --- a/test/extensions/filters/http/rbac/BUILD +++ b/test/extensions/filters/http/rbac/BUILD @@ -58,7 +58,7 @@ envoy_extension_cc_test( size = "large", srcs = ["rbac_filter_integration_test.cc"], extension_names = ["envoy.filters.http.rbac"], - shard_count = 10, + shard_count = 3, tags = ["skip_on_windows"], deps = [ "//source/extensions/clusters/dynamic_forward_proxy:cluster", diff --git a/test/extensions/upstreams/http/tcp/upstream_request_test.cc b/test/extensions/upstreams/http/tcp/upstream_request_test.cc index 9bacca7d18af..e48d6a4ad7e4 100644 --- a/test/extensions/upstreams/http/tcp/upstream_request_test.cc +++ b/test/extensions/upstreams/http/tcp/upstream_request_test.cc @@ -114,7 +114,7 @@ class TcpUpstreamTest : public ::testing::Test { EXPECT_CALL(mock_router_filter_, callbacks()).Times(AnyNumber()); upstream_request_ = std::make_unique( mock_router_filter_, std::make_unique>(), false, - false, false /*enable_tcp_tunneling*/); + false); auto data = std::make_unique>(); EXPECT_CALL(*data, connection()).Times(AnyNumber()).WillRepeatedly(ReturnRef(connection())); tcp_upstream_ = std::make_unique(upstream_request_.get(), std::move(data)); diff --git a/test/extensions/upstreams/http/udp/upstream_request_test.cc b/test/extensions/upstreams/http/udp/upstream_request_test.cc index c6020febfea6..423b21589589 100644 --- a/test/extensions/upstreams/http/udp/upstream_request_test.cc +++ b/test/extensions/upstreams/http/udp/upstream_request_test.cc @@ -46,7 +46,6 @@ class UdpUpstreamTest : public ::testing::Test { udp_upstream_ = std::make_unique(&mock_upstream_to_downstream_, std::move(mock_socket), std::move(mock_host), mock_dispatcher_); - EXPECT_NO_THROW(udp_upstream_->enableHalfClose()); } protected: diff --git a/test/extensions/upstreams/tcp/generic/BUILD b/test/extensions/upstreams/tcp/generic/BUILD index 57cc2e3fcc9a..2a9375294a53 100644 --- a/test/extensions/upstreams/tcp/generic/BUILD +++ b/test/extensions/upstreams/tcp/generic/BUILD @@ -17,6 +17,5 @@ envoy_cc_test( "//source/extensions/upstreams/tcp/generic:config", "//test/mocks/server:factory_context_mocks", "//test/mocks/upstream:upstream_mocks", - "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/upstreams/tcp/generic/config_test.cc b/test/extensions/upstreams/tcp/generic/config_test.cc index 08d2ff0ead46..f2c62b449174 100644 --- a/test/extensions/upstreams/tcp/generic/config_test.cc +++ b/test/extensions/upstreams/tcp/generic/config_test.cc @@ -1,10 +1,7 @@ -#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" - #include "source/common/stream_info/bool_accessor_impl.h" #include "source/common/tcp_proxy/tcp_proxy.h" #include "source/extensions/upstreams/tcp/generic/config.h" -#include "test/mocks/http/mocks.h" #include "test/mocks/server/factory_context.h" #include "test/mocks/tcp/mocks.h" #include "test/mocks/upstream/cluster_manager.h" @@ -17,13 +14,13 @@ using testing::_; using testing::AnyNumber; using testing::NiceMock; using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Extensions { namespace Upstreams { namespace Tcp { namespace Generic { + class TcpConnPoolTest : public ::testing::Test { public: TcpConnPoolTest() { @@ -35,10 +32,6 @@ class TcpConnPoolTest : public ::testing::Test { NiceMock downstream_stream_info_; NiceMock connection_; Upstream::MockLoadBalancerContext lb_context_; - envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy tcp_proxy_; - NiceMock store_; - Stats::MockScope& scope_{store_.mockScope()}; - NiceMock decoder_callbacks_; NiceMock context_; }; @@ -46,13 +39,13 @@ TEST_F(TcpConnPoolTest, TestNoTunnelingConfig) { EXPECT_CALL(thread_local_cluster_, tcpConnPool(_, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(), - &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, downstream_stream_info_)); } TEST_F(TcpConnPoolTest, TestTunnelingDisabledByFilterState) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto; - tcp_proxy_.mutable_tunneling_config()->set_hostname("host"); - const TcpProxy::TunnelingConfigHelperImpl config(scope_, tcp_proxy_, context_); + config_proto.set_hostname("host"); + const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_); downstream_stream_info_.filterState()->setData( TcpProxy::DisableTunnelingFilterStateKey, @@ -62,13 +55,13 @@ TEST_F(TcpConnPoolTest, TestTunnelingDisabledByFilterState) { EXPECT_CALL(thread_local_cluster_, tcpConnPool(_, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config), - &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, downstream_stream_info_)); } TEST_F(TcpConnPoolTest, TestTunnelingNotDisabledIfFilterStateHasFalseValue) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto; - tcp_proxy_.mutable_tunneling_config()->set_hostname("host"); - const TcpProxy::TunnelingConfigHelperImpl config(scope_, tcp_proxy_, context_); + config_proto.set_hostname("host"); + const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_); downstream_stream_info_.filterState()->setData( TcpProxy::DisableTunnelingFilterStateKey, @@ -78,47 +71,46 @@ TEST_F(TcpConnPoolTest, TestTunnelingNotDisabledIfFilterStateHasFalseValue) { EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config), - &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, downstream_stream_info_)); } TEST_F(TcpConnPoolTest, TestNoConnPool) { envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto; - tcp_proxy_.mutable_tunneling_config()->set_hostname("host"); - const TcpProxy::TunnelingConfigHelperImpl config(scope_, tcp_proxy_, context_); + config_proto.set_hostname("host"); + const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_); EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config), - &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, downstream_stream_info_)); } TEST_F(TcpConnPoolTest, Http2Config) { auto info = std::make_shared(); - const std::string fake_cluster_name = "fake_cluster"; - + EXPECT_CALL(*info, features()).WillOnce(Return(Upstream::ClusterInfo::Features::HTTP2)); + EXPECT_CALL(thread_local_cluster_, info).WillOnce(Return(info)); envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto; - tcp_proxy_.mutable_tunneling_config()->set_hostname("host"); - const TcpProxy::TunnelingConfigHelperImpl config(scope_, tcp_proxy_, context_); + config_proto.set_hostname("host"); + const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_); EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config), - &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, downstream_stream_info_)); } TEST_F(TcpConnPoolTest, Http3Config) { auto info = std::make_shared(); - const std::string fake_cluster_name = "fake_cluster"; EXPECT_CALL(*info, features()) .Times(AnyNumber()) .WillRepeatedly(Return(Upstream::ClusterInfo::Features::HTTP3)); EXPECT_CALL(thread_local_cluster_, info).Times(AnyNumber()).WillRepeatedly(Return(info)); envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy_TunnelingConfig config_proto; - tcp_proxy_.mutable_tunneling_config()->set_hostname("host"); - const TcpProxy::TunnelingConfigHelperImpl config(scope_, tcp_proxy_, context_); + config_proto.set_hostname("host"); + const TcpProxy::TunnelingConfigHelperImpl config(config_proto, context_); EXPECT_CALL(thread_local_cluster_, httpConnPool(_, _, _)).WillOnce(Return(absl::nullopt)); EXPECT_EQ(nullptr, factory_.createGenericConnPool( thread_local_cluster_, TcpProxy::TunnelingConfigHelperOptConstRef(config), - &lb_context_, callbacks_, decoder_callbacks_, downstream_stream_info_)); + &lb_context_, callbacks_, downstream_stream_info_)); } TEST(DisableTunnelingObjectFactory, CreateFromBytes) { diff --git a/test/integration/BUILD b/test/integration/BUILD index 72636c49d709..fce0e38d657d 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -329,7 +329,6 @@ envoy_cc_test( srcs = envoy_select_admin_functionality([ "drain_close_integration_test.cc", ]), - shard_count = 6, tags = [ "cpu:3", ], @@ -615,7 +614,6 @@ envoy_cc_test( srcs = [ "buffer_accounting_integration_test.cc", ], - shard_count = 2, tags = [ "cpu:3", ], @@ -987,7 +985,7 @@ envoy_cc_test( srcs = ["idle_timeout_integration_test.cc"], # As this test has many pauses for idle timeouts, it takes a while to run. # Shard it enough to bring the run time in line with other integration tests. - shard_count = 16, + shard_count = 4, tags = [ "cpu:3", ], @@ -1357,7 +1355,7 @@ envoy_cc_test( srcs = [ "redirect_integration_test.cc", ], - shard_count = 4, + shard_count = 2, tags = [ "cpu:3", "nofips", @@ -1393,7 +1391,6 @@ envoy_cc_test( name = "websocket_integration_test", size = "large", srcs = ["websocket_integration_test.cc"], - shard_count = 2, tags = [ "cpu:3", ], @@ -1540,7 +1537,7 @@ envoy_cc_test( name = "overload_integration_test", size = "large", srcs = ["overload_integration_test.cc"], - shard_count = 10, + shard_count = 4, tags = [ "cpu:3", ], @@ -1814,7 +1811,7 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 30, + shard_count = 8, tags = [ "cpu:3", ], @@ -1840,7 +1837,6 @@ envoy_cc_test( data = [ "//test/config/integration/certs", ], - shard_count = 4, deps = [ ":http_integration_lib", ":http_protocol_integration_lib", @@ -2285,7 +2281,6 @@ envoy_cc_test( srcs = [ "local_reply_integration_test.cc", ], - shard_count = 2, tags = [ "cpu:2", ], diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index baab4dd3be91..13295850af25 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -427,7 +427,6 @@ void BaseIntegrationTest::registerTestServerPorts(const std::vector const auto admin_addr = test_server->server().admin()->socket().connectionInfoProvider().localAddress(); if (admin_addr->type() == Network::Address::Type::Ip) { - ENVOY_LOG(debug, "registered 'admin' as port {}.", admin_addr->ip()->port()); registerPort("admin", admin_addr->ip()->port()); } } diff --git a/test/integration/http_protocol_integration.cc b/test/integration/http_protocol_integration.cc index 914fffefcd99..50f79fdf6366 100644 --- a/test/integration/http_protocol_integration.cc +++ b/test/integration/http_protocol_integration.cc @@ -39,17 +39,15 @@ std::vector HttpProtocolIntegrationTest::getProtocolTest #else use_header_validator_values.push_back(false); #endif - for (const bool tunneling_with_upstream_filters : {false, true}) { - for (Http1ParserImpl http1_implementation : http1_implementations) { - for (Http2Impl http2_implementation : http2_implementations) { - for (bool defer_processing : http2_bool_values) { - for (bool deprecate_callback_visitor : http2_bool_values) { - 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, - deprecate_callback_visitor, tunneling_with_upstream_filters}); - } + for (Http1ParserImpl http1_implementation : http1_implementations) { + for (Http2Impl http2_implementation : http2_implementations) { + for (bool defer_processing : http2_bool_values) { + for (bool deprecate_callback_visitor : http2_bool_values) { + 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, + deprecate_callback_visitor}); } } } @@ -104,11 +102,9 @@ std::string HttpProtocolIntegrationTest::protocolTestParamsToString( http2ImplementationToString(params.param.http2_implementation), params.param.defer_processing_backedup_streams ? "WithDeferredProcessing" : "NoDeferredProcessing", - params.param.use_universal_header_validator ? "Uhv" : "Legacy", params.param.deprecate_callback_visitor ? "WithCallbackVisitor" : "NoCallbackVisitor", - params.param.tunneling_with_upstream_filters ? "WithUpstreamHttpFilters" - : "WithoutUpstreamHttpFilters"); + 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 1fa57f3d1992..0c0dd6c1bbc0 100644 --- a/test/integration/http_protocol_integration.h +++ b/test/integration/http_protocol_integration.h @@ -15,7 +15,6 @@ struct HttpProtocolTestParams { Http2Impl http2_implementation; bool defer_processing_backedup_streams; bool use_universal_header_validator; - bool tunneling_with_upstream_filters; bool deprecate_callback_visitor; }; @@ -87,9 +86,6 @@ class HttpProtocolIntegrationTest : public testing::TestWithParam static void initializeMockStreamFilterCallbacks(T& callbacks) MockStreamDecoderFilterCallbacks::MockStreamDecoderFilterCallbacks() { initializeMockStreamFilterCallbacks(*this); - ON_CALL(*this, dispatcher()).WillByDefault(ReturnRef(dispatcher_)); ON_CALL(*this, decodingBuffer()).WillByDefault(Invoke(&buffer_, &Buffer::InstancePtr::get)); ON_CALL(*this, addDownstreamWatermarkCallbacks(_)) diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index f7770ac37b96..3ec3f7c5eb71 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -109,7 +109,6 @@ class MockFilterManagerCallbacks : public FilterManagerCallbacks { MOCK_METHOD(OptRef, tracingConfig, (), (const)); MOCK_METHOD(const ScopeTrackedObject&, scope, ()); MOCK_METHOD(void, restoreContextOnContinue, (ScopeTrackedObjectStack&)); - MOCK_METHOD(bool, isHalfCloseEnabled, ()); ResponseHeaderMapPtr informational_headers_; ResponseHeaderMapPtr response_headers_; @@ -331,7 +330,6 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, bool is_grpc_request_{}; bool is_head_request_{false}; bool stream_destroyed_{}; - NiceMock dispatcher_; }; class MockStreamEncoderFilterCallbacks : public StreamEncoderFilterCallbacks, diff --git a/test/mocks/router/BUILD b/test/mocks/router/BUILD index 2d1364e40bc1..79a35cb2b25d 100644 --- a/test/mocks/router/BUILD +++ b/test/mocks/router/BUILD @@ -49,12 +49,3 @@ envoy_cc_mock( "//test/test_common:test_time_lib", ], ) - -envoy_cc_mock( - name = "upstream_request", - srcs = ["upstream_request.cc"], - hdrs = ["upstream_request.h"], - deps = [ - "//source/common/router:router_lib", - ], -) diff --git a/test/mocks/router/upstream_request.cc b/test/mocks/router/upstream_request.cc deleted file mode 100644 index aabf4baf8a05..000000000000 --- a/test/mocks/router/upstream_request.cc +++ /dev/null @@ -1,13 +0,0 @@ -#include "test/mocks/router/upstream_request.h" - -namespace Envoy { -namespace Router { - -MockUpstreamRequest::MockUpstreamRequest(RouterFilterInterface& router_interface, - std::unique_ptr&& conn_pool) - : UpstreamRequest(router_interface, std::move(conn_pool), false, true, true) {} - -MockUpstreamRequest::~MockUpstreamRequest() = default; - -} // namespace Router -} // namespace Envoy diff --git a/test/mocks/router/upstream_request.h b/test/mocks/router/upstream_request.h deleted file mode 100644 index 10688fdde5d1..000000000000 --- a/test/mocks/router/upstream_request.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "source/common/router/upstream_request.h" - -#include "gmock/gmock.h" - -namespace Envoy { -namespace Router { - -class MockUpstreamRequest : public UpstreamRequest { -public: - MockUpstreamRequest(RouterFilterInterface& router_interface, std::unique_ptr&&); - ~MockUpstreamRequest() override; - MOCK_METHOD(void, acceptHeadersFromRouter, (bool end_stream), (override)); - MOCK_METHOD(void, acceptDataFromRouter, (Buffer::Instance & data, bool end_stream), (override)); - MOCK_METHOD(void, onAboveWriteBufferHighWatermark, (), (override)); - MOCK_METHOD(void, resetStream, (), (override)); -}; - -} // namespace Router -} // namespace Envoy