Skip to content

xds: Implementation of Unified Matcher and CEL Integration#12640

Open
shivaspeaks wants to merge 33 commits intogrpc:masterfrom
shivaspeaks:xds-unified-matcher-and-cel
Open

xds: Implementation of Unified Matcher and CEL Integration#12640
shivaspeaks wants to merge 33 commits intogrpc:masterfrom
shivaspeaks:xds-unified-matcher-and-cel

Conversation

@shivaspeaks
Copy link
Copy Markdown
Member

Implements gRFC A106: xDS Unified Matcher and CEL Integration
grpc/proposal#520

@shivaspeaks shivaspeaks force-pushed the xds-unified-matcher-and-cel branch from 082243d to f472c63 Compare February 3, 2026 10:11
@shivaspeaks shivaspeaks marked this pull request as ready for review February 8, 2026 09:01
Copy link
Copy Markdown

@l46kok l46kok left a comment

Choose a reason for hiding this comment

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

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.

Comment thread gradle/libs.versions.toml Outdated
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/CelCommon.java Outdated
* Compiles the CEL expression string into a CelMatcher.
*/
@VisibleForTesting
public static CelMatcher compile(String expression)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Move this to the testing code so we don't need a dependency on the Cel compiler. It looks unused for production.

Comment thread gradle/libs.versions.toml Outdated
Comment on lines +57 to +58
String id = over.toString();
return !id.equals("ADD_STRING") && !id.equals("ADD_LIST");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You should be able to compare directly against the overload enum instead of the stringified form (ex: over != AddOverload.ADD_STRING).

copybara-service Bot pushed a commit to google/cel-java that referenced this pull request Feb 18, 2026
copybara-service Bot pushed a commit to google/cel-java that referenced this pull request Feb 18, 2026
copybara-service Bot pushed a commit to google/cel-java that referenced this pull request Feb 18, 2026
copybara-service Bot pushed a commit to google/cel-java that referenced this pull request Feb 18, 2026
Also removes antlr dependency from `dev.cel:runtime` artifact

Fixes: #566
See also grpc/grpc-java#12640
PiperOrigin-RevId: 871541021
copybara-service Bot pushed a commit to google/cel-java that referenced this pull request Feb 18, 2026
Also removes antlr dependency from `dev.cel:runtime` artifact

Fixes: #566
See also grpc/grpc-java#12640
PiperOrigin-RevId: 871541021
copybara-service Bot pushed a commit to google/cel-java that referenced this pull request Feb 18, 2026
Also removes antlr dependency from `dev.cel:runtime` artifact

Fixes: #566
See also grpc/grpc-java#12640
PiperOrigin-RevId: 871541021
@shivaspeaks shivaspeaks requested review from ejona86 and sergiitk March 4, 2026 10:53
Copy link
Copy Markdown
Member

@asheshvidyut asheshvidyut left a comment

Choose a reason for hiding this comment

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

Reviewing for learning purposes.

Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/CelCommon.java Outdated
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/CelMatcher.java Outdated
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/CelCommon.java
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/CelCommon.java
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/CelStateMatcher.java Outdated
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/MatchInputRegistry.java Outdated
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/OnMatch.java Outdated
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/OnMatch.java
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/PredicateEvaluator.java Outdated
@ejona86 ejona86 removed their request for review April 8, 2026 17:30
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/CelStringExtractor.java Outdated
Comment thread xds/src/main/java/io/grpc/xds/internal/matcher/CelStringExtractor.java Outdated
@shivaspeaks
Copy link
Copy Markdown
Member Author

Note on CEL Allowed Functions and Overload IDs
During the implementation of the reference validation in CelCommon.java, I found that the CEL library represents standard operators and some conversion functions in the AST with an empty name in CelReference.name(), relying instead on specific overloadIds (e.g., equals for ==, index_map for map lookup [], and divide_int64 for /).

To enforce the gRFC A106 allowed list while supporting these permitted operations, I updated checkAllowedReferences to also validate overloadIds when the reference name is empty. Consequently, I added the following specific overload IDs to the ALLOWED_FUNCTIONS set to match the gRFC intent:

  • equals (maps to ==)
  • index_map (maps to map indexing [])
  • divide_int64 (maps to /)
  • int64_to_int64, string_to_int64, etc. (maps to int() conversions)

* 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",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should check for all overload ids in the list.

if (input instanceof CelVariableResolver) {
result = program.eval((CelVariableResolver) input);
} else {
throw new CelEvaluationException(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No need to double catch. Just check for default value here and decide whether to return default or to throw.

Copy link
Copy Markdown
Member Author

@shivaspeaks shivaspeaks Apr 21, 2026

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My comment was to avoid the double catch statement. Can we not make it a single one?

@kannanjgithub
Copy link
Copy Markdown
Contributor

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?

  1. CelCommon.java
  2. CelStringExtractor.java
  3. CelMatcher.java
  4. GrpcCelEnvironment.java
  5. HeadersWrapper.java
  6. CelStateMatcher.java

@kannanjgithub
Copy link
Copy Markdown
Contributor

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.

@kannanjgithub
Copy link
Copy Markdown
Contributor

kannanjgithub commented Apr 22, 2026

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.

Comment on lines +45 to +48
List<String> components = SPLITTER.splitToList(name);
if (components.size() == 2 && components.get(0).equals("request")) {
return Optional.ofNullable(getRequestField(components.get(1)));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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:

  1. 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.
  2. 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.
  3. 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|"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

@shivaspeaks shivaspeaks Apr 22, 2026

Choose a reason for hiding this comment

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

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)"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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");
  }

@kannanjgithub
Copy link
Copy Markdown
Contributor

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?

  1. CelCommon.java
  2. CelStringExtractor.java
  3. CelMatcher.java
  4. GrpcCelEnvironment.java
  5. HeadersWrapper.java
  6. CelStateMatcher.java

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:

  • CelMatcherTestHelper.java: Delete the compile(String expression) method that returns a CelMatcher.
  • CelEnvironmentTest.java: Delete the test cases that use CelMatcher.

Move these unit tests with the separate PR.

CelCommonTest.java
CelEnvironmentTest.java
CelStringExtractorTest.java

@kannanjgithub
Copy link
Copy Markdown
Contributor

A106 said only request variable will be supported but for use by ext-proc A93, we need response supported as well.

Comment on lines +53 to +72
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;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Comment on lines +34 to +42
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;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment on lines -24 to +26
/** 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) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we revert the whitespace changes in this file ?

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.

7 participants