-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
set_filter_state: enable per-route configuration override #37507
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
#include "gtest/gtest.h" | ||
|
||
using testing::NiceMock; | ||
using testing::Return; | ||
using testing::ReturnRef; | ||
|
||
namespace Envoy { | ||
|
@@ -66,6 +67,44 @@ class SetMetadataIntegrationTest : public testing::Test { | |
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter->decodeHeaders(headers_, true)); | ||
} | ||
|
||
void runPerRouteFilter(const std::string& listener_yaml_config, | ||
const std::string& per_route_yaml_config) { | ||
Server::GenericFactoryContextImpl generic_context(context_); | ||
|
||
envoy::extensions::filters::http::set_filter_state::v3::Config listener_proto_config; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: this actually is filter level rather than listener level, it would be better to name it filter_proto_config |
||
TestUtility::loadFromYaml(listener_yaml_config, listener_proto_config); | ||
auto listener_config = std::make_shared<Filters::Common::SetFilterState::Config>( | ||
listener_proto_config.on_request_headers(), StreamInfo::FilterState::LifeSpan::FilterChain, | ||
generic_context); | ||
|
||
envoy::extensions::filters::http::set_filter_state::v3::Config route_proto_config; | ||
TestUtility::loadFromYaml(per_route_yaml_config, route_proto_config); | ||
Filters::Common::SetFilterState::Config route_config( | ||
route_proto_config.on_request_headers(), StreamInfo::FilterState::LifeSpan::FilterChain, | ||
generic_context); | ||
|
||
NiceMock<Http::MockStreamDecoderFilterCallbacks> decoder_callbacks; | ||
EXPECT_CALL(*decoder_callbacks.route_, mostSpecificPerFilterConfig(_)) | ||
.WillOnce(Return(&route_config)); | ||
|
||
auto filter = std::make_shared<SetFilterState>(listener_config); | ||
filter->setDecoderFilterCallbacks(decoder_callbacks); | ||
EXPECT_CALL(decoder_callbacks, streamInfo()).WillRepeatedly(ReturnRef(info_)); | ||
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter->decodeHeaders(headers_, true)); | ||
|
||
// Test the factory method. | ||
{ | ||
NiceMock<Server::Configuration::MockServerFactoryContext> context; | ||
SetFilterStateConfig factory; | ||
Router::RouteSpecificFilterConfigConstSharedPtr route_config = | ||
factory | ||
.createRouteSpecificFilterConfig(route_proto_config, context, | ||
ProtobufMessage::getNullValidationVisitor()) | ||
.value(); | ||
EXPECT_TRUE(route_config.get()); | ||
} | ||
Comment on lines
+95
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be part of config_test, I think, if there is one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think there is one, just this test. I was following how the existing filter level test is written |
||
} | ||
|
||
NiceMock<Server::Configuration::MockFactoryContext> context_; | ||
Http::TestRequestHeaderMapImpl headers_{{"test-header", "test-value"}}; | ||
NiceMock<StreamInfo::MockStreamInfo> info_; | ||
|
@@ -85,6 +124,52 @@ TEST_F(SetMetadataIntegrationTest, FromHeader) { | |
EXPECT_EQ(foo->serializeAsString(), "test-value"); | ||
} | ||
|
||
TEST_F(SetMetadataIntegrationTest, RouteLevel) { | ||
const std::string listener_config = R"EOF( | ||
on_request_headers: | ||
- object_key: both | ||
factory_key: envoy.string | ||
format_string: | ||
text_format_source: | ||
inline_string: "listener-%REQ(test-header)%" | ||
- object_key: listener-only | ||
factory_key: envoy.string | ||
format_string: | ||
text_format_source: | ||
inline_string: "listener" | ||
)EOF"; | ||
const std::string route_config = R"EOF( | ||
on_request_headers: | ||
- object_key: both | ||
factory_key: envoy.string | ||
format_string: | ||
text_format_source: | ||
inline_string: "route-%REQ(test-header)%" | ||
- object_key: route-only | ||
factory_key: envoy.string | ||
format_string: | ||
text_format_source: | ||
inline_string: "route" | ||
)EOF"; | ||
runPerRouteFilter(listener_config, route_config); | ||
|
||
const auto* both = info_.filterState()->getDataReadOnly<Router::StringAccessor>("both"); | ||
ASSERT_NE(nullptr, both); | ||
// Route takes precedence | ||
EXPECT_EQ(both->serializeAsString(), "route-test-value"); | ||
|
||
const auto* listener = | ||
info_.filterState()->getDataReadOnly<Router::StringAccessor>("listener-only"); | ||
ASSERT_NE(nullptr, listener); | ||
// Only set on listener | ||
EXPECT_EQ(listener->serializeAsString(), "listener"); | ||
|
||
const auto* route = info_.filterState()->getDataReadOnly<Router::StringAccessor>("route-only"); | ||
ASSERT_NE(nullptr, route); | ||
// Only set on route | ||
EXPECT_EQ(route->serializeAsString(), "route"); | ||
} | ||
|
||
} // namespace SetFilterState | ||
} // namespace HttpFilters | ||
} // namespace Extensions | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason you run both, typically a route level config will override the listener level
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking it would be nice to have common listener level ones and then have additional route specific ones. You can override by applying both (route is last so it wins).
We do have this use case - but it could also be done by just copying the listener ones into every route of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say in that case you should have explicit options most likely, as in
override, merge, etc.
Also acceptable at first of course to just say there's only one behavior, but this would be different from most filters today afaikThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I partly agree with @EItanya . I will prefer let this filter:
It's a little weird to apply both filter level config and rotue level config, but ignore the vh config, esp they all are completely same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I did (2). Tbh I did not know there is vhost and route level?? I will need to look into that I thought it was just 2 levels