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

grpc: Add a pointer of server to ctx passed into stats handler #6750

Merged
merged 4 commits into from
Oct 26, 2023

Conversation

zasweq
Copy link
Contributor

@zasweq zasweq commented Oct 25, 2023

This PR adds a pointer to the grpc.Server into the context passed into the stats handler. It also adds an internal only helper on the server that returns whether a given method is registered or not. This will be used in OpenTelemetry instrumentation for security purposes with respect to the cardinality of the method attribute. The next step after these two PRs is to move the rest of the stats handler callouts to the gRPC layer, get registered methods plumbed client side, canoncalize Target() without breaking it and then the OpenTelemetry instrumentation itself :).

RELEASE NOTES: N/A

@zasweq zasweq added the Type: Feature New features or improvements in behavior label Oct 25, 2023
@zasweq zasweq added this to the 1.60 Release milestone Oct 25, 2023
@zasweq zasweq requested a review from dfawley October 25, 2023 22:12
@codecov
Copy link

codecov bot commented Oct 25, 2023

Codecov Report

Merging #6750 (646d55b) into master (c76d75f) will decrease coverage by 0.05%.
The diff coverage is 89.65%.

Additional details and impacted files

@@ -73,6 +73,11 @@ var (
// xDS-enabled server invokes this method on a grpc.Server when a particular
// listener moves to "not-serving" mode.
DrainServerTransports any // func(*grpc.Server, string)
// IsRegisteredMethod returns whether the passed in method is registered as
// a method on the server.
IsRegisteredMethod any // func(*grpc.Server, string)
Copy link
Member

Choose a reason for hiding this comment

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

This returns a bool right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, right, whoops. Added.

Comment on lines 34 to 36
// Guarantees we satisfy this interface; panics if unimplemented methods are
// called.
testgrpc.TestServiceServer
Copy link
Member

Choose a reason for hiding this comment

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

Why are you bundling a stats handler with a service implementation? That seems undesirable unless it's somehow important to keep the two things together. (And now that I've look at the rest of the code, it seems unused and unnecessary.)

Also, we should put this under testutils (either in that package or a subdirectory of it).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, whoops, this is leftover from the stubserver copy paste. Changed to embedding a stats.Handler.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved file to within testutils as internal/testutils/stubstatshandler.go.

// isRegisteredMethod returns whether the passed in method is registered as a
// method on the server. /service/method and service/method will match if the
// service and method are registered on the server.
func (s *Server) isRegisteredMethod(serviceMethod string) bool {
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this very similar to the code that looks up the handler for a method? Can we reuse code somehow, or even just call it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is very similar, but I chose not to do it because this line is very specific: https://github.com/grpc/grpc-go/blob/master/server.go#L1731, and I thought it would be hard to generalize what's shared into a helper. If you feel strongly about this after reading the handleStream() code, I can try and pull out logic into helper.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah please see what you can do. I think a method that returns (service string, method string, ok bool) would be good.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried writing it, and it seems distinct enough logically to not warrant a helper. Your function signature doesn't distinguish between a. malformed method name (important here for tracing and logging: https://github.com/grpc/grpc-go/blob/master/server.go#L1732) b. registered unary rpc c. registered streaming name (important here: https://github.com/grpc/grpc-go/blob/master/server.go#L1770, to chose to go through processUnary or processStreaming). This helper's scope is not to log anything in traces or channelz, but to parse a :method header received from the wire and determine whether it's registered or not registered. I think it's sufficiently different to not warrant a new helper.

UnaryCallF: func(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
return &testpb.SimpleResponse{}, nil
},
FullDuplexCallF: func(stream testpb.TestService_FullDuplexCallServer) error {
Copy link
Member

Choose a reason for hiding this comment

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

You never call this; either delete it or test it please.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I do test this. See line 1493. However, you are right that I never initiate a bidirectional streaming RPC from the test. Would you like me to add that?

Copy link
Member

Choose a reason for hiding this comment

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

This closure being set or not doesn't change the test at line 1494. The method is registered because it's in the service descriptor. I'm fine with deleting this; I don't think there's any need to test both streaming and unary RPCs unless there are different code paths that lead to setting the server in the context, which there aren't.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, right, these closures != service descriptor, it's all there anyway. Deleting.

isRegisteredMethod := internal.IsRegisteredMethod.(func(*grpc.Server, string) bool)
// /s/m and s/m are valid.
if !isRegisteredMethod(server, "/grpc.testing.TestService/UnaryCall") {
errorCh.Send(errors.New("UnaryCall should be a registered method according to server"))
Copy link
Member

Choose a reason for hiding this comment

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

You don't actually need the error channel for this. You can just do t.Errorf here. Then all you need to do is ensure the code actually was called. You can do this by closing a done, channel or with a waitgroup, etc. One benefit of this is that you'll see all the test case failures in a single run if multiple of these checks don't 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.

Oh, right, this was when it was a defined stats handler rather than a stub with methods declared in test. Thus, before I couldn't close on t *testing.T, and now I can :). Switched.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Chose a waitGroup.

@dfawley dfawley assigned zasweq and unassigned dfawley Oct 25, 2023
@zasweq zasweq assigned dfawley and unassigned zasweq Oct 25, 2023
Comment on lines 31 to 33
// Guarantees we satisfy this interface; panics if unimplemented methods are
// called.
testgrpc.TestServiceServer
stats.Handler
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're fully implementing it (which we must be given the earlier snapshot) then this is unnecessary; just delete it. If you want to have a paranoid check that we're implementing it, you can add a var _ stats.Handler = (*StubStatsHandler)(nil). But that's not strictly necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah ok deleted.

@dfawley dfawley assigned zasweq and unassigned dfawley Oct 26, 2023
@zasweq zasweq merged commit 8cb9846 into grpc:master Oct 26, 2023
14 checks passed
arvindbr8 pushed a commit to arvindbr8/grpc-go that referenced this pull request Nov 7, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 24, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Type: Feature New features or improvements in behavior
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants