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

xds: unify client and server handling HttpConnectionManager #8228

Conversation

voidzcy
Copy link
Contributor

@voidzcy voidzcy commented Jun 2, 2021

Enables parsing HttpConnectionManager filter for the server side TCP listener, with the same codepath for handling it on the client side. Major changes include:

  • Remodeled LdsUpdate with HttpConnectionManager. Now LdsUpdate is an oneof of HttpConnectionManager (for client side) or Listener (for server side). Each of Listener's FiliterChain contains an HttpConnectionManager (required).
  • Refactored code for validating and parsing the TCP Listener (for server side), put it into ClientXdsClient. The common part of validating/parsing HttpConnectionManager is reused/shared for client side.
  • Included the name of FilterChain in the parsed form. As specified by the API, each FilterChain has a unique name. If the name is not provided by the control plane, a UUID is used. FilterChain names can be used for bookkeeping a set of FilterChain easily (e.g., used as map key).
  • Added methods isSupportedOnClients() and isSupportedOnServers() to the Filter interface. Parsing the top-level HttpFilter requires knowing if the HttpFilter implementation is supported for the target usage (client-side or server-side). Note, parsing override HttpFilter configs does not need to know whether the config is used for an HttpFilter that is only supported for the client-side or server side.
  • Added a new kind of Route: Route with non-forwarding action. Updated the XdsNameResolver being able to handle Route with non-forwarding action: if such a Route is matched to an RPC, that RPC is failed. Note, it is possible that XdsNameResolver receives xDS updates with all Routes with non-forwarding action. That is, the service config will not reference any cluster. Such case can be handled by cluster_manager LB policy's LB config parser: the parser returns the error to Channel and the Channel will handle it as error service config.

@voidzcy voidzcy marked this pull request as ready for review June 3, 2021 21:20
voidzcy added 3 commits June 7, 2021 15:28
…nt and server side. This adds the requirement for server side HttpConnectionManager to have either RDS or inlined RouteConfiguration.
…ering cluster_manager LB policy config parser is able to handle config with empty childPolicy.
@voidzcy voidzcy force-pushed the refactor/use_unified_lds_message_handler_for_client_and_server branch from 7772fc8 to f735616 Compare June 15, 2021 17:17
@voidzcy
Copy link
Contributor Author

voidzcy commented Jun 15, 2021

Added support for Route with non-forwarding action and did more cleanup for tests. Also, the PR description is updated. PTAL.

@voidzcy
Copy link
Contributor Author

voidzcy commented Jun 15, 2021

Added a test case for covering parsing RBACPerRoute. CIs should pass after #8262 is merged.

@voidzcy voidzcy added the kokoro:force-run Add this label to a PR to tell Kokoro to re-run all tests. Not generally necessary label Jun 15, 2021
@grpc-kokoro grpc-kokoro removed the kokoro:force-run Add this label to a PR to tell Kokoro to re-run all tests. Not generally necessary label Jun 15, 2021
"FilterChain " + proto.getName() + " contains filter " + filter.getName()
+ " with unsupported typed_config type " + any.getTypeUrl());
}
// Any filters after the first HttpConnectionManager are ignored.
Copy link
Contributor

Choose a reason for hiding this comment

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

The code doesn't actually ignore filters after the first HCM. If you break the loop after line 335 it would have.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why? there is a if (httpConnectionManager == null) statement guarding the code of parsing HCM. We still want to loop over the list of network filters, just in case there are filters with duplicated name or filters other than HCM.

Copy link
Contributor

Choose a reason for hiding this comment

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

... We still want to loop over the list of network filters, just in case there are filters with duplicated name or filters other than HCM.

That is not ignoring and the comment is wrong. But if the code matches the spec (from A36 gRFC) then it's a minor issue. The spec tells to validate all of them but not use any for processing. So the code seems to be right (but the comment doesn't match)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, the comment is really intend to mean we will take the first HttpConnectionManager. I will update or delete the comment if you think that is confusing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Deleted the comment.

if (filter == null) {
Filter filter = filterRegistry.get(typeUrl);
if (filter == null || (!isForClient && filter.isSupportedOnClients())
|| (isForClient && filter.isSupportedOnServers())) {
Copy link
Contributor

@sanjaypujare sanjaypujare Jun 17, 2021

Choose a reason for hiding this comment

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

This expression should be:

    if (filter == null || (isForClient && !filter.isSupportedOnClients())
        || (!isForClient && !filter.isSupportedOnServers())) 

Basically: if I am processing for-client but the filter-is-not-supported-on-clients OR I am processing for-server but the filter-is-not-supported-on-servers

The 2 are not equivalent because isSupportedOnClients() and isSupportedOnServers() are not opposite of each other.

Tests should also be added/modified to enforce the correct logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahhh, good catch. !isForClient == isForServer, this is awkward. I wish I could use an enum instead of a boolean. But thought that's an overkill for such a small thing. I will fix and add some test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, and enabled Router filter for server side. It will be used for both client and server, and we can easily use it in the test to cover this logic. Also, I deleted the newly added interface methods isSupportedOnClients() and isSupportedOnServers(). Those are redundant, can just check with instance of ClientInterceptorBuilder and instance of ServerInterceptorBuilder. It's cumbersome to have two sources of truth.

Copy link
Contributor

Choose a reason for hiding this comment

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

Those are redundant, can just check with instance of ClientInterceptorBuilder and instance of ServerInterceptorBuilder. It's cumbersome to have two sources of truth.

Yes but isSupported... are more readable and I prefer those. These could have been made default methods in the interface with the correct Java version.

}
Message rawConfig = anyConfig;
if (anyConfig.getTypeUrl().equals(TYPE_URL_TYPED_STRUCT)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

anyConfig.getTypeUrl() should be same as typeUrl so no need to use the longer expression?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good. Updated.

case FILTER_ACTION:
return StructOrError.fromError("Unsupported action type: filter_action");
return StructOrError.fromError(
"Route [" + proto.getName() + "] with unsupported action type: filter_action");
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not simplify:

      case REDIRECT:
      case DIRECT_RESPONSE:
      case FILTER_ACTION:
        return StructOrError.fromError(
            "Route [" + proto.getName() + "] with unsupported action type: " + proto.getActionCase());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

if (isOptional) {
return null;
} else {
return StructOrError.fromError(
"HttpFilter [" + filterName + "] is not optional and has an unsupported config type: "
+ typeUrl);
"HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for "
Copy link
Contributor

Choose a reason for hiding this comment

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

I noticed you don't have a test to verify this error generation. So the original bug on lines 523-524 went undetected.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually it is tested. See tests started with parseHttpFilter_:

  • The RouterFilter is supported for both client and server, so we use it to cover the branch isForClient && (filter instanceof ClientInterceptorBuilder) and !isForClient && (filter instanceof ServerInterceptorBuilder).
  • The FaultFilter is supported for client only, so we used it to cover the branch !isForClient && !(filter instanceof ServerInterceptorBuilder)
  • The RbacFilter is supported for server only, so we used it to cover the branch isForClient && !(filter instanceof ClientInterceptorBuilder)

The four branches covered all forClient + supported/unsupported and forServer + supported/unsupported cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Eliminated the boolean zen in line 523-524, filter == null || is not needed, its included in instanceof.

import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.Nullable;

/**
* Router filter implementation. Currently this filter does not parse any field in the config.
*/
enum RouterFilter implements Filter, ClientInterceptorBuilder {
enum RouterFilter implements Filter, ClientInterceptorBuilder, ServerInterceptorBuilder {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not from this PR but why use enum when clearly this type is not used to enumerate constants? enum works given how this is used but I wouldn't think of the RouterFilter as an enum.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In Java, enum types are considered to be a special type of class. When creating an enum class, the compiler will create instances (objects) of each enum constants. So enum can be an easy trick to create singletons.

Copy link
Contributor

@sanjaypujare sanjaypujare left a comment

Choose a reason for hiding this comment

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

LG. I have left a comment about an error case not being tested. It will be good to address that.

@voidzcy voidzcy merged commit 9a8bc10 into grpc:master Jun 18, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 17, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants