From 664648a0852b5b3c78d4b4c52336c356c9651e79 Mon Sep 17 00:00:00 2001 From: Kuat Yessenov Date: Fri, 29 Sep 2023 21:58:53 +0000 Subject: [PATCH] wip Signed-off-by: Kuat Yessenov --- CODEOWNERS | 6 +- .../common/set_filter_state/filter_config.cc | 2 +- .../filters/common/set_filter_state/BUILD | 21 ++ .../set_filter_state/filter_config_test.cc | 257 ++++++++++++++++++ 4 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 test/extensions/filters/common/set_filter_state/BUILD create mode 100644 test/extensions/filters/common/set_filter_state/filter_config_test.cc diff --git a/CODEOWNERS b/CODEOWNERS index d3054329ccfc..06aac5864a6f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -196,9 +196,9 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123 /*/extensions/http/original_ip_detection/custom_header @alyssawilk @mattklein123 /*/extensions/http/original_ip_detection/xff @alyssawilk @mattklein123 # set_filter_state extension -/*/extensions/filters/common/set_filter_state @kyessenov -/*/extensions/filters/http/set_filter_state @kyessenov -/*/extensions/filters/network/set_filter_state @kyessenov +/*/extensions/filters/common/set_filter_state @kyessenov @wbpcode +/*/extensions/filters/http/set_filter_state @kyessenov @wbpcode +/*/extensions/filters/network/set_filter_state @kyessenov @wbpcode # set_metadata extension /*/extensions/filters/http/set_metadata @aguinet @mattklein123 # Formatters diff --git a/source/extensions/filters/common/set_filter_state/filter_config.cc b/source/extensions/filters/common/set_filter_state/filter_config.cc index ed9d00e25a5f..b495fcd5afcd 100644 --- a/source/extensions/filters/common/set_filter_state/filter_config.cc +++ b/source/extensions/filters/common/set_filter_state/filter_config.cc @@ -20,7 +20,7 @@ Config::parse(const Protobuf::RepeatedPtrField< rule.factory_ = Registry::FactoryRegistry::getFactory(rule.key_); if (rule.factory_ == nullptr) { - throw EnvoyException(fmt::format("{} does not have an object factory", rule.key_)); + throw EnvoyException(fmt::format("'{}' does not have an object factory", rule.key_)); } rule.state_type_ = proto_rule.read_only() ? StateType::ReadOnly : StateType::Mutable; switch (proto_rule.shared_with_upstream()) { diff --git a/test/extensions/filters/common/set_filter_state/BUILD b/test/extensions/filters/common/set_filter_state/BUILD new file mode 100644 index 000000000000..c671009cc110 --- /dev/null +++ b/test/extensions/filters/common/set_filter_state/BUILD @@ -0,0 +1,21 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_cc_test( + name = "filter_config_test", + srcs = ["filter_config_test.cc"], + deps = [ + "//source/common/router:string_accessor_lib", + "//source/extensions/filters/common/set_filter_state:filter_config_lib", + "//test/mocks/server:factory_context_mocks", + "//test/mocks/stream_info:stream_info_mocks", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/filters/common/set_filter_state/filter_config_test.cc b/test/extensions/filters/common/set_filter_state/filter_config_test.cc new file mode 100644 index 000000000000..fb1679a9f6ea --- /dev/null +++ b/test/extensions/filters/common/set_filter_state/filter_config_test.cc @@ -0,0 +1,257 @@ +#include "source/common/router/string_accessor_impl.h" +#include "source/extensions/filters/common/set_filter_state/filter_config.h" + +#include "test/mocks/server/factory_context.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Filters { +namespace Common { +namespace SetFilterState { +namespace { + +class ObjectBarFactory : public StreamInfo::FilterState::ObjectFactory { +public: + std::string name() const override { return "bar"; } + std::unique_ptr + createFromBytes(absl::string_view data) const override { + return std::make_unique(data); + } +}; + +class ObjectFooFactory : public StreamInfo::FilterState::ObjectFactory { +public: + std::string name() const override { return "foo"; } + std::unique_ptr + createFromBytes(absl::string_view data) const override { + if (data == "BAD_VALUE") { + return nullptr; + } + return std::make_unique(data); + } +}; + +REGISTER_FACTORY(ObjectBarFactory, StreamInfo::FilterState::ObjectFactory); +REGISTER_FACTORY(ObjectFooFactory, StreamInfo::FilterState::ObjectFactory); + +class ConfigTest : public testing::Test { +public: + void initialize(const std::vector& rules, + LifeSpan life_span = LifeSpan::FilterChain) { + using ProtoRule = envoy::extensions::filters::common::set_filter_state::v3::Rule; + std::vector proto_rules; + proto_rules.reserve(rules.size()); + for (const auto& rule : rules) { + ProtoRule proto_rule; + TestUtility::loadFromYaml(rule, proto_rule); + proto_rules.push_back(proto_rule); + } + config_ = std::make_shared( + Protobuf::RepeatedPtrField(proto_rules.begin(), proto_rules.end()), life_span, + context_); + } + void update() { config_->updateFilterState({&header_map_}, info_); } + NiceMock context_; + Http::TestRequestHeaderMapImpl header_map_{{"test-header", "test-value"}}; + NiceMock info_; + ConfigSharedPtr config_; +}; + +TEST_F(ConfigTest, SetValue) { + initialize({R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "XXX" + )YAML"}); + update(); + EXPECT_FALSE(info_.filterState()->hasDataAtOrAboveLifeSpan(LifeSpan::Request)); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(foo->serializeAsString(), "XXX"); + EXPECT_EQ(0, info_.filterState()->objectsSharedWithUpstreamConnection()->size()); +} + +TEST_F(ConfigTest, SetValueConnection) { + initialize({R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "XXX" + )YAML"}, + LifeSpan::Connection); + update(); + EXPECT_TRUE(info_.filterState()->hasDataAtOrAboveLifeSpan(LifeSpan::Request)); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(foo->serializeAsString(), "XXX"); + EXPECT_EQ(0, info_.filterState()->objectsSharedWithUpstreamConnection()->size()); +} + +TEST_F(ConfigTest, UpdateValue) { + initialize({R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "XXX" + )YAML"}); + info_.filterState()->setData("foo", std::make_unique("OLD"), + StateType::Mutable); + update(); + EXPECT_FALSE(info_.filterState()->hasDataAtOrAboveLifeSpan(LifeSpan::Request)); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(foo->serializeAsString(), "XXX"); + EXPECT_EQ(0, info_.filterState()->objectsSharedWithUpstreamConnection()->size()); +} + +TEST_F(ConfigTest, SetValueFromHeader) { + initialize({R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "%REQ(test-header)%" + )YAML"}); + update(); + EXPECT_FALSE(info_.filterState()->hasDataAtOrAboveLifeSpan(LifeSpan::Request)); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(foo->serializeAsString(), "test-value"); + EXPECT_EQ(0, info_.filterState()->objectsSharedWithUpstreamConnection()->size()); +} + +TEST_F(ConfigTest, MultipleRules) { + initialize({R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "XXX" + )YAML", + R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "YYY" + )YAML", + R"YAML( + key: bar + format_string: + text_format_source: + inline_string: "ZZZ" + )YAML"}); + update(); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + ASSERT_NE(nullptr, foo); + const auto* bar = info_.filterState()->getDataReadOnly("bar"); + ASSERT_NE(nullptr, bar); + EXPECT_EQ(foo->serializeAsString(), "YYY"); + EXPECT_EQ(bar->serializeAsString(), "ZZZ"); + EXPECT_EQ(0, info_.filterState()->objectsSharedWithUpstreamConnection()->size()); +} + +TEST_F(ConfigTest, BadValue) { + initialize({R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "BAD_VALUE" + )YAML"}); + update(); + EXPECT_FALSE(info_.filterState()->hasDataAtOrAboveLifeSpan(LifeSpan::Request)); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + EXPECT_EQ(nullptr, foo); +} + +TEST_F(ConfigTest, MissingKey) { + EXPECT_THROW_WITH_MESSAGE(initialize({R"YAML( + key: unknown_key + format_string: + text_format_source: + inline_string: "XXX" + )YAML"}), + EnvoyException, "'unknown_key' does not have an object factory"); +} + +TEST_F(ConfigTest, EmptyValue) { + initialize({R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "" + )YAML"}); + update(); + EXPECT_FALSE(info_.filterState()->hasDataAtOrAboveLifeSpan(LifeSpan::Request)); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(foo->serializeAsString(), ""); + EXPECT_EQ(0, info_.filterState()->objectsSharedWithUpstreamConnection()->size()); +} + +TEST_F(ConfigTest, EmptyValueSkip) { + initialize({R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "" + skip_if_empty: true + )YAML"}); + update(); + EXPECT_FALSE(info_.filterState()->hasDataAtOrAboveLifeSpan(LifeSpan::Request)); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + EXPECT_EQ(nullptr, foo); +} + +TEST_F(ConfigTest, SetValueUpstreamSharedOnce) { + initialize({R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "XXX" + shared_with_upstream: ONCE + )YAML"}); + update(); + EXPECT_FALSE(info_.filterState()->hasDataAtOrAboveLifeSpan(LifeSpan::Request)); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(foo->serializeAsString(), "XXX"); + const auto objects = info_.filterState()->objectsSharedWithUpstreamConnection(); + EXPECT_EQ(1, objects->size()); + EXPECT_EQ(StreamSharing::None, objects->at(0).stream_sharing_); + EXPECT_EQ(StateType::Mutable, objects->at(0).state_type_); + EXPECT_EQ("foo", objects->at(0).name_); + EXPECT_EQ(foo, objects->at(0).data_.get()); +} + +TEST_F(ConfigTest, SetValueUpstreamSharedTransitive) { + initialize({R"YAML( + key: foo + format_string: + text_format_source: + inline_string: "XXX" + shared_with_upstream: TRANSITIVE + read_only: true + )YAML"}); + update(); + EXPECT_FALSE(info_.filterState()->hasDataAtOrAboveLifeSpan(LifeSpan::Request)); + const auto* foo = info_.filterState()->getDataReadOnly("foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(foo->serializeAsString(), "XXX"); + const auto objects = info_.filterState()->objectsSharedWithUpstreamConnection(); + EXPECT_EQ(1, objects->size()); + EXPECT_EQ(StreamSharing::SharedWithUpstreamConnection, objects->at(0).stream_sharing_); + EXPECT_EQ(StateType::ReadOnly, objects->at(0).state_type_); + EXPECT_EQ("foo", objects->at(0).name_); + EXPECT_EQ(foo, objects->at(0).data_.get()); +} + +} // namespace +} // namespace SetFilterState +} // namespace Common +} // namespace Filters +} // namespace Extensions +} // namespace Envoy