xds: Implementation of Unified Matcher and CEL Integration#12640
xds: Implementation of Unified Matcher and CEL Integration#12640shivaspeaks wants to merge 33 commits intogrpc:masterfrom
Conversation
082243d to
f472c63
Compare
l46kok
left a comment
There was a problem hiding this comment.
Sorry about the drive-by -- I happened to notice this while putting together an unrelated PR in CEL-Java. I'll hold off on submitting it on our end.
| * Compiles the CEL expression string into a CelMatcher. | ||
| */ | ||
| @VisibleForTesting | ||
| public static CelMatcher compile(String expression) |
There was a problem hiding this comment.
Move this to the testing code so we don't need a dependency on the Cel compiler. It looks unused for production.
| String id = over.toString(); | ||
| return !id.equals("ADD_STRING") && !id.equals("ADD_LIST"); |
There was a problem hiding this comment.
You should be able to compare directly against the overload enum instead of the stringified form (ex: over != AddOverload.ADD_STRING).
Fixes: #566 See also grpc/grpc-java#12640 PiperOrigin-RevId: 871541021
Fixes: #566 See also grpc/grpc-java#12640 PiperOrigin-RevId: 871541021
Fixes: #566 See also grpc/grpc-java#12640 PiperOrigin-RevId: 871541021
Also removes antlr dependency from `dev.cel:runtime` artifact Fixes: #566 See also grpc/grpc-java#12640 PiperOrigin-RevId: 871541021
Also removes antlr dependency from `dev.cel:runtime` artifact Fixes: #566 See also grpc/grpc-java#12640 PiperOrigin-RevId: 871541021
Also removes antlr dependency from `dev.cel:runtime` artifact Fixes: #566 See also grpc/grpc-java#12640 PiperOrigin-RevId: 871541021
asheshvidyut
left a comment
There was a problem hiding this comment.
Reviewing for learning purposes.
|
Note on CEL Allowed Functions and Overload IDs To enforce the gRFC A106 allowed list while supporting these permitted operations, I updated
|
| * Set of allowed function names based on gRFC A106. | ||
| */ | ||
| private static final ImmutableSet<String> ALLOWED_FUNCTIONS = ImmutableSet.of( | ||
| "size", "matches", "contains", "startsWith", "endsWith", "timestamp", "duration", |
There was a problem hiding this comment.
The overload ids for standard functions will have suffixes like size_string, size_int64, etc and we should check that the overload id starts with one of these rather than use contains. Applies for timestamp and duration also.
We shouldn't limit overload IDs to specific data type (int64) as divide_int64, and instead should check for starts with divide.
I think we should use regex instead of individually checking each.
We really should have unit tests for each to make sure it is as we expect.
As we are matching by overload id equals for ==, we shouldn't have "equals" in the list.
| boolean allowed = false; | ||
| for (String id : ref.overloadIds()) { | ||
| if (ALLOWED_FUNCTIONS.contains(id)) { | ||
| allowed = true; |
There was a problem hiding this comment.
We should check for all overload ids in the list.
| if (input instanceof CelVariableResolver) { | ||
| result = program.eval((CelVariableResolver) input); | ||
| } else { | ||
| throw new CelEvaluationException( |
There was a problem hiding this comment.
No need to double catch. Just check for default value here and decide whether to return default or to throw.
There was a problem hiding this comment.
But the gRFC explicitly suggests that
If set, and the CEL expression evaluates to an error or a non-string type, this default value will be returned instead.
So the defaultValue check needs to be in catch.
There was a problem hiding this comment.
My comment was to avoid the double catch statement. Can we not make it a single one?
|
Can we move the "CEL foundation" classes to a separate PR so the ext-proc filter that needs Cel string extraction but not the unified matcher functionality can be unblocked sooner?
|
|
Actually, hold off on the above separate PR request. I might have misunderstood about the need to use CEL expression evaluator in the ext-proc filter. The grfc mentions that all CEL request attributes will be supported, not that we must evaluate them using the CEL runtime engine. |
|
I have concluded that ext-proc filter indeed needs to use the CEL evaluation logic to support sending attributes. So do proceed with the suggested refactoring into a separate PR as mentioned above. |
| List<String> components = SPLITTER.splitToList(name); | ||
| if (components.size() == 2 && components.get(0).equals("request")) { | ||
| return Optional.ofNullable(getRequestField(components.get(1))); | ||
| } |
There was a problem hiding this comment.
The CelVariableResolver is intended to resolve the root of an expression, while the engine itself handles the navigation (dots and brackets) using the objects you provide.
Remove these lines.
By manually splitting request.headers in your find method, you are doing the engine's job for it, which creates several issues:
- Redundancy: If you manually split request.headers, but the user writes request["headers"], your SPLITTER (which looks for a dot) might not trigger the same way, leading to inconsistent results.
- Shadowing: If you resolve request.headers as a single string name, you might accidentally "shadow" the engine's ability to treat request as a map/object.
- Fragility: If the user writes request.headers['x-user-id'], your manual splitter might get confused by the brackets or complex paths.
| private static final Pattern ALLOWED_OVERLOAD_ID_PATTERN = Pattern.compile( | ||
| "^(size|matches|contains|startsWith|endsWith|starts_with|ends_with|" | ||
| + "timestamp|duration|in|index|has|int|uint|double|string|bytes|bool|" | ||
| + "equals|not_equals|less|less_equals|greater|greater_equals|" |
There was a problem hiding this comment.
equals, not equals, logical and/or/not never have suffixes. We should therefore do exact matching for these. Have another set
private static final ImmutableSet<String> ALLOWED_EXACT_OVERLOAD_IDS = ImmutableSet.of(
"equals", "not_equals", "logical_and", "logical_or", "logical_not");
and do
if (ALLOWED_EXACT_OVERLOAD_IDS.contains(id)
|| ALLOWED_OVERLOAD_ID_PREFIX_PATTERN.matcher(id).matches()) {
allowed = true;
There was a problem hiding this comment.
Change the logic to reject any non-empty name when overloadIds is present. Because if a reference has a name, it's not a standard operator or a variable (handled by the first if), so it must be something we don't support, such as custom functions.
There was a problem hiding this comment.
Oh, so standard allowed functions never have a name in the AST, so we should remove ALLOWED_FUNCTIONS, which is now an obsolete list here?
| "^(size|matches|contains|startsWith|endsWith|starts_with|ends_with|" | ||
| + "timestamp|duration|in|index|has|int|uint|double|string|bytes|bool|" | ||
| + "equals|not_equals|less|less_equals|greater|greater_equals|" | ||
| + "add|subtract|multiply|divide|modulo|logical_and|logical_or|logical_not)" |
There was a problem hiding this comment.
Add negate to this list and unit test for it
@Test
public void checkAllowedReferences_negation() throws Exception {
assertAllowed("-(1) == -1");
assertAllowed("-(1.0) == -1.0");
}
We can remove CelMatcher and CelStateMatcher from this list (for the separate PR, since ext-proc doesn't need the matching functionality) with the following changes to unit tests:
Move these unit tests with the separate PR. |
|
A106 said only request variable will be supported but for use by ext-proc A93, we need response supported as well. |
| private Object getRequestField(String requestField) { | ||
| switch (requestField) { | ||
| case "headers": return new HeadersWrapper(context); | ||
| case "host": return orEmpty(context.getHost()); | ||
| case "id": return orEmpty(context.getId()); | ||
| case "method": return or(context.getMethod(), "POST"); | ||
| case "path": | ||
| case "url_path": | ||
| return orEmpty(context.getPath()); | ||
| case "query": return ""; | ||
| case "scheme": return ""; | ||
| case "protocol": return ""; | ||
| case "time": return null; | ||
| case "referer": return getHeader("referer"); | ||
| case "useragent": return getHeader("user-agent"); | ||
|
|
||
| default: | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
I noticed that GrpcCelEnvironment.java and MatchContext.java currently only provide support for request variables (headers, method, path, etc.). However, gRFC A103 (Composite Filter) explicitly mentions the need for source and connection variables (e.g., source.address, source.port, connection.tls_version) for server-side matching.
Is there a plan to extend MatchContext and the variable resolver in this PR to include these transport attributes, or should this be tracked as a follow-up task?
There was a problem hiding this comment.
I believe these needs to be added as part of Composite filter implementation itself. This gives you the framework for it. It's mentioned in gRFC 103:
In addition to the CEL request attributes described in A106, we will also add support for some additional CEL attributes that we expect to be useful for the composite filter.
| public MatchContext(Metadata metadata, @Nullable String path, | ||
| @Nullable String host, @Nullable String method, | ||
| @Nullable String id) { | ||
| this.metadata = Preconditions.checkNotNull(metadata, "metadata"); | ||
| this.path = path; | ||
| this.host = host; | ||
| this.method = method; | ||
| this.id = id; | ||
| } |
There was a problem hiding this comment.
To support future attributes beyond just headers (like the aforementioned transport info or cluster metadata), we might want to consider a more pluggable way to populate MatchContext.
Currently, it has a fixed set of fields. A map-based approach or a registry of attribute providers might make it easier to add new attributes without changing the core MatchContext class every time
| /** Translates envoy proto HeaderMatcher to internal HeaderMatcher.*/ | ||
| /** Translates envoy proto HeaderMatcher to internal HeaderMatcher. */ | ||
| public static Matchers.HeaderMatcher parseHeaderMatcher( | ||
| io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) { | ||
| io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) { |
There was a problem hiding this comment.
can we revert the whitespace changes in this file ?
Implements gRFC A106: xDS Unified Matcher and CEL Integration
grpc/proposal#520