internal/resolver: add target URI parsing with resolver validation#8876
internal/resolver: add target URI parsing with resolver validation#8876HueCodes wants to merge 4 commits intogrpc:masterfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #8876 +/- ##
==========================================
- Coverage 83.17% 83.07% -0.10%
==========================================
Files 414 412 -2
Lines 32751 32887 +136
==========================================
+ Hits 27241 27322 +81
- Misses 4091 4177 +86
+ Partials 1419 1388 -31
🚀 New features to boost your workflow:
|
|
Hi @HueCodes, thanks for picking up this issue. To ensure we have a reusable API, we should update existing call sites to use the new implementation. Specifically:
Both of these locations share common behavior:
To handle this, the new function would need to accept a |
|
Thanks for the response! I will work on fixing this today when I am off work. |
|
Agree with the comments by @arjan-bal. Another place where we would have to use the new API is here: grpc-go/internal/xds/bootstrap/bootstrap.go Line 399 in b7b1cce Currently, that code only verifies that the |
ea82c57 to
9c8aa04
Compare
|
Thanks for taking the time to review this. I appreciate it. I addressed the issues:
Let me know if there are any other changes you would like me to make. |
internal/grpcutil/target_test.go
Outdated
| name string | ||
| target string | ||
| defaultScheme string | ||
| wantScheme string |
There was a problem hiding this comment.
In addition to the scheme, I think we should also validate the host and the path (via Target.Endpoint()) since they're the most important components for gRPC. I've left a separate comment about returning a resolver.Target instead of a URL.
Move ParseTarget to resolver package per PR grpc#8876 review feedback. Add ParseTargetWithRegistry to support custom resolver registries. Return resolver.Target instead of *url.URL for better API ergonomics. Changes: - Add resolver.ParseTarget using global resolver registry - Add resolver.ParseTargetWithRegistry accepting custom registry function - Return resolver.Target instead of *url.URL - Use %v instead of %w in error messages per review - Update balancer/rls/config.go to use resolver.ParseTarget - Simplify clientconn.initParsedTargetAndResolverBuilder - Remove internal/grpcutil/target.go and tests
There was a problem hiding this comment.
I didn't realize that adding the function here would make the ParseTarget public. The issue explicitly mentions that the API should be internal. Can you please move the function to the internal/resolver package?
grpc-go/internal/resolver/config_selector.go
Lines 19 to 20 in c1a9239
Apologies for the back and forth.
There was a problem hiding this comment.
Thank you for taking the time to review my code. I am learning a ton as this is very helpful. I should be done tonight and will push the changes then.
48e43ac to
3a28eea
Compare
Fixes grpc#8747 Add ParseTarget to internal/resolver. The function parses a gRPC target string into a resolver.Target and verifies that a resolver is registered for the parsed scheme using a caller-supplied lookup function. Scheme lookup rules: - Registered scheme (hierarchical or opaque form): accepted immediately. - Unregistered scheme in hierarchical URI (scheme://...): rejected; the caller chose this scheme explicitly, so no silent fallback occurs. - Opaque URI (e.g. host:port) or empty-scheme URI with an unregistered scheme: retried by prepending defaultScheme + ":///" if provided. Accepting a builder func instead of calling resolver.Get directly lets clientconn.go pass cc.getResolver, which also checks resolvers registered via dial options. Update clientconn.go and balancer/rls/config.go to use ParseTarget, removing the duplicate parsing and resolver-lookup logic from both. RELEASE NOTES: n/a
3a28eea to
623f036
Compare
arjan-bal
left a comment
There was a problem hiding this comment.
Almost looks good, some minor comments.
| defScheme := cc.dopts.defaultScheme | ||
| if internal.UserSetDefaultScheme { | ||
| defScheme = resolver.GetDefaultScheme() | ||
| } |
There was a problem hiding this comment.
Can we move the computation of the default scheme higher up and pass the default scheme to the first call to iresolver.ParseTarget? That should allow us to get rid of the second ParseTarget below.
internal/resolver/target_test.go
Outdated
| errContain string | ||
| }{ | ||
| { | ||
| name: "known scheme resolves", |
There was a problem hiding this comment.
style: Could you replace spaces with underscores (_) in the subtest names? The Go test harness automatically rewrites spaces, which makes it difficult to search the logs or run specific subtests via the test filter (-run flag) when debugging.
internal/resolver/target.go
Outdated
| if u.Opaque == "" { | ||
| // Unregistered scheme in hierarchical URI form (scheme://...): the | ||
| // caller explicitly chose this scheme; do not silently fall back. | ||
| return resolver.Target{}, fmt.Errorf("no resolver registered for scheme %q in target %q", u.Scheme, target) | ||
| } |
There was a problem hiding this comment.
I believe this check isn't present in the current implementation. Is there a strong reason to add this? If no, I would suggest maintaining the existing behaviour.
There was a problem hiding this comment.
No strong reason, I removed it.
Simplify ParseTarget to a single conditional instead of separate hierarchical/opaque branches. Compute the default scheme in clientconn.go before calling ParseTarget so the two-pass logic lives in one place. Switch tests to black-box _test package with blank imports for resolver registration. Use underscores in subtest names and %v for error wrapping.
Restore the distinction between hierarchical URIs (scheme://...) and opaque URIs (host:port) when the scheme is unregistered. Hierarchical URIs indicate an explicit scheme choice so ParseTarget should not silently fall back to the default scheme. Opaque and empty-scheme URIs still fall back as before. Fixes the rls config_test ParseConfigErrors/unregistered_scheme case.
clientconn needs to allow fallback for unregistered hierarchical schemes (e.g. nonexist:///foo falls back to passthrough:///nonexist:///foo), while ParseTarget rejects them so that callers like RLS config validation get strict scheme checking. Restore the original two-pass approach in clientconn: first try parsing as-is, then retry with the default scheme prepended.
| // Hierarchical URI with an unregistered scheme: the caller explicitly | ||
| // chose this scheme, so do not silently fall back. | ||
| if err == nil && u.Scheme != "" && u.Opaque == "" { | ||
| return resolver.Target{}, fmt.Errorf("no resolver registered for scheme %q in target %q", u.Scheme, target) | ||
| } |
There was a problem hiding this comment.
Special handling for opaque URLs like this isn't present in the existing code. I assume this is required because the two places where the target is parsed behave differently:
- In
clientconn.go, if the URL is parsed successfully but the builder for the requested scheme is not found, the default scheme is prepended, and the target is re-parsed. - In
rls/config.go, if the URL is parsed successfully but the builder for the requested scheme is not found, an error is returned directly. The default scheme is only used when either URL parsing fails, or it succeeds but the scheme is empty.
If we pass localhost:8080 to the RLS server parsing logic, it will parse it as an opaque URL, look for a resolver for localhost and fail. I suspect that RLS config parsing should be consistent across all gRPC implementations, as it comes from an xDS management server whereas gRPC channel target parsing differs slightly across them.
@easwars, can you comment on whether it makes sense to unify these two usages? If so, what is the expected behavior?
Fixes #8747
Add resolver validation to ParseTarget in grpcutil. The function
parses a gRPC target URI string and verifies that a resolver is
registered for the parsed scheme.
Revert clientconn.go to url.Parse since it uses cc.getResolver()
for custom dial-option resolvers. Remove the redundant resolver.Get
check from rls/config.go.
RELEASE NOTES: n/a