-
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
Use dynamicMetadata to construct metadataMatchCriteria() #3254
Use dynamicMetadata to construct metadataMatchCriteria() #3254
Conversation
39783b5
to
475617f
Compare
source/common/router/router.h
Outdated
@@ -163,6 +163,10 @@ class Filter : Logger::Loggable<Logger::Id::router>, | |||
} | |||
const Router::MetadataMatchCriteria* metadataMatchCriteria() const override { | |||
if (route_entry_) { | |||
const auto impl = dynamic_cast<RequestInfo::RequestInfoImpl*>(&callbacks_->requestInfo()); |
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.
Instead of depending on the RequestInfoImpl here (and Router::MetadataMatchCriteriaImpl in the RequestInfoImpl), is there a reason you can't invoke callbacks_->requestInfo().dynamicMetadata()
here and then construct the merged MetadataMatchCriteriaImpl in this function?
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.
because metadataMatchCriteria()
needs to create a new Router::MetadataMatchCriteria
object, which I think should be attached to RequestInfo
. We could create it here, and then attach it to RequestInfo
... Any better ideas?
Btw, I know it's confusing that I added this to the Impl classes, I was just lazy and didn't want to change the interfaces for the base classes. I will do that, if you think this approach is generally sane.
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'm still not sure we should leak the match criteria into the RequestInfo interface. A new Router::Filter instance is created for each new stream, so I think it would be safe to track the Router::MetadataMatchCriteria in the filter.
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.
ah, i wasn't aware of that. let me change it then.
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.
hmm, it'll mean more interface breakage tho given that Router::Filter
inherits from Upstream::LoadBalancerContext
:
const Router::MetadataMatchCriteria* metadataMatchCriteria() const override {}
which now must mutate the object... Let me see how it'll look.
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.
ok, it's not that many places...
metadataMatchCriteria(const Router::MetadataMatchCriteria* metadata_match) { | ||
// The request's metadata, if present, takes precedence over the route's. | ||
const auto& metadata = dynamicMetadata().filter_metadata(); | ||
const auto filter_it = metadata.find("envoy.lb"); |
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.
See my comment about placement of this code, but you should use the ENVOY_LB
constant from common/config/well_known_names.h
.
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.
yep, will do. Thanks for the quick review!
This enables filter writers to construct metadataMatchCriteria() from a RequestInfo (e.g.: header value). This is useful given that currently routes need to be statically matched when using metadata, e.g.: ``` --- match: prefix: / route: cluster: cluster-name metadata_match: filter_metadata: envoy.lb: version: '1.0' ``` For setups in which RDS isn't used and routes cannot be constructed dynamically, a filter can be used to match which endpoints should be used. So a filter could do something like: ``` auto header_value = std::string(header_entry->value().getStringView()); auto keyval = MessageUtil::keyValueStruct("version", header_value); callbacks_->requestInfo().setDynamicMetadata("envoy.lb", keyval); ``` to determine the version for this request. Signed-off-by: Raul Gutierrez Segales <[email protected]>
475617f
to
bde0c9c
Compare
@zuercher updated to keep everything within It required relaxing the definition of I accidentally rebased... sorry about that. I'll add some tests. |
Signed-off-by: Raul Gutierrez Segales <[email protected]>
4fb6b8c
to
1747131
Compare
Signed-off-by: Raul Gutierrez Segales <[email protected]>
Signed-off-by: Raul Gutierrez Segales <[email protected]>
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.
Looks pretty good to me. I think there's one missing bit of code, though.
source/common/router/router.h
Outdated
const auto filter_it = request_metadata.find(Envoy::Config::MetadataFilters::get().ENVOY_LB); | ||
if (filter_it != request_metadata.end()) { | ||
metadata_match_ = | ||
route_entry_->metadataMatchCriteria()->mergeMatchCriteria(filter_it->second); |
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 think this needs to handle the case where route_entry_->metadataMatchCriteria()
is nullptr and add a test case for that path.
test/common/router/router_test.cc
Outdated
|
||
EXPECT_CALL(callbacks_.request_info_, dynamicMetadata()).WillRepeatedly(ReturnRef(metadata)); |
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 think you should modify the mock in test/mocks/request_info to return this by default.
Also: * add a test for the above * add default return value for MockRequestInfo.dynamicMetadata Signed-off-by: Raul Gutierrez Segales <[email protected]>
Signed-off-by: Raul Gutierrez Segales <[email protected]>
@zuercher thanks for the review! i added two more commits to address your latest comments. |
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.
Looks good, thanks, a couple of nits.
include/envoy/router/router.h
Outdated
* Creates a new MetadataMatchCriteria, merging existing | ||
* metadata criteria this criteria. The result criteria is the | ||
* combination of both sets of criteria, with those from the | ||
* ProtobufWkt::Struct taking precedence. |
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.
include/envoy/router/router.h
Outdated
|
||
/** | ||
* Creates a new MetadataMatchCriteria, merging existing | ||
* metadata criteria this criteria. The result criteria is the |
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.
Nit: "with this criteria"
Also, annotate with @param and @return. Signed-off-by: Raul Gutierrez Segales <[email protected]>
@htuch addressed -- thanks! |
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.
LGTM, small Q/comment.
@@ -161,8 +163,20 @@ class Filter : Logger::Loggable<Logger::Id::router>, | |||
} | |||
return {}; | |||
} | |||
const Router::MetadataMatchCriteria* metadataMatchCriteria() const override { | |||
const Router::MetadataMatchCriteria* metadataMatchCriteria() override { |
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.
In the retry case you may end up calling this method multiple times. Is it worth checking to see if metadata_match_
is not nullptr and returning if not? Is there any chance that doing the merge multiple times could have strange effects? If not to both of these, I would still add a comment about how this can happen multiple times for a single request.
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 think checking for nullptr and returning early is worth it. Multiple calls to merge should yield the same result, but we can avoid the computation and creating new objects.
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.
@mattklein123 hold on -- can the route_entry_
for a request change (e.g.: someone called clearRouteCache()
and then metadataMatchCriteria()
gets called again)? In that case, caching the result for metadataMatchCriteria()
would be wrong... we'd want to recompute.
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 think it's ok, as long as metadataMatchCriteria() isn't invoked until LoadBalancer::chooseHost, which happens during Router::Filter::getConnPool in Filter::decodeHeaders and again in Filter::doRetry. The route_ and route_entry_ get set in decodeHeaders before load balancing, and I believe that once that happens they do not change.
clearRouteCache operates on the Http::ConnectionManagerImpl::ActiveStreamFilterBase which ends up being the source of the route_ and therefore route_entry_ via its implementation of StreamFilterCallbacks::route().
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.
This seems right, can we add an ASSERT to this effect (i.e. if we get called twice, the route entry is the same)?
Signed-off-by: Raul Gutierrez Segales <[email protected]>
@htuch @mattklein123 addressed — thanks! |
// Have we been called before? If so, there's no need to recompute because | ||
// by the time this method is called for the first time, route_entry_ should | ||
// not change anymore. | ||
if (metadata_match_ != nullptr) { |
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.
Can you add a test for this as well? Thanks.
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.
@htuch done — plus two bonus tests:
- using > 1 key/value pair in dynamicMetadata()
- mixing a key that overrides the route entry with one that doesn't
thanks for the review!
This tests 3 things: * using > 1 key/value in dynamicMetadata() * mixing a key/value pair that overrides the route entry's config with another pair that doesn't override the route entry's config * caching metadataMatchCriteria()'s result, when it has been computed from the request's dynamic metadata Signed-off-by: Raul Gutierrez Segales <[email protected]>
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.
Very nice, thank you!
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.
Thanks!
This enables filter writers to construct metadataMatchCriteria()
from a RequestInfo (e.g.: header value). This is useful given
that currently routes need to be statically matched when
using metadata, e.g.:
For setups in which RDS isn't used and routes cannot be constructed
dynamically, a filter can be used to match which endpoints should be
used.
So a filter could do something like:
to determine the version for this request.
Signed-off-by: Raul Gutierrez Segales [email protected]