Skip to content

Conversation

qiwzhang
Copy link
Contributor

Description:

Add a new function in filter_factory to allow filter to generate a pre-processed object for per-filter-config.
In VHost, Route, and RouteEntry, add a new function perFilterConfigObject to retrieve this pre-processed object.

Risk Level: Low

@qiwzhang
Copy link
Contributor Author

@mattklein123 @rodaine, could you help to review this? Thanks

/**
* All pre-processed per route config objects should be derived from this class.
*/
class PerRouteConfigObject {
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 can be named "PerRouteConfig", or maybe "RouteSpecificConfig". Or "RouteSpecificFilterConfig".

Copy link
Member

Choose a reason for hiding this comment

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

I would vote for "RouteSpecificFilterConfig" but I don't feel that strongly about it.

Copy link
Member

Choose a reason for hiding this comment

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

Now, this is going to be painful isn't it? Every filter will now have two config objects in some sense. a preprocessed per-route thing and a filter level config object. I guess thats okay, given that the perf benefit

Copy link
Member

Choose a reason for hiding this comment

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

Every filter is basically doing this today, and it's entirely optional, so it seems OK to me?

Copy link
Member

Choose a reason for hiding this comment

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

fair enough.

*/
virtual Router::PerRouteConfigObjectConstSharedPtr
createPerRouteConfigObject(const Protobuf::Message& config) {
UNREFERENCED_PARAMETER(config);
Copy link
Member

Choose a reason for hiding this comment

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

Style: delete this line, change previous line to createPerRouteConfigObject(const Protobuf::Message&) {

@@ -895,5 +916,10 @@ const Protobuf::Message* PerFilterConfigs::get(const std::string& name) const {
return cfg == configs_.end() ? nullptr : cfg->second.get();
}

const PerRouteConfigObject* PerFilterConfigs::get_object(const std::string& name) const {
Copy link
Member

Choose a reason for hiding this comment

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

style: methods get camelCase, not snake_case

@@ -895,5 +916,10 @@ const Protobuf::Message* PerFilterConfigs::get(const std::string& name) const {
return cfg == configs_.end() ? nullptr : cfg->second.get();
}

const PerRouteConfigObject* PerFilterConfigs::get_object(const std::string& name) const {
auto obj = objects_.find(name);
Copy link
Member

Choose a reason for hiding this comment

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

nit: I'd name this "it" or "iterator"

Router::PerRouteConfigObjectConstSharedPtr
createPerRouteConfigObject(const Protobuf::Message& config) override {
const auto data = dynamic_cast<const ProtobufWkt::Timestamp&>(config);
auto obj = new TestFilterConfigObject;
Copy link
Member

Choose a reason for hiding this comment

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

std::make_shared();

* the given filter name. If not per-filter config for the filter, or the filter factory
* doesn't create such object, nullptr is returned.
*/
virtual const PerRouteConfigObject* perFilterConfigObject(const std::string& name) const PURE;
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 we should add an additional template method:

template <typename Derived>
const Derived* perFilterConfigObjectTyped(const std::string& name) const {
    const PerRouteConfigObject* obj = perFilterConfigObject(name);
    if (obj == nullptr) {
        return obj;
    }
    ASSERT(dynamic_cast<const Derived*>(obj) != nullptr);
    return static_cast<const Derived*>(obj);
}

Or similar.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah we have basically this for TLS. +1

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.

In general love this approach. Thanks @qiwzhang. cc @rodaine @rshriram this will allow you to trivially improve the perf of your filters? Should we wait until this lands?

I actually wonder if we should just kill the other functions and only have this type of config. If the filter just wants the proto they can just store it in a wrapper? WDYT?

/**
* All pre-processed per route config objects should be derived from this class.
*/
class PerRouteConfigObject {
Copy link
Member

Choose a reason for hiding this comment

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

I would vote for "RouteSpecificFilterConfig" but I don't feel that strongly about it.

* the given filter name. If not per-filter config for the filter, or the filter factory
* doesn't create such object, nullptr is returned.
*/
virtual const PerRouteConfigObject* perFilterConfigObject(const std::string& name) const PURE;
Copy link
Member

Choose a reason for hiding this comment

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

Yeah we have basically this for TLS. +1

const PerRouteConfigObject*
RouteEntryImplBase::WeightedClusterEntry::perFilterConfigObject(const std::string& name) const {
const PerRouteConfigObject* cfg = per_filter_configs_.get_object(name);
if (cfg != nullptr) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: Use ternary operator here. Can you fix other cases of this? I think @rodaine might have another one that I didn't comment on.

@mattklein123
Copy link
Member

As an aside, I don't think this solution will work for Lua which is more complicated. This is because the Lua code must be JITd per-thread and can't be constant global like this data. I will think about about the Lua case but I think we can probably proceed with this as it's blocking a few things.

@ggreenway
Copy link
Member

@mattklein123 for Lua, could the config object own a TLS slot, and store the Lua state in that? Might have to make this non-const, but that wouldn't substantially change the approach.

@rshriram
Copy link
Member

I actually wonder if we should just kill the other functions and only have this type of config. If the filter just wants the proto they can just store it in a wrapper? WDYT?

+1 to that.

* the given filter name. If not per-filter config for the filter, or the filter factory
* doesn't create such object, nullptr is returned.
*/
virtual const PerRouteConfigObject* perFilterConfigObject(const std::string& name) const PURE;
Copy link
Member

Choose a reason for hiding this comment

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

IF we are going down the path of merging with perFilterConfig, I would kill this function and change signature of the other..

Copy link
Member

Choose a reason for hiding this comment

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

+1

Copy link
Member

@rshriram rshriram left a comment

Choose a reason for hiding this comment

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

This is nice.. as noted earlier, just change the signature of the existing function (perFilterConfig) to return the wrapper object you have instead of proto message.

@mattklein123
Copy link
Member

yeah +1 to @rshriram suggestion. We can hold on the fault/buffer PRs until we finish this.

for Lua, could the config object own a TLS slot, and store the Lua state in that? Might have to make this non-const, but that wouldn't substantially change the approach.

Yes, that should work. The only downside to that approach is we would need a TLS slot per route which might be inefficient in the case where we have lots of routes, but, I think that's probably an edge case. IMO I would just leave the signature as const for now since the wrapper really does have to be const. If the implementation does non-const/mutable things under the hood in special cases that's probably OK...

Copy link
Member

@rodaine rodaine left a comment

Choose a reason for hiding this comment

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

Awesome! Agreed with @rshriram and @mattklein123, let's just replace the existing perFilterConfig methods.

* the given filter name. If not per-filter config for the filter, or the filter factory
* doesn't create such object, nullptr is returned.
*/
virtual const PerRouteConfigObject* perFilterConfigObject(const std::string& name) const PURE;
Copy link
Member

Choose a reason for hiding this comment

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

+1

@qiwzhang
Copy link
Contributor Author

qiwzhang commented Apr 19, 2018

Thanks for the review. Here is my plan:

  1. not to add another perFilterConfigObject(), change exising perFilterConfig() to return RouteSpecificFilterConfig

  2. In filter_factory, replace createEmptyRouteConfigProto() with

Router::RouteSpecificFilterConfigConstSharedPtr
createRouteSpecificFilterConfig(const ProtobufWkt::Struct&)

  1. will add

templated function as
const Derived* perFilterConfigTyped()

Thanks

@qiwzhang
Copy link
Contributor Author

PTAL

@mattklein123
Copy link
Member

I will review tomorrow, but @ggreenway @rshriram I don't think this by itself will work for Lua. The reason being that in order to use TLS and more complicated things, the factory will need access to the FactoryContext. @ggreenway this bring us back full circle and I think we will need to provide the full FactoryContext all the way to the route config, or think of some other cleaner callback way of getting it in.

I would still suggest we just get this in as is and we can figure out the Lua (and maybe WebSocket case after).

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, just some small nits. I think we can sort out the Lua case later.

*/
virtual const Protobuf::Message* perFilterConfig(const std::string& name) const PURE;
template <class Derived> const Derived* perFilterConfigTyped(const std::string& name) const {
const RouteSpecificFilterConfig* obj = perFilterConfig(name);
Copy link
Member

Choose a reason for hiding this comment

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

This entire function can be simplified to:

return dynamic_cast<const Derived*>(perFilterConfig(name));

We need to do the real dynamic_cast for correctness, and it's fine to dynamic_cast nullptr. Same for the other spots.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

* filter name configured for this virtual host. If none is present,
* nullptr is returned.
* @return const RouteSpecificFilterConfig* the per-filter config pre-processed object for
* the given filter name. If not per-filter config for the filter, or the filter factory
Copy link
Member

Choose a reason for hiding this comment

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

"If there is no per-filter config..." (same below)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

* nullptr is returned.
* @return const RouteSpecificFilterConfig* the per-filter config pre-processed object for
* the given filter name. If not per-filter config for the filter, or the filter factory
* doesn't create such object, nullptr is returned.
Copy link
Member

Choose a reason for hiding this comment

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

"or the filter factory returns nullptr, ..." (same below)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

* empty proto. By default, this method returns the same value as createEmptyConfigProto,
* and can be optionally overridden in implementations.
* @return RouteSpecificFilterConfigConstSharedPtr allow the filter to pre-process per route
* config. Returned object will be stored in the filter chain config.
Copy link
Member

Choose a reason for hiding this comment

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

"will be stored in the loaded route configuration."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

return createEmptyConfigProto();
virtual Router::RouteSpecificFilterConfigConstSharedPtr
createRouteSpecificFilterConfig(const ProtobufWkt::Struct&) {
return Router::RouteSpecificFilterConfigConstSharedPtr{};
Copy link
Member

Choose a reason for hiding this comment

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

nit: return {} or return nullptr should work

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Signed-off-by: Wayne Zhang <qiwzhang@google.com>
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
Signed-off-by: Wayne Zhang <qiwzhang@google.com>
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, thanks. @ggreenway if this works for you can you approve/merge?

@rshriram
Copy link
Member

Question: can we get similar functionality for tcp ?

@rshriram
Copy link
Member

I don’t know what gets carried through from SNI filter chain match.

@ggreenway
Copy link
Member

@rshriram for tcp, routes will be defined by the FilterChainMatch, so each route will have a completely independent filter-chain (with config) anyways, so no additional work will be needed.

@ggreenway ggreenway merged commit 47c63e5 into envoyproxy:master Apr 19, 2018
virtual ProtobufTypes::MessagePtr createEmptyRouteConfigProto() {
return createEmptyConfigProto();
virtual Router::RouteSpecificFilterConfigConstSharedPtr
createRouteSpecificFilterConfig(const ProtobufWkt::Struct&) {
Copy link
Member

@lizan lizan Apr 19, 2018

Choose a reason for hiding this comment

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

I would strongly -1 to exposing Struct to filter interface and in favor of wrapping them in an interface like #3109 did. Hiding Struct from filter interface is important when we see scaling issues with the config and want to migrate to Any (via oneof) in future, as mentioned in https://github.com/envoyproxy/data-plane-api/issues/540#issuecomment-373250850 . cc @htuch.

Copy link
Member

Choose a reason for hiding this comment

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

+1000

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.

7 participants