Skip to content
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

Merged

Conversation

rgs1
Copy link
Member

@rgs1 rgs1 commented Apr 30, 2018

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]

@@ -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());
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member Author

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.

Copy link
Member Author

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");
Copy link
Member

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.

Copy link
Member Author

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]>
@rgs1 rgs1 force-pushed the merge-request-info-dynamic-metadata-for-lb branch from 475617f to bde0c9c Compare April 30, 2018 19:54
@rgs1
Copy link
Member Author

rgs1 commented Apr 30, 2018

@zuercher updated to keep everything within Filter.

It required relaxing the definition of LoadBalancerContext.metadataMatchCriteria(). I also moved mergeMatchCriteria from MetadataMatchCriteriaImpl to its base class, to avoid the dynamic_cast bussiness.

I accidentally rebased... sorry about that. I'll add some tests.

Signed-off-by: Raul Gutierrez Segales <[email protected]>
@rgs1 rgs1 force-pushed the merge-request-info-dynamic-metadata-for-lb branch from 4fb6b8c to 1747131 Compare April 30, 2018 20:09
Raul Gutierrez Segales added 2 commits April 30, 2018 13:48
Signed-off-by: Raul Gutierrez Segales <[email protected]>
Copy link
Member

@zuercher zuercher left a 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.

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);
Copy link
Member

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.


EXPECT_CALL(callbacks_.request_info_, dynamicMetadata()).WillRepeatedly(ReturnRef(metadata));
Copy link
Member

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.

Raul Gutierrez Segales added 2 commits May 1, 2018 13:52
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]>
@rgs1
Copy link
Member Author

rgs1 commented May 1, 2018

@zuercher thanks for the review! i added two more commits to address your latest comments.

zuercher
zuercher previously approved these changes May 1, 2018
Copy link
Member

@htuch htuch left a 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.

* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: which ProtobufWkt::Struct? Do you want to use @param and @return here?


/**
* Creates a new MetadataMatchCriteria, merging existing
* metadata criteria this criteria. The result criteria is the
Copy link
Member

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]>
@rgs1
Copy link
Member Author

rgs1 commented May 3, 2018

@htuch addressed -- thanks!

Copy link
Member

@mattklein123 mattklein123 left a 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 {
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member Author

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.

cc: @zuercher @htuch

Copy link
Member

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().

Copy link
Member

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)?

@rgs1
Copy link
Member Author

rgs1 commented May 3, 2018

@htuch @mattklein123 addressed — thanks!

@htuch htuch self-assigned this May 3, 2018
// 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) {
Copy link
Member

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.

Copy link
Member Author

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]>
Copy link
Member

@mattklein123 mattklein123 left a 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!

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@htuch htuch merged commit 2d25067 into envoyproxy:master May 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants