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

thrift_proxy: simple thrift router #3863

Merged
merged 13 commits into from
Jul 26, 2018

Conversation

zuercher
Copy link
Member

Provides a very basic thrift router that can route to clusters
based on method name only. A Thrift DecoderFilter interface is
introduced, but the only available filter is the Router. The
Network filter and router are capable of translating transports
and protocols but presently cannot be configured to do so.

Relates to #2247.

Risk Level: low
Testing: unit and integration testing
Docs Changes: protobuf documentation updated
Release Notes: introduced a basic thrift_proxy routing extension

Signed-off-by: Stephan Zuercher stephan@turbinelabs.io

Provides a very basic thrift router that can route to clusters
based on method name only. A Thrift DecoderFilter interface is
introduced, but the only available filter is the Router. The
Network filter and router are capable of translating transports
and protocols but presently cannot be configured to do so.

Relates to envoyproxy#2247.

*Risk Level*: low
*Testing*: unit and integration testing
*Docs Changes*: protobuf documentation updated
*Release Notes*: introduced a basic thrift_proxy routing extension

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@zuercher
Copy link
Member Author

My apologies that this turned out to be such a large change. In the
end, the direction I took with the Thrift filter needed a lot of
revisions to work with a Thrift router that's capable of translating
transports and protocols. I'm willing to take a stab at breaking this
up into smaller pieces if folks think that would make reviews easier.
Just let me know.

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@junr03 junr03 requested a review from brian-pane July 16, 2018 16:32
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@rgs1
Copy link
Member

rgs1 commented Jul 18, 2018

@zuercher a bit of a pie in the sky question: would this be reusable within another filter? Lets say i've got incoming HTTP1.1 traffic, and I want to write a filter that decodes headers and then fans out to a thrift service to read/write something. Is that something you thought of or would it require a bit refactoring?

cc: @fishcakez

Copy link
Contributor

@brian-pane brian-pane left a comment

Choose a reason for hiding this comment

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

I'm half finished reading through this. I'll read the remaining half tomorrow.

}

if (event == Network::ConnectionEvent::RemoteClose) {
stats_.cx_destroy_local_with_active_rq_.inc();
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the logic for this reversed? Upon a remote close event, this is incrementing the cx_destroy_local_with_active_rq_ counter. Should it be the cx_destroy_remote_with_active_rq_ counter?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, but it also turns out that the connection pool will record these, so I'm going to remove this code.

Copy link
Member Author

Choose a reason for hiding this comment

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

Er. Sorry. No the conn pool tracks upstream destroy with active request. I'll fix this.

Copy link
Member

@rgs1 rgs1 left a comment

Choose a reason for hiding this comment

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

did a quick pass, will do another one tomorrow


// Route request to some upstream cluster.
RouteAction route = 2 [(validate.rules).message.required = true, (gogoproto.nullable) = false];
;
Copy link
Member

Choose a reason for hiding this comment

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

nit: trailing ;

message RouteMatch {
// If specified, the route must exactly match the request method name. As a special case, an
// empty string matches any request method name.
string method = 1;
Copy link
Member

Choose a reason for hiding this comment

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

is this case-sensitive?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes.

}

if (event == Network::ConnectionEvent::RemoteClose) {
stats_.cx_destroy_local_with_active_rq_.inc();
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? given we know what the event is and it can't be something else

zuercher added 2 commits July 18, 2018 09:57
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@zuercher
Copy link
Member Author

zuercher commented Jul 18, 2018

@rgs1 (re: filter making thrift requests from the http filter stack) I think you could reuse parts of this, but not the ConnManager/Router directly. To send thrift requests to an external server (for auth or whatever), you'd likely want to extend the approach in app_exception_impl.h/.cc with some kind of code generator that would consume Thrift IDL and generate code for objects that can marshal/unmarshal themselves to/from buffers via a Protocol instance. You need some kind of AsyncThriftClient that would handle the request encoding/response decoding and transport and such. You look at the ext_authz filter for more-or-less how that would look.

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Copy link
Contributor

@brian-pane brian-pane left a comment

Choose a reason for hiding this comment

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

Aside from nits, I have one big question: in the situation where the upstream and downstream use the same Thrift protocol (but potentially different Thrift transports), is it possible to skip the parsing, callbacks, and re-encoding of the message?

/**
* @return uint64_t the ID of the originating stream for logging purposes.
*/
virtual uint64_t streamId() PURE;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this be a const method?

Copy link
Contributor

Choose a reason for hiding this comment

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

I have the same question about many of the other methods of this class.

Copy link
Member Author

Choose a reason for hiding this comment

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

Some can be const, I'll update them.

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@zuercher
Copy link
Member Author

The proxy has to modify the sequence id to avoid the situation where the same sequence id from separate downstream clients is sent to a single upstream (there's an outstanding TODO in the code to actually do this). Modifying the sequence id can (for, say, the compact protocol) change the size of the message, which may necessitate modifying the transport frame.

For transports which report size (e.g. framed and header, but not unframed), I think that the mechanism for this would be:

  1. the ConnManager uses the frame size to track how much more data is in the current request
  2. the Router::Upstream detects (during onPoolReady) that the downstream & upstream are using the same protocol and can compute the correct transport size (handle compact proto size change and/or header size change). Some thought will have to be given as to whether this can be done in the presence of additional arbitrary DecoderFilters. Perhaps there's a way to distinguish between a filter that might modify any part of the request vs. one that only deals with headers/message begin.
  3. Router::Upstream writes the modified transport start and message begin and then calls back to ConnManager to indicate that it wishes to deal purely in Buffer::Instances going forward
  4. ConnManager passes remaining bytes (based on size from 1) directly to the DecoderFilter as Buffer::Instances which are written directly to the upstream connection.

Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
brian-pane
brian-pane previously approved these changes Jul 19, 2018
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
ProtocolError = 7,
InvalidTransform = 8,
InvalidProtocol = 9,
UnsupportedClientType = 10,
Copy link
Contributor

Choose a reason for hiding this comment

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

fbthrift has a few more exception types than apache thrift: https://github.com/facebook/fbthrift/blob/04ba53d4503d51a9a27f700b114eb7cea4678dc1/thrift/lib/cpp/TApplicationException.h#L57. Should these be supported too? As an example later in the code InternalError could be replaced by more specific code such as LoadShedding. Would that create sub-optimal experience for apache thrift as the enum value would not exist in the client services logic? Without using the fbthrift exception types there will be feature regressions for some client services as would no longer be able to differentiate.

Is apache thrift being treated as the standard here when there are competing standards?

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 short-term, we'll stick with the apache definitions. Longer term, we'll probably want to use internal identifiers for various error conditions and then provide a configuration point to control whether they get mapped to apache or fb's values.

zuercher added 3 commits July 23, 2018 13:37
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
Signed-off-by: Stephan Zuercher <stephan@turbinelabs.io>
@zuercher zuercher merged commit 72a964c into envoyproxy:master Jul 26, 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.

5 participants